This patch adds redirect support for nft. The syntax is: % nft add rule nat prerouting redirect [port|nat_flags] Signed-off-by: Arturo Borrero Gonzalez <arturo.borrero.glez@xxxxxxxxx> --- v2: include regression tests-files include/statement.h | 10 ++++++++ src/evaluate.c | 40 ++++++++++++++++++++++++++++++ src/netlink_delinearize.c | 52 +++++++++++++++++++++++++++++++++++++++ src/netlink_linearize.c | 49 +++++++++++++++++++++++++++++++++++++ src/parser.y | 23 ++++++++++++++++- src/scanner.l | 1 + src/statement.c | 29 ++++++++++++++++++++++ tests/regression/ip/redirect.t | 41 +++++++++++++++++++++++++++++++ tests/regression/ip6/redirect.t | 42 ++++++++++++++++++++++++++++++++ 9 files changed, 285 insertions(+), 2 deletions(-) create mode 100644 tests/regression/ip/redirect.t create mode 100644 tests/regression/ip6/redirect.t diff --git a/include/statement.h b/include/statement.h index 35c1b7a..d143121 100644 --- a/include/statement.h +++ b/include/statement.h @@ -79,6 +79,13 @@ struct masq_stmt { extern struct stmt *masq_stmt_alloc(const struct location *loc); +struct redir_stmt { + struct expr *proto; + uint32_t flags; +}; + +extern struct stmt *redir_stmt_alloc(const struct location *loc); + struct queue_stmt { struct expr *queue; uint16_t flags; @@ -110,6 +117,7 @@ extern struct stmt *ct_stmt_alloc(const struct location *loc, * @STMT_REJECT: REJECT statement * @STMT_NAT: NAT statement * @STMT_MASQ: masquerade statement + * @STMT_REDIR: redirect statement * @STMT_QUEUE: QUEUE statement * @STMT_CT: conntrack statement */ @@ -124,6 +132,7 @@ enum stmt_types { STMT_REJECT, STMT_NAT, STMT_MASQ, + STMT_REDIR, STMT_QUEUE, STMT_CT, }; @@ -172,6 +181,7 @@ struct stmt { struct reject_stmt reject; struct nat_stmt nat; struct masq_stmt masq; + struct redir_stmt redir; struct queue_stmt queue; struct ct_stmt ct; }; diff --git a/src/evaluate.c b/src/evaluate.c index b722567..3eeb614 100644 --- a/src/evaluate.c +++ b/src/evaluate.c @@ -1546,6 +1546,44 @@ out: return 0; } +static int stmt_evaluate_redir(struct eval_ctx *ctx, struct stmt *stmt) +{ + int err; + struct proto_ctx *pctx = &ctx->pctx; + + if (!pctx) + goto out; + + switch (pctx->family) { + case AF_INET: + expr_set_context(&ctx->ectx, &ipaddr_type, + 4 * BITS_PER_BYTE); + break; + case AF_INET6: + expr_set_context(&ctx->ectx, &ip6addr_type, + 16 * BITS_PER_BYTE); + break; + default: + return stmt_error(ctx, stmt, "ip and ip6 support only"); + } + + if (stmt->redir.proto != NULL) { + if (pctx->protocol[PROTO_BASE_TRANSPORT_HDR].desc == NULL) + return stmt_binary_error(ctx, stmt->redir.proto, stmt, + "missing transport protocol match"); + + expr_set_context(&ctx->ectx, &inet_service_type, + 2 * BITS_PER_BYTE); + err = expr_evaluate(ctx, &stmt->redir.proto); + if (err < 0) + return err; + } + +out: + stmt->flags |= STMT_F_TERMINAL; + return 0; +} + static int stmt_evaluate_ct(struct eval_ctx *ctx, struct stmt *stmt) { expr_set_context(&ctx->ectx, stmt->ct.tmpl->dtype, @@ -1608,6 +1646,8 @@ int stmt_evaluate(struct eval_ctx *ctx, struct stmt *stmt) return stmt_evaluate_nat(ctx, stmt); case STMT_MASQ: return stmt_evaluate_masq(ctx, stmt); + case STMT_REDIR: + return stmt_evaluate_redir(ctx, stmt); case STMT_QUEUE: return stmt_evaluate_queue(ctx, stmt); case STMT_CT: diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c index 8f90cc0..1be409b 100644 --- a/src/netlink_delinearize.c +++ b/src/netlink_delinearize.c @@ -583,6 +583,52 @@ static void netlink_parse_masq(struct netlink_parse_ctx *ctx, list_add_tail(&stmt->list, &ctx->rule->stmts); } +static void netlink_parse_redir(struct netlink_parse_ctx *ctx, + const struct location *loc, + const struct nft_rule_expr *nle) +{ + struct stmt *stmt; + struct expr *proto; + enum nft_registers reg1, reg2; + uint32_t flags; + + stmt = redir_stmt_alloc(loc); + + if (nft_rule_expr_is_set(nle, NFT_EXPR_REDIR_FLAGS)) { + flags = nft_rule_expr_get_u32(nle, NFT_EXPR_REDIR_FLAGS); + stmt->redir.flags = flags; + } + + reg1 = nft_rule_expr_get_u32(nle, NFT_EXPR_REDIR_REG_PROTO_MIN); + if (reg1) { + proto = netlink_get_register(ctx, loc, reg1); + if (proto == NULL) + return netlink_error(ctx, loc, + "redirect statement has no proto " + "expression"); + + expr_set_type(proto, &inet_service_type, BYTEORDER_BIG_ENDIAN); + stmt->redir.proto = proto; + } + + reg2 = nft_rule_expr_get_u32(nle, NFT_EXPR_REDIR_REG_PROTO_MAX); + if (reg2 && reg2 != reg1) { + proto = netlink_get_register(ctx, loc, reg2); + if (proto == NULL) + return netlink_error(ctx, loc, + "redirect statement has no proto " + "expression"); + + expr_set_type(proto, &inet_service_type, BYTEORDER_BIG_ENDIAN); + if (stmt->redir.proto != NULL) + proto = range_expr_alloc(loc, stmt->redir.proto, + proto); + stmt->redir.proto = proto; + } + + list_add_tail(&stmt->list, &ctx->rule->stmts); +} + static void netlink_parse_queue(struct netlink_parse_ctx *ctx, const struct location *loc, const struct nft_rule_expr *nle) @@ -630,6 +676,7 @@ static const struct { { .name = "reject", .parse = netlink_parse_reject }, { .name = "nat", .parse = netlink_parse_nat }, { .name = "masq", .parse = netlink_parse_masq }, + { .name = "redir", .parse = netlink_parse_redir }, { .name = "queue", .parse = netlink_parse_queue }, }; @@ -1014,6 +1061,11 @@ static void rule_parse_postprocess(struct netlink_parse_ctx *ctx, struct rule *r if (stmt->nat.proto != NULL) expr_postprocess(&rctx, stmt, &stmt->nat.proto); break; + case STMT_REDIR: + if (stmt->redir.proto != NULL) + expr_postprocess(&rctx, stmt, + &stmt->redir.proto); + break; case STMT_REJECT: stmt_reject_postprocess(rctx, stmt); break; diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c index 62155cc..de338cb 100644 --- a/src/netlink_linearize.c +++ b/src/netlink_linearize.c @@ -701,6 +701,53 @@ static void netlink_gen_masq_stmt(struct netlink_linearize_ctx *ctx, nft_rule_add_expr(ctx->nlr, nle); } +static void netlink_gen_redir_stmt(struct netlink_linearize_ctx *ctx, + const struct stmt *stmt) +{ + struct nft_rule_expr *nle; + enum nft_registers pmin_reg, pmax_reg; + int registers = 0; + + nle = alloc_nft_expr("redir"); + + if (stmt->redir.flags != 0) + nft_rule_expr_set_u32(nle, NFT_EXPR_REDIR_FLAGS, + stmt->redir.flags); + + if (stmt->redir.proto) { + pmin_reg = get_register(ctx); + registers++; + + if (stmt->redir.proto->ops->type == EXPR_RANGE) { + pmax_reg = get_register(ctx); + registers++; + + netlink_gen_expr(ctx, stmt->redir.proto->left, + pmin_reg); + netlink_gen_expr(ctx, stmt->redir.proto->right, + pmax_reg); + nft_rule_expr_set_u32(nle, + NFT_EXPR_REDIR_REG_PROTO_MIN, + pmin_reg); + nft_rule_expr_set_u32(nle, + NFT_EXPR_REDIR_REG_PROTO_MAX, + pmax_reg); + } else { + netlink_gen_expr(ctx, stmt->redir.proto, pmin_reg); + nft_rule_expr_set_u32(nle, + NFT_EXPR_REDIR_REG_PROTO_MIN, + pmin_reg); + } + } + + while (registers > 0) { + release_register(ctx); + registers--; + } + + nft_rule_add_expr(ctx->nlr, nle); +} + static void netlink_gen_queue_stmt(struct netlink_linearize_ctx *ctx, const struct stmt *stmt) { @@ -767,6 +814,8 @@ static void netlink_gen_stmt(struct netlink_linearize_ctx *ctx, return netlink_gen_nat_stmt(ctx, stmt); case STMT_MASQ: return netlink_gen_masq_stmt(ctx, stmt); + case STMT_REDIR: + return netlink_gen_redir_stmt(ctx, stmt); case STMT_QUEUE: return netlink_gen_queue_stmt(ctx, stmt); case STMT_CT: diff --git a/src/parser.y b/src/parser.y index 9e9a839..6209e9e 100644 --- a/src/parser.y +++ b/src/parser.y @@ -375,6 +375,7 @@ static void location_update(struct location *loc, struct location *rhs, int n) %token SNAT "snat" %token DNAT "dnat" %token MASQUERADE "masquerade" +%token REDIRECT "redirect" %token RANDOM "random" %token RANDOM_FULLY "random-fully" %token PERSISTENT "persistent" @@ -440,8 +441,8 @@ static void location_update(struct location *loc, struct location *rhs, int n) %type <val> time_unit %type <stmt> reject_stmt reject_stmt_alloc %destructor { stmt_free($$); } reject_stmt reject_stmt_alloc -%type <stmt> nat_stmt nat_stmt_alloc masq_stmt masq_stmt_alloc -%destructor { stmt_free($$); } nat_stmt nat_stmt_alloc masq_stmt masq_stmt_alloc +%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 %type <stmt> queue_stmt queue_stmt_alloc %destructor { stmt_free($$); } queue_stmt queue_stmt_alloc @@ -1186,6 +1187,7 @@ stmt : verdict_stmt | queue_stmt | ct_stmt | masq_stmt + | redir_stmt ; verdict_stmt : verdict_expr @@ -1420,6 +1422,23 @@ masq_stmt : masq_stmt_alloc masq_stmt_alloc : MASQUERADE { $$ = masq_stmt_alloc(&@$); } ; +redir_stmt : redir_stmt_alloc redir_stmt_arg + | redir_stmt_alloc + ; + +redir_stmt_alloc : REDIRECT { $$ = redir_stmt_alloc(&@$); } + ; + +redir_stmt_arg : COLON expr + { + $<stmt>0->redir.proto = $2; + } + | nf_nat_flags + { + $<stmt>0->redir.flags = $1; + } + ; + nf_nat_flags : nf_nat_flag | nf_nat_flags COMMA nf_nat_flag { diff --git a/src/scanner.l b/src/scanner.l index 32e59d9..e36c3b1 100644 --- a/src/scanner.l +++ b/src/scanner.l @@ -317,6 +317,7 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) "snat" { return SNAT; } "dnat" { return DNAT; } "masquerade" { return MASQUERADE; } +"redirect" { return REDIRECT; } "random" { return RANDOM; } "random-fully" { return RANDOM_FULLY; } "persistent" { return PERSISTENT; } diff --git a/src/statement.c b/src/statement.c index 0ae616a..2587d27 100644 --- a/src/statement.c +++ b/src/statement.c @@ -348,3 +348,32 @@ struct stmt *masq_stmt_alloc(const struct location *loc) { return stmt_alloc(loc, &masq_stmt_ops); } + +static void redir_stmt_print(const struct stmt *stmt) +{ + printf("redirect"); + + if (stmt->redir.proto) { + printf(" :"); + expr_print(stmt->redir.proto); + } + + print_nf_nat_flags(stmt->redir.flags); +} + +static void redir_stmt_destroy(struct stmt *stmt) +{ + expr_free(stmt->redir.proto); +} + +static const struct stmt_ops redir_stmt_ops = { + .type = STMT_REDIR, + .name = "redir", + .print = redir_stmt_print, + .destroy = redir_stmt_destroy, +}; + +struct stmt *redir_stmt_alloc(const struct location *loc) +{ + return stmt_alloc(loc, &redir_stmt_ops); +} diff --git a/tests/regression/ip/redirect.t b/tests/regression/ip/redirect.t new file mode 100644 index 0000000..8e0f783 --- /dev/null +++ b/tests/regression/ip/redirect.t @@ -0,0 +1,41 @@ +*ip;test-ip4 +:output;type nat hook output priority 0 + +# without arguments +udp dport 53 redirect ;ok + +# nf_nat flags combination +udp dport 53 redirect random ;ok +udp dport 53 redirect random,persistent ;ok +udp dport 53 redirect random,persistent,random-fully ;ok ;udp dport 53 redirect random,random-fully,persistent +udp dport 53 redirect random,random-fully ;ok +udp dport 53 redirect random,random-fully,persistent ;ok +udp dport 53 redirect persistent ;ok +udp dport 53 redirect persistent,random ;ok ;udp dport 53 redirect random,persistent +udp dport 53 redirect persistent,random,random-fully ;ok ;udp dport 53 redirect random,random-fully,persistent +udp dport 53 redirect persistent,random-fully ;ok ;udp dport 53 redirect random-fully,persistent +udp dport 53 redirect persistent,random-fully,random;ok ;udp dport 53 redirect random,random-fully,persistent + +# port specification +tcp dport 22 redirect :22 ;ok +udp dport 1234 redirect :4321 ;ok +ip daddr 172.16.0.1 udp dport 9998 redirect :6515 ;ok +tcp dport 39128 redirect :993 ;ok +redirect :1234 ;nok +redirect :12341111 ;nok + +# invalid arguments +tcp dport 9128 redirect :993 random ;nok +tcp dport 9128 redirect :993 random-fully ;nok +tcp dport 9128 redirect persistent :123 ;nok +tcp dport 9128 redirect random,persistent :123 ;nok + +# redirect is a terminal statement +tcp dport 22 redirect counter packets 0 bytes 0 accept ;nok +tcp sport 22 redirect accept ;nok +ip saddr 10.1.1.1 redirect drop ;nok + +# redirect with sets +tcp dport {1,2,3,4,5,6,7,8,101,202,303,1001,2002,3003} redirect ;ok +ip daddr 10.0.0.0-10.2.3.4 udp dport 53 counter packets 0 bytes 0 redirect ;ok ;ip daddr >= 10.0.0.0 ip daddr <= 10.2.3.4 udp dport 53 counter packets 0 bytes 0 redirect +iifname eth0 ct state new,established tcp dport vmap {22 : drop, 222 : drop } redirect ;ok diff --git a/tests/regression/ip6/redirect.t b/tests/regression/ip6/redirect.t new file mode 100644 index 0000000..84ed88f --- /dev/null +++ b/tests/regression/ip6/redirect.t @@ -0,0 +1,42 @@ +*ip6;test-ip6 +:output;type nat hook output priority 0 + +# with no arguments +redirect ;ok +udp dport 954 redirect ;ok +ip6 saddr fe00::cafe counter packets 0 bytes 0 redirect ;ok + +# nf_nat flags combination +udp dport 53 redirect random ;ok +udp dport 53 redirect random,persistent ;ok +udp dport 53 redirect random,persistent,random-fully ;ok ;udp dport 53 redirect random,random-fully,persistent +udp dport 53 redirect random,random-fully ;ok +udp dport 53 redirect random,random-fully,persistent ;ok +udp dport 53 redirect persistent ;ok +udp dport 53 redirect persistent,random ;ok ;udp dport 53 redirect random,persistent +udp dport 53 redirect persistent,random,random-fully ;ok ;udp dport 53 redirect random,random-fully,persistent +udp dport 53 redirect persistent,random-fully ;ok ;udp dport 53 redirect random-fully,persistent +udp dport 53 redirect persistent,random-fully,random;ok ;udp dport 53 redirect random,random-fully,persistent + +# port specification +udp dport 1234 redirect :1234 ;ok +ip6 daddr fe00::cafe udp dport 9998 redirect :6515 ;ok +tcp dport 39128 redirect :993 ;ok +redirect :1234 ;nok +redirect :12341111 ;nok + +# invalid arguments +tcp dport 9128 redirect :993 random ;nok +tcp dport 9128 redirect :993 random-fully ;nok +tcp dport 9128 redirect persistent :123 ;nok +tcp dport 9128 redirect random,persistent :123 ;nok + +# redirect is a terminal statement +tcp dport 22 redirect counter packets 0 bytes 0 accept ;nok +tcp sport 22 redirect accept ;nok +ip6 saddr ::1 redirect drop ;nok + +# redirect with sets +tcp dport {1,2,3,4,5,6,7,8,101,202,303,1001,2002,3003} redirect ;ok +ip6 daddr fe00::1-fe00::200 udp dport 53 counter packets 0 bytes 0 redirect ;ok ;ip6 daddr >= fe00::1 ip6 daddr <= fe00::200 udp dport 53 counter packets 0 bytes 0 redirect +iifname eth0 ct state new,established tcp dport vmap {22 : drop, 222 : drop } redirect ;ok -- 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