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@xxxxxxxxxxxxxxxxxxx> Signed-off-by: Anthony Lineham <anthony.lineham@xxxxxxxxxxxxxxxxxxx> Co-developed-by: Scott Parlane <scott.parlane@xxxxxxxxxxxxxxxxxxx> Signed-off-by: Scott Parlane <scott.parlane@xxxxxxxxxxxxxxxxxxx> Co-developed-by: Blair Steven <blair.steven@xxxxxxxxxxxxxxxxxxx> Signed-off-by: Blair Steven <blair.steven@xxxxxxxxxxxxxxxxxxx> Signed-off-by: Cole Dishington <Cole.Dishington@xxxxxxxxxxxxxxxxxxx> --- 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