From mboxrd@z Thu Jan 1 00:00:00 1970 From: Felix Jia Subject: [PATCH v1 2/2] netfilter: Add PSID mode to MASQUERADE Date: Tue, 20 Nov 2018 14:31:14 +1300 Message-ID: <20181120013114.22020-2-felix.jia@alliedtelesis.co.nz> References: <20181120013114.22020-1-felix.jia@alliedtelesis.co.nz> Mime-Version: 1.0 Content-Transfer-Encoding: quoted-printable Cc: Blair Steven , netdev@vger.kernel.org, Anthony Lineham , Scott Parlane , Felix Jia To: "David S . Miller" , Pablo Neira Ayuso Return-path: Received: from gate2.alliedtelesis.co.nz ([202.36.163.20]:60340 "EHLO gate2.alliedtelesis.co.nz" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726119AbeKTL6o (ORCPT ); Tue, 20 Nov 2018 06:58:44 -0500 In-Reply-To: <20181120013114.22020-1-felix.jia@alliedtelesis.co.nz> Sender: netdev-owner@vger.kernel.org List-ID: From: Blair Steven This 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, which allows multiple devices to share an IPv4 address by splitting the L4 port / id into ranges by both masquerading and encapsulating IPv4 packets inside an IPv6 carrier. Co-developed-by: Anthony Lineham Co-developed-by: Scott Parlane Signed-off-by: Blair Steven Signed-off-by: Anthony Lineham Signed-off-by: Scott Parlane Signed-off-by: Felix Jia --- =20include/net/netfilter/nf_nat_l4proto.h | 6 +-- =20.../netfilter/nf_conntrack_tuple_common.h | 5 ++ =20include/uapi/linux/netfilter/nf_nat.h | 4 +- =20net/ipv4/netfilter/nf_nat_proto_icmp.c | 51 ++++++++++++++++++-= =20net/netfilter/nf_nat_core.c | 25 ++++++--- =20net/netfilter/nf_nat_proto_common.c | 51 +++++++++++++++++--= =20net/netfilter/nf_nat_proto_unknown.c | 3 +- =207 files changed, 126 insertions(+), 19 deletions(-) diff --git a/include/net/netfilter/nf_nat_l4proto.h b/include/net/netfilt= er/nf_nat_l4proto.h index b4d6b29bca62..d3fb8f138d0a 100644 --- a/include/net/netfilter/nf_nat_l4proto.h +++ b/include/net/netfilter/nf_nat_l4proto.h @@ -24,8 +24,7 @@ struct nf_nat_l4proto { =20 /* Is the manipable part of the tuple between min and max incl? */ =20 bool (*in_range)(const struct nf_conntrack_tuple *tuple, =20 enum nf_nat_manip_type maniptype, - const union nf_conntrack_man_proto *min, - const union nf_conntrack_man_proto *max); + const struct nf_nat_range2 *range); =20 =20 /* Alter the per-proto part of the tuple (depending on =20 * maniptype), to give a unique tuple in the given range if @@ -67,8 +66,7 @@ extern const struct nf_nat_l4proto nf_nat_l4proto_udpli= te; =20 =20bool nf_nat_l4proto_in_range(const struct nf_conntrack_tuple *tuple, =20 enum nf_nat_manip_type maniptype, - const union nf_conntrack_man_proto *min, - const union nf_conntrack_man_proto *max); + const struct nf_nat_range2 *range); =20 =20void nf_nat_l4proto_unique_tuple(const struct nf_nat_l3proto *l3proto,= =20 struct nf_conntrack_tuple *tuple, diff --git a/include/uapi/linux/netfilter/nf_conntrack_tuple_common.h b/i= nclude/uapi/linux/netfilter/nf_conntrack_tuple_common.h index 64390fac6f7e..36d16d47c2b0 100644 --- a/include/uapi/linux/netfilter/nf_conntrack_tuple_common.h +++ b/include/uapi/linux/netfilter/nf_conntrack_tuple_common.h @@ -39,6 +39,11 @@ union nf_conntrack_man_proto { =20 struct { =20 __be16 key; /* GRE key is 32bit, PPtP only uses 16bit */ =20 } gre; + struct { + unsigned char psid_length; + unsigned char offset; + __be16 psid; + } psid; =20}; =20 =20#define CTINFO2DIR(ctinfo) ((ctinfo) >=3D IP_CT_IS_REPLY ? IP_CT_DIR_R= EPLY : IP_CT_DIR_ORIGINAL) diff --git a/include/uapi/linux/netfilter/nf_nat.h b/include/uapi/linux/n= etfilter/nf_nat.h index 4a95c0db14d4..d145d3eca25f 100644 --- a/include/uapi/linux/netfilter/nf_nat.h +++ b/include/uapi/linux/netfilter/nf_nat.h @@ -11,6 +11,7 @@ =20#define NF_NAT_RANGE_PERSISTENT (1 << 3) =20#define NF_NAT_RANGE_PROTO_RANDOM_FULLY (1 << 4) =20#define NF_NAT_RANGE_PROTO_OFFSET (1 << 5) +#define NF_NAT_RANGE_PSID (1 << 6) =20 =20#define NF_NAT_RANGE_PROTO_RANDOM_ALL \ =20 (NF_NAT_RANGE_PROTO_RANDOM | NF_NAT_RANGE_PROTO_RANDOM_FULLY) @@ -18,7 +19,8 @@ =20#define NF_NAT_RANGE_MASK \ =20 (NF_NAT_RANGE_MAP_IPS | NF_NAT_RANGE_PROTO_SPECIFIED | \ =20 NF_NAT_RANGE_PROTO_RANDOM | NF_NAT_RANGE_PERSISTENT | \ - NF_NAT_RANGE_PROTO_RANDOM_FULLY | NF_NAT_RANGE_PROTO_OFFSET) + NF_NAT_RANGE_PROTO_RANDOM_FULLY | NF_NAT_RANGE_PROTO_OFFSET | \ + NF_NAT_RANGE_PSID) =20 =20struct nf_nat_ipv4_range { =20 unsigned int flags; diff --git a/net/ipv4/netfilter/nf_nat_proto_icmp.c b/net/ipv4/netfilter/= nf_nat_proto_icmp.c index 6d7cf1d79baf..39efac4930b6 100644 --- a/net/ipv4/netfilter/nf_nat_proto_icmp.c +++ b/net/ipv4/netfilter/nf_nat_proto_icmp.c @@ -20,9 +20,23 @@ =20static bool =20icmp_in_range(const struct nf_conntrack_tuple *tuple, =20 enum nf_nat_manip_type maniptype, - const union nf_conntrack_man_proto *min, - const union nf_conntrack_man_proto *max) + const struct nf_nat_range2 *range) =20{ + const union nf_conntrack_man_proto *min =3D &range->min_proto; + const union nf_conntrack_man_proto *max =3D &range->max_proto; + + /* In this case we are in PSID mode and the rules are all different */ + if (range->flags & NF_NAT_RANGE_PSID) { + u16 offset_mask =3D ((1 << min->psid.offset) - 1) << + (16 - min->psid.offset); + u16 psid_mask =3D ((1 << min->psid.psid_length) - 1) << + (16 - min->psid.offset - min->psid.psid_length); + u16 id =3D ntohs(tuple->src.u.icmp.id); + + return ((id & offset_mask) !=3D 0) && + ((id & psid_mask) =3D=3D min->psid.psid); + } + =20 return ntohs(tuple->src.u.icmp.id) >=3D ntohs(min->icmp.id) && =20 ntohs(tuple->src.u.icmp.id) <=3D ntohs(max->icmp.id); =20} @@ -38,6 +52,39 @@ icmp_unique_tuple(const struct nf_nat_l3proto *l3proto= , =20 unsigned int range_size; =20 unsigned int i; =20 + if (range->flags & NF_NAT_RANGE_PSID) { + /* m =3D number of bits in each valid range */ + u16 off; + int m =3D 16 - range->min_proto.psid.psid_length - + range->min_proto.psid.offset; + + range_size =3D (1 << (16 - range->min_proto.psid.psid_length)) - + (1 << m); + off =3D ntohs(tuple->src.u.icmp.id); + for (i =3D 0; ; ++off) { + /* Find the non-PSID parts of the Index. + * To do this we look for an unused ID that is + * comprised of [t_chunk|PSID|b_chunk]. The size of + * these pieces is defined by the psid_length and + * offset. + */ + int b_chunk =3D (off % range_size) & ((1 << (m)) - 1); + int t_chunk =3D (((off % range_size) >> m) & + ((1 << range->min_proto.psid.offset) - + 1)) << + (m + range->min_proto.psid.psid_length); + /* Skip the all-zeroes reserved chunk */ + t_chunk +=3D (1 << (16 - range->min_proto.psid.offset)); + + tuple->src.u.icmp.id =3D htons(t_chunk | + (range->min_proto.psid.psid << m) + | b_chunk); + + if (++i =3D=3D range_size || !nf_nat_used_tuple(tuple, ct)) + return; + } + } + =20 range_size =3D ntohs(range->max_proto.icmp.id) - =20 ntohs(range->min_proto.icmp.id) + 1; =20 /* If no range specified... */ diff --git a/net/netfilter/nf_nat_core.c b/net/netfilter/nf_nat_core.c index e2b196054dfc..18e39af3838d 100644 --- a/net/netfilter/nf_nat_core.c +++ b/net/netfilter/nf_nat_core.c @@ -187,9 +187,15 @@ static int in_range(const struct nf_nat_l3proto *l3p= roto, =20 !l3proto->in_range(tuple, range)) =20 return 0; =20 + /* If we are using PSID mode all protocols need to be checked + * to see that they fit inside the range. + */ + if ((range->flags & NF_NAT_RANGE_PSID) && + !l4proto->in_range(tuple, NF_NAT_MANIP_SRC, range)) + return 0; + =20 if (!(range->flags & NF_NAT_RANGE_PROTO_SPECIFIED) || - l4proto->in_range(tuple, NF_NAT_MANIP_SRC, - &range->min_proto, &range->max_proto)) + l4proto->in_range(tuple, NF_NAT_MANIP_SRC, range)) =20 return 1; =20 =20 return 0; @@ -369,11 +375,18 @@ get_unique_tuple(struct nf_conntrack_tuple *tuple, =20 =20 /* Only bother mapping if it's not already in range and unique */ =20 if (!(range->flags & NF_NAT_RANGE_PROTO_RANDOM_ALL)) { - if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED) { + /* Now that the PSID mode is present we always need to check + * to see if the source ports are in range. + */ + if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED || + (range->flags & NF_NAT_RANGE_PSID && + !in_range(l3proto, l4proto, orig_tuple, range))) { + /* The in_range prototype has been changed to take a + * whole range rather than min and max protocol + * information. + */ =20 if (!(range->flags & NF_NAT_RANGE_PROTO_OFFSET) && - l4proto->in_range(tuple, maniptype, - &range->min_proto, - &range->max_proto) && + l4proto->in_range(tuple, maniptype, range) && =20 (range->min_proto.all =3D=3D range->max_proto.all || =20 !nf_nat_used_tuple(tuple, ct))) =20 goto out; diff --git a/net/netfilter/nf_nat_proto_common.c b/net/netfilter/nf_nat_p= roto_common.c index 5d849d835561..4ca3b2715e7c 100644 --- a/net/netfilter/nf_nat_proto_common.c +++ b/net/netfilter/nf_nat_proto_common.c @@ -19,16 +19,30 @@ =20 =20bool nf_nat_l4proto_in_range(const struct nf_conntrack_tuple *tuple, =20 enum nf_nat_manip_type maniptype, - const union nf_conntrack_man_proto *min, - const union nf_conntrack_man_proto *max) + const struct nf_nat_range2 *range) =20{ =20 __be16 port; + const union nf_conntrack_man_proto *min =3D &range->min_proto; + const union nf_conntrack_man_proto *max =3D &range->max_proto; =20 =20 if (maniptype =3D=3D NF_NAT_MANIP_SRC) =20 port =3D tuple->src.u.all; =20 else =20 port =3D tuple->dst.u.all; =20 + /* In this case we are in PSID mode and the rules are all different */ + if (range->flags & NF_NAT_RANGE_PSID) { + /* m =3D number of bits in each valid range */ + int m =3D 16 - min->psid.psid_length - min->psid.offset; + u16 offset_mask =3D ((1 << min->psid.offset) - 1) << + (16 - min->psid.offset); + u16 psid_mask =3D ((1 << min->psid.psid_length) - 1) << m; + + return ((ntohs(port) & offset_mask) !=3D 0) && + (((ntohs(port) & psid_mask) >> m) =3D=3D + min->psid.psid); + } + =20 return ntohs(port) >=3D ntohs(min->all) && =20 ntohs(port) <=3D ntohs(max->all); =20} @@ -46,9 +60,38 @@ void nf_nat_l4proto_unique_tuple(const struct nf_nat_l= 3proto *l3proto, =20 u_int16_t off; =20 =20 if (maniptype =3D=3D NF_NAT_MANIP_SRC) - portptr =3D &tuple->src.u.all; + portptr =3D &tuple->src.u.tcp.port; =20 else - portptr =3D &tuple->dst.u.all; + portptr =3D &tuple->dst.u.tcp.port; + + if (range->flags & NF_NAT_RANGE_PSID) { + /* Find the non-PSID parts of the port. + * To do this we look for an unused port that is + * comprised of [t_chunk|PSID|b_chunk]. The size of + * these pieces is defined by the psid_length and + * offset. + */ + int m =3D 16 - range->min_proto.psid.psid_length - + range->min_proto.psid.offset; + range_size =3D (1 << (16 - range->min_proto.psid.psid_length)) - + (1 << m); + off =3D ntohs(*portptr); + for (i =3D 0; ; ++off) { + int b_chunk =3D (off % range_size) & ((1 << (m)) - 1); + int t_chunk =3D (((off % range_size) >> m) & + ((1 << range->min_proto.psid.offset) - + 1)) << + (m + range->min_proto.psid.psid_length); + /* Skip the all-zeroes reserved chunk */ + t_chunk +=3D (1 << (16 - range->min_proto.psid.offset)); + + *portptr =3D htons(t_chunk | + (range->min_proto.psid.psid << m) + | b_chunk); + if (++i =3D=3D range_size || !nf_nat_used_tuple(tuple, ct)) + return; + } + } =20 =20 /* If no range specified... */ =20 if (!(range->flags & NF_NAT_RANGE_PROTO_SPECIFIED)) { diff --git a/net/netfilter/nf_nat_proto_unknown.c b/net/netfilter/nf_nat_= proto_unknown.c index c5db3e251232..82140ffc706d 100644 --- a/net/netfilter/nf_nat_proto_unknown.c +++ b/net/netfilter/nf_nat_proto_unknown.c @@ -19,8 +19,7 @@ =20 =20static bool unknown_in_range(const struct nf_conntrack_tuple *tuple, =20 enum nf_nat_manip_type manip_type, - const union nf_conntrack_man_proto *min, - const union nf_conntrack_man_proto *max) + const struct nf_nat_range2 *range) =20{ =20 return true; =20} --=20 2.19.1