Adds support for masquerading into a smaller subset of ports -
defined by the PSID values from RFC-7597 Section 5.1. This is part of
the support for MAP-E and Lightweight 4over6, which allows multiple
devices to share an IPv4 address by splitting the L4 port / id into
ranges.
Co-developed-by: Anthony Lineham <redacted>
Signed-off-by: Anthony Lineham <redacted>
Co-developed-by: Scott Parlane <redacted>
Signed-off-by: Scott Parlane <redacted>
Signed-off-by: Blair Steven <redacted>
Signed-off-by: Cole Dishington <redacted>
---
Notes:
Changes in v3:
- Select pseudo random port range (for a given psid) to search in
nf_nat_l4proto_unique_tuple(), rather than exhausive search of all
port ranges (for a given psid).
- Remove extra check in get_unique_tuple for psid, it is not needed
if NF_NAT_RANGE_PROTO_SPECIFIED is set.
net/netfilter/nf_nat_core.c | 33 +++++++++++++++++++++++++++----
net/netfilter/nf_nat_masquerade.c | 17 ++++++++++++++--
2 files changed, 44 insertions(+), 6 deletions(-)
@@ -195,13 +195,30 @@ static bool nf_nat_inet_in_range(const struct nf_conntrack_tuple *t,staticbooll4proto_in_range(conststructnf_conntrack_tuple*tuple,enumnf_nat_manip_typemaniptype,constunionnf_conntrack_man_proto*min,-constunionnf_conntrack_man_proto*max)+constunionnf_conntrack_man_proto*max,+constunionnf_conntrack_man_proto*base,+boolis_psid){__be16port;+u16psid,psid_mask,offset_mask;++/* In this case we are in PSID mode, avoid checking all ranges by computing bitmasks */+if(is_psid){+u16j=ntohs(max->all)-ntohs(min->all)+1;+u16a=(1<<16)/ntohs(base->all);++offset_mask=(a-1)*ntohs(base->all);+psid_mask=((ntohs(base->all)/j)<<1)-1;+psid=ntohs(min->all)&psid_mask;+}switch(tuple->dst.protonum){caseIPPROTO_ICMP:caseIPPROTO_ICMPV6:+if(is_psid){+return((ntohs(tuple->src.u.icmp.id)&offset_mask)!=0)&&+((ntohs(tuple->src.u.icmp.id)&psid_mask)==psid);+}returnntohs(tuple->src.u.icmp.id)>=ntohs(min->icmp.id)&&ntohs(tuple->src.u.icmp.id)<=ntohs(max->icmp.id);caseIPPROTO_GRE:/* all fall though */
@@ -55,8 +55,21 @@ nf_nat_masquerade_ipv4(struct sk_buff *skb, unsigned int hooknum,newrange.flags=range->flags|NF_NAT_RANGE_MAP_IPS;newrange.min_addr.ip=newsrc;newrange.max_addr.ip=newsrc;-newrange.min_proto=range->min_proto;-newrange.max_proto=range->max_proto;++if(range->flags&NF_NAT_RANGE_PSID){+u16off=prandom_u32();+u16base=ntohs(range->base_proto.all);+u16min=ntohs(range->min_proto.all);+u16max_off=((1<<16)/base)-1;++newrange.flags=newrange.flags|NF_NAT_RANGE_PROTO_SPECIFIED;+newrange.min_proto.all=htons(min+base*(off%max_off));+newrange.max_proto.all=htons(ntohs(newrange.min_proto.all)+ntohs(range->max_proto.all)-min);+newrange.base_proto=range->base_proto;+}else{+newrange.min_proto=range->min_proto;+newrange.max_proto=range->max_proto;+}/* Hand modified range to generic setup. */returnnf_nat_setup_info(ct,&newrange,NF_NAT_MANIP_SRC);
Adds support for masquerading into a smaller subset of ports -
defined by the PSID values from RFC-7597 Section 5.1. This is part of
the support for MAP-E and Lightweight 4over6, which allows multiple
devices to share an IPv4 address by splitting the L4 port / id into
ranges.
Co-developed-by: Anthony Lineham <redacted>
Signed-off-by: Anthony Lineham <redacted>
Co-developed-by: Scott Parlane <redacted>
Signed-off-by: Scott Parlane <redacted>
Signed-off-by: Blair Steven <redacted>
Signed-off-by: Cole Dishington <redacted>
---
Just a quick review:
+ /* In this case we are in PSID mode, avoid checking all ranges by computing bitmasks */
+ if (is_psid) {
+ u16 j = ntohs(max->all) - ntohs(min->all) + 1;
+ u16 a = (1 << 16) / ntohs(base->all);
This gives crash when base->all is 0.
If this is impossible, please add a comment, otherwise this needs
a sanity test on the divisor.
Adds support for masquerading into a smaller subset of ports -
defined by the PSID values from RFC-7597 Section 5.1. This is part of
the support for MAP-E and Lightweight 4over6, which allows multiple
devices to share an IPv4 address by splitting the L4 port / id into
ranges.
Co-developed-by: Anthony Lineham <redacted>
Signed-off-by: Anthony Lineham <redacted>
Co-developed-by: Scott Parlane <redacted>
Signed-off-by: Scott Parlane <redacted>
Signed-off-by: Blair Steven <redacted>
Signed-off-by: Cole Dishington <redacted>
---
Notes:
Thanks for your time reviewing!
Changes in v4:
- Add support for a=0 (A=2^16) special case.
- Now that offset=0 is used for the special case of 2^16 (as it cannot fit
in a u16) the divide by zero issues are no longer present.
net/netfilter/nf_nat_core.c | 39 +++++++++++++++++++++++++++----
net/netfilter/nf_nat_masquerade.c | 20 ++++++++++++++--
2 files changed, 53 insertions(+), 6 deletions(-)
@@ -195,13 +195,36 @@ static bool nf_nat_inet_in_range(const struct nf_conntrack_tuple *t,staticbooll4proto_in_range(conststructnf_conntrack_tuple*tuple,enumnf_nat_manip_typemaniptype,constunionnf_conntrack_man_proto*min,-constunionnf_conntrack_man_proto*max)+constunionnf_conntrack_man_proto*max,+constunionnf_conntrack_man_proto*base,+boolis_psid){__be16port;+u16psid,psid_mask,offset_mask;++/* In this case we are in PSID mode, avoid checking all ranges by computing bitmasks */+if(is_psid){+u16power_j=ntohs(max->all)-ntohs(min->all)+1;+u32offset=ntohs(base->all);+u16power_a;++if(offset==0)+offset=1<<16;++power_a=(1<<16)/offset;+offset_mask=(power_a-1)*offset;+psid_mask=((offset/power_j)<<1)-1;+psid=ntohs(min->all)&psid_mask;+}switch(tuple->dst.protonum){caseIPPROTO_ICMP:caseIPPROTO_ICMPV6:+if(is_psid){+return(offset_mask==0||+(ntohs(tuple->src.u.icmp.id)&offset_mask)!=0)&&+((ntohs(tuple->src.u.icmp.id)&psid_mask)==psid);+}returnntohs(tuple->src.u.icmp.id)>=ntohs(min->icmp.id)&&ntohs(tuple->src.u.icmp.id)<=ntohs(max->icmp.id);caseIPPROTO_GRE:/* all fall though */
@@ -55,8 +55,24 @@ nf_nat_masquerade_ipv4(struct sk_buff *skb, unsigned int hooknum,newrange.flags=range->flags|NF_NAT_RANGE_MAP_IPS;newrange.min_addr.ip=newsrc;newrange.max_addr.ip=newsrc;-newrange.min_proto=range->min_proto;-newrange.max_proto=range->max_proto;++if(range->flags&NF_NAT_RANGE_PSID){+u16base=ntohs(range->base_proto.all);+u16min=ntohs(range->min_proto.all);+u16off=0;++/* If offset=0, port range is in one contiguous block */+if(base)+off=prandom_u32()%(((1<<16)/base)-1);++newrange.min_proto.all=htons(min+base*off);+newrange.max_proto.all=htons(ntohs(newrange.min_proto.all)+ntohs(range->max_proto.all)-min);+newrange.base_proto=range->base_proto;+newrange.flags=newrange.flags|NF_NAT_RANGE_PROTO_SPECIFIED;+}else{+newrange.min_proto=range->min_proto;+newrange.max_proto=range->max_proto;+}/* Hand modified range to generic setup. */returnnf_nat_setup_info(ct,&newrange,NF_NAT_MANIP_SRC);
Add selftests for masquerading into a smaller subset of ports defined by
PSID.
Signed-off-by: Cole Dishington <redacted>
---
Notes:
Thanks for your time reviewing!
Changes in v4:
- Add tests for a=0 (A=2^16) special case.
- Update to use offset_length (from iptables change).
.../netfilter/nat_masquerade_psid.sh | 182 ++++++++++++++++++
1 file changed, 182 insertions(+)
create mode 100644 tools/testing/selftests/netfilter/nat_masquerade_psid.sh
@@ -0,0 +1,182 @@+#!/bin/bash+# SPDX-License-Identifier: GPL-2.0+#+# <:copyright-gpl+# Copyright (C) 2021 Allied Telesis Labs NZ+#+# check that NAT can masquerade using PSID defined ranges.+#+# Setup is:+#+# nsclient1(veth0) -> (veth1)nsrouter(veth2) -> (veth0)nsclient2+# Setup a nat masquerade rule with psid defined ranges.+#++# Kselftest framework requirement - SKIP code is 4.+ksft_skip=4+ret=0+ns_all="nsclient1 nsrouter nsclient2"++readonlyinfile="$(mktemp)"+readonlyoutfile="$(mktemp)"+readonlydatalen=32+readonlyserver_port=8080++conntrack-V>/dev/null2>&1+if[$?-ne0];then+echo"SKIP: Could not run test without conntrack tool"+exit$ksft_skip+fi++iptables--version>/dev/null2>&1+if[$?-ne0];then+echo"SKIP: Could not run test without iptables tool"+exit$ksft_skip+fi++ip-Version>/dev/null2>&1+if[$?-ne0];then+echo"SKIP: Could not run test without ip tool"+exit$ksft_skip+fi++ipv4(){+echo-n192.168.$1.$2+}++cleanup(){+fornin$ns_all;doipnetnsdel$n;done++if[-f"${outfile}"];then+rm"$outfile"+fi+if[-f"${infile}"];then+rm"$infile"+fi+}++server_listen(){+ipnetnsexecnsclient2nc-l-p"$server_port">"$outfile"&+server_pid=$!+sleep0.2+}++client_connect(){+ipnetnsexecnsclient1timeout2nc-w1-p"$port"$(ipv422)"$server_port"<$infile+}++verify_data(){+local_ret=0+wait"$server_pid"+cmp"$infile""$outfile"2>/dev/null+_ret=$?+rm"$outfile"+return$_ret+}++test_service(){+server_listen+client_connect+verify_data+}++check_connection(){+local_ret=0+entry=$(ipnetnsexecnsrouterconntrack-ptcp--sport$port-L2>&1)+entry=${entry##*sport=8080 dport=}+entry=${entry%% *}++if[["x$((($entry&$psid_mask)/$two_power_j))"!="x$psid"]];then+_ret=1+echo"Failed psid mask check for $offset_len:$psid:$psid_length with port $entry"+fi++if[["x$_ret"="x0"]]&&+[["x$offset_mask"!="x0"-a"x$((($entry&$offset_mask)))"=="x0"]];then+_ret=1+echo"Failed offset mask check for $offset_len:$psid:$psid_length with port $entry"+fi+return$_ret+}++run_test(){+ipnetnsexecnsrouteriptables-AFORWARD-iveth1-jACCEPT+ipnetnsexecnsrouteriptables-PFORWARDDROP+ipnetnsexecnsrouteriptables-AFORWARD-mstate--stateESTABLISHED,RELATED-jACCEPT+ipnetnsexecnsrouteriptables-tnat--newpsid+ipnetnsexecnsrouteriptables-tnat--insertpsid-jMASQUERADE\+--psid$offset_len:$psid:$psid_length+ipnetnsexecnsrouteriptables-tnat-IPOSTROUTING-oveth2-jpsid++# calculate psid mask+offset=$((1<<(16-$offset_len)))+two_power_j=$(($offset/(1<<$psid_length)))+offset_mask=$((((1<<$offset_len)-1)<<(16-$offset_len)))+psid_mask=$((((1<<$psid_length)-1)*$two_power_j))++# Create file+ddif=/dev/urandomof="${infile}"bs="${datalen}"count=1>/dev/null2>&1++# Test multiple ports+forpin12345;do+port=1080$p++test_service+if[$?-ne0];then+ret=1+break+fi++check_connection+if[$?-ne0];then+ret=1+break+fi+done++# tidy up test rules+ipnetnsexecnsrouteriptables-F+ipnetnsexecnsrouteriptables-tnat-F+ipnetnsexecnsrouteriptables-tnat-Xpsid+}++fornin$ns_all;do+ipnetnsadd$n+ip-net$nlinksetloup+done++foriin12;do+iplinkaddveth0netnsnsclient$itypevethpeernameveth$inetnsnsrouter++ip-netnsclient$ilinksetveth0up+ip-netnsclient$iaddradd$(ipv4$i2)/24devveth0++ip-netnsrouterlinksetveth$iup+ip-netnsrouteraddradd$(ipv4$i1)/24devveth$i+done++ip-netnsclient1routeadddefaultvia$(ipv411)+ip-netnsclient2routeadddefaultvia$(ipv421)++ipnetnsexecnsroutersysctl-qnet.ipv4.conf.all.forwarding=1++offset_len=0+psid_length=8+forpsidin052;do+run_test+if[$?-ne0];then+break+fi+done++offset_len=6+psid_length=8+forpsidin052;do+run_test+if[$?-ne0];then+break+fi+done++cleanup+exit$ret
@@ -195,13 +195,36 @@ static bool nf_nat_inet_in_range(const struct nf_conntrack_tuple *t,staticbooll4proto_in_range(conststructnf_conntrack_tuple*tuple,enumnf_nat_manip_typemaniptype,constunionnf_conntrack_man_proto*min,-constunionnf_conntrack_man_proto*max)+constunionnf_conntrack_man_proto*max,+constunionnf_conntrack_man_proto*base,+boolis_psid){__be16port;+u16psid,psid_mask,offset_mask;++/* In this case we are in PSID mode, avoid checking all ranges by computing bitmasks */+if(is_psid){+u16power_j=ntohs(max->all)-ntohs(min->all)+1;+u32offset=ntohs(base->all);+u16power_a;++if(offset==0)+offset=1<<16;++power_a=(1<<16)/offset;
Since the dividie is only needed nat setup and not for each packet I
think its ok.
+ if (range->flags & NF_NAT_RANGE_PSID) {
+ u16 base = ntohs(range->base_proto.all);
+ u16 min = ntohs(range->min_proto.all);
+ u16 off = 0;
+
+ /* If offset=0, port range is in one contiguous block */
+ if (base)
+ off = prandom_u32() % (((1 << 16) / base) - 1);
Bases 32769 > gives 0 for the modulo value, so perhaps compute that
independently.
You could reject > 32769 in the iptables checkentry target.
Also, base of 21846 and above always give 0 result (% 1).
I don't know psid well enough to give a recommendation here.
If such inputs are nonsensical, just reject it when userspace asks for
this and add a
if (WARN_ON_ONCE(base > bogus))
return NF_DROP;
with s small coment explaining that xtables is supposed to not provide
such value.
Other than this I think its ok.
I still dislike the 'bool is_psid' in the nat core, but I can't find
a better solution.
Adds support for masquerading into a smaller subset of ports -
defined by the PSID values from RFC-7597 Section 5.1. This is part of
the support for MAP-E and Lightweight 4over6, which allows multiple
devices to share an IPv4 address by splitting the L4 port / id into
ranges.
Co-developed-by: Anthony Lineham <redacted>
Signed-off-by: Anthony Lineham <redacted>
Co-developed-by: Scott Parlane <redacted>
Signed-off-by: Scott Parlane <redacted>
Signed-off-by: Blair Steven <redacted>
Signed-off-by: Cole Dishington <redacted>
---
Notes:
Thanks for time reviewing!
Changes in v5:
- Add WARN_ON_ONCE for invalid value of range->base.
net/netfilter/nf_nat_core.c | 39 +++++++++++++++++++++++++++----
net/netfilter/nf_nat_masquerade.c | 27 +++++++++++++++++++--
2 files changed, 60 insertions(+), 6 deletions(-)
@@ -195,13 +195,36 @@ static bool nf_nat_inet_in_range(const struct nf_conntrack_tuple *t,staticbooll4proto_in_range(conststructnf_conntrack_tuple*tuple,enumnf_nat_manip_typemaniptype,constunionnf_conntrack_man_proto*min,-constunionnf_conntrack_man_proto*max)+constunionnf_conntrack_man_proto*max,+constunionnf_conntrack_man_proto*base,+boolis_psid){__be16port;+u16psid,psid_mask,offset_mask;++/* In this case we are in PSID mode, avoid checking all ranges by computing bitmasks */+if(is_psid){+u16power_j=ntohs(max->all)-ntohs(min->all)+1;+u32offset=ntohs(base->all);+u16power_a;++if(offset==0)+offset=1<<16;++power_a=(1<<16)/offset;+offset_mask=(power_a-1)*offset;+psid_mask=((offset/power_j)<<1)-1;+psid=ntohs(min->all)&psid_mask;+}switch(tuple->dst.protonum){caseIPPROTO_ICMP:caseIPPROTO_ICMPV6:+if(is_psid){+return(offset_mask==0||+(ntohs(tuple->src.u.icmp.id)&offset_mask)!=0)&&+((ntohs(tuple->src.u.icmp.id)&psid_mask)==psid);+}returnntohs(tuple->src.u.icmp.id)>=ntohs(min->icmp.id)&&ntohs(tuple->src.u.icmp.id)<=ntohs(max->icmp.id);caseIPPROTO_GRE:/* all fall though */
@@ -55,8 +55,31 @@ nf_nat_masquerade_ipv4(struct sk_buff *skb, unsigned int hooknum,newrange.flags=range->flags|NF_NAT_RANGE_MAP_IPS;newrange.min_addr.ip=newsrc;newrange.max_addr.ip=newsrc;-newrange.min_proto=range->min_proto;-newrange.max_proto=range->max_proto;++if(range->flags&NF_NAT_RANGE_PSID){+u16base=ntohs(range->base_proto.all);+u16min=ntohs(range->min_proto.all);+u16off=0;++/* xtables should stop base > 2^15 by enforcement of+*0<=offset_len<16argument,withoffset_len=0+*asaspecialcaseinwhichbase=0.+*/+if(WARN_ON_ONCE(base>(1<<15)))+returnNF_DROP;++/* If offset=0, port range is in one contiguous block */+if(base)+off=prandom_u32()%(((1<<16)/base)-1);++newrange.min_proto.all=htons(min+base*off);+newrange.max_proto.all=htons(ntohs(newrange.min_proto.all)+ntohs(range->max_proto.all)-min);+newrange.base_proto=range->base_proto;+newrange.flags=newrange.flags|NF_NAT_RANGE_PROTO_SPECIFIED;+}else{+newrange.min_proto=range->min_proto;+newrange.max_proto=range->max_proto;+}/* Hand modified range to generic setup. */returnnf_nat_setup_info(ct,&newrange,NF_NAT_MANIP_SRC);
Adds support for masquerading into a smaller subset of ports -
defined by the PSID values from RFC-7597 Section 5.1. This is part of
the support for MAP-E and Lightweight 4over6, which allows multiple
devices to share an IPv4 address by splitting the L4 port / id into
ranges.
Co-developed-by: Anthony Lineham <redacted>
Signed-off-by: Anthony Lineham <redacted>
Co-developed-by: Scott Parlane <redacted>
Signed-off-by: Scott Parlane <redacted>
Signed-off-by: Blair Steven <redacted>
Signed-off-by: Cole Dishington <redacted>
---
+
+ /* In this case we are in PSID mode, avoid checking all ranges by computing bitmasks */
+ if (is_psid) {
+ u16 power_j = ntohs(max->all) - ntohs(min->all) + 1;
I think this needs to be 'u32 power_j' to prevent overflow of
65535 + 1 -> 0.
+ if (base)
+ off = prandom_u32() % (((1 << 16) / base) - 1);
I think this can use prandom_u32_max(((1 << 16) / base) - 1).
I have no other comments. Other kernel patches LGTM.
Adds support for masquerading into a smaller subset of ports -
defined by the PSID values from RFC-7597 Section 5.1. This is part of
the support for MAP-E and Lightweight 4over6, which allows multiple
devices to share an IPv4 address by splitting the L4 port / id into
ranges.
Co-developed-by: Anthony Lineham <redacted>
Signed-off-by: Anthony Lineham <redacted>
Co-developed-by: Scott Parlane <redacted>
Signed-off-by: Scott Parlane <redacted>
Signed-off-by: Blair Steven <redacted>
Signed-off-by: Cole Dishington <redacted>
---
Notes:
Thanks for your time reviewing!
Changes in v6:
- Use prandom_u32_max() rather than prandom_u32() % max for generating PSID sub-range offset.
- Use u32 for power_j for the case of a=0,psid_len=0.
net/netfilter/nf_nat_core.c | 39 +++++++++++++++++++++++++++----
net/netfilter/nf_nat_masquerade.c | 27 +++++++++++++++++++--
2 files changed, 60 insertions(+), 6 deletions(-)
@@ -195,13 +195,36 @@ static bool nf_nat_inet_in_range(const struct nf_conntrack_tuple *t,staticbooll4proto_in_range(conststructnf_conntrack_tuple*tuple,enumnf_nat_manip_typemaniptype,constunionnf_conntrack_man_proto*min,-constunionnf_conntrack_man_proto*max)+constunionnf_conntrack_man_proto*max,+constunionnf_conntrack_man_proto*base,+boolis_psid){__be16port;+u16psid,psid_mask,offset_mask;++/* In this case we are in PSID mode, avoid checking all ranges by computing bitmasks */+if(is_psid){+u32power_j=ntohs(max->all)-ntohs(min->all)+1;+u32offset=ntohs(base->all);+u16power_a;++if(offset==0)+offset=1<<16;++power_a=(1<<16)/offset;+offset_mask=(power_a-1)*offset;+psid_mask=((offset/power_j)<<1)-1;+psid=ntohs(min->all)&psid_mask;+}switch(tuple->dst.protonum){caseIPPROTO_ICMP:caseIPPROTO_ICMPV6:+if(is_psid){+return(offset_mask==0||+(ntohs(tuple->src.u.icmp.id)&offset_mask)!=0)&&+((ntohs(tuple->src.u.icmp.id)&psid_mask)==psid);+}returnntohs(tuple->src.u.icmp.id)>=ntohs(min->icmp.id)&&ntohs(tuple->src.u.icmp.id)<=ntohs(max->icmp.id);caseIPPROTO_GRE:/* all fall though */
@@ -55,8 +55,31 @@ nf_nat_masquerade_ipv4(struct sk_buff *skb, unsigned int hooknum,newrange.flags=range->flags|NF_NAT_RANGE_MAP_IPS;newrange.min_addr.ip=newsrc;newrange.max_addr.ip=newsrc;-newrange.min_proto=range->min_proto;-newrange.max_proto=range->max_proto;++if(range->flags&NF_NAT_RANGE_PSID){+u16base=ntohs(range->base_proto.all);+u16min=ntohs(range->min_proto.all);+u16off=0;++/* xtables should stop base > 2^15 by enforcement of+*0<=offset_len<16argument,withoffset_len=0+*asaspecialcaseinwhichbase=0.+*/+if(WARN_ON_ONCE(base>(1<<15)))+returnNF_DROP;++/* If offset=0, port range is in one contiguous block */+if(base)+off=prandom_u32_max(((1<<16)/base)-1);++newrange.min_proto.all=htons(min+base*off);+newrange.max_proto.all=htons(ntohs(newrange.min_proto.all)+ntohs(range->max_proto.all)-min);+newrange.base_proto=range->base_proto;+newrange.flags=newrange.flags|NF_NAT_RANGE_PROTO_SPECIFIED;+}else{+newrange.min_proto=range->min_proto;+newrange.max_proto=range->max_proto;+}/* Hand modified range to generic setup. */returnnf_nat_setup_info(ct,&newrange,NF_NAT_MANIP_SRC);
Adds support for masquerading into a smaller subset of ports -
defined by the PSID values from RFC-7597 Section 5.1. This is part of
the support for MAP-E and Lightweight 4over6, which allows multiple
devices to share an IPv4 address by splitting the L4 port / id into
ranges.
Co-developed-by: Anthony Lineham <redacted>
Signed-off-by: Anthony Lineham <redacted>
Co-developed-by: Scott Parlane <redacted>
Signed-off-by: Scott Parlane <redacted>
Signed-off-by: Blair Steven <redacted>
Signed-off-by: Cole Dishington <redacted>
---
Thanks for your patience and addressing all the comments.
Reviewed-by: Florian Westphal <fw@strlen.de>
Add selftests for masquerading into a smaller subset of ports defined by
PSID.
Signed-off-by: Cole Dishington <redacted>
---
Notes:
No changes.
.../netfilter/nat_masquerade_psid.sh | 182 ++++++++++++++++++
1 file changed, 182 insertions(+)
create mode 100644 tools/testing/selftests/netfilter/nat_masquerade_psid.sh
@@ -0,0 +1,182 @@+#!/bin/bash+# SPDX-License-Identifier: GPL-2.0+#+# <:copyright-gpl+# Copyright (C) 2021 Allied Telesis Labs NZ+#+# check that NAT can masquerade using PSID defined ranges.+#+# Setup is:+#+# nsclient1(veth0) -> (veth1)nsrouter(veth2) -> (veth0)nsclient2+# Setup a nat masquerade rule with psid defined ranges.+#++# Kselftest framework requirement - SKIP code is 4.+ksft_skip=4+ret=0+ns_all="nsclient1 nsrouter nsclient2"++readonlyinfile="$(mktemp)"+readonlyoutfile="$(mktemp)"+readonlydatalen=32+readonlyserver_port=8080++conntrack-V>/dev/null2>&1+if[$?-ne0];then+echo"SKIP: Could not run test without conntrack tool"+exit$ksft_skip+fi++iptables--version>/dev/null2>&1+if[$?-ne0];then+echo"SKIP: Could not run test without iptables tool"+exit$ksft_skip+fi++ip-Version>/dev/null2>&1+if[$?-ne0];then+echo"SKIP: Could not run test without ip tool"+exit$ksft_skip+fi++ipv4(){+echo-n192.168.$1.$2+}++cleanup(){+fornin$ns_all;doipnetnsdel$n;done++if[-f"${outfile}"];then+rm"$outfile"+fi+if[-f"${infile}"];then+rm"$infile"+fi+}++server_listen(){+ipnetnsexecnsclient2nc-l-p"$server_port">"$outfile"&+server_pid=$!+sleep0.2+}++client_connect(){+ipnetnsexecnsclient1timeout2nc-w1-p"$port"$(ipv422)"$server_port"<$infile+}++verify_data(){+local_ret=0+wait"$server_pid"+cmp"$infile""$outfile"2>/dev/null+_ret=$?+rm"$outfile"+return$_ret+}++test_service(){+server_listen+client_connect+verify_data+}++check_connection(){+local_ret=0+entry=$(ipnetnsexecnsrouterconntrack-ptcp--sport$port-L2>&1)+entry=${entry##*sport=8080 dport=}+entry=${entry%% *}++if[["x$((($entry&$psid_mask)/$two_power_j))"!="x$psid"]];then+_ret=1+echo"Failed psid mask check for $offset_len:$psid:$psid_length with port $entry"+fi++if[["x$_ret"="x0"]]&&+[["x$offset_mask"!="x0"-a"x$((($entry&$offset_mask)))"=="x0"]];then+_ret=1+echo"Failed offset mask check for $offset_len:$psid:$psid_length with port $entry"+fi+return$_ret+}++run_test(){+ipnetnsexecnsrouteriptables-AFORWARD-iveth1-jACCEPT+ipnetnsexecnsrouteriptables-PFORWARDDROP+ipnetnsexecnsrouteriptables-AFORWARD-mstate--stateESTABLISHED,RELATED-jACCEPT+ipnetnsexecnsrouteriptables-tnat--newpsid+ipnetnsexecnsrouteriptables-tnat--insertpsid-jMASQUERADE\+--psid$offset_len:$psid:$psid_length+ipnetnsexecnsrouteriptables-tnat-IPOSTROUTING-oveth2-jpsid++# calculate psid mask+offset=$((1<<(16-$offset_len)))+two_power_j=$(($offset/(1<<$psid_length)))+offset_mask=$((((1<<$offset_len)-1)<<(16-$offset_len)))+psid_mask=$((((1<<$psid_length)-1)*$two_power_j))++# Create file+ddif=/dev/urandomof="${infile}"bs="${datalen}"count=1>/dev/null2>&1++# Test multiple ports+forpin12345;do+port=1080$p++test_service+if[$?-ne0];then+ret=1+break+fi++check_connection+if[$?-ne0];then+ret=1+break+fi+done++# tidy up test rules+ipnetnsexecnsrouteriptables-F+ipnetnsexecnsrouteriptables-tnat-F+ipnetnsexecnsrouteriptables-tnat-Xpsid+}++fornin$ns_all;do+ipnetnsadd$n+ip-net$nlinksetloup+done++foriin12;do+iplinkaddveth0netnsnsclient$itypevethpeernameveth$inetnsnsrouter++ip-netnsclient$ilinksetveth0up+ip-netnsclient$iaddradd$(ipv4$i2)/24devveth0++ip-netnsrouterlinksetveth$iup+ip-netnsrouteraddradd$(ipv4$i1)/24devveth$i+done++ip-netnsclient1routeadddefaultvia$(ipv411)+ip-netnsclient2routeadddefaultvia$(ipv421)++ipnetnsexecnsroutersysctl-qnet.ipv4.conf.all.forwarding=1++offset_len=0+psid_length=8+forpsidin052;do+run_test+if[$?-ne0];then+break+fi+done++offset_len=6+psid_length=8+forpsidin052;do+run_test+if[$?-ne0];then+break+fi+done++cleanup+exit$ret
Adds support for masquerading into a smaller subset of ports -
defined by the PSID values from RFC-7597 Section 5.1. This is part of
the support for MAP-E and Lightweight 4over6, which allows multiple
devices to share an IPv4 address by splitting the L4 port / id into
ranges.
Co-developed-by: Anthony Lineham <redacted>
Signed-off-by: Anthony Lineham <redacted>
Co-developed-by: Scott Parlane <redacted>
Signed-off-by: Scott Parlane <redacted>
Signed-off-by: Blair Steven <redacted>
Signed-off-by: Cole Dishington <redacted>
Reviewed-by: Florian Westphal <fw@strlen.de>
---
Notes:
Changes:
- Added Reviewed-by: Florian Westphal [off-list ref].
net/netfilter/nf_nat_core.c | 39 +++++++++++++++++++++++++++----
net/netfilter/nf_nat_masquerade.c | 27 +++++++++++++++++++--
2 files changed, 60 insertions(+), 6 deletions(-)
@@ -195,13 +195,36 @@ static bool nf_nat_inet_in_range(const struct nf_conntrack_tuple *t,staticbooll4proto_in_range(conststructnf_conntrack_tuple*tuple,enumnf_nat_manip_typemaniptype,constunionnf_conntrack_man_proto*min,-constunionnf_conntrack_man_proto*max)+constunionnf_conntrack_man_proto*max,+constunionnf_conntrack_man_proto*base,+boolis_psid){__be16port;+u16psid,psid_mask,offset_mask;++/* In this case we are in PSID mode, avoid checking all ranges by computing bitmasks */+if(is_psid){+u32power_j=ntohs(max->all)-ntohs(min->all)+1;+u32offset=ntohs(base->all);+u16power_a;++if(offset==0)+offset=1<<16;++power_a=(1<<16)/offset;+offset_mask=(power_a-1)*offset;+psid_mask=((offset/power_j)<<1)-1;+psid=ntohs(min->all)&psid_mask;+}switch(tuple->dst.protonum){caseIPPROTO_ICMP:caseIPPROTO_ICMPV6:+if(is_psid){+return(offset_mask==0||+(ntohs(tuple->src.u.icmp.id)&offset_mask)!=0)&&+((ntohs(tuple->src.u.icmp.id)&psid_mask)==psid);+}returnntohs(tuple->src.u.icmp.id)>=ntohs(min->icmp.id)&&ntohs(tuple->src.u.icmp.id)<=ntohs(max->icmp.id);caseIPPROTO_GRE:/* all fall though */
@@ -55,8 +55,31 @@ nf_nat_masquerade_ipv4(struct sk_buff *skb, unsigned int hooknum,newrange.flags=range->flags|NF_NAT_RANGE_MAP_IPS;newrange.min_addr.ip=newsrc;newrange.max_addr.ip=newsrc;-newrange.min_proto=range->min_proto;-newrange.max_proto=range->max_proto;++if(range->flags&NF_NAT_RANGE_PSID){+u16base=ntohs(range->base_proto.all);+u16min=ntohs(range->min_proto.all);+u16off=0;++/* xtables should stop base > 2^15 by enforcement of+*0<=offset_len<16argument,withoffset_len=0+*asaspecialcaseinwhichbase=0.+*/+if(WARN_ON_ONCE(base>(1<<15)))+returnNF_DROP;++/* If offset=0, port range is in one contiguous block */+if(base)+off=prandom_u32_max(((1<<16)/base)-1);++newrange.min_proto.all=htons(min+base*off);+newrange.max_proto.all=htons(ntohs(newrange.min_proto.all)+ntohs(range->max_proto.all)-min);+newrange.base_proto=range->base_proto;+newrange.flags=newrange.flags|NF_NAT_RANGE_PROTO_SPECIFIED;+}else{+newrange.min_proto=range->min_proto;+newrange.max_proto=range->max_proto;+}/* Hand modified range to generic setup. */returnnf_nat_setup_info(ct,&newrange,NF_NAT_MANIP_SRC);
From: Pablo Neira Ayuso <pablo@netfilter.org> Date: 2021-08-25 17:05:39
Hi,
On Mon, Aug 09, 2021 at 04:10:36PM +1200, Cole Dishington wrote:
Adds support for masquerading into a smaller subset of ports -
defined by the PSID values from RFC-7597 Section 5.1. This is part of
the support for MAP-E and Lightweight 4over6, which allows multiple
devices to share an IPv4 address by splitting the L4 port / id into
ranges.
Co-developed-by: Anthony Lineham <redacted>
Signed-off-by: Anthony Lineham <redacted>
Co-developed-by: Scott Parlane <redacted>
Signed-off-by: Scott Parlane <redacted>
Signed-off-by: Blair Steven <redacted>
Signed-off-by: Cole Dishington <redacted>
Reviewed-by: Florian Westphal <fw@strlen.de>
Assuming offset_len = 6, then you skip 0-1023 ports, OK.
psid = psid << (16 - offset_len - psid_len);
This psid calculation is correct? Maybe:
psid = psid << (16 - offset_len);
instead?
psid=0 => 0 << (16 - 6) = 1024
psid=1 => 1 << (16 - 6) = 2048
This is implicitly assuming that 64 PSIDs are available, each of them
taking 1024 ports, ie. psid_len is 6 bits. But why are you subtracting
the psid_len above?
/* Handle the special case of no offset bits (a=0), so offset loops */
min = psid;
OK, this line above is the minimal port in the range
if (offset)
min += offset;
... which is incremented by the offset (to skip the 0-1023 ports).
@@ -55,8 +55,31 @@ nf_nat_masquerade_ipv4(struct sk_buff *skb, unsigned int hooknum,newrange.flags=range->flags|NF_NAT_RANGE_MAP_IPS;newrange.min_addr.ip=newsrc;newrange.max_addr.ip=newsrc;-newrange.min_proto=range->min_proto;-newrange.max_proto=range->max_proto;++if(range->flags&NF_NAT_RANGE_PSID){+u16base=ntohs(range->base_proto.all);+u16min=ntohs(range->min_proto.all);+u16off=0;++/* xtables should stop base > 2^15 by enforcement of+*0<=offset_len<16argument,withoffset_len=0+*asaspecialcaseinwhichbase=0.
I don't understand this comment.
+ */
+ if (WARN_ON_ONCE(base > (1 << 15)))
+ return NF_DROP;
+
+ /* If offset=0, port range is in one contiguous block */
+ if (base)
+ off = prandom_u32_max(((1 << 16) / base) - 1);
Assuming the example above, base is set to 1024. Then, off is a random
value between UINT16_MAX (you expressed this as 1 << 16) and the base
which is 1024 minus 1.
So this is picking a random off (actually the PSID?) between 0 and 63.
What about clashes? I mean, two different machines behind the NAT
might get the same off.
+ newrange.min_proto.all = htons(min + base * off);
min could be 1024, 2048, 3072... you add base which is 1024 * off.
Is this duplicated? Both calculated in user and kernel space?
I'm stopping here, I'm getting lost.
My understanding about this RFC is that you would like to split the
16-bit ports in ranges to uniquely identify the host behind the NAT.
Why don't you just you just select the port range from userspace
utilizing the existing infrastructure? I mean, why do you need this
kernel patch?
Florian already suggested:
Is it really needed to place all of this in the nat core?
The only thing that has to be done in the NAT core, afaics, is to
suppress port reallocation attmepts when NF_NAT_RANGE_PSID is set.
Is there a reason why nf_nat_masquerade_ipv4/6 can't be changed instead
to do what you want?
AFAICS its enough to set NF_NAT_RANGE_PROTO_SPECIFIED and init the
upper/lower boundaries, i.e. change input given to nf_nat_setup_info().
Hello,
Thanks for your time reviewing!
On Wed, 2021-08-25 at 19:05 +0200, Pablo Neira Ayuso wrote:
Hi,
On Mon, Aug 09, 2021 at 04:10:36PM +1200, Cole Dishington wrote:
quoted
Adds support for masquerading into a smaller subset of ports -
defined by the PSID values from RFC-7597 Section 5.1. This is part of
the support for MAP-E and Lightweight 4over6, which allows multiple
devices to share an IPv4 address by splitting the L4 port / id into
ranges.
Co-developed-by: Anthony Lineham <redacted>
Signed-off-by: Anthony Lineham <redacted>
Co-developed-by: Scott Parlane <redacted>
Signed-off-by: Scott Parlane <redacted>
Signed-off-by: Blair Steven <redacted>
Signed-off-by: Cole Dishington <redacted>
Reviewed-by: Florian Westphal <fw@strlen.de>
Assuming offset_len = 6, then you skip 0-1023 ports, OK.
quoted
psid = psid << (16 - offset_len - psid_len);
This psid calculation is correct? Maybe:
psid = psid << (16 - offset_len);
PSID port numbers have the form
[offset|PSID|j]
and
16 = offset_length + PSID_length + j_length.
The PSID calculation above is bit shifting the passed psid up j_length.
The userspace tool accepts the unshifted psid to be consistent with how RFC7597 specified it (see RFC7597 Appendix A. Examples).
instead?
psid=0 => 0 << (16 - 6) = 1024
psid=1 => 1 << (16 - 6) = 2048
This is implicitly assuming that 64 PSIDs are available, each of them
taking 1024 ports, ie. psid_len is 6 bits. But why are you subtracting
the psid_len above?
quoted
/* Handle the special case of no offset bits (a=0), so offset loops */
min = psid;
OK, this line above is the minimal port in the range
quoted
if (offset)
min += offset;
... which is incremented by the offset (to skip the 0-1023 ports).
Each PSID port range is made up of many smaller contiguous port sub-ranges (except for the special case of offset_len = 0) e.g. for PSID=0x34,psid_length=8,psid_offset=6 the ranges are 1232-1235, 2256-2259, ..., 63696-63699, 64720-64723 (Taken from rfc7597 Appendix A. Examples).
The above calculation is selecting the first sub-range. Max is computed by finding j_length and filling it with 1's.
@@ -55,8 +55,31 @@ nf_nat_masquerade_ipv4(struct sk_buff *skb, unsigned int hooknum,newrange.flags=range->flags|NF_NAT_RANGE_MAP_IPS;newrange.min_addr.ip=newsrc;newrange.max_addr.ip=newsrc;-newrange.min_proto=range->min_proto;-newrange.max_proto=range->max_proto;++if(range->flags&NF_NAT_RANGE_PSID){+u16base=ntohs(range->base_proto.all);+u16min=ntohs(range->min_proto.all);+u16off=0;++/* xtables should stop base > 2^15 by enforcement of+*0<=offset_len<16argument,withoffset_len=0+*asaspecialcaseinwhichbase=0.
I don't understand this comment.
This is a sanity check. The userspace tool restricts offset_len to the specified range and since base = 2^(16 - offset_len) (or base = 0 for the special case of offset_len = 16) the below condition should never be true.
However, if base greater than 1<<15 was allowed, a divide by zero error would occur on the block below.
quoted
+ */
+ if (WARN_ON_ONCE(base > (1 << 15)))
+ return NF_DROP;
+
+ /* If offset=0, port range is in one contiguous block */
+ if (base)
+ off = prandom_u32_max(((1 << 16) / base) - 1);
Assuming the example above, base is set to 1024. Then, off is a random
value between UINT16_MAX (you expressed this as 1 << 16) and the base
which is 1024 minus 1.
So this is picking a random off (actually the PSID?) between 0 and 63.
What about clashes? I mean, two different machines behind the NAT
might get the same off.
quoted
+ newrange.min_proto.all = htons(min + base * off);
min could be 1024, 2048, 3072... you add base which is 1024 * off.
Is this duplicated? Both calculated in user and kernel space?
Each PSID value defines many contiguous port sub-ranges. The randomly chosen off selects the ith sub-range for a given PSID e.g. off=1 would select 2256-2259 for rfc7597 Appendix A. Examples.
The userspace tool calculates the min and max of the first sub-range for a given psid, whereas the above randomly selects one of the sub-ranges for a given psid.
j_length determines how large each sub-range will be, so for small j_length values there still is the risk the chosen sub-range will be exhausted.
I'm stopping here, I'm getting lost.
My understanding about this RFC is that you would like to split the
16-bit ports in ranges to uniquely identify the host behind the NAT.
Why don't you just you just select the port range from userspace
utilizing the existing infrastructure? I mean, why do you need this
kernel patch?
If utilizing existing infrastruture to install PSID port ranges a lot of rules would be required as each PSID port range is made up of many smaller sub-ranges.
e.g. (from rfc7597 Appendix A. Examples)
for psid_length=8,offset_length=6 each PSID would need 63 NF_NAT_RANGE_PROTO_SPECIFIED rules, hence a total of 16128 rules if all the PSIDs were allocated.
Florian already suggested:
quoted
Is it really needed to place all of this in the nat core?
The only thing that has to be done in the NAT core, afaics, is to
suppress port reallocation attmepts when NF_NAT_RANGE_PSID is set.
Is there a reason why nf_nat_masquerade_ipv4/6 can't be changed instead
to do what you want?
AFAICS its enough to set NF_NAT_RANGE_PROTO_SPECIFIED and init the
upper/lower boundaries, i.e. change input given to nf_nat_setup_info().