This patch is built on the commit not applied yet with the title: evaluate: Detect address family in inet context -- 8< -- This patch adds support for transparent proxy functionality which is supported in ip, ip6 and inet tables. The syntax is the following: tproxy [to [<ip address>][:<port>]] Without arguments it looks for a socket listening on the ingress interface and a port with the same destination port as the packet destination port. With the arguments specified, it looks for a socket listening on the specified address or port. In an inet table, a packet matches for both families until address is specified. Example ruleset: table inet x { chain y { type filter hook prerouting priority -150; policy accept; socket transparent 1 mark set 0x00000001 accept tproxy mark set 0x00000001 counter packets 611 bytes 46181 meta l4proto tcp tproxy to :50080 mark set 0x00000001 counter packets 202 bytes 13600 } } Signed-off-by: Máté Eckl <ecklm94@xxxxxxxxx> --- include/linux/netfilter/nf_tables.h | 16 +++++++++ include/statement.h | 10 ++++++ src/evaluate.c | 44 ++++++++++++++++++++++++ src/netlink_delinearize.c | 52 +++++++++++++++++++++++++++++ src/netlink_linearize.c | 48 ++++++++++++++++++++++++++ src/parser_bison.y | 27 +++++++++++++++ src/scanner.l | 2 ++ src/statement.c | 43 ++++++++++++++++++++++++ 8 files changed, 242 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..edda98f 100644 --- a/include/statement.h +++ b/include/statement.h @@ -128,6 +128,14 @@ 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; +}; + +extern struct stmt *tproxy_stmt_alloc(const struct location *loc); + struct queue_stmt { struct expr *queue; uint16_t flags; @@ -271,6 +279,7 @@ enum stmt_types { STMT_LOG, STMT_REJECT, STMT_NAT, + STMT_TPROXY, STMT_QUEUE, STMT_CT, STMT_SET, @@ -337,6 +346,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 61b4697..f017ca8 100644 --- a/src/evaluate.c +++ b/src/evaluate.c @@ -2497,6 +2497,48 @@ 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) +{ + int err; + + 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"); + } + stmt->tproxy.family = ctx->pctx.family; + + if (stmt->tproxy.addr != NULL) { + if (stmt->tproxy.addr->ops->type == EXPR_RANGE) + return -EOPNOTSUPP; + err = evaluate_addr(ctx, stmt, &stmt->tproxy.addr); + if (err < 0) + return err; + switch (stmt->tproxy.addr->dtype->type) { + case TYPE_IPADDR: + stmt->tproxy.family = NFPROTO_IPV4; + break; + case TYPE_IP6ADDR: + stmt->tproxy.family = NFPROTO_IPV6; + break; + } + } + + if (stmt->tproxy.port != NULL) { + if (stmt->tproxy.port->ops->type == EXPR_RANGE) + return -EOPNOTSUPP; + 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; @@ -2780,6 +2822,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..6ede617 100644 --- a/src/netlink_delinearize.c +++ b/src/netlink_delinearize.c @@ -969,6 +969,49 @@ 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); + + 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 +1405,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 +2476,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..33ec06d 100644 --- a/src/netlink_linearize.c +++ b/src/netlink_linearize.c @@ -1071,6 +1071,52 @@ 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++; + + if (stmt->tproxy.addr->ops->type != EXPR_RANGE) { + 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++; + + if (stmt->tproxy.port->ops->type != EXPR_RANGE) { + 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 +1347,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..c45d6e4 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,8 @@ 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 <stmt> queue_stmt queue_stmt_alloc %destructor { stmt_free($$); } queue_stmt queue_stmt_alloc %type <val> queue_stmt_flags queue_stmt_flag @@ -2082,6 +2086,7 @@ stmt : verdict_stmt | quota_stmt | reject_stmt | nat_stmt + | tproxy_stmt | queue_stmt | ct_stmt | masq_stmt @@ -2477,6 +2482,28 @@ nat_stmt_alloc : SNAT { $$ = nat_stmt_alloc(&@$, NFT_NAT_SNAT); } | DNAT { $$ = nat_stmt_alloc(&@$, NFT_NAT_DNAT); } ; +tproxy_stmt : TPROXY + { + $$ = tproxy_stmt_alloc(&@$); + } + | TPROXY TO stmt_expr + { + $$ = tproxy_stmt_alloc(&@$); + $$->tproxy.addr = $3; + } + | TPROXY TO COLON stmt_expr + { + $$ = tproxy_stmt_alloc(&@$); + $$->tproxy.port = $4; + } + | TPROXY TO stmt_expr COLON stmt_expr + { + $$ = tproxy_stmt_alloc(&@$); + $$->tproxy.addr = $3; + $$->tproxy.port = $5; + } + ; + 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..4537e45 100644 --- a/src/statement.c +++ b/src/statement.c @@ -619,6 +619,49 @@ struct stmt *nat_stmt_alloc(const struct location *loc, return stmt; } +static void tproxy_stmt_print(const struct stmt *stmt, struct output_ctx *octx) +{ + nft_print(octx, "tproxy"); + + if (stmt->tproxy.addr || stmt->tproxy.port) + 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); +} + const char * const set_stmt_op_names[] = { [NFT_DYNSET_OP_ADD] = "add", [NFT_DYNSET_OP_UPDATE] = "update", -- 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