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 <fw@xxxxxxxxx> Acked-by: Pablo Neira Ayuso <pablo@xxxxxxxxxxxxx> 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 >