On Wed, Mar 27, 2019 at 08:32:08AM +0100, Florian Westphal wrote:
> consider a simple ip6 nat table:
>
> table ip6 nat { chain output {
> type nat hook output priority 0; policy accept;
> dnat to dead:2::99
> }
>
> Now consider same ruleset, but using 'table inet nat':
> nft now lacks context to determine address family to parse 'to $address'.
>
> This adds code to make the following work:
>
> table inet nat { [ .. ]
> # detect af from network protocol context:
> ip6 daddr dead::2::1 dnat to dead:2::99
>
> # use new dnat ip6 keyword:
> dnat ip6 to dead:2::99
> }
>
> On list side, the keyword is only shown in the inet family, else the
> short version (dnat to ...) is used as the family is redundant when the
> table already mandates the ip protocol version supported.
>
> Address mismatches such as
>
> table ip6 { ..
> dnat ip to 1.2.3.4
>
> are detected/handled during the evaluation phase.
>
> Signed-off-by: Florian Westphal <[email protected]>
Acked-by: Pablo Neira Ayuso <[email protected]>
Thanks!
>
> extern struct stmt *nat_stmt_alloc(const struct location *loc,
> diff --git a/src/evaluate.c b/src/evaluate.c
> index 54afc3340186..4914129711b8 100644
> --- a/src/evaluate.c
> +++ b/src/evaluate.c
> @@ -2504,9 +2504,28 @@ static int stmt_evaluate_reject(struct eval_ctx *ctx,
> struct stmt *stmt)
>
> static int nat_evaluate_family(struct eval_ctx *ctx, struct stmt *stmt)
> {
> + const struct proto_desc *nproto;
> +
> switch (ctx->pctx.family) {
> case NFPROTO_IPV4:
> case NFPROTO_IPV6:
> + if (stmt->nat.family == NFPROTO_UNSPEC)
> + stmt->nat.family = ctx->pctx.family;
> + return 0;
> + case NFPROTO_INET:
> + if (!stmt->nat.addr)
> + return 0;
> +
> + if (stmt->nat.family != NFPROTO_UNSPEC)
> + return 0;
> +
> + nproto = ctx->pctx.protocol[PROTO_BASE_NETWORK_HDR].desc;
> +
> + if (nproto == &proto_ip)
> + stmt->nat.family = NFPROTO_IPV4;
> + else if (nproto == &proto_ip6)
> + stmt->nat.family = NFPROTO_IPV6;
> +
> return 0;
> default:
> return stmt_error(ctx, stmt,
> @@ -2548,6 +2567,53 @@ static int nat_evaluate_transport(struct eval_ctx
> *ctx, struct stmt *stmt,
> BYTEORDER_BIG_ENDIAN, expr);
> }
>
> +static int stmt_evaluate_l3proto(struct eval_ctx *ctx,
> + struct stmt *stmt, uint8_t family)
> +{
> + const struct proto_desc *nproto;
> +
> + nproto = ctx->pctx.protocol[PROTO_BASE_NETWORK_HDR].desc;
> +
> + if ((nproto == &proto_ip && family != NFPROTO_IPV4) ||
> + (nproto == &proto_ip6 && family != NFPROTO_IPV6))
> + return stmt_error(ctx, stmt,
> + "Conflicting network layer protocols (context
> %s vs. family %d)",
> + nproto->name, family);
> + return 0;
> +}
> +
> +static int stmt_evaluate_addr(struct eval_ctx *ctx, struct stmt *stmt,
> + uint8_t family,
> + struct expr **addr)
> +{
> + const struct datatype *dtype;
> + unsigned int len;
> + int err;
> +
> + if (ctx->pctx.family == NFPROTO_INET) {
> + switch (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,
> + "ip or ip6 must be specified with
> address for inet tables.");
> + }
> +
> + err = stmt_evaluate_arg(ctx, stmt, dtype, len,
> + BYTEORDER_BIG_ENDIAN, addr);
> + } else {
> + err = evaluate_addr(ctx, stmt, addr);
> + }
> +
> + return err;
> +}
> +
> static int stmt_evaluate_nat(struct eval_ctx *ctx, struct stmt *stmt)
> {
> int err;
> @@ -2557,7 +2623,12 @@ static int stmt_evaluate_nat(struct eval_ctx *ctx,
> struct stmt *stmt)
> return err;
>
> if (stmt->nat.addr != NULL) {
> - err = evaluate_addr(ctx, stmt, &stmt->nat.addr);
> + err = stmt_evaluate_l3proto(ctx, stmt, stmt->nat.family);
> + if (err < 0)
> + return err;
> +
> + err = stmt_evaluate_addr(ctx, stmt, stmt->nat.family,
> + &stmt->nat.addr);
> if (err < 0)
> return err;
> }
> @@ -2573,9 +2644,7 @@ static int stmt_evaluate_nat(struct eval_ctx *ctx,
> struct stmt *stmt)
>
> static int stmt_evaluate_tproxy(struct eval_ctx *ctx, struct stmt *stmt)
> {
> - const struct proto_desc *nproto;
> - const struct datatype *dtype;
> - int err, len;
> + int err;
>
> switch (ctx->pctx.family) {
> case NFPROTO_IPV4:
> @@ -2597,43 +2666,19 @@ static int stmt_evaluate_tproxy(struct eval_ctx *ctx,
> struct stmt *stmt)
> if (!stmt->tproxy.addr && !stmt->tproxy.port)
> return stmt_error(ctx, stmt, "Either address or port must be
> specified!");
>
> - nproto = ctx->pctx.protocol[PROTO_BASE_NETWORK_HDR].desc;
> - if ((nproto == &proto_ip && stmt->tproxy.family != NFPROTO_IPV4) ||
> - (nproto == &proto_ip6 && stmt->tproxy.family != NFPROTO_IPV6))
> - /* this prevents us from rules like
> - * ip protocol tcp tproxy ip6 to [dead::beef]
> - */
> - return stmt_error(ctx, stmt,
> - "Conflicting network layer protocols.");
> + err = stmt_evaluate_l3proto(ctx, stmt, stmt->tproxy.family);
> + if (err < 0)
> + return err;
>
> if (stmt->tproxy.addr != NULL) {
> if (stmt->tproxy.addr->etype == 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;
> - }
> +
> + err = stmt_evaluate_addr(ctx, stmt, stmt->tproxy.family,
> + &stmt->tproxy.addr);
> +
> + if (err < 0)
> + return err;
> }
>
> if (stmt->tproxy.port != NULL) {
> diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c
> index d0eaf5b62203..7b09dba60a2c 100644
> --- a/src/netlink_delinearize.c
> +++ b/src/netlink_delinearize.c
> @@ -930,6 +930,9 @@ static void netlink_parse_nat(struct netlink_parse_ctx
> *ctx,
>
> family = nftnl_expr_get_u32(nle, NFTNL_EXPR_NAT_FAMILY);
>
> + if (ctx->table->handle.family == NFPROTO_INET)
> + stmt->nat.family = family;
> +
> if (nftnl_expr_is_set(nle, NFTNL_EXPR_NAT_FLAGS))
> stmt->nat.flags = nftnl_expr_get_u32(nle, NFTNL_EXPR_NAT_FLAGS);
>
> diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c
> index 61149bffcc83..df40ee7b6cff 100644
> --- a/src/netlink_linearize.c
> +++ b/src/netlink_linearize.c
> @@ -1024,7 +1024,7 @@ static void netlink_gen_nat_stmt(struct
> netlink_linearize_ctx *ctx,
> nle = alloc_nft_expr("nat");
> nftnl_expr_set_u32(nle, NFTNL_EXPR_NAT_TYPE, stmt->nat.type);
>
> - family = nftnl_rule_get_u32(ctx->nlr, NFTNL_RULE_FAMILY);
> + family = stmt->nat.family;
> nftnl_expr_set_u32(nle, NFTNL_EXPR_NAT_FAMILY, family);
>
> nftnl_flag_attr = NFTNL_EXPR_NAT_FLAGS;
> diff --git a/src/parser_bison.y b/src/parser_bison.y
> index 65b3fb3ebac2..9764b7e13aa6 100644
> --- a/src/parser_bison.y
> +++ b/src/parser_bison.y
> @@ -2810,6 +2810,11 @@ nat_stmt_args : stmt_expr
> {
> $<stmt>0->nat.addr = $2;
> }
> + | nf_key_proto TO stmt_expr
> + {
> + $<stmt>0->nat.family = $1;
> + $<stmt>0->nat.addr = $3;
> + }
> | stmt_expr COLON stmt_expr
> {
> $<stmt>0->nat.addr = $1;
> @@ -2820,6 +2825,12 @@ nat_stmt_args : stmt_expr
> $<stmt>0->nat.addr = $2;
> $<stmt>0->nat.proto = $4;
> }
> + | nf_key_proto TO stmt_expr COLON
> stmt_expr
> + {
> + $<stmt>0->nat.family = $1;
> + $<stmt>0->nat.addr = $3;
> + $<stmt>0->nat.proto = $5;
> + }
> | COLON stmt_expr
> {
> $<stmt>0->nat.proto = $2;
> diff --git a/src/statement.c b/src/statement.c
> index b9324fd7b2ed..de74a15529b0 100644
> --- a/src/statement.c
> +++ b/src/statement.c
> @@ -567,8 +567,18 @@ const char *nat_etype2str(enum nft_nat_etypes type)
> static void nat_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
> {
> nft_print(octx, "%s", nat_etype2str(stmt->nat.type));
> - if (stmt->nat.addr || stmt->nat.proto)
> + if (stmt->nat.addr || stmt->nat.proto) {
> + switch (stmt->nat.family) {
> + case NFPROTO_IPV4:
> + nft_print(octx, " ip");
> + break;
> + case NFPROTO_IPV6:
> + nft_print(octx, " ip6");
> + break;
> + }
> +
> nft_print(octx, " to");
> + }
>
> if (stmt->nat.addr) {
> nft_print(octx, " ");
> diff --git a/tests/py/inet/dnat.t b/tests/py/inet/dnat.t
> new file mode 100644
> index 000000000000..fcdf9436c676
> --- /dev/null
> +++ b/tests/py/inet/dnat.t
> @@ -0,0 +1,16 @@
> +:prerouting;type nat hook prerouting priority 0
> +
> +*inet;test-inet;prerouting
> +
> +iifname "foo" tcp dport 80 redirect to :8080;ok
> +
> +iifname "eth0" tcp dport 443 dnat ip to 192.168.3.2;ok
> +iifname "eth0" tcp dport 443 dnat ip6 to [dead::beef]:4443;ok
> +
> +dnat ip to ct mark map { 0x00000014 : 1.2.3.4};ok
> +dnat ip to ct mark . ip daddr map { 0x00000014 . 1.1.1.1 : 1.2.3.4};ok
> +
> +dnat ip6 to 1.2.3.4;fail
> +dnat to 1.2.3.4;fail
> +dnat ip6 to ct mark . ip daddr map { 0x00000014 . 1.1.1.1 : 1.2.3.4};fail
> +ip6 daddr dead::beef dnat to 10.1.2.3;fail
> diff --git a/tests/py/inet/dnat.t.payload b/tests/py/inet/dnat.t.payload
> new file mode 100644
> index 000000000000..b81caf7bc66d
> --- /dev/null
> +++ b/tests/py/inet/dnat.t.payload
> @@ -0,0 +1,54 @@
> +# iifname "foo" tcp dport 80 redirect to :8080
> +inet test-inet prerouting
> + [ meta load iifname => reg 1 ]
> + [ cmp eq reg 1 0x006f6f66 0x00000000 0x00000000 0x00000000 ]
> + [ meta load l4proto => reg 1 ]
> + [ cmp eq reg 1 0x00000006 ]
> + [ payload load 2b @ transport header + 2 => reg 1 ]
> + [ cmp eq reg 1 0x00005000 ]
> + [ immediate reg 1 0x0000901f ]
> + [ redir proto_min reg 1 ]
> +
> +# iifname "eth0" tcp dport 443 dnat ip to 192.168.3.2
> +inet test-inet prerouting
> + [ meta load iifname => reg 1 ]
> + [ cmp eq reg 1 0x30687465 0x00000000 0x00000000 0x00000000 ]
> + [ meta load l4proto => reg 1 ]
> + [ cmp eq reg 1 0x00000006 ]
> + [ payload load 2b @ transport header + 2 => reg 1 ]
> + [ cmp eq reg 1 0x0000bb01 ]
> + [ immediate reg 1 0x0203a8c0 ]
> + [ nat dnat ip addr_min reg 1 addr_max reg 0 ]
> +
> +# iifname "eth0" tcp dport 443 dnat ip6 to [dead::beef]:4443
> +inet test-inet prerouting
> + [ meta load iifname => reg 1 ]
> + [ cmp eq reg 1 0x30687465 0x00000000 0x00000000 0x00000000 ]
> + [ meta load l4proto => reg 1 ]
> + [ cmp eq reg 1 0x00000006 ]
> + [ payload load 2b @ transport header + 2 => reg 1 ]
> + [ cmp eq reg 1 0x0000bb01 ]
> + [ immediate reg 1 0x0000adde 0x00000000 0x00000000 0xefbe0000 ]
> + [ immediate reg 2 0x00005b11 ]
> + [ nat dnat ip6 addr_min reg 1 addr_max reg 0 proto_min reg 2 proto_max reg
> 0 ]
> +
> +# dnat ip to ct mark map { 0x00000014 : 1.2.3.4}
> +__map%d test-inet b size 1
> +__map%d test-inet 0
> + element 00000014 : 04030201 0 [end]
> +inet test-inet prerouting
> + [ ct load mark => reg 1 ]
> + [ lookup reg 1 set __map%d dreg 1 ]
> + [ nat dnat ip addr_min reg 1 addr_max reg 0 ]
> +
> +# dnat ip to ct mark . ip daddr map { 0x00000014 . 1.1.1.1 : 1.2.3.4}
> +__map%d test-inet b size 1
> +__map%d test-inet 0
> + element 00000014 01010101 : 04030201 0 [end]
> +inet test-inet prerouting
> + [ meta load nfproto => reg 1 ]
> + [ cmp eq reg 1 0x00000002 ]
> + [ ct load mark => reg 1 ]
> + [ payload load 4b @ network header + 16 => reg 9 ]
> + [ lookup reg 1 set __map%d dreg 1 ]
> + [ nat dnat ip addr_min reg 1 addr_max reg 0 ]
> diff --git a/tests/py/inet/snat.t b/tests/py/inet/snat.t
> new file mode 100644
> index 000000000000..cf23b5cff1bb
> --- /dev/null
> +++ b/tests/py/inet/snat.t
> @@ -0,0 +1,21 @@
> +:postrouting;type nat hook postrouting priority 0
> +
> +*inet;test-inet;postrouting
> +
> +# explicit family: 'snat to ip':
> +iifname "eth0" tcp dport 81 snat ip to 192.168.3.2;ok
> +
> +# infer snat target family from network header base:
> +iifname "eth0" tcp dport 81 ip saddr 10.1.1.1 snat to 192.168.3.2;ok;iifname
> "eth0" tcp dport 81 ip saddr 10.1.1.1 snat ip to 192.168.3.2
> +iifname "eth0" tcp dport 81 snat ip6 to dead::beef;ok
> +
> +iifname "foo" masquerade random;ok
> +
> +
> +snat to 192.168.3.2;fail
> +snat ip6 to 192.168.3.2;fail
> +snat to dead::beef;fail
> +snat ip to dead::beef;fail
> +snat ip daddr 1.2.3.4 to dead::beef;fail
> +snat ip daddr 1.2.3.4 ip6 to dead::beef;fail
> +snat ip6 saddr dead::beef to 1.2.3.4;fail
> diff --git a/tests/py/inet/snat.t.payload b/tests/py/inet/snat.t.payload
> new file mode 100644
> index 000000000000..00bb937fd843
> --- /dev/null
> +++ b/tests/py/inet/snat.t.payload
> @@ -0,0 +1,42 @@
> +# iifname "eth0" tcp dport 81 snat ip to 192.168.3.2
> +inet test-inet postrouting
> + [ meta load iifname => reg 1 ]
> + [ cmp eq reg 1 0x30687465 0x00000000 0x00000000 0x00000000 ]
> + [ meta load l4proto => reg 1 ]
> + [ cmp eq reg 1 0x00000006 ]
> + [ payload load 2b @ transport header + 2 => reg 1 ]
> + [ cmp eq reg 1 0x00005100 ]
> + [ immediate reg 1 0x0203a8c0 ]
> + [ nat snat ip addr_min reg 1 addr_max reg 0 ]
> +
> +# iifname "eth0" tcp dport 81 ip saddr 10.1.1.1 snat to 192.168.3.2
> +inet test-inet postrouting
> + [ meta load iifname => reg 1 ]
> + [ cmp eq reg 1 0x30687465 0x00000000 0x00000000 0x00000000 ]
> + [ meta load l4proto => reg 1 ]
> + [ cmp eq reg 1 0x00000006 ]
> + [ payload load 2b @ transport header + 2 => reg 1 ]
> + [ cmp eq reg 1 0x00005100 ]
> + [ meta load nfproto => reg 1 ]
> + [ cmp eq reg 1 0x00000002 ]
> + [ payload load 4b @ network header + 12 => reg 1 ]
> + [ cmp eq reg 1 0x0101010a ]
> + [ immediate reg 1 0x0203a8c0 ]
> + [ nat snat ip addr_min reg 1 addr_max reg 0 ]
> +
> +# iifname "eth0" tcp dport 81 snat ip6 to dead::beef
> +inet test-inet postrouting
> + [ meta load iifname => reg 1 ]
> + [ cmp eq reg 1 0x30687465 0x00000000 0x00000000 0x00000000 ]
> + [ meta load l4proto => reg 1 ]
> + [ cmp eq reg 1 0x00000006 ]
> + [ payload load 2b @ transport header + 2 => reg 1 ]
> + [ cmp eq reg 1 0x00005100 ]
> + [ immediate reg 1 0x0000adde 0x00000000 0x00000000 0xefbe0000 ]
> + [ nat snat ip6 addr_min reg 1 addr_max reg 0 ]
> +
> +# iifname "foo" masquerade random
> +inet test-inet postrouting
> + [ meta load iifname => reg 1 ]
> + [ cmp eq reg 1 0x006f6f66 0x00000000 0x00000000 0x00000000 ]
> + [ masq flags 0x4 ]
> --
> 2.19.2
>