From 2be1d52644cf77bb2634fb504a265da480c5e901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Eckl?= Date: Fri, 20 Jul 2018 09:40:09 +0200 Subject: src: Add tproxy support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds support for transparent proxy functionality which is supported in ip, ip6 and inet tables. The syntax is the following: tproxy [{|ip|ip6}] to {|:|:} It looks for a socket listening on the specified address or port and assigns it to the matching packet. In an inet table, a packet matches for both families until address is specified. Network protocol family has to be specified **only** in inet tables if address is specified. As transparent proxy support is implemented for sockets with layer 4 information, a transport protocol header criterion has to be set in the same rule. eg. 'meta l4proto tcp' or 'udp dport 4444' Example ruleset: table ip x { chain y { type filter hook prerouting priority -150; policy accept; tcp dport ntp tproxy to 1.1.1.1 udp dport ssh tproxy to :2222 } } table ip6 x { chain y { type filter hook prerouting priority -150; policy accept; tcp dport ntp tproxy to [dead::beef] udp dport ssh tproxy to :2222 } } table inet x { chain y { type filter hook prerouting priority -150; policy accept; tcp dport 321 tproxy to :ssh tcp dport 99 tproxy ip to 1.1.1.1:999 udp dport 155 tproxy ip6 to [dead::beef]:smux } } Signed-off-by: Máté Eckl Signed-off-by: Pablo Neira Ayuso --- src/evaluate.c | 82 +++++++++++++++++++++++++++++++++++++++++++++++ src/netlink_delinearize.c | 53 ++++++++++++++++++++++++++++++ src/netlink_linearize.c | 41 ++++++++++++++++++++++++ src/parser_bison.y | 44 +++++++++++++++++++++++++ src/scanner.l | 2 ++ src/statement.c | 45 ++++++++++++++++++++++++++ 6 files changed, 267 insertions(+) (limited to 'src') diff --git a/src/evaluate.c b/src/evaluate.c index ae881ccd..da95cdf9 100644 --- a/src/evaluate.c +++ b/src/evaluate.c @@ -2478,6 +2478,86 @@ static int stmt_evaluate_nat(struct eval_ctx *ctx, struct stmt *stmt) return 0; } +static int stmt_evaluate_tproxy(struct eval_ctx *ctx, struct stmt *stmt) +{ + const struct datatype *dtype; + int err, len; + + switch (ctx->pctx.family) { + case NFPROTO_IPV4: + case NFPROTO_IPV6: + case NFPROTO_INET: + break; + default: + return stmt_error(ctx, stmt, + "tproxy is only supported for IPv4/IPv6/INET"); + } + + if (ctx->pctx.protocol[PROTO_BASE_TRANSPORT_HDR].desc == NULL) + return stmt_error(ctx, stmt, "Transparent proxy support requires" + " transport protocol match"); + + if (!stmt->tproxy.addr && !stmt->tproxy.port) + return stmt_error(ctx, stmt, "Either address or port must be specified!"); + + if (ctx->pctx.family != NFPROTO_INET) { + if (stmt->tproxy.family != NFPROTO_UNSPEC) + return stmt_error(ctx, stmt, "Family can only be specified in inet tables."); + stmt->tproxy.family = ctx->pctx.family; + } + else { + const struct proto_desc *nproto = + ctx->pctx.protocol[PROTO_BASE_NETWORK_HDR].desc; + if ((nproto == &proto_ip && stmt->tproxy.family == NFPROTO_IPV6) || + (nproto == &proto_ip6 && stmt->tproxy.family == NFPROTO_IPV4)) + /* this prevents us from rules like + * ip protocol tcp tproxy ip6 to [dead::beef] + */ + return stmt_error(ctx, stmt, + "Conflicting network layer protocols."); + } + + if (stmt->tproxy.addr != NULL) { + if (stmt->tproxy.addr->ops->type == EXPR_RANGE) + return stmt_error(ctx, stmt, "Address ranges are not supported for tproxy."); + if (ctx->pctx.family == NFPROTO_INET) { + switch (stmt->tproxy.family) { + case NFPROTO_IPV4: + dtype = &ipaddr_type; + len = 4 * BITS_PER_BYTE; + break; + case NFPROTO_IPV6: + dtype = &ip6addr_type; + len = 16 * BITS_PER_BYTE; + break; + default: + return stmt_error(ctx, stmt, + "Family must be specified in tproxy statement with address for inet tables."); + } + err = stmt_evaluate_arg(ctx, stmt, dtype, len, + BYTEORDER_BIG_ENDIAN, + &stmt->tproxy.addr); + if (err < 0) + return err; + } + else { + err = evaluate_addr(ctx, stmt, &stmt->tproxy.addr); + if (err < 0) + return err; + } + } + + if (stmt->tproxy.port != NULL) { + if (stmt->tproxy.port->ops->type == EXPR_RANGE) + return stmt_error(ctx, stmt, "Port ranges are not supported for tproxy."); + err = nat_evaluate_transport(ctx, stmt, &stmt->tproxy.port); + if (err < 0) + return err; + } + + return 0; +} + static int stmt_evaluate_dup(struct eval_ctx *ctx, struct stmt *stmt) { int err; @@ -2761,6 +2841,8 @@ int stmt_evaluate(struct eval_ctx *ctx, struct stmt *stmt) return stmt_evaluate_reject(ctx, stmt); case STMT_NAT: return stmt_evaluate_nat(ctx, stmt); + case STMT_TPROXY: + return stmt_evaluate_tproxy(ctx, stmt); case STMT_QUEUE: return stmt_evaluate_queue(ctx, stmt); case STMT_DUP: diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c index 7e9765cf..c886ff98 100644 --- a/src/netlink_delinearize.c +++ b/src/netlink_delinearize.c @@ -969,6 +969,50 @@ out_err: xfree(stmt); } +static void netlink_parse_tproxy(struct netlink_parse_ctx *ctx, + const struct location *loc, + const struct nftnl_expr *nle) +{ + struct stmt *stmt; + struct expr *addr, *port; + enum nft_registers reg; + + stmt = tproxy_stmt_alloc(loc); + stmt->tproxy.family = nftnl_expr_get_u32(nle, NFTNL_EXPR_TPROXY_FAMILY); + stmt->tproxy.table_family = ctx->table->handle.family; + + reg = netlink_parse_register(nle, NFTNL_EXPR_TPROXY_REG_ADDR); + if (reg) { + addr = netlink_get_register(ctx, loc, reg); + + switch (stmt->tproxy.family) { + case NFPROTO_IPV4: + expr_set_type(addr, &ipaddr_type, BYTEORDER_BIG_ENDIAN); + break; + case NFPROTO_IPV6: + expr_set_type(addr, &ip6addr_type, BYTEORDER_BIG_ENDIAN); + break; + default: + netlink_error(ctx, loc, + "tproxy address must be IPv4 or IPv6"); + goto err; + } + stmt->tproxy.addr = addr; + } + + reg = netlink_parse_register(nle, NFTNL_EXPR_TPROXY_REG_PORT); + if (reg) { + port = netlink_get_register(ctx, loc, reg); + expr_set_type(port, &inet_service_type, BYTEORDER_BIG_ENDIAN); + stmt->tproxy.port = port; + } + + ctx->stmt = stmt; + return; +err: + xfree(stmt); +} + static void netlink_parse_masq(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nftnl_expr *nle) @@ -1362,6 +1406,7 @@ static const struct { { .name = "range", .parse = netlink_parse_range }, { .name = "reject", .parse = netlink_parse_reject }, { .name = "nat", .parse = netlink_parse_nat }, + { .name = "tproxy", .parse = netlink_parse_tproxy }, { .name = "notrack", .parse = netlink_parse_notrack }, { .name = "masq", .parse = netlink_parse_masq }, { .name = "redir", .parse = netlink_parse_redir }, @@ -2435,6 +2480,14 @@ static void rule_parse_postprocess(struct netlink_parse_ctx *ctx, struct rule *r expr_postprocess(&rctx, &stmt->nat.proto); } break; + case STMT_TPROXY: + if (stmt->tproxy.addr) + expr_postprocess(&rctx, &stmt->tproxy.addr); + if (stmt->tproxy.port) { + payload_dependency_reset(&rctx.pdctx); + expr_postprocess(&rctx, &stmt->tproxy.port); + } + break; case STMT_REJECT: stmt_reject_postprocess(&rctx); break; diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c index 8471e837..aa00564a 100644 --- a/src/netlink_linearize.c +++ b/src/netlink_linearize.c @@ -1071,6 +1071,45 @@ static void netlink_gen_nat_stmt(struct netlink_linearize_ctx *ctx, nftnl_rule_add_expr(ctx->nlr, nle); } +static void netlink_gen_tproxy_stmt(struct netlink_linearize_ctx *ctx, + const struct stmt *stmt) +{ + struct nftnl_expr *nle; + enum nft_registers addr_reg; + enum nft_registers port_reg; + int registers = 0; + const int family = stmt->tproxy.family; + int nftnl_reg_port; + + nle = alloc_nft_expr("tproxy"); + + nftnl_expr_set_u32(nle, NFTNL_EXPR_TPROXY_FAMILY, family); + + nftnl_reg_port = NFTNL_EXPR_TPROXY_REG_PORT; + + if (stmt->tproxy.addr) { + addr_reg = get_register(ctx, NULL); + registers++; + netlink_gen_expr(ctx, stmt->tproxy.addr, addr_reg); + netlink_put_register(nle, NFTNL_EXPR_TPROXY_REG_ADDR, + addr_reg); + } + + if (stmt->tproxy.port) { + port_reg = get_register(ctx, NULL); + registers++; + netlink_gen_expr(ctx, stmt->tproxy.port, port_reg); + netlink_put_register(nle, nftnl_reg_port, port_reg); + } + + while (registers > 0) { + release_register(ctx, NULL); + registers--; + } + + nftnl_rule_add_expr(ctx->nlr, nle); +} + static void netlink_gen_dup_stmt(struct netlink_linearize_ctx *ctx, const struct stmt *stmt) { @@ -1301,6 +1340,8 @@ static void netlink_gen_stmt(struct netlink_linearize_ctx *ctx, return netlink_gen_reject_stmt(ctx, stmt); case STMT_NAT: return netlink_gen_nat_stmt(ctx, stmt); + case STMT_TPROXY: + return netlink_gen_tproxy_stmt(ctx, stmt); case STMT_DUP: return netlink_gen_dup_stmt(ctx, stmt); case STMT_QUEUE: diff --git a/src/parser_bison.y b/src/parser_bison.y index 98bfebad..fe3c10ba 100644 --- a/src/parser_bison.y +++ b/src/parser_bison.y @@ -192,6 +192,8 @@ int nft_lex(void *, void *, void *); %token SOCKET "socket" %token TRANSPARENT "transparent" +%token TPROXY "tproxy" + %token HOOK "hook" %token DEVICE "device" %token DEVICES "devices" @@ -572,6 +574,9 @@ int nft_lex(void *, void *, void *); %type nat_stmt nat_stmt_alloc masq_stmt masq_stmt_alloc redir_stmt redir_stmt_alloc %destructor { stmt_free($$); } nat_stmt nat_stmt_alloc masq_stmt masq_stmt_alloc redir_stmt redir_stmt_alloc %type nf_nat_flags nf_nat_flag offset_opt +%type tproxy_stmt +%destructor { stmt_free($$); } tproxy_stmt +%type tproxy_family_spec %type queue_stmt queue_stmt_alloc %destructor { stmt_free($$); } queue_stmt queue_stmt_alloc %type queue_stmt_flags queue_stmt_flag @@ -2082,6 +2087,7 @@ stmt : verdict_stmt | quota_stmt | reject_stmt | nat_stmt + | tproxy_stmt | queue_stmt | ct_stmt | masq_stmt @@ -2477,6 +2483,44 @@ nat_stmt_alloc : SNAT { $$ = nat_stmt_alloc(&@$, NFT_NAT_SNAT); } | DNAT { $$ = nat_stmt_alloc(&@$, NFT_NAT_DNAT); } ; +tproxy_family_spec : IP { $$ = NFPROTO_IPV4; } + | IP6 { $$ = NFPROTO_IPV6; } + ; + +tproxy_stmt : TPROXY TO stmt_expr + { + $$ = tproxy_stmt_alloc(&@$); + $$->tproxy.family = NFPROTO_UNSPEC; + $$->tproxy.addr = $3; + } + | TPROXY tproxy_family_spec TO stmt_expr + { + $$ = tproxy_stmt_alloc(&@$); + $$->tproxy.family = $2; + $$->tproxy.addr = $4; + } + | TPROXY TO COLON stmt_expr + { + $$ = tproxy_stmt_alloc(&@$); + $$->tproxy.family = NFPROTO_UNSPEC; + $$->tproxy.port = $4; + } + | TPROXY TO stmt_expr COLON stmt_expr + { + $$ = tproxy_stmt_alloc(&@$); + $$->tproxy.family = NFPROTO_UNSPEC; + $$->tproxy.addr = $3; + $$->tproxy.port = $5; + } + | TPROXY tproxy_family_spec TO stmt_expr COLON stmt_expr + { + $$ = tproxy_stmt_alloc(&@$); + $$->tproxy.family = $2; + $$->tproxy.addr = $4; + $$->tproxy.port = $6; + } + ; + primary_stmt_expr : symbol_expr { $$ = $1; } | integer_expr { $$ = $1; } | boolean_expr { $$ = $1; } diff --git a/src/scanner.l b/src/scanner.l index ed01b5e7..703700fe 100644 --- a/src/scanner.l +++ b/src/scanner.l @@ -261,6 +261,8 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) "socket" { return SOCKET; } "transparent" { return TRANSPARENT;} +"tproxy" { return TPROXY; } + "accept" { return ACCEPT; } "drop" { return DROP; } "continue" { return CONTINUE; } diff --git a/src/statement.c b/src/statement.c index 6f5e6660..3040476f 100644 --- a/src/statement.c +++ b/src/statement.c @@ -760,6 +760,51 @@ struct stmt *fwd_stmt_alloc(const struct location *loc) return stmt_alloc(loc, &fwd_stmt_ops); } +static void tproxy_stmt_print(const struct stmt *stmt, struct output_ctx *octx) +{ + nft_print(octx, "tproxy"); + + if (stmt->tproxy.table_family == NFPROTO_INET && + stmt->tproxy.family != NFPROTO_UNSPEC) + nft_print(octx, " %s", nfproto_family_name(stmt->tproxy.family)); + nft_print(octx, " to"); + if (stmt->tproxy.addr) { + nft_print(octx, " "); + if (stmt->tproxy.addr->ops->type == EXPR_VALUE && + stmt->tproxy.addr->dtype->type == TYPE_IP6ADDR) { + nft_print(octx, "["); + expr_print(stmt->tproxy.addr, octx); + nft_print(octx, "]"); + } else { + expr_print(stmt->tproxy.addr, octx); + } + } + if (stmt->tproxy.port && stmt->tproxy.port->ops->type == EXPR_VALUE) { + if (!stmt->tproxy.addr) + nft_print(octx, " "); + nft_print(octx, ":"); + expr_print(stmt->tproxy.port, octx); + } +} + +static void tproxy_stmt_destroy(struct stmt *stmt) +{ + expr_free(stmt->tproxy.addr); + expr_free(stmt->tproxy.port); +} + +static const struct stmt_ops tproxy_stmt_ops = { + .type = STMT_TPROXY, + .name = "tproxy", + .print = tproxy_stmt_print, + .destroy = tproxy_stmt_destroy, +}; + +struct stmt *tproxy_stmt_alloc(const struct location *loc) +{ + return stmt_alloc(loc, &tproxy_stmt_ops); +} + static void xt_stmt_print(const struct stmt *stmt, struct output_ctx *octx) { xt_stmt_xlate(stmt); -- cgit v1.2.3