Re: [PATCH nftables] src: add nat support for the inet family

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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
> 



[Index of Archives]     [Netfitler Users]     [Berkeley Packet Filter]     [LARTC]     [Bugtraq]     [Yosemite Forum]

  Powered by Linux