v2: - tproxy statement without arguments is not supported - Add transport protocol matching criterion to address evaluation. - Specify network layer protocol in inet tables -- 8< -- 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 {<ip address>|:<port>|<ip address>:<port>} 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 <ecklm94@xxxxxxxxx> --- include/linux/netfilter/nf_tables.h | 16 ++++++ include/statement.h | 11 ++++ src/evaluate.c | 84 +++++++++++++++++++++++++++++ src/netlink_delinearize.c | 53 ++++++++++++++++++ src/netlink_linearize.c | 41 ++++++++++++++ src/parser_bison.y | 44 +++++++++++++++ src/scanner.l | 2 + src/statement.c | 45 ++++++++++++++++ 8 files changed, 296 insertions(+) diff --git a/include/linux/netfilter/nf_tables.h b/include/linux/netfilter/nf_tables.h index 88e0ca1..d98cebb 100644 --- a/include/linux/netfilter/nf_tables.h +++ b/include/linux/netfilter/nf_tables.h @@ -1231,6 +1231,22 @@ enum nft_nat_attributes { }; #define NFTA_NAT_MAX (__NFTA_NAT_MAX - 1) +/** + * enum nft_tproxy_attributes - nf_tables tproxy expression netlink attributes + * + * NFTA_TPROXY_FAMILY: Target address family (NLA_U32: nft_registers) + * NFTA_TPROXY_REG_ADDR: Target address register (NLA_U32: nft_registers) + * NFTA_TPROXY_REG_PORT: Target port register (NLA_U32: nft_registers) + */ +enum nft_tproxy_attributes { + NFTA_TPROXY_UNSPEC, + NFTA_TPROXY_FAMILY, + NFTA_TPROXY_REG_ADDR, + NFTA_TPROXY_REG_PORT, + __NFTA_TPROXY_MAX +}; +#define NFTA_TPROXY_MAX (__NFTA_TPROXY_MAX - 1) + /** * enum nft_masq_attributes - nf_tables masquerade expression attributes * diff --git a/include/statement.h b/include/statement.h index 5a907aa..7840e9d 100644 --- a/include/statement.h +++ b/include/statement.h @@ -128,6 +128,15 @@ struct nat_stmt { extern struct stmt *nat_stmt_alloc(const struct location *loc, enum nft_nat_etypes type); +struct tproxy_stmt { + struct expr *addr; + struct expr *port; + uint8_t family; + uint8_t table_family; /* only used for printing the rule */ +}; + +extern struct stmt *tproxy_stmt_alloc(const struct location *loc); + struct queue_stmt { struct expr *queue; uint16_t flags; @@ -271,6 +280,7 @@ enum stmt_types { STMT_LOG, STMT_REJECT, STMT_NAT, + STMT_TPROXY, STMT_QUEUE, STMT_CT, STMT_SET, @@ -337,6 +347,7 @@ struct stmt { struct limit_stmt limit; struct reject_stmt reject; struct nat_stmt nat; + struct tproxy_stmt tproxy; struct queue_stmt queue; struct quota_stmt quota; struct ct_stmt ct; diff --git a/src/evaluate.c b/src/evaluate.c index 9ff2c0b..bde38ce 100644 --- a/src/evaluate.c +++ b/src/evaluate.c @@ -2481,6 +2481,88 @@ 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 suppoerted for tproxy."); + if (stmt->tproxy.family == NFPROTO_UNSPEC) + stmt->tproxy.family = NFPROTO_INET; + 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; @@ -2764,6 +2846,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 31d6242..b811135 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 }, @@ -2432,6 +2477,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 8471e83..aa00564 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 98bfeba..fe3c10b 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 <stmt> 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 <val> nf_nat_flags nf_nat_flag offset_opt +%type <stmt> tproxy_stmt +%destructor { stmt_free($$); } tproxy_stmt +%type <val> tproxy_family_spec %type <stmt> queue_stmt queue_stmt_alloc %destructor { stmt_free($$); } queue_stmt queue_stmt_alloc %type <val> 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 ed01b5e..703700f 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 6f5e666..56d8d80 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_INET) + 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); -- ecklm -- To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html