diff mbox series

net: netfilter: Fix port selection of FTP for NF_NAT_RANGE_PROTO_SPECIFIED

Message ID 20210728032134.21983-1-Cole.Dishington@alliedtelesis.co.nz (mailing list archive)
State Awaiting Upstream
Delegated to: Netdev Maintainers
Headers show
Series net: netfilter: Fix port selection of FTP for NF_NAT_RANGE_PROTO_SPECIFIED | expand

Checks

Context Check Description
netdev/cover_letter success Link
netdev/fixes_present success Link
netdev/patch_count success Link
netdev/tree_selection success Guessed tree name to be net-next
netdev/subject_prefix warning Target tree name not specified in the subject
netdev/cc_maintainers success CCed 8 of 8 maintainers
netdev/source_inline success Was 0 now: 0
netdev/verify_signedoff success Link
netdev/module_param success Was 0 now: 0
netdev/build_32bit fail Errors and warnings before: 152 this patch: 158
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/verify_fixes success Link
netdev/checkpatch success total: 0 errors, 0 warnings, 0 checks, 100 lines checked
netdev/build_allmodconfig_warn fail Errors and warnings before: 150 this patch: 156
netdev/header_inline success Link

Commit Message

Cole Dishington July 28, 2021, 3:21 a.m. UTC
FTP port selection ignores specified port ranges (with iptables
masquerade --to-ports) when creating an expectation, based on
FTP commands PORT or PASV, for the data connection.

Co-developed-by: Anthony Lineham <anthony.lineham@alliedtelesis.co.nz>
Signed-off-by: Anthony Lineham <anthony.lineham@alliedtelesis.co.nz>
Co-developed-by: Scott Parlane <scott.parlane@alliedtelesis.co.nz>
Signed-off-by: Scott Parlane <scott.parlane@alliedtelesis.co.nz>
Co-developed-by: Blair Steven <blair.steven@alliedtelesis.co.nz>
Signed-off-by: Blair Steven <blair.steven@alliedtelesis.co.nz>
Signed-off-by: Cole Dishington <Cole.Dishington@alliedtelesis.co.nz>
---

Notes:
    Currently with iptables -t nat -j MASQUERADE -p tcp --to-ports 10000-10005,
    creating a passive ftp connection from a client will result in the control
    connection being within the specified port range but the data connection being
    outside of the range. This patch fixes this behaviour to have both connections
    be in the specified range.

 include/net/netfilter/nf_conntrack.h |  3 +++
 net/netfilter/nf_nat_core.c          | 10 ++++++----
 net/netfilter/nf_nat_ftp.c           | 26 ++++++++++++--------------
 net/netfilter/nf_nat_helper.c        | 12 ++++++++----
 4 files changed, 29 insertions(+), 22 deletions(-)

Comments

kernel test robot July 28, 2021, 5:23 a.m. UTC | #1
Hi Cole,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on nf-next/master]
[also build test WARNING on nf/master ipvs/master v5.14-rc3 next-20210727]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Cole-Dishington/net-netfilter-Fix-port-selection-of-FTP-for-NF_NAT_RANGE_PROTO_SPECIFIED/20210728-112306
base:   https://git.kernel.org/pub/scm/linux/kernel/git/pablo/nf-next.git master
config: xtensa-allyesconfig (attached as .config)
compiler: xtensa-linux-gcc (GCC) 10.3.0
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/0day-ci/linux/commit/2e0f4c593d92890a9a5b0098b3f20a6486b4019d
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review Cole-Dishington/net-netfilter-Fix-port-selection-of-FTP-for-NF_NAT_RANGE_PROTO_SPECIFIED/20210728-112306
        git checkout 2e0f4c593d92890a9a5b0098b3f20a6486b4019d
        # save the attached .config to linux build tree
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-10.3.0 make.cross ARCH=xtensa 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All warnings (new ones prefixed by >>):

>> net/netfilter/nf_nat_core.c:363:6: warning: no previous prototype for 'nf_nat_l4proto_unique_tuple' [-Wmissing-prototypes]
     363 | void nf_nat_l4proto_unique_tuple(struct nf_conntrack_tuple *tuple,
         |      ^~~~~~~~~~~~~~~~~~~~~~~~~~~


vim +/nf_nat_l4proto_unique_tuple +363 net/netfilter/nf_nat_core.c

   357	
   358	/* Alter the per-proto part of the tuple (depending on maniptype), to
   359	 * give a unique tuple in the given range if possible.
   360	 *
   361	 * Per-protocol part of tuple is initialized to the incoming packet.
   362	 */
 > 363	void nf_nat_l4proto_unique_tuple(struct nf_conntrack_tuple *tuple,
   364					 const struct nf_nat_range2 *range,
   365					 enum nf_nat_manip_type maniptype,
   366					 const struct nf_conn *ct)
   367	{
   368		unsigned int range_size, min, max, i, attempts;
   369		__be16 *keyptr;
   370		u16 off;
   371		static const unsigned int max_attempts = 128;
   372	
   373		switch (tuple->dst.protonum) {
   374		case IPPROTO_ICMP:
   375		case IPPROTO_ICMPV6:
   376			/* id is same for either direction... */
   377			keyptr = &tuple->src.u.icmp.id;
   378			if (!(range->flags & NF_NAT_RANGE_PROTO_SPECIFIED)) {
   379				min = 0;
   380				range_size = 65536;
   381			} else {
   382				min = ntohs(range->min_proto.icmp.id);
   383				range_size = ntohs(range->max_proto.icmp.id) -
   384					     ntohs(range->min_proto.icmp.id) + 1;
   385			}
   386			goto find_free_id;
   387	#if IS_ENABLED(CONFIG_NF_CT_PROTO_GRE)
   388		case IPPROTO_GRE:
   389			/* If there is no master conntrack we are not PPTP,
   390			   do not change tuples */
   391			if (!ct->master)
   392				return;
   393	
   394			if (maniptype == NF_NAT_MANIP_SRC)
   395				keyptr = &tuple->src.u.gre.key;
   396			else
   397				keyptr = &tuple->dst.u.gre.key;
   398	
   399			if (!(range->flags & NF_NAT_RANGE_PROTO_SPECIFIED)) {
   400				min = 1;
   401				range_size = 65535;
   402			} else {
   403				min = ntohs(range->min_proto.gre.key);
   404				range_size = ntohs(range->max_proto.gre.key) - min + 1;
   405			}
   406			goto find_free_id;
   407	#endif
   408		case IPPROTO_UDP:
   409		case IPPROTO_UDPLITE:
   410		case IPPROTO_TCP:
   411		case IPPROTO_SCTP:
   412		case IPPROTO_DCCP:
   413			if (maniptype == NF_NAT_MANIP_SRC)
   414				keyptr = &tuple->src.u.all;
   415			else
   416				keyptr = &tuple->dst.u.all;
   417	
   418			break;
   419		default:
   420			return;
   421		}
   422	
   423		/* If no range specified... */
   424		if (!(range->flags & NF_NAT_RANGE_PROTO_SPECIFIED)) {
   425			/* If it's dst rewrite, can't change port */
   426			if (maniptype == NF_NAT_MANIP_DST)
   427				return;
   428	
   429			if (ntohs(*keyptr) < 1024) {
   430				/* Loose convention: >> 512 is credential passing */
   431				if (ntohs(*keyptr) < 512) {
   432					min = 1;
   433					range_size = 511 - min + 1;
   434				} else {
   435					min = 600;
   436					range_size = 1023 - min + 1;
   437				}
   438			} else {
   439				min = 1024;
   440				range_size = 65535 - 1024 + 1;
   441			}
   442		} else {
   443			min = ntohs(range->min_proto.all);
   444			max = ntohs(range->max_proto.all);
   445			if (unlikely(max < min))
   446				swap(max, min);
   447			range_size = max - min + 1;
   448		}
   449	
   450	find_free_id:
   451		if (range->flags & NF_NAT_RANGE_PROTO_OFFSET)
   452			off = (ntohs(*keyptr) - ntohs(range->base_proto.all));
   453		else
   454			off = prandom_u32();
   455	
   456		attempts = range_size;
   457		if (attempts > max_attempts)
   458			attempts = max_attempts;
   459	
   460		/* We are in softirq; doing a search of the entire range risks
   461		 * soft lockup when all tuples are already used.
   462		 *
   463		 * If we can't find any free port from first offset, pick a new
   464		 * one and try again, with ever smaller search window.
   465		 */
   466	another_round:
   467		for (i = 0; i < attempts; i++, off++) {
   468			*keyptr = htons(min + off % range_size);
   469			if (!nf_nat_used_tuple(tuple, ct))
   470				return;
   471		}
   472	
   473		if (attempts >= range_size || attempts < 16)
   474			return;
   475		attempts /= 2;
   476		off = prandom_u32();
   477		goto another_round;
   478	}
   479	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
Florian Westphal July 28, 2021, 9:06 a.m. UTC | #2
Cole Dishington <Cole.Dishington@alliedtelesis.co.nz> wrote:
> FTP port selection ignores specified port ranges (with iptables
> masquerade --to-ports) when creating an expectation, based on
> FTP commands PORT or PASV, for the data connection.
> 
> Co-developed-by: Anthony Lineham <anthony.lineham@alliedtelesis.co.nz>
> Signed-off-by: Anthony Lineham <anthony.lineham@alliedtelesis.co.nz>
> Co-developed-by: Scott Parlane <scott.parlane@alliedtelesis.co.nz>
> Signed-off-by: Scott Parlane <scott.parlane@alliedtelesis.co.nz>
> Co-developed-by: Blair Steven <blair.steven@alliedtelesis.co.nz>
> Signed-off-by: Blair Steven <blair.steven@alliedtelesis.co.nz>
> Signed-off-by: Cole Dishington <Cole.Dishington@alliedtelesis.co.nz>
> ---
> 
> Notes:
>     Currently with iptables -t nat -j MASQUERADE -p tcp --to-ports 10000-10005,
>     creating a passive ftp connection from a client will result in the control
>     connection being within the specified port range but the data connection being
>     outside of the range. This patch fixes this behaviour to have both connections
>     be in the specified range.
> 
>  include/net/netfilter/nf_conntrack.h |  3 +++
>  net/netfilter/nf_nat_core.c          | 10 ++++++----
>  net/netfilter/nf_nat_ftp.c           | 26 ++++++++++++--------------
>  net/netfilter/nf_nat_helper.c        | 12 ++++++++----
>  4 files changed, 29 insertions(+), 22 deletions(-)
> 
> diff --git a/include/net/netfilter/nf_conntrack.h b/include/net/netfilter/nf_conntrack.h
> index cc663c68ddc4..b98d5d04c7ab 100644
> --- a/include/net/netfilter/nf_conntrack.h
> +++ b/include/net/netfilter/nf_conntrack.h
> @@ -24,6 +24,8 @@
>  
>  #include <net/netfilter/nf_conntrack_tuple.h>
>  
> +#include <uapi/linux/netfilter/nf_nat.h>
> +
>  struct nf_ct_udp {
>  	unsigned long	stream_ts;
>  };
> @@ -99,6 +101,7 @@ struct nf_conn {
>  
>  #if IS_ENABLED(CONFIG_NF_NAT)
>  	struct hlist_node	nat_bysource;
> +	struct nf_nat_range2 range;
>  #endif

Thats almost a 20% size increase of this structure.

Could you try to rework it based on this?
diff --git a/include/net/netfilter/nf_nat.h b/include/net/netfilter/nf_nat.h
--- a/include/net/netfilter/nf_nat.h
+++ b/include/net/netfilter/nf_nat.h
@@ -27,12 +27,18 @@ union nf_conntrack_nat_help {
 #endif
 };
 
+struct nf_conn_nat_range_info {
+	union nf_conntrack_man_proto	min_proto;
+	union nf_conntrack_man_proto	max_proto;
+};
+
 /* The structure embedded in the conntrack structure. */
 struct nf_conn_nat {
 	union nf_conntrack_nat_help help;
 #if IS_ENABLED(CONFIG_NF_NAT_MASQUERADE)
 	int masq_index;
 #endif
+	struct nf_conn_nat_range_info range_info;
 };
 
 /* Set up the info structure to map into this range. */

... and then store the range min/max proto iff nf_nat_setup_info had
NF_NAT_RANGE_PROTO_SPECIFIED flag set.

I don't think there is a need to keep the information in nf_conn.
kernel test robot July 28, 2021, 10:30 a.m. UTC | #3
Hi Cole,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on nf-next/master]
[also build test ERROR on nf/master ipvs/master v5.14-rc3 next-20210727]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Cole-Dishington/net-netfilter-Fix-port-selection-of-FTP-for-NF_NAT_RANGE_PROTO_SPECIFIED/20210728-112306
base:   https://git.kernel.org/pub/scm/linux/kernel/git/pablo/nf-next.git master
config: x86_64-randconfig-a016-20210728 (attached as .config)
compiler: clang version 13.0.0 (https://github.com/llvm/llvm-project c49df15c278857adecd12db6bb1cdc96885f7079)
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # install x86_64 cross compiling tool for clang build
        # apt-get install binutils-x86-64-linux-gnu
        # https://github.com/0day-ci/linux/commit/2e0f4c593d92890a9a5b0098b3f20a6486b4019d
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review Cole-Dishington/net-netfilter-Fix-port-selection-of-FTP-for-NF_NAT_RANGE_PROTO_SPECIFIED/20210728-112306
        git checkout 2e0f4c593d92890a9a5b0098b3f20a6486b4019d
        # save the attached .config to linux build tree
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross ARCH=x86_64 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All error/warnings (new ones prefixed by >>, old ones prefixed by <<):

>> ERROR: modpost: "nf_nat_l4proto_unique_tuple" [net/netfilter/nf_nat_ftp.ko] undefined!
--
>> net/netfilter/nf_nat_core.c:363:6: warning: no previous prototype for function 'nf_nat_l4proto_unique_tuple' [-Wmissing-prototypes]
   void nf_nat_l4proto_unique_tuple(struct nf_conntrack_tuple *tuple,
        ^
   net/netfilter/nf_nat_core.c:363:1: note: declare 'static' if the function is not intended to be used outside of this translation unit
   void nf_nat_l4proto_unique_tuple(struct nf_conntrack_tuple *tuple,
   ^
   static 
   1 warning generated.

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
kernel test robot July 28, 2021, 11:09 a.m. UTC | #4
Hi Cole,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on nf-next/master]
[also build test ERROR on nf/master ipvs/master v5.14-rc3]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Cole-Dishington/net-netfilter-Fix-port-selection-of-FTP-for-NF_NAT_RANGE_PROTO_SPECIFIED/20210728-112306
base:   https://git.kernel.org/pub/scm/linux/kernel/git/pablo/nf-next.git master
config: x86_64-randconfig-c002-20210728 (attached as .config)
compiler: gcc-10 (Ubuntu 10.3.0-1ubuntu1~20.04) 10.3.0
reproduce (this is a W=1 build):
        # https://github.com/0day-ci/linux/commit/2e0f4c593d92890a9a5b0098b3f20a6486b4019d
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review Cole-Dishington/net-netfilter-Fix-port-selection-of-FTP-for-NF_NAT_RANGE_PROTO_SPECIFIED/20210728-112306
        git checkout 2e0f4c593d92890a9a5b0098b3f20a6486b4019d
        # save the attached .config to linux build tree
        make W=1 ARCH=x86_64 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All errors (new ones prefixed by >>, old ones prefixed by <<):

>> ERROR: modpost: "nf_nat_l4proto_unique_tuple" [net/netfilter/nf_nat_ftp.ko] undefined!

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
diff mbox series

Patch

diff --git a/include/net/netfilter/nf_conntrack.h b/include/net/netfilter/nf_conntrack.h
index cc663c68ddc4..b98d5d04c7ab 100644
--- a/include/net/netfilter/nf_conntrack.h
+++ b/include/net/netfilter/nf_conntrack.h
@@ -24,6 +24,8 @@ 
 
 #include <net/netfilter/nf_conntrack_tuple.h>
 
+#include <uapi/linux/netfilter/nf_nat.h>
+
 struct nf_ct_udp {
 	unsigned long	stream_ts;
 };
@@ -99,6 +101,7 @@  struct nf_conn {
 
 #if IS_ENABLED(CONFIG_NF_NAT)
 	struct hlist_node	nat_bysource;
+	struct nf_nat_range2 range;
 #endif
 	/* all members below initialized via memset */
 	struct { } __nfct_init_offset;
diff --git a/net/netfilter/nf_nat_core.c b/net/netfilter/nf_nat_core.c
index 7de595ead06a..4772c8457ef2 100644
--- a/net/netfilter/nf_nat_core.c
+++ b/net/netfilter/nf_nat_core.c
@@ -360,10 +360,10 @@  find_best_ips_proto(const struct nf_conntrack_zone *zone,
  *
  * Per-protocol part of tuple is initialized to the incoming packet.
  */
-static void nf_nat_l4proto_unique_tuple(struct nf_conntrack_tuple *tuple,
-					const struct nf_nat_range2 *range,
-					enum nf_nat_manip_type maniptype,
-					const struct nf_conn *ct)
+void nf_nat_l4proto_unique_tuple(struct nf_conntrack_tuple *tuple,
+				 const struct nf_nat_range2 *range,
+				 enum nf_nat_manip_type maniptype,
+				 const struct nf_conn *ct)
 {
 	unsigned int range_size, min, max, i, attempts;
 	__be16 *keyptr;
@@ -586,6 +586,8 @@  nf_nat_setup_info(struct nf_conn *ct,
 			   &ct->tuplehash[IP_CT_DIR_REPLY].tuple);
 
 	get_unique_tuple(&new_tuple, &curr_tuple, range, ct, maniptype);
+	if (range)
+		ct->range = *range;
 
 	if (!nf_ct_tuple_equal(&new_tuple, &curr_tuple)) {
 		struct nf_conntrack_tuple reply;
diff --git a/net/netfilter/nf_nat_ftp.c b/net/netfilter/nf_nat_ftp.c
index aace6768a64e..6794aa77162b 100644
--- a/net/netfilter/nf_nat_ftp.c
+++ b/net/netfilter/nf_nat_ftp.c
@@ -17,6 +17,10 @@ 
 #include <net/netfilter/nf_conntrack_helper.h>
 #include <net/netfilter/nf_conntrack_expect.h>
 #include <linux/netfilter/nf_conntrack_ftp.h>
+void nf_nat_l4proto_unique_tuple(struct nf_conntrack_tuple *tuple,
+				 const struct nf_nat_range2 *range,
+				 enum nf_nat_manip_type maniptype,
+				 const struct nf_conn *ct);
 
 #define NAT_HELPER_NAME "ftp"
 
@@ -74,6 +78,7 @@  static unsigned int nf_nat_ftp(struct sk_buff *skb,
 	struct nf_conn *ct = exp->master;
 	char buffer[sizeof("|1||65535|") + INET6_ADDRSTRLEN];
 	unsigned int buflen;
+	int ret;
 
 	pr_debug("type %i, off %u len %u\n", type, matchoff, matchlen);
 
@@ -86,21 +91,14 @@  static unsigned int nf_nat_ftp(struct sk_buff *skb,
 	 * this one. */
 	exp->expectfn = nf_nat_follow_master;
 
-	/* Try to get same port: if not, try to change it. */
-	for (port = ntohs(exp->saved_proto.tcp.port); port != 0; port++) {
-		int ret;
-
-		exp->tuple.dst.u.tcp.port = htons(port);
-		ret = nf_ct_expect_related(exp, 0);
-		if (ret == 0)
-			break;
-		else if (ret != -EBUSY) {
-			port = 0;
-			break;
-		}
-	}
+	/* Find a port that matches the NAT rule */
+	nf_nat_l4proto_unique_tuple(&exp->tuple, &ct->range,
+				    dir ? NF_NAT_MANIP_SRC : NF_NAT_MANIP_DST,
+				    ct);
+	port = ntohs(exp->tuple.dst.u.tcp.port);
+	ret = nf_ct_expect_related(exp, 0);
 
-	if (port == 0) {
+	if ((ret != 0 && ret != -EBUSY) || port == 0) {
 		nf_ct_helper_log(skb, ct, "all ports in use");
 		return NF_DROP;
 	}
diff --git a/net/netfilter/nf_nat_helper.c b/net/netfilter/nf_nat_helper.c
index a263505455fc..912bf50be58a 100644
--- a/net/netfilter/nf_nat_helper.c
+++ b/net/netfilter/nf_nat_helper.c
@@ -184,10 +184,14 @@  void nf_nat_follow_master(struct nf_conn *ct,
 	/* This must be a fresh one. */
 	BUG_ON(ct->status & IPS_NAT_DONE_MASK);
 
-	/* Change src to where master sends to */
-	range.flags = NF_NAT_RANGE_MAP_IPS;
-	range.min_addr = range.max_addr
-		= ct->master->tuplehash[!exp->dir].tuple.dst.u3;
+	if (exp->master && !exp->dir) {
+		range = exp->master->range;
+	} else {
+		/* Change src to where master sends to */
+		range.flags = NF_NAT_RANGE_MAP_IPS;
+		range.min_addr = ct->master->tuplehash[!exp->dir].tuple.dst.u3;
+		range.max_addr = ct->master->tuplehash[!exp->dir].tuple.dst.u3;
+	}
 	nf_nat_setup_info(ct, &range, NF_NAT_MANIP_SRC);
 
 	/* For DST manip, map port here to where it's expected. */