2021-07-28 03:23:41

by Cole Dishington

[permalink] [raw]
Subject: [PATCH] net: netfilter: Fix port selection of FTP for NF_NAT_RANGE_PROTO_SPECIFIED

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 <[email protected]>
Signed-off-by: Anthony Lineham <[email protected]>
Co-developed-by: Scott Parlane <[email protected]>
Signed-off-by: Scott Parlane <[email protected]>
Co-developed-by: Blair Steven <[email protected]>
Signed-off-by: Blair Steven <[email protected]>
Signed-off-by: Cole Dishington <[email protected]>
---

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
/* 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. */
--
2.32.0



2021-07-28 05:26:26

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH] net: netfilter: Fix port selection of FTP for NF_NAT_RANGE_PROTO_SPECIFIED

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 <[email protected]>

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/[email protected]


Attachments:
(No filename) (5.91 kB)
.config.gz (66.29 kB)
Download all attachments

2021-07-28 09:07:46

by Florian Westphal

[permalink] [raw]
Subject: Re: [PATCH] net: netfilter: Fix port selection of FTP for NF_NAT_RANGE_PROTO_SPECIFIED

Cole Dishington <[email protected]> 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 <[email protected]>
> Signed-off-by: Anthony Lineham <[email protected]>
> Co-developed-by: Scott Parlane <[email protected]>
> Signed-off-by: Scott Parlane <[email protected]>
> Co-developed-by: Blair Steven <[email protected]>
> Signed-off-by: Blair Steven <[email protected]>
> Signed-off-by: Cole Dishington <[email protected]>
> ---
>
> 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.


2021-07-28 10:35:50

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH] net: netfilter: Fix port selection of FTP for NF_NAT_RANGE_PROTO_SPECIFIED

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 <[email protected]>

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/[email protected]


Attachments:
(No filename) (2.40 kB)
.config.gz (28.69 kB)
Download all attachments

2021-07-28 11:15:40

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH] net: netfilter: Fix port selection of FTP for NF_NAT_RANGE_PROTO_SPECIFIED

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 <[email protected]>

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/[email protected]


Attachments:
(No filename) (1.56 kB)
.config.gz (37.79 kB)
Download all attachments