summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/statement.h10
-rw-r--r--src/evaluate.c40
-rw-r--r--src/netlink_delinearize.c52
-rw-r--r--src/netlink_linearize.c49
-rw-r--r--src/parser.y23
-rw-r--r--src/scanner.l1
-rw-r--r--src/statement.c29
-rw-r--r--tests/regression/ip/redirect.t41
-rw-r--r--tests/regression/ip6/redirect.t42
9 files changed, 285 insertions, 2 deletions
diff --git a/include/statement.h b/include/statement.h
index 35c1b7ae..d1431215 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 b722567c..3eeb614c 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 8f90cc03..1be409b1 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 62155cc3..de338cb7 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 9e9a8393..6209e9eb 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 32e59d98..e36c3b13 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 0ae616a8..2587d275 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 00000000..8e0f783b
--- /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 00000000..84ed88ff
--- /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