diff mbox series

[nf-next,05/13] netfilter: nft_nat: add support for shifted port-ranges

Message ID 20230305121817.2234734-6-jeremy@azazel.net
State Changes Requested
Delegated to: Pablo Neira
Headers show
Series Support for shifted port-ranges in NAT | expand

Commit Message

Jeremy Sowden March 5, 2023, 12:18 p.m. UTC
Commit 2eb0f624b709 ("netfilter: add NAT support for shifted portmap
ranges") introduced support for shifting port-ranges in NAT.  This
allows one to redirect packets intended for one port to another in a
range in such a way that the new port chosen has the same offset in the
range as the original port had from a specified base value.

For example, by using the base value 2000, one could redirect packets
intended for 10.0.0.1:2000-3000 to 10.10.0.1:12000-13000 so that the old
and new ports were at the same offset in their respective ranges, i.e.:

  10.0.0.1:2345 -> 10.10.0.1:12345

However, while support for this was added to the common NAT infra-
structure, only the xt_nat module was updated to make use of it.  This
commit updates the nft_nat module to allow shifted port-ranges to be
used by nftables.

In contrast to xt_nat, where shifting is only available for DNAT, both
DNAT and SNAT are supported.

Link: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=970672
Link: https://bugzilla.netfilter.org/show_bug.cgi?id=1501
Signed-off-by: Jeremy Sowden <jeremy@azazel.net>
---
 include/uapi/linux/netfilter/nf_tables.h |  2 ++
 net/netfilter/nft_nat.c                  | 38 +++++++++++++++++-------
 2 files changed, 29 insertions(+), 11 deletions(-)

Comments

Florian Westphal March 7, 2023, 12:27 p.m. UTC | #1
Jeremy Sowden <jeremy@azazel.net> wrote:
> index 5c29915ab028..0517a3efb259 100644
> --- a/net/netfilter/nft_nat.c
> +++ b/net/netfilter/nft_nat.c
> @@ -25,6 +25,7 @@ struct nft_nat {
>  	u8			sreg_addr_max;
>  	u8			sreg_proto_min;
>  	u8			sreg_proto_max;
> +	u8			sreg_proto_base;
>  	enum nf_nat_manip_type  type:8;
>  	u8			family;
>  	u16			flags;
> @@ -58,6 +59,8 @@ static void nft_nat_setup_proto(struct nf_nat_range2 *range,
>  		nft_reg_load16(&regs->data[priv->sreg_proto_min]);
>  	range->max_proto.all = (__force __be16)
>  		nft_reg_load16(&regs->data[priv->sreg_proto_max]);
> +	range->base_proto.all = (__force __be16)
> +		nft_reg_load16(&regs->data[priv->sreg_proto_base]);

Hmmm!  See below.

> -	plen = sizeof_field(struct nf_nat_range, min_proto.all);
> +	plen = sizeof_field(struct nf_nat_range2, min_proto.all);
>  	if (tb[NFTA_NAT_REG_PROTO_MIN]) {
>  		err = nft_parse_register_load(tb[NFTA_NAT_REG_PROTO_MIN],
>  					      &priv->sreg_proto_min, plen);
> @@ -239,6 +243,16 @@ static int nft_nat_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
>  						      plen);
>  			if (err < 0)
>  				return err;
> +
> +			if (tb[NFTA_NAT_REG_PROTO_BASE]) {
> +				err = nft_parse_register_load
> +					(tb[NFTA_NAT_REG_PROTO_BASE],
> +					 &priv->sreg_proto_base, plen);
> +				if (err < 0)
> +					return err;
> +
> +				priv->flags |= NF_NAT_RANGE_PROTO_OFFSET;

So sreg_proto_base is only set if tb[NFTA_NAT_REG_PROTO_BASE] gets
passed.

So, I would expect that all accesses to priv->sreg_proto_base are
guarded with a 'if (priv->sreg_proto_base)' check.

> @@ -286,7 +300,9 @@ static int nft_nat_dump(struct sk_buff *skb,
>  		if (nft_dump_register(skb, NFTA_NAT_REG_PROTO_MIN,
>  				      priv->sreg_proto_min) ||
>  		    nft_dump_register(skb, NFTA_NAT_REG_PROTO_MAX,
> -				      priv->sreg_proto_max))
> +				      priv->sreg_proto_max) ||
> +		    nft_dump_register(skb, NFTA_NAT_REG_PROTO_BASE,
> +				      priv->sreg_proto_base))

sreg_proto_min/max are only dumped when set, so NFTA_NAT_REG_PROTO_BASE
should not be dumped unconditionally either?
Jeremy Sowden March 7, 2023, 6:42 p.m. UTC | #2
On 2023-03-07, at 13:27:51 +0100, Florian Westphal wrote:
> Jeremy Sowden <jeremy@azazel.net> wrote:
> > index 5c29915ab028..0517a3efb259 100644
> > --- a/net/netfilter/nft_nat.c
> > +++ b/net/netfilter/nft_nat.c
> > @@ -25,6 +25,7 @@ struct nft_nat {
> >  	u8			sreg_addr_max;
> >  	u8			sreg_proto_min;
> >  	u8			sreg_proto_max;
> > +	u8			sreg_proto_base;
> >  	enum nf_nat_manip_type  type:8;
> >  	u8			family;
> >  	u16			flags;
> > @@ -58,6 +59,8 @@ static void nft_nat_setup_proto(struct nf_nat_range2 *range,
> >  		nft_reg_load16(&regs->data[priv->sreg_proto_min]);
> >  	range->max_proto.all = (__force __be16)
> >  		nft_reg_load16(&regs->data[priv->sreg_proto_max]);
> > +	range->base_proto.all = (__force __be16)
> > +		nft_reg_load16(&regs->data[priv->sreg_proto_base]);
> 
> Hmmm!  See below.
> 
> > -	plen = sizeof_field(struct nf_nat_range, min_proto.all);
> > +	plen = sizeof_field(struct nf_nat_range2, min_proto.all);
> >  	if (tb[NFTA_NAT_REG_PROTO_MIN]) {
> >  		err = nft_parse_register_load(tb[NFTA_NAT_REG_PROTO_MIN],
> >  					      &priv->sreg_proto_min, plen);
> > @@ -239,6 +243,16 @@ static int nft_nat_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
> >  						      plen);
> >  			if (err < 0)
> >  				return err;
> > +
> > +			if (tb[NFTA_NAT_REG_PROTO_BASE]) {
> > +				err = nft_parse_register_load
> > +					(tb[NFTA_NAT_REG_PROTO_BASE],
> > +					 &priv->sreg_proto_base, plen);
> > +				if (err < 0)
> > +					return err;
> > +
> > +				priv->flags |= NF_NAT_RANGE_PROTO_OFFSET;
> 
> So sreg_proto_base is only set if tb[NFTA_NAT_REG_PROTO_BASE] gets
> passed.
> 
> So, I would expect that all accesses to priv->sreg_proto_base are
> guarded with a 'if (priv->sreg_proto_base)' check.
> 
> > @@ -286,7 +300,9 @@ static int nft_nat_dump(struct sk_buff *skb,
> >  		if (nft_dump_register(skb, NFTA_NAT_REG_PROTO_MIN,
> >  				      priv->sreg_proto_min) ||
> >  		    nft_dump_register(skb, NFTA_NAT_REG_PROTO_MAX,
> > -				      priv->sreg_proto_max))
> > +				      priv->sreg_proto_max) ||
> > +		    nft_dump_register(skb, NFTA_NAT_REG_PROTO_BASE,
> > +				      priv->sreg_proto_base))
> 
> sreg_proto_min/max are only dumped when set, so NFTA_NAT_REG_PROTO_BASE
> should not be dumped unconditionally either?
> 

Agreed.  Will fix.

J.
diff mbox series

Patch

diff --git a/include/uapi/linux/netfilter/nf_tables.h b/include/uapi/linux/netfilter/nf_tables.h
index ff677f3a6cad..af6032720c78 100644
--- a/include/uapi/linux/netfilter/nf_tables.h
+++ b/include/uapi/linux/netfilter/nf_tables.h
@@ -1432,6 +1432,7 @@  enum nft_nat_types {
  * @NFTA_NAT_REG_PROTO_MIN: source register of proto range start (NLA_U32: nft_registers)
  * @NFTA_NAT_REG_PROTO_MAX: source register of proto range end (NLA_U32: nft_registers)
  * @NFTA_NAT_FLAGS: NAT flags (see NF_NAT_RANGE_* in linux/netfilter/nf_nat.h) (NLA_U32)
+ * @NFTA_NAT_REG_PROTO_BASE: source register of proto range base offset (NLA_U32: nft_registers)
  */
 enum nft_nat_attributes {
 	NFTA_NAT_UNSPEC,
@@ -1442,6 +1443,7 @@  enum nft_nat_attributes {
 	NFTA_NAT_REG_PROTO_MIN,
 	NFTA_NAT_REG_PROTO_MAX,
 	NFTA_NAT_FLAGS,
+	NFTA_NAT_REG_PROTO_BASE,
 	__NFTA_NAT_MAX
 };
 #define NFTA_NAT_MAX		(__NFTA_NAT_MAX - 1)
diff --git a/net/netfilter/nft_nat.c b/net/netfilter/nft_nat.c
index 5c29915ab028..0517a3efb259 100644
--- a/net/netfilter/nft_nat.c
+++ b/net/netfilter/nft_nat.c
@@ -25,6 +25,7 @@  struct nft_nat {
 	u8			sreg_addr_max;
 	u8			sreg_proto_min;
 	u8			sreg_proto_max;
+	u8			sreg_proto_base;
 	enum nf_nat_manip_type  type:8;
 	u8			family;
 	u16			flags;
@@ -58,6 +59,8 @@  static void nft_nat_setup_proto(struct nf_nat_range2 *range,
 		nft_reg_load16(&regs->data[priv->sreg_proto_min]);
 	range->max_proto.all = (__force __be16)
 		nft_reg_load16(&regs->data[priv->sreg_proto_max]);
+	range->base_proto.all = (__force __be16)
+		nft_reg_load16(&regs->data[priv->sreg_proto_base]);
 }
 
 static void nft_nat_setup_netmap(struct nf_nat_range2 *range,
@@ -126,13 +129,14 @@  static void nft_nat_eval(const struct nft_expr *expr,
 }
 
 static const struct nla_policy nft_nat_policy[NFTA_NAT_MAX + 1] = {
-	[NFTA_NAT_TYPE]		 = { .type = NLA_U32 },
-	[NFTA_NAT_FAMILY]	 = { .type = NLA_U32 },
-	[NFTA_NAT_REG_ADDR_MIN]	 = { .type = NLA_U32 },
-	[NFTA_NAT_REG_ADDR_MAX]	 = { .type = NLA_U32 },
-	[NFTA_NAT_REG_PROTO_MIN] = { .type = NLA_U32 },
-	[NFTA_NAT_REG_PROTO_MAX] = { .type = NLA_U32 },
-	[NFTA_NAT_FLAGS]	 = { .type = NLA_U32 },
+	[NFTA_NAT_TYPE]		  = { .type = NLA_U32 },
+	[NFTA_NAT_FAMILY]	  = { .type = NLA_U32 },
+	[NFTA_NAT_REG_ADDR_MIN]	  = { .type = NLA_U32 },
+	[NFTA_NAT_REG_ADDR_MAX]	  = { .type = NLA_U32 },
+	[NFTA_NAT_REG_PROTO_MIN]  = { .type = NLA_U32 },
+	[NFTA_NAT_REG_PROTO_MAX]  = { .type = NLA_U32 },
+	[NFTA_NAT_REG_PROTO_BASE] = { .type = NLA_U32 },
+	[NFTA_NAT_FLAGS]	  = { .type = NLA_U32 },
 };
 
 static int nft_nat_validate(const struct nft_ctx *ctx,
@@ -195,10 +199,10 @@  static int nft_nat_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
 
 	switch (family) {
 	case NFPROTO_IPV4:
-		alen = sizeof_field(struct nf_nat_range, min_addr.ip);
+		alen = sizeof_field(struct nf_nat_range2, min_addr.ip);
 		break;
 	case NFPROTO_IPV6:
-		alen = sizeof_field(struct nf_nat_range, min_addr.ip6);
+		alen = sizeof_field(struct nf_nat_range2, min_addr.ip6);
 		break;
 	default:
 		if (tb[NFTA_NAT_REG_ADDR_MIN])
@@ -226,7 +230,7 @@  static int nft_nat_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
 		priv->flags |= NF_NAT_RANGE_MAP_IPS;
 	}
 
-	plen = sizeof_field(struct nf_nat_range, min_proto.all);
+	plen = sizeof_field(struct nf_nat_range2, min_proto.all);
 	if (tb[NFTA_NAT_REG_PROTO_MIN]) {
 		err = nft_parse_register_load(tb[NFTA_NAT_REG_PROTO_MIN],
 					      &priv->sreg_proto_min, plen);
@@ -239,6 +243,16 @@  static int nft_nat_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
 						      plen);
 			if (err < 0)
 				return err;
+
+			if (tb[NFTA_NAT_REG_PROTO_BASE]) {
+				err = nft_parse_register_load
+					(tb[NFTA_NAT_REG_PROTO_BASE],
+					 &priv->sreg_proto_base, plen);
+				if (err < 0)
+					return err;
+
+				priv->flags |= NF_NAT_RANGE_PROTO_OFFSET;
+			}
 		} else {
 			priv->sreg_proto_max = priv->sreg_proto_min;
 		}
@@ -286,7 +300,9 @@  static int nft_nat_dump(struct sk_buff *skb,
 		if (nft_dump_register(skb, NFTA_NAT_REG_PROTO_MIN,
 				      priv->sreg_proto_min) ||
 		    nft_dump_register(skb, NFTA_NAT_REG_PROTO_MAX,
-				      priv->sreg_proto_max))
+				      priv->sreg_proto_max) ||
+		    nft_dump_register(skb, NFTA_NAT_REG_PROTO_BASE,
+				      priv->sreg_proto_base))
 			goto nla_put_failure;
 	}