All of lore.kernel.org
 help / color / mirror / Atom feed
* [iptables PATCH 0/9] extensions: Merge *_DNAT and *_REDIRECT
@ 2022-03-30 15:58 Phil Sutter
  2022-03-30 15:58 ` [iptables PATCH 1/9] man: DNAT: Describe shifted port range feature Phil Sutter
                   ` (8 more replies)
  0 siblings, 9 replies; 14+ messages in thread
From: Phil Sutter @ 2022-03-30 15:58 UTC (permalink / raw)
  To: Pablo Neira Ayuso; +Cc: netfilter-devel

The multitude of data structures for kernel communication aside, code
for parsing/printing of DNAT and REDIRECT targets in both IPv4 and IPv6
are pretty similar. Stick them into a common source file and share as
much code as possible.

The first two patches are basically fallout from initial code-review.
The next three patches optimize and prepare libipt_DNAT.c as it will
serve as the source to merge into. Therefore patch 6 renames it to
libxt_DNAT.c and finally patches 7 and 8 merge the code.

As an extra, patch 9 "fixes" for service names in ranges by searching
the longest match.

Phil Sutter (9):
  man: DNAT: Describe shifted port range feature
  Revert "libipt_[SD]NAT: avoid false error about multiple destinations
    specified"
  extensions: ipt_DNAT: Merge v1 and v2 parsers
  extensions: ipt_DNAT: Merge v1/v2 print/save code
  extensions: ipt_DNAT: Combine xlate functions also
  extensions: DNAT: Rename from libipt to libxt
  extensions: Merge IPv4 and IPv6 DNAT targets
  extensions: Merge REDIRECT into DNAT
  extensions: DNAT: Support service names in all spots

 extensions/GNUmakefile.in          |   4 +-
 extensions/libip6t_DNAT.c          | 409 -----------------
 extensions/libip6t_DNAT.txlate     |  11 -
 extensions/libip6t_REDIRECT.c      | 170 -------
 extensions/libip6t_REDIRECT.t      |   6 -
 extensions/libip6t_REDIRECT.txlate |   5 -
 extensions/libip6t_SNAT.c          |   9 +-
 extensions/libipt_DNAT.c           | 507 ---------------------
 extensions/libipt_DNAT.t           |   6 +
 extensions/libipt_DNAT.txlate      |  14 -
 extensions/libipt_REDIRECT.c       | 174 --------
 extensions/libipt_REDIRECT.t       |   6 -
 extensions/libipt_REDIRECT.txlate  |   5 -
 extensions/libipt_SNAT.c           |   3 -
 extensions/libxt_DNAT.c            | 685 +++++++++++++++++++++++++++++
 extensions/libxt_DNAT.man          |   5 +-
 extensions/libxt_DNAT.txlate       |  35 ++
 extensions/libxt_REDIRECT.t        |  11 +
 extensions/libxt_REDIRECT.txlate   |  17 +
 19 files changed, 762 insertions(+), 1320 deletions(-)
 delete mode 100644 extensions/libip6t_DNAT.c
 delete mode 100644 extensions/libip6t_DNAT.txlate
 delete mode 100644 extensions/libip6t_REDIRECT.c
 delete mode 100644 extensions/libip6t_REDIRECT.t
 delete mode 100644 extensions/libip6t_REDIRECT.txlate
 delete mode 100644 extensions/libipt_DNAT.c
 delete mode 100644 extensions/libipt_DNAT.txlate
 delete mode 100644 extensions/libipt_REDIRECT.c
 delete mode 100644 extensions/libipt_REDIRECT.t
 delete mode 100644 extensions/libipt_REDIRECT.txlate
 create mode 100644 extensions/libxt_DNAT.c
 create mode 100644 extensions/libxt_DNAT.txlate
 create mode 100644 extensions/libxt_REDIRECT.t
 create mode 100644 extensions/libxt_REDIRECT.txlate

-- 
2.34.1


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [iptables PATCH 1/9] man: DNAT: Describe shifted port range feature
  2022-03-30 15:58 [iptables PATCH 0/9] extensions: Merge *_DNAT and *_REDIRECT Phil Sutter
@ 2022-03-30 15:58 ` Phil Sutter
  2022-03-30 15:58 ` [iptables PATCH 2/9] Revert "libipt_[SD]NAT: avoid false error about multiple destinations specified" Phil Sutter
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 14+ messages in thread
From: Phil Sutter @ 2022-03-30 15:58 UTC (permalink / raw)
  To: Pablo Neira Ayuso; +Cc: netfilter-devel

This wasn't mentioned anywhere.

Signed-off-by: Phil Sutter <phil@nwl.cc>
---
 extensions/libxt_DNAT.man | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/extensions/libxt_DNAT.man b/extensions/libxt_DNAT.man
index c3daea9a40394..e044c8216fc09 100644
--- a/extensions/libxt_DNAT.man
+++ b/extensions/libxt_DNAT.man
@@ -10,7 +10,7 @@ should be modified (and all future packets in this connection will
 also be mangled), and rules should cease being examined.  It takes the
 following options:
 .TP
-\fB\-\-to\-destination\fP [\fIipaddr\fP[\fB\-\fP\fIipaddr\fP]][\fB:\fP\fIport\fP[\fB\-\fP\fIport\fP]]
+\fB\-\-to\-destination\fP [\fIipaddr\fP[\fB\-\fP\fIipaddr\fP]][\fB:\fP\fIport\fP[\fB\-\fP\fIport\fP[\fB/\fIbaseport\fP]]]
 which can specify a single new destination IP address, an inclusive
 range of IP addresses. Optionally a port range,
 if the rule also specifies one of the following protocols:
@@ -18,6 +18,9 @@ if the rule also specifies one of the following protocols:
 If no port range is specified, then the destination port will never be
 modified. If no IP address is specified then only the destination port
 will be modified.
+If \fBbaseport\fP is given, the difference of the original destination port and
+its value is used as offset into the mapping port range. This allows to create
+shifted portmap ranges and is available since kernel version 4.18.
 .TP
 \fB\-\-random\fP
 If option
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [iptables PATCH 2/9] Revert "libipt_[SD]NAT: avoid false error about multiple destinations specified"
  2022-03-30 15:58 [iptables PATCH 0/9] extensions: Merge *_DNAT and *_REDIRECT Phil Sutter
  2022-03-30 15:58 ` [iptables PATCH 1/9] man: DNAT: Describe shifted port range feature Phil Sutter
@ 2022-03-30 15:58 ` Phil Sutter
  2022-03-30 15:58 ` [iptables PATCH 3/9] extensions: ipt_DNAT: Merge v1 and v2 parsers Phil Sutter
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 14+ messages in thread
From: Phil Sutter @ 2022-03-30 15:58 UTC (permalink / raw)
  To: Pablo Neira Ayuso; +Cc: netfilter-devel

This reverts commit f25b2355e889290879c8cecad3dd24ec0c384fb8.

The workaround is not needed anymore since commit 30b178b9bf11e
("extensions: *NAT: Kill multiple IPv4 range support").

While being at it, drop the same hidden flag logic from
libip6t_[SD]NAT extensions as well and just don't set XTOPT_MULTI so
guided option parser will reject multiple parameters automatically.

Signed-off-by: Phil Sutter <phil@nwl.cc>
---
 extensions/libip6t_DNAT.c | 9 +--------
 extensions/libip6t_SNAT.c | 9 +--------
 extensions/libipt_DNAT.c  | 8 ++------
 extensions/libipt_SNAT.c  | 3 ---
 4 files changed, 4 insertions(+), 25 deletions(-)

diff --git a/extensions/libip6t_DNAT.c b/extensions/libip6t_DNAT.c
index f1ad81436316b..d51994c09e7f2 100644
--- a/extensions/libip6t_DNAT.c
+++ b/extensions/libip6t_DNAT.c
@@ -19,10 +19,8 @@ enum {
 	O_TO_DEST = 0,
 	O_RANDOM,
 	O_PERSISTENT,
-	O_X_TO_DEST,
 	F_TO_DEST   = 1 << O_TO_DEST,
 	F_RANDOM   = 1 << O_RANDOM,
-	F_X_TO_DEST = 1 << O_X_TO_DEST,
 };
 
 static void DNAT_help(void)
@@ -45,7 +43,7 @@ static void DNAT_help_v2(void)
 
 static const struct xt_option_entry DNAT_opts[] = {
 	{.name = "to-destination", .id = O_TO_DEST, .type = XTTYPE_STRING,
-	 .flags = XTOPT_MAND | XTOPT_MULTI},
+	 .flags = XTOPT_MAND},
 	{.name = "random", .id = O_RANDOM, .type = XTTYPE_NONE},
 	{.name = "persistent", .id = O_PERSISTENT, .type = XTTYPE_NONE},
 	XTOPT_TABLEEND,
@@ -183,12 +181,7 @@ static void _DNAT_parse(struct xt_option_call *cb,
 	xtables_option_parse(cb);
 	switch (cb->entry->id) {
 	case O_TO_DEST:
-		if (cb->xflags & F_X_TO_DEST) {
-			xtables_error(PARAMETER_PROBLEM,
-				      "DNAT: Multiple --to-destination not supported");
-		}
 		parse_to(cb->arg, portok, range, rev);
-		cb->xflags |= F_X_TO_DEST;
 		break;
 	case O_PERSISTENT:
 		range->flags |= NF_NAT_RANGE_PERSISTENT;
diff --git a/extensions/libip6t_SNAT.c b/extensions/libip6t_SNAT.c
index 6d19614c7c708..4fe272b262a3d 100644
--- a/extensions/libip6t_SNAT.c
+++ b/extensions/libip6t_SNAT.c
@@ -20,11 +20,9 @@ enum {
 	O_RANDOM,
 	O_RANDOM_FULLY,
 	O_PERSISTENT,
-	O_X_TO_SRC,
 	F_TO_SRC       = 1 << O_TO_SRC,
 	F_RANDOM       = 1 << O_RANDOM,
 	F_RANDOM_FULLY = 1 << O_RANDOM_FULLY,
-	F_X_TO_SRC     = 1 << O_X_TO_SRC,
 };
 
 static void SNAT_help(void)
@@ -38,7 +36,7 @@ static void SNAT_help(void)
 
 static const struct xt_option_entry SNAT_opts[] = {
 	{.name = "to-source", .id = O_TO_SRC, .type = XTTYPE_STRING,
-	 .flags = XTOPT_MAND | XTOPT_MULTI},
+	 .flags = XTOPT_MAND},
 	{.name = "random", .id = O_RANDOM, .type = XTTYPE_NONE},
 	{.name = "random-fully", .id = O_RANDOM_FULLY, .type = XTTYPE_NONE},
 	{.name = "persistent", .id = O_PERSISTENT, .type = XTTYPE_NONE},
@@ -163,12 +161,7 @@ static void SNAT_parse(struct xt_option_call *cb)
 	xtables_option_parse(cb);
 	switch (cb->entry->id) {
 	case O_TO_SRC:
-		if (cb->xflags & F_X_TO_SRC) {
-			xtables_error(PARAMETER_PROBLEM,
-				      "SNAT: Multiple --to-source not supported");
-		}
 		parse_to(cb->arg, portok, range);
-		cb->xflags |= F_X_TO_SRC;
 		break;
 	case O_PERSISTENT:
 		range->flags |= NF_NAT_RANGE_PERSISTENT;
diff --git a/extensions/libipt_DNAT.c b/extensions/libipt_DNAT.c
index eefa95eb73630..e93ab6958969b 100644
--- a/extensions/libipt_DNAT.c
+++ b/extensions/libipt_DNAT.c
@@ -12,10 +12,8 @@ enum {
 	O_TO_DEST = 0,
 	O_RANDOM,
 	O_PERSISTENT,
-	O_X_TO_DEST, /* hidden flag */
-	F_TO_DEST   = 1 << O_TO_DEST,
-	F_RANDOM    = 1 << O_RANDOM,
-	F_X_TO_DEST = 1 << O_X_TO_DEST,
+	F_TO_DEST = 1 << O_TO_DEST,
+	F_RANDOM  = 1 << O_RANDOM,
 };
 
 static void DNAT_help(void)
@@ -145,7 +143,6 @@ static void DNAT_parse(struct xt_option_call *cb)
 	switch (cb->entry->id) {
 	case O_TO_DEST:
 		parse_to(cb->arg, portok, mr->range);
-		cb->xflags |= F_X_TO_DEST;
 		break;
 	case O_PERSISTENT:
 		mr->range->flags |= NF_NAT_RANGE_PERSISTENT;
@@ -367,7 +364,6 @@ static void DNAT_parse_v2(struct xt_option_call *cb)
 	switch (cb->entry->id) {
 	case O_TO_DEST:
 		parse_to_v2(cb->arg, portok, range);
-		cb->xflags |= F_X_TO_DEST;
 		break;
 	case O_PERSISTENT:
 		range->flags |= NF_NAT_RANGE_PERSISTENT;
diff --git a/extensions/libipt_SNAT.c b/extensions/libipt_SNAT.c
index bd36830ae91ce..211a20bc45bfe 100644
--- a/extensions/libipt_SNAT.c
+++ b/extensions/libipt_SNAT.c
@@ -13,11 +13,9 @@ enum {
 	O_RANDOM,
 	O_RANDOM_FULLY,
 	O_PERSISTENT,
-	O_X_TO_SRC,
 	F_TO_SRC       = 1 << O_TO_SRC,
 	F_RANDOM       = 1 << O_RANDOM,
 	F_RANDOM_FULLY = 1 << O_RANDOM_FULLY,
-	F_X_TO_SRC     = 1 << O_X_TO_SRC,
 };
 
 static void SNAT_help(void)
@@ -139,7 +137,6 @@ static void SNAT_parse(struct xt_option_call *cb)
 	switch (cb->entry->id) {
 	case O_TO_SRC:
 		parse_to(cb->arg, portok, mr->range);
-		cb->xflags |= F_X_TO_SRC;
 		break;
 	case O_PERSISTENT:
 		mr->range->flags |= NF_NAT_RANGE_PERSISTENT;
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [iptables PATCH 3/9] extensions: ipt_DNAT: Merge v1 and v2 parsers
  2022-03-30 15:58 [iptables PATCH 0/9] extensions: Merge *_DNAT and *_REDIRECT Phil Sutter
  2022-03-30 15:58 ` [iptables PATCH 1/9] man: DNAT: Describe shifted port range feature Phil Sutter
  2022-03-30 15:58 ` [iptables PATCH 2/9] Revert "libipt_[SD]NAT: avoid false error about multiple destinations specified" Phil Sutter
@ 2022-03-30 15:58 ` Phil Sutter
  2022-03-30 15:58 ` [iptables PATCH 4/9] extensions: ipt_DNAT: Merge v1/v2 print/save code Phil Sutter
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 14+ messages in thread
From: Phil Sutter @ 2022-03-30 15:58 UTC (permalink / raw)
  To: Pablo Neira Ayuso; +Cc: netfilter-devel

Use v2 parser for both and copy field values into v1 data structure if
needed.

While being at it:

* Introduce parse_ports() function similar to the one in
  libipt_REDIRECT.c.
* Use xtables_strtoui() in the above instead of atoi() for integrated
  range checking.
* Parse IP addresses using inet_pton(), writing directly into
  struct nf_nat_range2 fields.

Signed-off-by: Phil Sutter <phil@nwl.cc>
---
 extensions/libipt_DNAT.c | 290 +++++++++++++++------------------------
 1 file changed, 111 insertions(+), 179 deletions(-)

diff --git a/extensions/libipt_DNAT.c b/extensions/libipt_DNAT.c
index e93ab6958969b..2a7b1bc4ec0a6 100644
--- a/extensions/libipt_DNAT.c
+++ b/extensions/libipt_DNAT.c
@@ -5,6 +5,7 @@
 #include <xtables.h>
 #include <iptables.h> /* get_kernel_version */
 #include <limits.h> /* INT_MAX in ip_tables.h */
+#include <arpa/inet.h>
 #include <linux/netfilter_ipv4/ip_tables.h>
 #include <linux/netfilter/nf_nat.h>
 
@@ -42,54 +43,83 @@ static const struct xt_option_entry DNAT_opts[] = {
 	XTOPT_TABLEEND,
 };
 
+/* Parses ports */
+static void
+parse_ports(const char *arg, bool portok, struct nf_nat_range2 *range)
+{
+	unsigned int port, maxport, baseport;
+	char *end = NULL;
+
+	if (!portok)
+		xtables_error(PARAMETER_PROBLEM,
+			      "Need TCP, UDP, SCTP or DCCP with port specification");
+
+	range->flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
+
+	if (!xtables_strtoui(arg, &end, &port, 1, UINT16_MAX))
+		xtables_error(PARAMETER_PROBLEM,
+			      "Port `%s' not valid", arg);
+
+	switch (*end) {
+	case '\0':
+		range->min_proto.tcp.port
+			= range->max_proto.tcp.port
+			= htons(port);
+		return;
+	case '-':
+		arg = end + 1;
+		break;
+	case ':':
+		xtables_error(PARAMETER_PROBLEM,
+			      "Invalid port:port syntax - use dash");
+	default:
+		xtables_error(PARAMETER_PROBLEM,
+			      "Garbage after port value: `%s'", end);
+	}
+
+	if (!xtables_strtoui(arg, &end, &maxport, 1, UINT16_MAX))
+		xtables_error(PARAMETER_PROBLEM,
+			      "Port `%s' not valid", arg);
+
+	if (maxport < port)
+		/* People are stupid. */
+		xtables_error(PARAMETER_PROBLEM,
+			   "Port range `%s' funky", arg);
+
+	range->min_proto.tcp.port = htons(port);
+	range->max_proto.tcp.port = htons(maxport);
+
+	switch (*end) {
+	case '\0':
+		return;
+	case '/':
+		arg = end + 1;
+		break;
+	default:
+		xtables_error(PARAMETER_PROBLEM,
+			      "Garbage after port range: `%s'", end);
+	}
+
+	if (!xtables_strtoui(arg, &end, &baseport, 1, UINT16_MAX))
+		xtables_error(PARAMETER_PROBLEM,
+			      "Port `%s' not valid", arg);
+
+	range->flags |= NF_NAT_RANGE_PROTO_OFFSET;
+	range->base_proto.tcp.port = htons(baseport);
+}
+
 /* Ranges expected in network order. */
 static void
-parse_to(const char *orig_arg, int portok, struct nf_nat_ipv4_range *range)
+parse_to(const char *orig_arg, bool portok, struct nf_nat_range2 *range)
 {
-	char *arg, *colon, *dash, *error;
-	const struct in_addr *ip;
+	char *arg, *colon, *dash;
 
 	arg = xtables_strdup(orig_arg);
 	colon = strchr(arg, ':');
 
 	if (colon) {
-		int port;
-
-		if (!portok)
-			xtables_error(PARAMETER_PROBLEM,
-				   "Need TCP, UDP, SCTP or DCCP with port specification");
-
-		range->flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
+		parse_ports(colon + 1, portok, range);
 
-		port = atoi(colon+1);
-		if (port <= 0 || port > 65535)
-			xtables_error(PARAMETER_PROBLEM,
-				   "Port `%s' not valid\n", colon+1);
-
-		error = strchr(colon+1, ':');
-		if (error)
-			xtables_error(PARAMETER_PROBLEM,
-				   "Invalid port:port syntax - use dash\n");
-
-		dash = strchr(colon, '-');
-		if (!dash) {
-			range->min.tcp.port
-				= range->max.tcp.port
-				= htons(port);
-		} else {
-			int maxport;
-
-			maxport = atoi(dash + 1);
-			if (maxport <= 0 || maxport > 65535)
-				xtables_error(PARAMETER_PROBLEM,
-					   "Port `%s' not valid\n", dash+1);
-			if (maxport < port)
-				/* People are stupid. */
-				xtables_error(PARAMETER_PROBLEM,
-					   "Port range `%s' funky\n", colon+1);
-			range->min.tcp.port = htons(port);
-			range->max.tcp.port = htons(maxport);
-		}
 		/* Starts with a colon? No IP info...*/
 		if (colon == arg) {
 			free(arg);
@@ -106,46 +136,57 @@ parse_to(const char *orig_arg, int portok, struct nf_nat_ipv4_range *range)
 	if (dash)
 		*dash = '\0';
 
-	ip = xtables_numeric_to_ipaddr(arg);
-	if (!ip)
-		xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
-			   arg);
-	range->min_ip = ip->s_addr;
+	if (!inet_pton(AF_INET, arg, &range->min_addr))
+		xtables_error(PARAMETER_PROBLEM,
+			      "Bad IP address \"%s\"\n", arg);
 	if (dash) {
-		ip = xtables_numeric_to_ipaddr(dash+1);
-		if (!ip)
-			xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
-				   dash+1);
-		range->max_ip = ip->s_addr;
-	} else
-		range->max_ip = range->min_ip;
-
+		if (!inet_pton(AF_INET, dash + 1, &range->max_addr))
+			xtables_error(PARAMETER_PROBLEM,
+				      "Bad IP address \"%s\"\n", dash + 1);
+	} else {
+		range->max_addr = range->min_addr;
+	}
 	free(arg);
 	return;
 }
 
+static void __DNAT_parse(struct xt_option_call *cb, __u16 proto,
+			 struct nf_nat_range2 *range)
+{
+	bool portok = proto == IPPROTO_TCP ||
+		      proto == IPPROTO_UDP ||
+		      proto == IPPROTO_SCTP ||
+		      proto == IPPROTO_DCCP ||
+		      proto == IPPROTO_ICMP;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_TO_DEST:
+		parse_to(cb->arg, portok, range);
+		break;
+	case O_PERSISTENT:
+		range->flags |= NF_NAT_RANGE_PERSISTENT;
+		break;
+	}
+}
+
 static void DNAT_parse(struct xt_option_call *cb)
 {
 	struct nf_nat_ipv4_multi_range_compat *mr = (void *)cb->data;
 	const struct ipt_entry *entry = cb->xt_entry;
-	int portok;
+	struct nf_nat_range2 range = {};
 
-	if (entry->ip.proto == IPPROTO_TCP
-	    || entry->ip.proto == IPPROTO_UDP
-	    || entry->ip.proto == IPPROTO_SCTP
-	    || entry->ip.proto == IPPROTO_DCCP
-	    || entry->ip.proto == IPPROTO_ICMP)
-		portok = 1;
-	else
-		portok = 0;
+	__DNAT_parse(cb, entry->ip.proto, &range);
 
-	xtables_option_parse(cb);
 	switch (cb->entry->id) {
 	case O_TO_DEST:
-		parse_to(cb->arg, portok, mr->range);
-		break;
+		mr->range->min_ip = range.min_addr.ip;
+		mr->range->max_ip = range.max_addr.ip;
+		mr->range->min = range.min_proto;
+		mr->range->max = range.max_proto;
+		/* fall through */
 	case O_PERSISTENT:
-		mr->range->flags |= NF_NAT_RANGE_PERSISTENT;
+		mr->range->flags |= range.flags;
 		break;
 	}
 }
@@ -159,6 +200,10 @@ static void DNAT_fcheck(struct xt_fcheck_call *cb)
 		mr->range[0].flags |= NF_NAT_RANGE_PROTO_RANDOM;
 
 	mr->rangesize = 1;
+
+	if (mr->range[0].flags & NF_NAT_RANGE_PROTO_OFFSET)
+		xtables_error(PARAMETER_PROBLEM,
+			      "Shifted portmap ranges not supported with this kernel");
 }
 
 static void print_range(const struct nf_nat_ipv4_range *r)
@@ -251,124 +296,11 @@ static int DNAT_xlate(struct xt_xlate *xl,
 	return 1;
 }
 
-static void
-parse_to_v2(const char *orig_arg, int portok, struct nf_nat_range2 *range)
-{
-	char *arg, *colon, *dash, *error;
-	const struct in_addr *ip;
-
-	arg = xtables_strdup(orig_arg);
-
-	colon = strchr(arg, ':');
-	if (colon) {
-		int port;
-
-		if (!portok)
-			xtables_error(PARAMETER_PROBLEM,
-				   "Need TCP, UDP, SCTP or DCCP with port specification");
-
-		range->flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
-
-		port = atoi(colon+1);
-		if (port <= 0 || port > 65535)
-			xtables_error(PARAMETER_PROBLEM,
-				   "Port `%s' not valid\n", colon+1);
-
-		error = strchr(colon+1, ':');
-		if (error)
-			xtables_error(PARAMETER_PROBLEM,
-				   "Invalid port:port syntax - use dash\n");
-
-		dash = strchr(colon, '-');
-		if (!dash) {
-			range->min_proto.tcp.port
-				= range->max_proto.tcp.port
-				= htons(port);
-		} else {
-			int maxport;
-			char *slash;
-
-			maxport = atoi(dash + 1);
-			if (maxport <= 0 || maxport > 65535)
-				xtables_error(PARAMETER_PROBLEM,
-					   "Port `%s' not valid\n", dash+1);
-			if (maxport < port)
-				/* People are stupid. */
-				xtables_error(PARAMETER_PROBLEM,
-					   "Port range `%s' funky\n", colon+1);
-			range->min_proto.tcp.port = htons(port);
-			range->max_proto.tcp.port = htons(maxport);
-
-			slash = strchr(dash, '/');
-			if (slash) {
-				int baseport;
-
-				baseport = atoi(slash + 1);
-				if (baseport <= 0 || baseport > 65535)
-					xtables_error(PARAMETER_PROBLEM,
-							 "Port `%s' not valid\n", slash+1);
-				range->flags |= NF_NAT_RANGE_PROTO_OFFSET;
-				range->base_proto.tcp.port = htons(baseport);
-			}
-		}
-		/* Starts with a colon? No IP info...*/
-		if (colon == arg) {
-			free(arg);
-			return;
-		}
-		*colon = '\0';
-	}
-
-	range->flags |= NF_NAT_RANGE_MAP_IPS;
-	dash = strchr(arg, '-');
-	if (colon && dash && dash > colon)
-		dash = NULL;
-
-	if (dash)
-		*dash = '\0';
-
-	ip = xtables_numeric_to_ipaddr(arg);
-	if (!ip)
-		xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
-			   arg);
-	range->min_addr.in = *ip;
-	if (dash) {
-		ip = xtables_numeric_to_ipaddr(dash+1);
-		if (!ip)
-			xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
-				   dash+1);
-		range->max_addr.in = *ip;
-	} else
-		range->max_addr = range->min_addr;
-
-	free(arg);
-	return;
-}
-
 static void DNAT_parse_v2(struct xt_option_call *cb)
 {
 	const struct ipt_entry *entry = cb->xt_entry;
-	struct nf_nat_range2 *range = cb->data;
-	int portok;
-
-	if (entry->ip.proto == IPPROTO_TCP
-	    || entry->ip.proto == IPPROTO_UDP
-	    || entry->ip.proto == IPPROTO_SCTP
-	    || entry->ip.proto == IPPROTO_DCCP
-	    || entry->ip.proto == IPPROTO_ICMP)
-		portok = 1;
-	else
-		portok = 0;
 
-	xtables_option_parse(cb);
-	switch (cb->entry->id) {
-	case O_TO_DEST:
-		parse_to_v2(cb->arg, portok, range);
-		break;
-	case O_PERSISTENT:
-		range->flags |= NF_NAT_RANGE_PERSISTENT;
-		break;
-	}
+	__DNAT_parse(cb, entry->ip.proto, cb->data);
 }
 
 static void DNAT_fcheck_v2(struct xt_fcheck_call *cb)
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [iptables PATCH 4/9] extensions: ipt_DNAT: Merge v1/v2 print/save code
  2022-03-30 15:58 [iptables PATCH 0/9] extensions: Merge *_DNAT and *_REDIRECT Phil Sutter
                   ` (2 preceding siblings ...)
  2022-03-30 15:58 ` [iptables PATCH 3/9] extensions: ipt_DNAT: Merge v1 and v2 parsers Phil Sutter
@ 2022-03-30 15:58 ` Phil Sutter
  2022-03-30 15:58 ` [iptables PATCH 5/9] extensions: ipt_DNAT: Combine xlate functions also Phil Sutter
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 14+ messages in thread
From: Phil Sutter @ 2022-03-30 15:58 UTC (permalink / raw)
  To: Pablo Neira Ayuso; +Cc: netfilter-devel

Turn print_range() function into sprint_range() so it becomes more
versatile. Make it accept the new nf_nat_range2 data structure and
make v1 callers convert their nf_nat_ipv4_multi_range_compat structs
to that.
This allows to introduce an inner __DNAT_print() which acts for v1 and
v2 and prints either 'print' or 'save' syntax.

Signed-off-by: Phil Sutter <phil@nwl.cc>
---
 extensions/libipt_DNAT.c | 111 ++++++++++++++++-----------------------
 1 file changed, 46 insertions(+), 65 deletions(-)

diff --git a/extensions/libipt_DNAT.c b/extensions/libipt_DNAT.c
index 2a7b1bc4ec0a6..b72437d5e92f2 100644
--- a/extensions/libipt_DNAT.c
+++ b/extensions/libipt_DNAT.c
@@ -9,6 +9,15 @@
 #include <linux/netfilter_ipv4/ip_tables.h>
 #include <linux/netfilter/nf_nat.h>
 
+#define TO_IPV4_MRC(ptr) ((const struct nf_nat_ipv4_multi_range_compat *)(ptr))
+#define RANGE2_INIT_FROM_IPV4_MRC(ptr) {			\
+	.flags		= TO_IPV4_MRC(ptr)->range[0].flags,	\
+	.min_addr.ip	= TO_IPV4_MRC(ptr)->range[0].min_ip,	\
+	.max_addr.ip	= TO_IPV4_MRC(ptr)->range[0].max_ip,	\
+	.min_proto	= TO_IPV4_MRC(ptr)->range[0].min,	\
+	.max_proto	= TO_IPV4_MRC(ptr)->range[0].max,	\
+};
+
 enum {
 	O_TO_DEST = 0,
 	O_RANDOM,
@@ -206,51 +215,55 @@ static void DNAT_fcheck(struct xt_fcheck_call *cb)
 			      "Shifted portmap ranges not supported with this kernel");
 }
 
-static void print_range(const struct nf_nat_ipv4_range *r)
+static char *sprint_range(const struct nf_nat_range2 *r)
 {
-	if (r->flags & NF_NAT_RANGE_MAP_IPS) {
-		struct in_addr a;
+	static char buf[INET_ADDRSTRLEN * 2 + 1 + 6 * 3];
 
-		a.s_addr = r->min_ip;
-		printf("%s", xtables_ipaddr_to_numeric(&a));
-		if (r->max_ip != r->min_ip) {
-			a.s_addr = r->max_ip;
-			printf("-%s", xtables_ipaddr_to_numeric(&a));
-		}
+	if (r->flags & NF_NAT_RANGE_MAP_IPS) {
+		sprintf(buf, "%s", xtables_ipaddr_to_numeric(&r->min_addr.in));
+		if (memcmp(&r->min_addr, &r->max_addr, sizeof(r->min_addr)))
+			sprintf(buf + strlen(buf), "-%s",
+				xtables_ipaddr_to_numeric(&r->max_addr.in));
+	} else {
+		buf[0] = '\0';
 	}
 	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
-		printf(":");
-		printf("%hu", ntohs(r->min.tcp.port));
-		if (r->max.tcp.port != r->min.tcp.port)
-			printf("-%hu", ntohs(r->max.tcp.port));
+		sprintf(buf + strlen(buf), ":%hu",
+			ntohs(r->min_proto.tcp.port));
+		if (r->max_proto.tcp.port != r->min_proto.tcp.port)
+			sprintf(buf + strlen(buf), "-%hu",
+				ntohs(r->max_proto.tcp.port));
+		if (r->flags & NF_NAT_RANGE_PROTO_OFFSET)
+			sprintf(buf + strlen(buf), "/%hu",
+				ntohs(r->base_proto.tcp.port));
 	}
+	return buf;
+}
+
+static void __DNAT_print(const struct nf_nat_range2 *r, bool save)
+{
+	const char *dashdash = save ? "--" : "";
+
+	printf(" %s%s", save ? "--to-destination " : "to:", sprint_range(r));
+	if (r->flags & NF_NAT_RANGE_PROTO_RANDOM)
+		printf(" %srandom", dashdash);
+	if (r->flags & NF_NAT_RANGE_PERSISTENT)
+		printf(" %spersistent", dashdash);
 }
 
 static void DNAT_print(const void *ip, const struct xt_entry_target *target,
                        int numeric)
 {
-	const struct nf_nat_ipv4_multi_range_compat *mr =
-				(const void *)target->data;
-
-	printf(" to:");
-	print_range(mr->range);
-	if (mr->range->flags & NF_NAT_RANGE_PROTO_RANDOM)
-		printf(" random");
-	if (mr->range->flags & NF_NAT_RANGE_PERSISTENT)
-		printf(" persistent");
+	struct nf_nat_range2 range = RANGE2_INIT_FROM_IPV4_MRC(target->data);
+
+	__DNAT_print(&range, false);
 }
 
 static void DNAT_save(const void *ip, const struct xt_entry_target *target)
 {
-	const struct nf_nat_ipv4_multi_range_compat *mr =
-				(const void *)target->data;
-
-	printf(" --to-destination ");
-	print_range(mr->range);
-	if (mr->range->flags & NF_NAT_RANGE_PROTO_RANDOM)
-		printf(" --random");
-	if (mr->range->flags & NF_NAT_RANGE_PERSISTENT)
-		printf(" --persistent");
+	struct nf_nat_range2 range = RANGE2_INIT_FROM_IPV4_MRC(target->data);
+
+	__DNAT_print(&range, true);
 }
 
 static void print_range_xlate(const struct nf_nat_ipv4_range *r,
@@ -312,47 +325,15 @@ static void DNAT_fcheck_v2(struct xt_fcheck_call *cb)
 		range->flags |= NF_NAT_RANGE_PROTO_RANDOM;
 }
 
-static void print_range_v2(const struct nf_nat_range2 *range)
-{
-	if (range->flags & NF_NAT_RANGE_MAP_IPS) {
-		printf("%s", xtables_ipaddr_to_numeric(&range->min_addr.in));
-		if (memcmp(&range->min_addr, &range->max_addr,
-			   sizeof(range->min_addr)))
-			printf("-%s", xtables_ipaddr_to_numeric(&range->max_addr.in));
-	}
-	if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
-		printf(":");
-		printf("%hu", ntohs(range->min_proto.tcp.port));
-		if (range->max_proto.tcp.port != range->min_proto.tcp.port)
-			printf("-%hu", ntohs(range->max_proto.tcp.port));
-		if (range->flags & NF_NAT_RANGE_PROTO_OFFSET)
-			printf("/%hu", ntohs(range->base_proto.tcp.port));
-	}
-}
-
 static void DNAT_print_v2(const void *ip, const struct xt_entry_target *target,
                        int numeric)
 {
-	const struct nf_nat_range2 *range = (const void *)target->data;
-
-	printf(" to:");
-	print_range_v2(range);
-	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
-		printf(" random");
-	if (range->flags & NF_NAT_RANGE_PERSISTENT)
-		printf(" persistent");
+	__DNAT_print((const void *)target->data, false);
 }
 
 static void DNAT_save_v2(const void *ip, const struct xt_entry_target *target)
 {
-	const struct nf_nat_range2 *range = (const void *)target->data;
-
-	printf(" --to-destination ");
-	print_range_v2(range);
-	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
-		printf(" --random");
-	if (range->flags & NF_NAT_RANGE_PERSISTENT)
-		printf(" --persistent");
+	__DNAT_print((const void *)target->data, true);
 }
 
 static void print_range_xlate_v2(const struct nf_nat_range2 *range,
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [iptables PATCH 5/9] extensions: ipt_DNAT: Combine xlate functions also
  2022-03-30 15:58 [iptables PATCH 0/9] extensions: Merge *_DNAT and *_REDIRECT Phil Sutter
                   ` (3 preceding siblings ...)
  2022-03-30 15:58 ` [iptables PATCH 4/9] extensions: ipt_DNAT: Merge v1/v2 print/save code Phil Sutter
@ 2022-03-30 15:58 ` Phil Sutter
  2022-03-30 15:58 ` [iptables PATCH 6/9] extensions: DNAT: Rename from libipt to libxt Phil Sutter
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 14+ messages in thread
From: Phil Sutter @ 2022-03-30 15:58 UTC (permalink / raw)
  To: Pablo Neira Ayuso; +Cc: netfilter-devel

Make use of the new sprint_range() to introduce a common inner function
for both v1 and v2 xlate functions.

Also abort translation with shifted port ranges to not hide the missing
feature in nftables.

Signed-off-by: Phil Sutter <phil@nwl.cc>
---
 extensions/libipt_DNAT.c | 88 ++++++++++------------------------------
 1 file changed, 21 insertions(+), 67 deletions(-)

diff --git a/extensions/libipt_DNAT.c b/extensions/libipt_DNAT.c
index b72437d5e92f2..9a179919f522d 100644
--- a/extensions/libipt_DNAT.c
+++ b/extensions/libipt_DNAT.c
@@ -266,47 +266,36 @@ static void DNAT_save(const void *ip, const struct xt_entry_target *target)
 	__DNAT_print(&range, true);
 }
 
-static void print_range_xlate(const struct nf_nat_ipv4_range *r,
-			struct xt_xlate *xl)
+static int __DNAT_xlate(struct xt_xlate *xl, const struct nf_nat_range2 *r)
 {
-	if (r->flags & NF_NAT_RANGE_MAP_IPS) {
-		struct in_addr a;
+	char *range_str = sprint_range(r);
+	const char *sep = " ";
 
-		a.s_addr = r->min_ip;
-		xt_xlate_add(xl, "%s", xtables_ipaddr_to_numeric(&a));
-		if (r->max_ip != r->min_ip) {
-			a.s_addr = r->max_ip;
-			xt_xlate_add(xl, "-%s", xtables_ipaddr_to_numeric(&a));
-		}
+	/* shifted portmap ranges are not supported by nftables */
+	if (r->flags & NF_NAT_RANGE_PROTO_OFFSET)
+		return 0;
+
+	xt_xlate_add(xl, "dnat");
+	if (strlen(range_str))
+		xt_xlate_add(xl, " to %s", range_str);
+	if (r->flags & NF_NAT_RANGE_PROTO_RANDOM) {
+		xt_xlate_add(xl, "%srandom", sep);
+		sep = ",";
 	}
-	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
-		xt_xlate_add(xl, ":%hu", ntohs(r->min.tcp.port));
-		if (r->max.tcp.port != r->min.tcp.port)
-			xt_xlate_add(xl, "-%hu", ntohs(r->max.tcp.port));
+	if (r->flags & NF_NAT_RANGE_PERSISTENT) {
+		xt_xlate_add(xl, "%spersistent", sep);
+		sep = ",";
 	}
+	return 1;
 }
 
 static int DNAT_xlate(struct xt_xlate *xl,
 		      const struct xt_xlate_tg_params *params)
 {
-	const struct nf_nat_ipv4_multi_range_compat *mr =
-			(const void *)params->target->data;
-	bool sep_need = false;
-	const char *sep = " ";
-
-	xt_xlate_add(xl, "dnat to ");
-	print_range_xlate(mr->range, xl);
-	if (mr->range->flags & NF_NAT_RANGE_PROTO_RANDOM) {
-		xt_xlate_add(xl, " random");
-		sep_need = true;
-	}
-	if (mr->range->flags & NF_NAT_RANGE_PERSISTENT) {
-		if (sep_need)
-			sep = ",";
-		xt_xlate_add(xl, "%spersistent", sep);
-	}
+	struct nf_nat_range2 range =
+		RANGE2_INIT_FROM_IPV4_MRC(params->target->data);
 
-	return 1;
+	return __DNAT_xlate(xl, &range);
 }
 
 static void DNAT_parse_v2(struct xt_option_call *cb)
@@ -336,45 +325,10 @@ static void DNAT_save_v2(const void *ip, const struct xt_entry_target *target)
 	__DNAT_print((const void *)target->data, true);
 }
 
-static void print_range_xlate_v2(const struct nf_nat_range2 *range,
-			      struct xt_xlate *xl)
-{
-	if (range->flags & NF_NAT_RANGE_MAP_IPS) {
-		xt_xlate_add(xl, "%s", xtables_ipaddr_to_numeric(&range->min_addr.in));
-		if (memcmp(&range->min_addr, &range->max_addr,
-			   sizeof(range->min_addr))) {
-			xt_xlate_add(xl, "-%s", xtables_ipaddr_to_numeric(&range->max_addr.in));
-		}
-	}
-	if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
-		xt_xlate_add(xl, ":%hu", ntohs(range->min_proto.tcp.port));
-		if (range->max_proto.tcp.port != range->min_proto.tcp.port)
-			xt_xlate_add(xl, "-%hu", ntohs(range->max_proto.tcp.port));
-		if (range->flags & NF_NAT_RANGE_PROTO_OFFSET)
-			xt_xlate_add(xl, ";%hu", ntohs(range->base_proto.tcp.port));
-	}
-}
-
 static int DNAT_xlate_v2(struct xt_xlate *xl,
 		      const struct xt_xlate_tg_params *params)
 {
-	const struct nf_nat_range2 *range = (const void *)params->target->data;
-	bool sep_need = false;
-	const char *sep = " ";
-
-	xt_xlate_add(xl, "dnat to ");
-	print_range_xlate_v2(range, xl);
-	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM) {
-		xt_xlate_add(xl, " random");
-		sep_need = true;
-	}
-	if (range->flags & NF_NAT_RANGE_PERSISTENT) {
-		if (sep_need)
-			sep = ",";
-		xt_xlate_add(xl, "%spersistent", sep);
-	}
-
-	return 1;
+	return __DNAT_xlate(xl, (const void *)params->target->data);
 }
 
 static struct xtables_target dnat_tg_reg[] = {
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [iptables PATCH 6/9] extensions: DNAT: Rename from libipt to libxt
  2022-03-30 15:58 [iptables PATCH 0/9] extensions: Merge *_DNAT and *_REDIRECT Phil Sutter
                   ` (4 preceding siblings ...)
  2022-03-30 15:58 ` [iptables PATCH 5/9] extensions: ipt_DNAT: Combine xlate functions also Phil Sutter
@ 2022-03-30 15:58 ` Phil Sutter
  2022-03-30 15:58 ` [iptables PATCH 7/9] extensions: Merge IPv4 and IPv6 DNAT targets Phil Sutter
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 14+ messages in thread
From: Phil Sutter @ 2022-03-30 15:58 UTC (permalink / raw)
  To: Pablo Neira Ayuso; +Cc: netfilter-devel

Prepare for merge of libipt and libip6t DNAT extensions, allow for
better code review.

Signed-off-by: Phil Sutter <phil@nwl.cc>
---
 extensions/{libipt_DNAT.c => libxt_DNAT.c} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename extensions/{libipt_DNAT.c => libxt_DNAT.c} (100%)

diff --git a/extensions/libipt_DNAT.c b/extensions/libxt_DNAT.c
similarity index 100%
rename from extensions/libipt_DNAT.c
rename to extensions/libxt_DNAT.c
-- 
2.34.1


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [iptables PATCH 7/9] extensions: Merge IPv4 and IPv6 DNAT targets
  2022-03-30 15:58 [iptables PATCH 0/9] extensions: Merge *_DNAT and *_REDIRECT Phil Sutter
                   ` (5 preceding siblings ...)
  2022-03-30 15:58 ` [iptables PATCH 6/9] extensions: DNAT: Rename from libipt to libxt Phil Sutter
@ 2022-03-30 15:58 ` Phil Sutter
  2022-03-30 15:58 ` [iptables PATCH 8/9] extensions: Merge REDIRECT into DNAT Phil Sutter
  2022-03-30 15:58 ` [iptables PATCH 9/9] extensions: DNAT: Support service names in all spots Phil Sutter
  8 siblings, 0 replies; 14+ messages in thread
From: Phil Sutter @ 2022-03-30 15:58 UTC (permalink / raw)
  To: Pablo Neira Ayuso; +Cc: netfilter-devel

Make parse_to() family-aware so it serves for both IPv4 and IPv6. Have a
core _DNAT_parse() function which parses into the most modern
(nf_nat_range2) data structure and a bunch of wrappers to copy into
legacy data structures if needed. Treat other callbacks analogous.

Signed-off-by: Phil Sutter <phil@nwl.cc>
---
 extensions/libip6t_DNAT.c      | 402 ---------------------------------
 extensions/libip6t_DNAT.txlate |  11 -
 extensions/libipt_DNAT.txlate  |  14 --
 extensions/libxt_DNAT.c        | 222 ++++++++++++++----
 extensions/libxt_DNAT.txlate   |  35 +++
 5 files changed, 216 insertions(+), 468 deletions(-)
 delete mode 100644 extensions/libip6t_DNAT.c
 delete mode 100644 extensions/libip6t_DNAT.txlate
 delete mode 100644 extensions/libipt_DNAT.txlate
 create mode 100644 extensions/libxt_DNAT.txlate

diff --git a/extensions/libip6t_DNAT.c b/extensions/libip6t_DNAT.c
deleted file mode 100644
index d51994c09e7f2..0000000000000
--- a/extensions/libip6t_DNAT.c
+++ /dev/null
@@ -1,402 +0,0 @@
-/*
- * Copyright (c) 2011 Patrick McHardy <kaber@trash.net>
- *
- * Based on Rusty Russell's IPv4 DNAT target. Development of IPv6 NAT
- * funded by Astaro.
- */
-
-#include <stdio.h>
-#include <netdb.h>
-#include <string.h>
-#include <stdlib.h>
-#include <xtables.h>
-#include <iptables.h>
-#include <limits.h> /* INT_MAX in ip_tables.h */
-#include <linux/netfilter_ipv6/ip6_tables.h>
-#include <linux/netfilter/nf_nat.h>
-
-enum {
-	O_TO_DEST = 0,
-	O_RANDOM,
-	O_PERSISTENT,
-	F_TO_DEST   = 1 << O_TO_DEST,
-	F_RANDOM   = 1 << O_RANDOM,
-};
-
-static void DNAT_help(void)
-{
-	printf(
-"DNAT target options:\n"
-" --to-destination [<ipaddr>[-<ipaddr>]][:port[-port]]\n"
-"				Address to map destination to.\n"
-"[--random] [--persistent]\n");
-}
-
-static void DNAT_help_v2(void)
-{
-	printf(
-"DNAT target options:\n"
-" --to-destination [<ipaddr>[-<ipaddr>]][:port[-port[/port]]]\n"
-"				Address to map destination to.\n"
-"[--random] [--persistent]\n");
-}
-
-static const struct xt_option_entry DNAT_opts[] = {
-	{.name = "to-destination", .id = O_TO_DEST, .type = XTTYPE_STRING,
-	 .flags = XTOPT_MAND},
-	{.name = "random", .id = O_RANDOM, .type = XTTYPE_NONE},
-	{.name = "persistent", .id = O_PERSISTENT, .type = XTTYPE_NONE},
-	XTOPT_TABLEEND,
-};
-
-/* Ranges expected in network order. */
-static void
-parse_to(const char *orig_arg, int portok, struct nf_nat_range2 *range, int rev)
-{
-	char *arg, *start, *end = NULL, *colon = NULL, *dash, *error;
-	const struct in6_addr *ip;
-
-	arg = xtables_strdup(orig_arg);
-
-	start = strchr(arg, '[');
-	if (start == NULL) {
-		start = arg;
-		/* Lets assume one colon is port information. Otherwise its an IPv6 address */
-		colon = strchr(arg, ':');
-		if (colon && strchr(colon+1, ':'))
-			colon = NULL;
-	}
-	else {
-		start++;
-		end = strchr(start, ']');
-		if (end == NULL)
-			xtables_error(PARAMETER_PROBLEM,
-				      "Invalid address format");
-
-		*end = '\0';
-		colon = strchr(end + 1, ':');
-	}
-
-	if (colon) {
-		int port;
-
-		if (!portok)
-			xtables_error(PARAMETER_PROBLEM,
-				   "Need TCP, UDP, SCTP or DCCP with port specification");
-
-		range->flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
-
-		port = atoi(colon+1);
-		if (port <= 0 || port > 65535)
-			xtables_error(PARAMETER_PROBLEM,
-				   "Port `%s' not valid\n", colon+1);
-
-		error = strchr(colon+1, ':');
-		if (error)
-			xtables_error(PARAMETER_PROBLEM,
-				   "Invalid port:port syntax - use dash\n");
-
-		dash = strchr(colon, '-');
-		if (!dash) {
-			range->min_proto.tcp.port
-				= range->max_proto.tcp.port
-				= htons(port);
-		} else {
-			int maxport;
-
-			maxport = atoi(dash + 1);
-			if (maxport <= 0 || maxport > 65535)
-				xtables_error(PARAMETER_PROBLEM,
-					   "Port `%s' not valid\n", dash+1);
-			if (maxport < port)
-				/* People are stupid. */
-				xtables_error(PARAMETER_PROBLEM,
-					   "Port range `%s' funky\n", colon+1);
-			range->min_proto.tcp.port = htons(port);
-			range->max_proto.tcp.port = htons(maxport);
-
-			if (rev >= 2) {
-				char *slash = strchr(dash, '/');
-				if (slash) {
-					int baseport;
-
-					baseport = atoi(slash + 1);
-					if (baseport <= 0 || baseport > 65535)
-						xtables_error(PARAMETER_PROBLEM,
-								 "Port `%s' not valid\n", slash+1);
-					range->flags |= NF_NAT_RANGE_PROTO_OFFSET;
-					range->base_proto.tcp.port = htons(baseport);
-				}
-			}
-		}
-		/* Starts with colon or [] colon? No IP info...*/
-		if (colon == arg || colon == arg+2) {
-			free(arg);
-			return;
-		}
-		*colon = '\0';
-	}
-
-	range->flags |= NF_NAT_RANGE_MAP_IPS;
-	dash = strchr(start, '-');
-	if (colon && dash && dash > colon)
-		dash = NULL;
-
-	if (dash)
-		*dash = '\0';
-
-	ip = xtables_numeric_to_ip6addr(start);
-	if (!ip)
-		xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
-			      start);
-	range->min_addr.in6 = *ip;
-	if (dash) {
-		ip = xtables_numeric_to_ip6addr(dash + 1);
-		if (!ip)
-			xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
-				      dash+1);
-		range->max_addr.in6 = *ip;
-	} else
-		range->max_addr = range->min_addr;
-
-	free(arg);
-	return;
-}
-
-static void _DNAT_parse(struct xt_option_call *cb,
-		struct nf_nat_range2 *range, int rev)
-{
-	const struct ip6t_entry *entry = cb->xt_entry;
-	int portok;
-
-	if (entry->ipv6.proto == IPPROTO_TCP ||
-	    entry->ipv6.proto == IPPROTO_UDP ||
-	    entry->ipv6.proto == IPPROTO_SCTP ||
-	    entry->ipv6.proto == IPPROTO_DCCP ||
-	    entry->ipv6.proto == IPPROTO_ICMP)
-		portok = 1;
-	else
-		portok = 0;
-
-	xtables_option_parse(cb);
-	switch (cb->entry->id) {
-	case O_TO_DEST:
-		parse_to(cb->arg, portok, range, rev);
-		break;
-	case O_PERSISTENT:
-		range->flags |= NF_NAT_RANGE_PERSISTENT;
-		break;
-	}
-}
-
-static void DNAT_parse(struct xt_option_call *cb)
-{
-	struct nf_nat_range *range_v1 = (void *)cb->data;
-	struct nf_nat_range2 range = {};
-
-	memcpy(&range, range_v1, sizeof(*range_v1));
-	_DNAT_parse(cb, &range, 1);
-	memcpy(range_v1, &range, sizeof(*range_v1));
-}
-
-static void DNAT_parse_v2(struct xt_option_call *cb)
-{
-	_DNAT_parse(cb, (struct nf_nat_range2 *)cb->data, 2);
-}
-
-static void _DNAT_fcheck(struct xt_fcheck_call *cb, unsigned int *flags)
-{
-	static const unsigned int f = F_TO_DEST | F_RANDOM;
-
-	if ((cb->xflags & f) == f)
-		*flags |= NF_NAT_RANGE_PROTO_RANDOM;
-}
-
-static void DNAT_fcheck(struct xt_fcheck_call *cb)
-{
-	_DNAT_fcheck(cb, &((struct nf_nat_range *)cb->data)->flags);
-}
-
-static void DNAT_fcheck_v2(struct xt_fcheck_call *cb)
-{
-	_DNAT_fcheck(cb, &((struct nf_nat_range2 *)cb->data)->flags);
-}
-
-static void print_range(const struct nf_nat_range2 *range, int rev)
-{
-	if (range->flags & NF_NAT_RANGE_MAP_IPS) {
-		if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED)
-			printf("[");
-		printf("%s", xtables_ip6addr_to_numeric(&range->min_addr.in6));
-		if (memcmp(&range->min_addr, &range->max_addr,
-			   sizeof(range->min_addr)))
-			printf("-%s", xtables_ip6addr_to_numeric(&range->max_addr.in6));
-		if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED)
-			printf("]");
-	}
-	if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
-		printf(":");
-		printf("%hu", ntohs(range->min_proto.tcp.port));
-		if (range->max_proto.tcp.port != range->min_proto.tcp.port)
-			printf("-%hu", ntohs(range->max_proto.tcp.port));
-		if (rev >= 2 && (range->flags & NF_NAT_RANGE_PROTO_OFFSET))
-			printf("/%hu", ntohs(range->base_proto.tcp.port));
-	}
-}
-
-static void _DNAT_print(const struct nf_nat_range2 *range, int rev)
-{
-	printf(" to:");
-	print_range(range, rev);
-	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
-		printf(" random");
-	if (range->flags & NF_NAT_RANGE_PERSISTENT)
-		printf(" persistent");
-}
-
-static void DNAT_print(const void *ip, const struct xt_entry_target *target,
-                       int numeric)
-{
-	const struct nf_nat_range *range_v1 = (const void *)target->data;
-	struct nf_nat_range2 range = {};
-
-	memcpy(&range, range_v1, sizeof(*range_v1));
-	_DNAT_print(&range, 1);
-}
-
-static void DNAT_print_v2(const void *ip, const struct xt_entry_target *target,
-                          int numeric)
-{
-	_DNAT_print((const struct nf_nat_range2 *)target->data, 2);
-}
-
-static void _DNAT_save(const struct nf_nat_range2 *range, int rev)
-{
-	printf(" --to-destination ");
-	print_range(range, rev);
-	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
-		printf(" --random");
-	if (range->flags & NF_NAT_RANGE_PERSISTENT)
-		printf(" --persistent");
-}
-
-static void DNAT_save(const void *ip, const struct xt_entry_target *target)
-{
-	const struct nf_nat_range *range_v1 = (const void *)target->data;
-	struct nf_nat_range2 range = {};
-
-	memcpy(&range, range_v1, sizeof(*range_v1));
-	_DNAT_save(&range, 1);
-}
-
-static void DNAT_save_v2(const void *ip, const struct xt_entry_target *target)
-{
-	_DNAT_save((const struct nf_nat_range2 *)target->data, 2);
-}
-
-static void print_range_xlate(const struct nf_nat_range2 *range,
-			      struct xt_xlate *xl, int rev)
-{
-	bool proto_specified = range->flags & NF_NAT_RANGE_PROTO_SPECIFIED;
-
-	if (range->flags & NF_NAT_RANGE_MAP_IPS) {
-		xt_xlate_add(xl, "%s%s%s",
-			     proto_specified ? "[" : "",
-			     xtables_ip6addr_to_numeric(&range->min_addr.in6),
-			     proto_specified ? "]" : "");
-
-		if (memcmp(&range->min_addr, &range->max_addr,
-			   sizeof(range->min_addr))) {
-			xt_xlate_add(xl, "-%s%s%s",
-				     proto_specified ? "[" : "",
-				     xtables_ip6addr_to_numeric(&range->max_addr.in6),
-				     proto_specified ? "]" : "");
-		}
-	}
-	if (proto_specified) {
-		xt_xlate_add(xl, ":%hu", ntohs(range->min_proto.tcp.port));
-
-		if (range->max_proto.tcp.port != range->min_proto.tcp.port)
-			xt_xlate_add(xl, "-%hu",
-				   ntohs(range->max_proto.tcp.port));
-	}
-}
-
-static int _DNAT_xlate(struct xt_xlate *xl,
-		      const struct nf_nat_range2 *range, int rev)
-{
-	bool sep_need = false;
-	const char *sep = " ";
-
-	xt_xlate_add(xl, "dnat to ");
-	print_range_xlate(range, xl, rev);
-	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM) {
-		xt_xlate_add(xl, " random");
-		sep_need = true;
-	}
-	if (range->flags & NF_NAT_RANGE_PERSISTENT) {
-		if (sep_need)
-			sep = ",";
-		xt_xlate_add(xl, "%spersistent", sep);
-	}
-
-	return 1;
-}
-
-static int DNAT_xlate(struct xt_xlate *xl,
-		      const struct xt_xlate_tg_params *params)
-{
-	const struct nf_nat_range *range_v1 = (const void *)params->target->data;
-	struct nf_nat_range2 range = {};
-
-	memcpy(&range, range_v1, sizeof(*range_v1));
-	_DNAT_xlate(xl, &range, 1);
-
-	return 1;
-}
-
-static int DNAT_xlate_v2(struct xt_xlate *xl,
-		      const struct xt_xlate_tg_params *params)
-{
-	_DNAT_xlate(xl, (const struct nf_nat_range2 *)params->target->data, 2);
-
-	return 1;
-}
-
-static struct xtables_target dnat_tg_reg[] = {
-	{
-		.name		= "DNAT",
-		.version	= XTABLES_VERSION,
-		.family		= NFPROTO_IPV6,
-		.revision	= 1,
-		.size		= XT_ALIGN(sizeof(struct nf_nat_range)),
-		.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_range)),
-		.help		= DNAT_help,
-		.print		= DNAT_print,
-		.save		= DNAT_save,
-		.x6_parse	= DNAT_parse,
-		.x6_fcheck	= DNAT_fcheck,
-		.x6_options	= DNAT_opts,
-		.xlate		= DNAT_xlate,
-	},
-	{
-		.name		= "DNAT",
-		.version	= XTABLES_VERSION,
-		.family		= NFPROTO_IPV6,
-		.revision	= 2,
-		.size		= XT_ALIGN(sizeof(struct nf_nat_range2)),
-		.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_range2)),
-		.help		= DNAT_help_v2,
-		.print		= DNAT_print_v2,
-		.save		= DNAT_save_v2,
-		.x6_parse	= DNAT_parse_v2,
-		.x6_fcheck	= DNAT_fcheck_v2,
-		.x6_options	= DNAT_opts,
-		.xlate		= DNAT_xlate_v2,
-	},
-};
-
-void _init(void)
-{
-	xtables_register_targets(dnat_tg_reg, ARRAY_SIZE(dnat_tg_reg));
-}
diff --git a/extensions/libip6t_DNAT.txlate b/extensions/libip6t_DNAT.txlate
deleted file mode 100644
index 03c4caf7e87c4..0000000000000
--- a/extensions/libip6t_DNAT.txlate
+++ /dev/null
@@ -1,11 +0,0 @@
-ip6tables-translate -t nat -A prerouting -i eth1 -p tcp --dport 8080 -j DNAT --to-destination [fec0::1234]:80
-nft add rule ip6 nat prerouting iifname "eth1" tcp dport 8080 counter dnat to [fec0::1234]:80
-
-ip6tables-translate -t nat -A prerouting -p tcp -j DNAT --to-destination [fec0::1234]:1-20
-nft add rule ip6 nat prerouting meta l4proto tcp counter dnat to [fec0::1234]:1-20
-
-ip6tables-translate -t nat -A prerouting -p tcp -j DNAT --to-destination [fec0::1234]:80 --persistent
-nft add rule ip6 nat prerouting meta l4proto tcp counter dnat to [fec0::1234]:80 persistent
-
-ip6tables-translate -t nat -A prerouting -p tcp -j DNAT --to-destination [fec0::1234]:80 --random --persistent
-nft add rule ip6 nat prerouting meta l4proto tcp counter dnat to [fec0::1234]:80 random,persistent
diff --git a/extensions/libipt_DNAT.txlate b/extensions/libipt_DNAT.txlate
deleted file mode 100644
index e88314d9dba59..0000000000000
--- a/extensions/libipt_DNAT.txlate
+++ /dev/null
@@ -1,14 +0,0 @@
-iptables-translate -t nat -A prerouting -p tcp -o eth0 -j DNAT --to-destination 1.2.3.4
-nft add rule ip nat prerouting oifname "eth0" ip protocol tcp counter dnat to 1.2.3.4
-
-iptables-translate -t nat -A prerouting -p tcp -d 15.45.23.67 --dport 80 -j DNAT --to-destination 192.168.1.1-192.168.1.10
-nft add rule ip nat prerouting ip daddr 15.45.23.67 tcp dport 80 counter dnat to 192.168.1.1-192.168.1.10
-
-iptables-translate -t nat -A prerouting -p tcp -o eth0 -j DNAT --to-destination 1.2.3.4:1-1023
-nft add rule ip nat prerouting oifname "eth0" ip protocol tcp counter dnat to 1.2.3.4:1-1023
-
-iptables-translate -t nat -A prerouting -p tcp -o eth0 -j DNAT --to-destination 1.2.3.4 --random
-nft add rule ip nat prerouting oifname "eth0" ip protocol tcp counter dnat to 1.2.3.4 random
-
-iptables-translate -t nat -A prerouting -p tcp -o eth0 -j DNAT --to-destination 1.2.3.4 --random --persistent
-nft add rule ip nat prerouting oifname "eth0" ip protocol tcp counter dnat to 1.2.3.4 random,persistent
diff --git a/extensions/libxt_DNAT.c b/extensions/libxt_DNAT.c
index 9a179919f522d..83ff95b0013c7 100644
--- a/extensions/libxt_DNAT.c
+++ b/extensions/libxt_DNAT.c
@@ -1,3 +1,10 @@
+/*
+ * Copyright (c) 2011 Patrick McHardy <kaber@trash.net>
+ *
+ * Based on Rusty Russell's IPv4 DNAT target. Development of IPv6 NAT
+ * funded by Astaro.
+ */
+
 #include <stdio.h>
 #include <netdb.h>
 #include <string.h>
@@ -7,6 +14,7 @@
 #include <limits.h> /* INT_MAX in ip_tables.h */
 #include <arpa/inet.h>
 #include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
 #include <linux/netfilter/nf_nat.h>
 
 #define TO_IPV4_MRC(ptr) ((const struct nf_nat_ipv4_multi_range_compat *)(ptr))
@@ -119,18 +127,36 @@ parse_ports(const char *arg, bool portok, struct nf_nat_range2 *range)
 
 /* Ranges expected in network order. */
 static void
-parse_to(const char *orig_arg, bool portok, struct nf_nat_range2 *range)
+parse_to(const char *orig_arg, bool portok,
+	 struct nf_nat_range2 *range, int family)
 {
-	char *arg, *colon, *dash;
+	char *arg, *start, *end, *colon, *dash;
 
 	arg = xtables_strdup(orig_arg);
-	colon = strchr(arg, ':');
+	start = strchr(arg, '[');
+	if (!start) {
+		start = arg;
+		/* Lets assume one colon is port information.
+		 * Otherwise its an IPv6 address */
+		colon = strchr(arg, ':');
+		if (colon && strchr(colon + 1, ':'))
+			colon = NULL;
+	} else {
+		start++;
+		end = strchr(start, ']');
+		if (end == NULL || family == AF_INET)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Invalid address format");
+
+		*end = '\0';
+		colon = strchr(end + 1, ':');
+	}
 
 	if (colon) {
 		parse_ports(colon + 1, portok, range);
 
-		/* Starts with a colon? No IP info...*/
-		if (colon == arg) {
+		/* Starts with colon or [] colon? No IP info...*/
+		if (colon == arg || colon == arg + 2) {
 			free(arg);
 			return;
 		}
@@ -138,20 +164,20 @@ parse_to(const char *orig_arg, bool portok, struct nf_nat_range2 *range)
 	}
 
 	range->flags |= NF_NAT_RANGE_MAP_IPS;
-	dash = strchr(arg, '-');
+	dash = strchr(start, '-');
 	if (colon && dash && dash > colon)
 		dash = NULL;
 
 	if (dash)
 		*dash = '\0';
 
-	if (!inet_pton(AF_INET, arg, &range->min_addr))
+	if (!inet_pton(family, start, &range->min_addr))
 		xtables_error(PARAMETER_PROBLEM,
-			      "Bad IP address \"%s\"\n", arg);
+			      "Bad IP address \"%s\"", arg);
 	if (dash) {
-		if (!inet_pton(AF_INET, dash + 1, &range->max_addr))
+		if (!inet_pton(family, dash + 1, &range->max_addr))
 			xtables_error(PARAMETER_PROBLEM,
-				      "Bad IP address \"%s\"\n", dash + 1);
+				      "Bad IP address \"%s\"", dash + 1);
 	} else {
 		range->max_addr = range->min_addr;
 	}
@@ -160,7 +186,7 @@ parse_to(const char *orig_arg, bool portok, struct nf_nat_range2 *range)
 }
 
 static void __DNAT_parse(struct xt_option_call *cb, __u16 proto,
-			 struct nf_nat_range2 *range)
+			 struct nf_nat_range2 *range, int family)
 {
 	bool portok = proto == IPPROTO_TCP ||
 		      proto == IPPROTO_UDP ||
@@ -171,7 +197,7 @@ static void __DNAT_parse(struct xt_option_call *cb, __u16 proto,
 	xtables_option_parse(cb);
 	switch (cb->entry->id) {
 	case O_TO_DEST:
-		parse_to(cb->arg, portok, range);
+		parse_to(cb->arg, portok, range, family);
 		break;
 	case O_PERSISTENT:
 		range->flags |= NF_NAT_RANGE_PERSISTENT;
@@ -185,7 +211,7 @@ static void DNAT_parse(struct xt_option_call *cb)
 	const struct ipt_entry *entry = cb->xt_entry;
 	struct nf_nat_range2 range = {};
 
-	__DNAT_parse(cb, entry->ip.proto, &range);
+	__DNAT_parse(cb, entry->ip.proto, &range, AF_INET);
 
 	switch (cb->entry->id) {
 	case O_TO_DEST:
@@ -200,32 +226,45 @@ static void DNAT_parse(struct xt_option_call *cb)
 	}
 }
 
-static void DNAT_fcheck(struct xt_fcheck_call *cb)
+static void __DNAT_fcheck(struct xt_fcheck_call *cb, unsigned int *flags)
 {
 	static const unsigned int f = F_TO_DEST | F_RANDOM;
-	struct nf_nat_ipv4_multi_range_compat *mr = cb->data;
 
 	if ((cb->xflags & f) == f)
-		mr->range[0].flags |= NF_NAT_RANGE_PROTO_RANDOM;
+		*flags |= NF_NAT_RANGE_PROTO_RANDOM;
+}
+
+static void DNAT_fcheck(struct xt_fcheck_call *cb)
+{
+	struct nf_nat_ipv4_multi_range_compat *mr = cb->data;
 
 	mr->rangesize = 1;
 
 	if (mr->range[0].flags & NF_NAT_RANGE_PROTO_OFFSET)
 		xtables_error(PARAMETER_PROBLEM,
 			      "Shifted portmap ranges not supported with this kernel");
+
+	__DNAT_fcheck(cb, &mr->range[0].flags);
 }
 
-static char *sprint_range(const struct nf_nat_range2 *r)
+static char *sprint_range(const struct nf_nat_range2 *r, int family)
 {
-	static char buf[INET_ADDRSTRLEN * 2 + 1 + 6 * 3];
+	bool brackets = family == AF_INET6 &&
+			r->flags & NF_NAT_RANGE_PROTO_SPECIFIED;
+	static char buf[INET6_ADDRSTRLEN * 2 + 3 + 6 * 3] = "";
 
 	if (r->flags & NF_NAT_RANGE_MAP_IPS) {
-		sprintf(buf, "%s", xtables_ipaddr_to_numeric(&r->min_addr.in));
-		if (memcmp(&r->min_addr, &r->max_addr, sizeof(r->min_addr)))
-			sprintf(buf + strlen(buf), "-%s",
-				xtables_ipaddr_to_numeric(&r->max_addr.in));
-	} else {
-		buf[0] = '\0';
+		if (brackets)
+			strcat(buf, "[");
+		inet_ntop(family, &r->min_addr,
+			  buf + strlen(buf), INET6_ADDRSTRLEN);
+		if (memcmp(&r->min_addr, &r->max_addr, sizeof(r->min_addr))) {
+			strcat(buf, "-");
+			inet_ntop(family, &r->max_addr,
+				  buf + strlen(buf), INET6_ADDRSTRLEN);
+		}
+		if (brackets)
+			strcat(buf, "]");
 	}
 	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
 		sprintf(buf + strlen(buf), ":%hu",
@@ -240,11 +279,12 @@ static char *sprint_range(const struct nf_nat_range2 *r)
 	return buf;
 }
 
-static void __DNAT_print(const struct nf_nat_range2 *r, bool save)
+static void __DNAT_print(const struct nf_nat_range2 *r, bool save, int family)
 {
 	const char *dashdash = save ? "--" : "";
 
-	printf(" %s%s", save ? "--to-destination " : "to:", sprint_range(r));
+	printf(" %s%s", save ? "--to-destination " : "to:",
+	       sprint_range(r, family));
 	if (r->flags & NF_NAT_RANGE_PROTO_RANDOM)
 		printf(" %srandom", dashdash);
 	if (r->flags & NF_NAT_RANGE_PERSISTENT)
@@ -256,19 +296,20 @@ static void DNAT_print(const void *ip, const struct xt_entry_target *target,
 {
 	struct nf_nat_range2 range = RANGE2_INIT_FROM_IPV4_MRC(target->data);
 
-	__DNAT_print(&range, false);
+	__DNAT_print(&range, false, AF_INET);
 }
 
 static void DNAT_save(const void *ip, const struct xt_entry_target *target)
 {
 	struct nf_nat_range2 range = RANGE2_INIT_FROM_IPV4_MRC(target->data);
 
-	__DNAT_print(&range, true);
+	__DNAT_print(&range, true, AF_INET);
 }
 
-static int __DNAT_xlate(struct xt_xlate *xl, const struct nf_nat_range2 *r)
+static int
+__DNAT_xlate(struct xt_xlate *xl, const struct nf_nat_range2 *r, int family)
 {
-	char *range_str = sprint_range(r);
+	char *range_str = sprint_range(r, family);
 	const char *sep = " ";
 
 	/* shifted portmap ranges are not supported by nftables */
@@ -295,40 +336,109 @@ static int DNAT_xlate(struct xt_xlate *xl,
 	struct nf_nat_range2 range =
 		RANGE2_INIT_FROM_IPV4_MRC(params->target->data);
 
-	return __DNAT_xlate(xl, &range);
+	return __DNAT_xlate(xl, &range, AF_INET);
 }
 
 static void DNAT_parse_v2(struct xt_option_call *cb)
 {
 	const struct ipt_entry *entry = cb->xt_entry;
 
-	__DNAT_parse(cb, entry->ip.proto, cb->data);
+	__DNAT_parse(cb, entry->ip.proto, cb->data, AF_INET);
 }
 
 static void DNAT_fcheck_v2(struct xt_fcheck_call *cb)
 {
-	static const unsigned int f = F_TO_DEST | F_RANDOM;
-	struct nf_nat_range2 *range = cb->data;
-
-	if ((cb->xflags & f) == f)
-		range->flags |= NF_NAT_RANGE_PROTO_RANDOM;
+	__DNAT_fcheck(cb, &((struct nf_nat_range2 *)cb->data)->flags);
 }
 
 static void DNAT_print_v2(const void *ip, const struct xt_entry_target *target,
                        int numeric)
 {
-	__DNAT_print((const void *)target->data, false);
+	__DNAT_print((const void *)target->data, false, AF_INET);
 }
 
 static void DNAT_save_v2(const void *ip, const struct xt_entry_target *target)
 {
-	__DNAT_print((const void *)target->data, true);
+	__DNAT_print((const void *)target->data, true, AF_INET);
 }
 
 static int DNAT_xlate_v2(struct xt_xlate *xl,
-		      const struct xt_xlate_tg_params *params)
+			  const struct xt_xlate_tg_params *params)
 {
-	return __DNAT_xlate(xl, (const void *)params->target->data);
+	return __DNAT_xlate(xl, (const void *)params->target->data, AF_INET);
+}
+
+static void DNAT_parse6(struct xt_option_call *cb)
+{
+	const struct ip6t_entry *entry = cb->xt_entry;
+	struct nf_nat_range *range_v1 = (void *)cb->data;
+	struct nf_nat_range2 range = {};
+
+	memcpy(&range, range_v1, sizeof(*range_v1));
+	__DNAT_parse(cb, entry->ipv6.proto, &range, AF_INET6);
+	memcpy(range_v1, &range, sizeof(*range_v1));
+}
+
+static void DNAT_fcheck6(struct xt_fcheck_call *cb)
+{
+	struct nf_nat_range *range = (void *)cb->data;
+
+	if (range->flags & NF_NAT_RANGE_PROTO_OFFSET)
+		xtables_error(PARAMETER_PROBLEM,
+			      "Shifted portmap ranges not supported with this kernel");
+
+	__DNAT_fcheck(cb, &range->flags);
+}
+
+static void DNAT_print6(const void *ip, const struct xt_entry_target *target,
+			int numeric)
+{
+	struct nf_nat_range2 range = {};
+
+	memcpy(&range, (const void *)target->data, sizeof(struct nf_nat_range));
+	__DNAT_print(&range, true, AF_INET6);
+}
+
+static void DNAT_save6(const void *ip, const struct xt_entry_target *target)
+{
+	struct nf_nat_range2 range = {};
+
+	memcpy(&range, (const void *)target->data, sizeof(struct nf_nat_range));
+	__DNAT_print(&range, true, AF_INET6);
+}
+
+static int DNAT_xlate6(struct xt_xlate *xl,
+		       const struct xt_xlate_tg_params *params)
+{
+	struct nf_nat_range2 range = {};
+
+	memcpy(&range, (const void *)params->target->data,
+	       sizeof(struct nf_nat_range));
+	return __DNAT_xlate(xl, &range, AF_INET6);
+}
+
+static void DNAT_parse6_v2(struct xt_option_call *cb)
+{
+	const struct ip6t_entry *entry = cb->xt_entry;
+
+	__DNAT_parse(cb, entry->ipv6.proto, cb->data, AF_INET6);
+}
+
+static void DNAT_print6_v2(const void *ip, const struct xt_entry_target *target,
+			   int numeric)
+{
+	__DNAT_print((const void *)target->data, true, AF_INET6);
+}
+
+static void DNAT_save6_v2(const void *ip, const struct xt_entry_target *target)
+{
+	__DNAT_print((const void *)target->data, true, AF_INET6);
+}
+
+static int DNAT_xlate6_v2(struct xt_xlate *xl,
+			  const struct xt_xlate_tg_params *params)
+{
+	return __DNAT_xlate(xl, (const void *)params->target->data, AF_INET6);
 }
 
 static struct xtables_target dnat_tg_reg[] = {
@@ -347,6 +457,21 @@ static struct xtables_target dnat_tg_reg[] = {
 		.x6_options	= DNAT_opts,
 		.xlate		= DNAT_xlate,
 	},
+	{
+		.name		= "DNAT",
+		.version	= XTABLES_VERSION,
+		.family		= NFPROTO_IPV6,
+		.revision	= 1,
+		.size		= XT_ALIGN(sizeof(struct nf_nat_range)),
+		.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_range)),
+		.help		= DNAT_help,
+		.print		= DNAT_print6,
+		.save		= DNAT_save6,
+		.x6_parse	= DNAT_parse6,
+		.x6_fcheck	= DNAT_fcheck6,
+		.x6_options	= DNAT_opts,
+		.xlate		= DNAT_xlate6,
+	},
 	{
 		.name		= "DNAT",
 		.version	= XTABLES_VERSION,
@@ -362,6 +487,21 @@ static struct xtables_target dnat_tg_reg[] = {
 		.x6_options	= DNAT_opts,
 		.xlate		= DNAT_xlate_v2,
 	},
+	{
+		.name		= "DNAT",
+		.version	= XTABLES_VERSION,
+		.family		= NFPROTO_IPV6,
+		.revision	= 2,
+		.size		= XT_ALIGN(sizeof(struct nf_nat_range2)),
+		.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_range2)),
+		.help		= DNAT_help_v2,
+		.print		= DNAT_print6_v2,
+		.save		= DNAT_save6_v2,
+		.x6_parse	= DNAT_parse6_v2,
+		.x6_fcheck	= DNAT_fcheck_v2,
+		.x6_options	= DNAT_opts,
+		.xlate		= DNAT_xlate6_v2,
+	},
 };
 
 void _init(void)
diff --git a/extensions/libxt_DNAT.txlate b/extensions/libxt_DNAT.txlate
new file mode 100644
index 0000000000000..a65976562ef53
--- /dev/null
+++ b/extensions/libxt_DNAT.txlate
@@ -0,0 +1,35 @@
+iptables-translate -t nat -A prerouting -p tcp -o eth0 -j DNAT --to-destination 1.2.3.4
+nft add rule ip nat prerouting oifname "eth0" ip protocol tcp counter dnat to 1.2.3.4
+
+iptables-translate -t nat -A prerouting -p tcp -d 15.45.23.67 --dport 80 -j DNAT --to-destination 192.168.1.1-192.168.1.10
+nft add rule ip nat prerouting ip daddr 15.45.23.67 tcp dport 80 counter dnat to 192.168.1.1-192.168.1.10
+
+iptables-translate -t nat -A prerouting -p tcp -o eth0 -j DNAT --to-destination 1.2.3.4:1-1023
+nft add rule ip nat prerouting oifname "eth0" ip protocol tcp counter dnat to 1.2.3.4:1-1023
+
+iptables-translate -t nat -A prerouting -p tcp -o eth0 -j DNAT --to-destination 1.2.3.4 --random
+nft add rule ip nat prerouting oifname "eth0" ip protocol tcp counter dnat to 1.2.3.4 random
+
+iptables-translate -t nat -A prerouting -p tcp -o eth0 -j DNAT --to-destination 1.2.3.4 --random --persistent
+nft add rule ip nat prerouting oifname "eth0" ip protocol tcp counter dnat to 1.2.3.4 random,persistent
+
+ip6tables-translate -t nat -A prerouting -p tcp --dport 8080 -j DNAT --to-destination fec0::1234
+nft add rule ip6 nat prerouting tcp dport 8080 counter dnat to fec0::1234
+
+ip6tables-translate -t nat -A prerouting -p tcp --dport 8080 -j DNAT --to-destination fec0::1234-fec0::2000
+nft add rule ip6 nat prerouting tcp dport 8080 counter dnat to fec0::1234-fec0::2000
+
+ip6tables-translate -t nat -A prerouting -i eth1 -p tcp --dport 8080 -j DNAT --to-destination [fec0::1234]:80
+nft add rule ip6 nat prerouting iifname "eth1" tcp dport 8080 counter dnat to [fec0::1234]:80
+
+ip6tables-translate -t nat -A prerouting -p tcp -j DNAT --to-destination [fec0::1234]:1-20
+nft add rule ip6 nat prerouting meta l4proto tcp counter dnat to [fec0::1234]:1-20
+
+ip6tables-translate -t nat -A prerouting -p tcp -j DNAT --to-destination [fec0::1234-fec0::2000]:1-20
+nft add rule ip6 nat prerouting meta l4proto tcp counter dnat to [fec0::1234-fec0::2000]:1-20
+
+ip6tables-translate -t nat -A prerouting -p tcp -j DNAT --to-destination [fec0::1234]:80 --persistent
+nft add rule ip6 nat prerouting meta l4proto tcp counter dnat to [fec0::1234]:80 persistent
+
+ip6tables-translate -t nat -A prerouting -p tcp -j DNAT --to-destination [fec0::1234]:80 --random --persistent
+nft add rule ip6 nat prerouting meta l4proto tcp counter dnat to [fec0::1234]:80 random,persistent
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [iptables PATCH 8/9] extensions: Merge REDIRECT into DNAT
  2022-03-30 15:58 [iptables PATCH 0/9] extensions: Merge *_DNAT and *_REDIRECT Phil Sutter
                   ` (6 preceding siblings ...)
  2022-03-30 15:58 ` [iptables PATCH 7/9] extensions: Merge IPv4 and IPv6 DNAT targets Phil Sutter
@ 2022-03-30 15:58 ` Phil Sutter
  2022-03-30 15:58 ` [iptables PATCH 9/9] extensions: DNAT: Support service names in all spots Phil Sutter
  8 siblings, 0 replies; 14+ messages in thread
From: Phil Sutter @ 2022-03-30 15:58 UTC (permalink / raw)
  To: Pablo Neira Ayuso; +Cc: netfilter-devel

Code is very similar, join them to reuse parsing code at least.

As a side-effect, this enables parsing of service names for ports in
DNAT.

Signed-off-by: Phil Sutter <phil@nwl.cc>
---
 extensions/GNUmakefile.in          |   4 +-
 extensions/libip6t_REDIRECT.c      | 170 ----------------------------
 extensions/libip6t_REDIRECT.t      |   6 -
 extensions/libip6t_REDIRECT.txlate |   5 -
 extensions/libipt_DNAT.t           |   2 +
 extensions/libipt_REDIRECT.c       | 174 -----------------------------
 extensions/libipt_REDIRECT.t       |   6 -
 extensions/libipt_REDIRECT.txlate  |   5 -
 extensions/libxt_DNAT.c            | 169 +++++++++++++++++++++++++---
 extensions/libxt_REDIRECT.t        |   9 ++
 extensions/libxt_REDIRECT.txlate   |  17 +++
 11 files changed, 186 insertions(+), 381 deletions(-)
 delete mode 100644 extensions/libip6t_REDIRECT.c
 delete mode 100644 extensions/libip6t_REDIRECT.t
 delete mode 100644 extensions/libip6t_REDIRECT.txlate
 delete mode 100644 extensions/libipt_REDIRECT.c
 delete mode 100644 extensions/libipt_REDIRECT.t
 delete mode 100644 extensions/libipt_REDIRECT.txlate
 create mode 100644 extensions/libxt_REDIRECT.t
 create mode 100644 extensions/libxt_REDIRECT.txlate

diff --git a/extensions/GNUmakefile.in b/extensions/GNUmakefile.in
index 956ccb38b2ab9..6dad4e02481bd 100644
--- a/extensions/GNUmakefile.in
+++ b/extensions/GNUmakefile.in
@@ -42,7 +42,7 @@ endif
 pfx_build_mod := $(patsubst ${srcdir}/libxt_%.c,%,$(sort $(wildcard ${srcdir}/libxt_*.c)))
 @ENABLE_NFTABLES_TRUE@ pfb_build_mod := $(patsubst ${srcdir}/libebt_%.c,%,$(sort $(wildcard ${srcdir}/libebt_*.c)))
 @ENABLE_NFTABLES_TRUE@ pfa_build_mod := $(patsubst ${srcdir}/libarpt_%.c,%,$(sort $(wildcard ${srcdir}/libarpt_*.c)))
-pfx_symlinks  := NOTRACK state
+pfx_symlinks  := NOTRACK state REDIRECT
 @ENABLE_IPV4_TRUE@ pf4_build_mod := $(patsubst ${srcdir}/libipt_%.c,%,$(sort $(wildcard ${srcdir}/libipt_*.c)))
 @ENABLE_IPV6_TRUE@ pf6_build_mod := $(patsubst ${srcdir}/libip6t_%.c,%,$(sort $(wildcard ${srcdir}/libip6t_*.c)))
 pfx_build_mod := $(filter-out @blacklist_modules@ @blacklist_x_modules@,${pfx_build_mod})
@@ -130,6 +130,8 @@ libxt_NOTRACK.so: libxt_CT.so
 	ln -fs $< $@
 libxt_state.so: libxt_conntrack.so
 	ln -fs $< $@
+libxt_REDIRECT.so: libxt_DNAT.so
+	ln -fs $< $@
 
 # Need the LIBADDs in iptables/Makefile.am too for libxtables_la_LIBADD
 xt_RATEEST_LIBADD   = -lm
diff --git a/extensions/libip6t_REDIRECT.c b/extensions/libip6t_REDIRECT.c
deleted file mode 100644
index 8e04d2cd33d50..0000000000000
--- a/extensions/libip6t_REDIRECT.c
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (c) 2011 Patrick McHardy <kaber@trash.net>
- *
- * Based on Rusty Russell's IPv4 REDIRECT target. Development of IPv6 NAT
- * funded by Astaro.
- */
-
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <xtables.h>
-#include <limits.h> /* INT_MAX in ip_tables.h */
-#include <linux/netfilter_ipv6/ip6_tables.h>
-#include <linux/netfilter/nf_nat.h>
-
-enum {
-	O_TO_PORTS = 0,
-	O_RANDOM,
-	F_TO_PORTS = 1 << O_TO_PORTS,
-	F_RANDOM   = 1 << O_RANDOM,
-};
-
-static void REDIRECT_help(void)
-{
-	printf(
-"REDIRECT target options:\n"
-" --to-ports <port>[-<port>]\n"
-"				Port (range) to map to.\n"
-" [--random]\n");
-}
-
-static const struct xt_option_entry REDIRECT_opts[] = {
-	{.name = "to-ports", .id = O_TO_PORTS, .type = XTTYPE_STRING},
-	{.name = "random", .id = O_RANDOM, .type = XTTYPE_NONE},
-	XTOPT_TABLEEND,
-};
-
-/* Parses ports */
-static void
-parse_ports(const char *arg, struct nf_nat_range *range)
-{
-	char *end = "";
-	unsigned int port, maxport;
-
-	range->flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
-
-	if (!xtables_strtoui(arg, &end, &port, 0, UINT16_MAX) &&
-	    (port = xtables_service_to_port(arg, NULL)) == (unsigned)-1)
-		xtables_param_act(XTF_BAD_VALUE, "REDIRECT", "--to-ports", arg);
-
-	switch (*end) {
-	case '\0':
-		range->min_proto.tcp.port
-			= range->max_proto.tcp.port
-			= htons(port);
-		return;
-	case '-':
-		if (!xtables_strtoui(end + 1, NULL, &maxport, 0, UINT16_MAX) &&
-		    (maxport = xtables_service_to_port(end + 1, NULL)) == (unsigned)-1)
-			break;
-
-		if (maxport < port)
-			break;
-
-		range->min_proto.tcp.port = htons(port);
-		range->max_proto.tcp.port = htons(maxport);
-		return;
-	default:
-		break;
-	}
-	xtables_param_act(XTF_BAD_VALUE, "REDIRECT", "--to-ports", arg);
-}
-
-static void REDIRECT_parse(struct xt_option_call *cb)
-{
-	const struct ip6t_entry *entry = cb->xt_entry;
-	struct nf_nat_range *range = (void *)(*cb->target)->data;
-	int portok;
-
-	if (entry->ipv6.proto == IPPROTO_TCP
-	    || entry->ipv6.proto == IPPROTO_UDP
-	    || entry->ipv6.proto == IPPROTO_SCTP
-	    || entry->ipv6.proto == IPPROTO_DCCP
-	    || entry->ipv6.proto == IPPROTO_ICMP)
-		portok = 1;
-	else
-		portok = 0;
-
-	xtables_option_parse(cb);
-	switch (cb->entry->id) {
-	case O_TO_PORTS:
-		if (!portok)
-			xtables_error(PARAMETER_PROBLEM,
-				   "Need TCP, UDP, SCTP or DCCP with port specification");
-		parse_ports(cb->arg, range);
-		if (cb->xflags & F_RANDOM)
-			range->flags |= NF_NAT_RANGE_PROTO_RANDOM;
-		break;
-	case O_RANDOM:
-		if (cb->xflags & F_TO_PORTS)
-			range->flags |= NF_NAT_RANGE_PROTO_RANDOM;
-		break;
-	}
-}
-
-static void REDIRECT_print(const void *ip, const struct xt_entry_target *target,
-                           int numeric)
-{
-	const struct nf_nat_range *range = (const void *)target->data;
-
-	if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
-		printf(" redir ports ");
-		printf("%hu", ntohs(range->min_proto.tcp.port));
-		if (range->max_proto.tcp.port != range->min_proto.tcp.port)
-			printf("-%hu", ntohs(range->max_proto.tcp.port));
-		if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
-			printf(" random");
-	}
-}
-
-static void REDIRECT_save(const void *ip, const struct xt_entry_target *target)
-{
-	const struct nf_nat_range *range = (const void *)target->data;
-
-	if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
-		printf(" --to-ports ");
-		printf("%hu", ntohs(range->min_proto.tcp.port));
-		if (range->max_proto.tcp.port != range->min_proto.tcp.port)
-			printf("-%hu", ntohs(range->max_proto.tcp.port));
-		if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
-			printf(" --random");
-	}
-}
-
-static int REDIRECT_xlate(struct xt_xlate *xl,
-			  const struct xt_xlate_tg_params *params)
-{
-	const struct nf_nat_range *range = (const void *)params->target->data;
-
-	if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
-		xt_xlate_add(xl, "redirect to :%hu",
-			   ntohs(range->min_proto.tcp.port));
-		if (range->max_proto.tcp.port != range->min_proto.tcp.port)
-			xt_xlate_add(xl, "-%hu ",
-				   ntohs(range->max_proto.tcp.port));
-		if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
-			xt_xlate_add(xl, " random ");
-	}
-
-	return 1;
-}
-
-static struct xtables_target redirect_tg_reg = {
-	.name		= "REDIRECT",
-	.version	= XTABLES_VERSION,
-	.family		= NFPROTO_IPV6,
-	.size		= XT_ALIGN(sizeof(struct nf_nat_range)),
-	.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_range)),
-	.help		= REDIRECT_help,
-	.x6_parse	= REDIRECT_parse,
-	.print		= REDIRECT_print,
-	.save		= REDIRECT_save,
-	.x6_options	= REDIRECT_opts,
-	.xlate		= REDIRECT_xlate,
-};
-
-void _init(void)
-{
-	xtables_register_target(&redirect_tg_reg);
-}
diff --git a/extensions/libip6t_REDIRECT.t b/extensions/libip6t_REDIRECT.t
deleted file mode 100644
index a0fb0ed19a5ea..0000000000000
--- a/extensions/libip6t_REDIRECT.t
+++ /dev/null
@@ -1,6 +0,0 @@
-:PREROUTING,OUTPUT
-*nat
--p tcp -j REDIRECT --to-ports 42;=;OK
--p udp -j REDIRECT --to-ports 42-1234;=;OK
--p tcp -j REDIRECT --to-ports 42-1234 --random;=;OK
--j REDIRECT --to-ports 42;;FAIL
diff --git a/extensions/libip6t_REDIRECT.txlate b/extensions/libip6t_REDIRECT.txlate
deleted file mode 100644
index 209f67a4235f9..0000000000000
--- a/extensions/libip6t_REDIRECT.txlate
+++ /dev/null
@@ -1,5 +0,0 @@
-ip6tables-translate -t nat -A prerouting -p tcp --dport 80 -j REDIRECT --to-ports 8080
-nft add rule ip6 nat prerouting tcp dport 80 counter redirect to :8080
-
-ip6tables-translate -t nat -A prerouting -p tcp --dport 80 -j REDIRECT --to-ports 8080 --random
-nft add rule ip6 nat prerouting tcp dport 80 counter redirect to :8080 random
diff --git a/extensions/libipt_DNAT.t b/extensions/libipt_DNAT.t
index 1c4413b9b3bb5..eb187bc91053b 100644
--- a/extensions/libipt_DNAT.t
+++ b/extensions/libipt_DNAT.t
@@ -13,4 +13,6 @@
 -p tcp -j DNAT --to-destination 1.1.1.1:1000-2000/65535;=;OK
 -p tcp -j DNAT --to-destination 1.1.1.1:1000-2000/0;;FAIL
 -p tcp -j DNAT --to-destination 1.1.1.1:1000-2000/65536;;FAIL
+-p tcp -j DNAT --to-destination 1.1.1.1:ssh;-p tcp -j DNAT --to-destination 1.1.1.1:22;OK
+-p tcp -j DNAT --to-destination 1.1.1.1:ftp-data;-p tcp -j DNAT --to-destination 1.1.1.1:20;OK
 -j DNAT;;FAIL
diff --git a/extensions/libipt_REDIRECT.c b/extensions/libipt_REDIRECT.c
deleted file mode 100644
index 7850306f5fe25..0000000000000
--- a/extensions/libipt_REDIRECT.c
+++ /dev/null
@@ -1,174 +0,0 @@
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <xtables.h>
-#include <limits.h> /* INT_MAX in ip_tables.h */
-#include <linux/netfilter_ipv4/ip_tables.h>
-#include <linux/netfilter/nf_nat.h>
-
-enum {
-	O_TO_PORTS = 0,
-	O_RANDOM,
-	F_TO_PORTS = 1 << O_TO_PORTS,
-	F_RANDOM   = 1 << O_RANDOM,
-};
-
-static void REDIRECT_help(void)
-{
-	printf(
-"REDIRECT target options:\n"
-" --to-ports <port>[-<port>]\n"
-"				Port (range) to map to.\n"
-" [--random]\n");
-}
-
-static const struct xt_option_entry REDIRECT_opts[] = {
-	{.name = "to-ports", .id = O_TO_PORTS, .type = XTTYPE_STRING},
-	{.name = "random", .id = O_RANDOM, .type = XTTYPE_NONE},
-	XTOPT_TABLEEND,
-};
-
-static void REDIRECT_init(struct xt_entry_target *t)
-{
-	struct nf_nat_ipv4_multi_range_compat *mr = (struct nf_nat_ipv4_multi_range_compat *)t->data;
-
-	/* Actually, it's 0, but it's ignored at the moment. */
-	mr->rangesize = 1;
-}
-
-/* Parses ports */
-static void
-parse_ports(const char *arg, struct nf_nat_ipv4_multi_range_compat *mr)
-{
-	char *end = "";
-	unsigned int port, maxport;
-
-	mr->range[0].flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
-
-	if (!xtables_strtoui(arg, &end, &port, 0, UINT16_MAX) &&
-	    (port = xtables_service_to_port(arg, NULL)) == (unsigned)-1)
-		xtables_param_act(XTF_BAD_VALUE, "REDIRECT", "--to-ports", arg);
-
-	switch (*end) {
-	case '\0':
-		mr->range[0].min.tcp.port
-			= mr->range[0].max.tcp.port
-			= htons(port);
-		return;
-	case '-':
-		if (!xtables_strtoui(end + 1, NULL, &maxport, 0, UINT16_MAX) &&
-		    (maxport = xtables_service_to_port(end + 1, NULL)) == (unsigned)-1)
-			break;
-
-		if (maxport < port)
-			break;
-
-		mr->range[0].min.tcp.port = htons(port);
-		mr->range[0].max.tcp.port = htons(maxport);
-		return;
-	default:
-		break;
-	}
-	xtables_param_act(XTF_BAD_VALUE, "REDIRECT", "--to-ports", arg);
-}
-
-static void REDIRECT_parse(struct xt_option_call *cb)
-{
-	const struct ipt_entry *entry = cb->xt_entry;
-	struct nf_nat_ipv4_multi_range_compat *mr = (void *)(*cb->target)->data;
-	int portok;
-
-	if (entry->ip.proto == IPPROTO_TCP
-	    || entry->ip.proto == IPPROTO_UDP
-	    || entry->ip.proto == IPPROTO_SCTP
-	    || entry->ip.proto == IPPROTO_DCCP
-	    || entry->ip.proto == IPPROTO_ICMP)
-		portok = 1;
-	else
-		portok = 0;
-
-	xtables_option_parse(cb);
-	switch (cb->entry->id) {
-	case O_TO_PORTS:
-		if (!portok)
-			xtables_error(PARAMETER_PROBLEM,
-				   "Need TCP, UDP, SCTP or DCCP with port specification");
-		parse_ports(cb->arg, mr);
-		if (cb->xflags & F_RANDOM)
-			mr->range[0].flags |= NF_NAT_RANGE_PROTO_RANDOM;
-		break;
-	case O_RANDOM:
-		if (cb->xflags & F_TO_PORTS)
-			mr->range[0].flags |= NF_NAT_RANGE_PROTO_RANDOM;
-		break;
-	}
-}
-
-static void REDIRECT_print(const void *ip, const struct xt_entry_target *target,
-                           int numeric)
-{
-	const struct nf_nat_ipv4_multi_range_compat *mr = (const void *)target->data;
-	const struct nf_nat_ipv4_range *r = &mr->range[0];
-
-	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
-		printf(" redir ports ");
-		printf("%hu", ntohs(r->min.tcp.port));
-		if (r->max.tcp.port != r->min.tcp.port)
-			printf("-%hu", ntohs(r->max.tcp.port));
-		if (mr->range[0].flags & NF_NAT_RANGE_PROTO_RANDOM)
-			printf(" random");
-	}
-}
-
-static void REDIRECT_save(const void *ip, const struct xt_entry_target *target)
-{
-	const struct nf_nat_ipv4_multi_range_compat *mr = (const void *)target->data;
-	const struct nf_nat_ipv4_range *r = &mr->range[0];
-
-	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
-		printf(" --to-ports ");
-		printf("%hu", ntohs(r->min.tcp.port));
-		if (r->max.tcp.port != r->min.tcp.port)
-			printf("-%hu", ntohs(r->max.tcp.port));
-		if (mr->range[0].flags & NF_NAT_RANGE_PROTO_RANDOM)
-			printf(" --random");
-	}
-}
-
-static int REDIRECT_xlate(struct xt_xlate *xl,
-			  const struct xt_xlate_tg_params *params)
-{
-	const struct nf_nat_ipv4_multi_range_compat *mr =
-		(const void *)params->target->data;
-	const struct nf_nat_ipv4_range *r = &mr->range[0];
-
-	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
-		xt_xlate_add(xl, "redirect to :%hu", ntohs(r->min.tcp.port));
-		if (r->max.tcp.port != r->min.tcp.port)
-			xt_xlate_add(xl, "-%hu ", ntohs(r->max.tcp.port));
-		if (mr->range[0].flags & NF_NAT_RANGE_PROTO_RANDOM)
-			xt_xlate_add(xl, " random ");
-	}
-
-	return 1;
-}
-
-static struct xtables_target redirect_tg_reg = {
-	.name		= "REDIRECT",
-	.version	= XTABLES_VERSION,
-	.family		= NFPROTO_IPV4,
-	.size		= XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)),
-	.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)),
-	.help		= REDIRECT_help,
-	.init		= REDIRECT_init,
- 	.x6_parse	= REDIRECT_parse,
-	.print		= REDIRECT_print,
-	.save		= REDIRECT_save,
-	.x6_options	= REDIRECT_opts,
-	.xlate		= REDIRECT_xlate,
-};
-
-void _init(void)
-{
-	xtables_register_target(&redirect_tg_reg);
-}
diff --git a/extensions/libipt_REDIRECT.t b/extensions/libipt_REDIRECT.t
deleted file mode 100644
index a0fb0ed19a5ea..0000000000000
--- a/extensions/libipt_REDIRECT.t
+++ /dev/null
@@ -1,6 +0,0 @@
-:PREROUTING,OUTPUT
-*nat
--p tcp -j REDIRECT --to-ports 42;=;OK
--p udp -j REDIRECT --to-ports 42-1234;=;OK
--p tcp -j REDIRECT --to-ports 42-1234 --random;=;OK
--j REDIRECT --to-ports 42;;FAIL
diff --git a/extensions/libipt_REDIRECT.txlate b/extensions/libipt_REDIRECT.txlate
deleted file mode 100644
index 815bb7714138d..0000000000000
--- a/extensions/libipt_REDIRECT.txlate
+++ /dev/null
@@ -1,5 +0,0 @@
-iptables-translate -t nat -A prerouting -p tcp --dport 80 -j REDIRECT --to-ports 8080
-nft add rule ip nat prerouting tcp dport 80 counter redirect to :8080
-
-iptables-translate -t nat -A prerouting -p tcp --dport 80 -j REDIRECT --to-ports 8080 --random
-nft add rule ip nat prerouting tcp dport 80 counter redirect to :8080 random
diff --git a/extensions/libxt_DNAT.c b/extensions/libxt_DNAT.c
index 83ff95b0013c7..754e244e0dbe6 100644
--- a/extensions/libxt_DNAT.c
+++ b/extensions/libxt_DNAT.c
@@ -28,10 +28,12 @@
 
 enum {
 	O_TO_DEST = 0,
+	O_TO_PORTS,
 	O_RANDOM,
 	O_PERSISTENT,
-	F_TO_DEST = 1 << O_TO_DEST,
-	F_RANDOM  = 1 << O_RANDOM,
+	F_TO_DEST  = 1 << O_TO_DEST,
+	F_TO_PORTS = 1 << O_TO_PORTS,
+	F_RANDOM   = 1 << O_RANDOM,
 };
 
 static void DNAT_help(void)
@@ -52,6 +54,15 @@ static void DNAT_help_v2(void)
 "[--random] [--persistent]\n");
 }
 
+static void REDIRECT_help(void)
+{
+	printf(
+"REDIRECT target options:\n"
+" --to-ports <port>[-<port>]\n"
+"				Port (range) to map to.\n"
+" [--random]\n");
+}
+
 static const struct xt_option_entry DNAT_opts[] = {
 	{.name = "to-destination", .id = O_TO_DEST, .type = XTTYPE_STRING,
 	 .flags = XTOPT_MAND},
@@ -60,6 +71,12 @@ static const struct xt_option_entry DNAT_opts[] = {
 	XTOPT_TABLEEND,
 };
 
+static const struct xt_option_entry REDIRECT_opts[] = {
+	{.name = "to-ports", .id = O_TO_PORTS, .type = XTTYPE_STRING},
+	{.name = "random", .id = O_RANDOM, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+
 /* Parses ports */
 static void
 parse_ports(const char *arg, bool portok, struct nf_nat_range2 *range)
@@ -73,9 +90,12 @@ parse_ports(const char *arg, bool portok, struct nf_nat_range2 *range)
 
 	range->flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
 
-	if (!xtables_strtoui(arg, &end, &port, 1, UINT16_MAX))
-		xtables_error(PARAMETER_PROBLEM,
-			      "Port `%s' not valid", arg);
+	if (!xtables_strtoui(arg, &end, &port, 1, UINT16_MAX)) {
+		port = xtables_service_to_port(arg, NULL);
+		if (port == (unsigned)-1)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Port `%s' not valid", arg);
+	}
 
 	switch (*end) {
 	case '\0':
@@ -94,10 +114,12 @@ parse_ports(const char *arg, bool portok, struct nf_nat_range2 *range)
 			      "Garbage after port value: `%s'", end);
 	}
 
-	if (!xtables_strtoui(arg, &end, &maxport, 1, UINT16_MAX))
-		xtables_error(PARAMETER_PROBLEM,
-			      "Port `%s' not valid", arg);
-
+	if (!xtables_strtoui(arg, &end, &maxport, 1, UINT16_MAX)) {
+		maxport = xtables_service_to_port(arg, NULL);
+		if (maxport == (unsigned)-1)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Port `%s' not valid", arg);
+	}
 	if (maxport < port)
 		/* People are stupid. */
 		xtables_error(PARAMETER_PROBLEM,
@@ -117,9 +139,12 @@ parse_ports(const char *arg, bool portok, struct nf_nat_range2 *range)
 			      "Garbage after port range: `%s'", end);
 	}
 
-	if (!xtables_strtoui(arg, &end, &baseport, 1, UINT16_MAX))
-		xtables_error(PARAMETER_PROBLEM,
-			      "Port `%s' not valid", arg);
+	if (!xtables_strtoui(arg, &end, &baseport, 1, UINT16_MAX)) {
+		baseport = xtables_service_to_port(arg, NULL);
+		if (baseport == (unsigned)-1)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Port `%s' not valid", arg);
+	}
 
 	range->flags |= NF_NAT_RANGE_PROTO_OFFSET;
 	range->base_proto.tcp.port = htons(baseport);
@@ -199,6 +224,9 @@ static void __DNAT_parse(struct xt_option_call *cb, __u16 proto,
 	case O_TO_DEST:
 		parse_to(cb->arg, portok, range, family);
 		break;
+	case O_TO_PORTS:
+		parse_ports(cb->arg, portok, range);
+		break;
 	case O_PERSISTENT:
 		range->flags |= NF_NAT_RANGE_PERSISTENT;
 		break;
@@ -217,6 +245,8 @@ static void DNAT_parse(struct xt_option_call *cb)
 	case O_TO_DEST:
 		mr->range->min_ip = range.min_addr.ip;
 		mr->range->max_ip = range.max_addr.ip;
+		/* fall through */
+	case O_TO_PORTS:
 		mr->range->min = range.min_proto;
 		mr->range->max = range.max_proto;
 		/* fall through */
@@ -228,9 +258,13 @@ static void DNAT_parse(struct xt_option_call *cb)
 
 static void __DNAT_fcheck(struct xt_fcheck_call *cb, unsigned int *flags)
 {
-	static const unsigned int f = F_TO_DEST | F_RANDOM;
+	static const unsigned int redir_f = F_TO_PORTS | F_RANDOM;
+	static const unsigned int dnat_f = F_TO_DEST | F_RANDOM;
+
 
-	if ((cb->xflags & f) == f)
+
+	if ((cb->xflags & redir_f) == redir_f ||
+	    (cb->xflags & dnat_f) == dnat_f)
 		*flags |= NF_NAT_RANGE_PROTO_RANDOM;
 }
 
@@ -441,6 +475,84 @@ static int DNAT_xlate6_v2(struct xt_xlate *xl,
 	return __DNAT_xlate(xl, (const void *)params->target->data, AF_INET6);
 }
 
+static void __REDIRECT_print(const struct nf_nat_range2 *range, bool save)
+{
+	char *range_str = sprint_range(range, AF_INET);
+	const char *dashdash = save ? "--" : "";
+
+	if (strlen(range_str))
+		/* range_str starts with colon, skip over them */
+		printf(" %s %s", save ? "--to-ports" : "redir ports",
+		       range_str + 1);
+	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
+		printf(" %srandom", dashdash);
+}
+
+static int __REDIRECT_xlate(struct xt_xlate *xl,
+			    const struct nf_nat_range2 *range)
+{
+	char *range_str = sprint_range(range, AF_INET);
+
+	xt_xlate_add(xl, "redirect");
+	if (strlen(range_str))
+		xt_xlate_add(xl, " to %s", range_str);
+	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
+		xt_xlate_add(xl, " random");
+
+	return 1;
+}
+
+static void REDIRECT_print(const void *ip, const struct xt_entry_target *target,
+                           int numeric)
+{
+	struct nf_nat_range2 range = RANGE2_INIT_FROM_IPV4_MRC(target->data);
+
+	__REDIRECT_print(&range, false);
+}
+
+static void REDIRECT_save(const void *ip, const struct xt_entry_target *target)
+{
+	struct nf_nat_range2 range = RANGE2_INIT_FROM_IPV4_MRC(target->data);
+
+	__REDIRECT_print(&range, true);
+}
+
+static int REDIRECT_xlate(struct xt_xlate *xl,
+			   const struct xt_xlate_tg_params *params)
+{
+	struct nf_nat_range2 range =
+		RANGE2_INIT_FROM_IPV4_MRC(params->target->data);
+
+	return __REDIRECT_xlate(xl, &range);
+}
+
+static void REDIRECT_print6(const void *ip, const struct xt_entry_target *target,
+                            int numeric)
+{
+	struct nf_nat_range2 range = {};
+
+	memcpy(&range, (const void *)target->data, sizeof(struct nf_nat_range));
+	__REDIRECT_print(&range, false);
+}
+
+static void REDIRECT_save6(const void *ip, const struct xt_entry_target *target)
+{
+	struct nf_nat_range2 range = {};
+
+	memcpy(&range, (const void *)target->data, sizeof(struct nf_nat_range));
+	__REDIRECT_print(&range, true);
+}
+
+static int REDIRECT_xlate6(struct xt_xlate *xl,
+			   const struct xt_xlate_tg_params *params)
+{
+	struct nf_nat_range2 range = {};
+
+	memcpy(&range, (const void *)params->target->data,
+	       sizeof(struct nf_nat_range));
+	return __REDIRECT_xlate(xl, &range);
+}
+
 static struct xtables_target dnat_tg_reg[] = {
 	{
 		.name		= "DNAT",
@@ -457,6 +569,21 @@ static struct xtables_target dnat_tg_reg[] = {
 		.x6_options	= DNAT_opts,
 		.xlate		= DNAT_xlate,
 	},
+	{
+		.name		= "REDIRECT",
+		.version	= XTABLES_VERSION,
+		.family		= NFPROTO_IPV4,
+		.revision	= 0,
+		.size		= XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)),
+		.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)),
+		.help		= REDIRECT_help,
+		.print		= REDIRECT_print,
+		.save		= REDIRECT_save,
+		.x6_parse	= DNAT_parse,
+		.x6_fcheck	= DNAT_fcheck,
+		.x6_options	= REDIRECT_opts,
+		.xlate		= REDIRECT_xlate,
+	},
 	{
 		.name		= "DNAT",
 		.version	= XTABLES_VERSION,
@@ -472,6 +599,20 @@ static struct xtables_target dnat_tg_reg[] = {
 		.x6_options	= DNAT_opts,
 		.xlate		= DNAT_xlate6,
 	},
+	{
+		.name		= "REDIRECT",
+		.version	= XTABLES_VERSION,
+		.family		= NFPROTO_IPV6,
+		.size		= XT_ALIGN(sizeof(struct nf_nat_range)),
+		.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_range)),
+		.help		= REDIRECT_help,
+		.print		= REDIRECT_print6,
+		.save		= REDIRECT_save6,
+		.x6_parse	= DNAT_parse6,
+		.x6_fcheck	= DNAT_fcheck6,
+		.x6_options	= REDIRECT_opts,
+		.xlate		= REDIRECT_xlate6,
+	},
 	{
 		.name		= "DNAT",
 		.version	= XTABLES_VERSION,
diff --git a/extensions/libxt_REDIRECT.t b/extensions/libxt_REDIRECT.t
new file mode 100644
index 0000000000000..3f0b8a6000445
--- /dev/null
+++ b/extensions/libxt_REDIRECT.t
@@ -0,0 +1,9 @@
+:PREROUTING,OUTPUT
+*nat
+-p tcp -j REDIRECT --to-ports 42;=;OK
+-p udp -j REDIRECT --to-ports 42-1234;=;OK
+-p tcp -j REDIRECT --to-ports 42-1234 --random;=;OK
+-p tcp -j REDIRECT --to-ports 42-1234/567;;FAIL
+-p tcp -j REDIRECT --to-ports ssh;-p tcp -j REDIRECT --to-ports 22;OK
+-p tcp -j REDIRECT --to-ports ftp-data;-p tcp -j REDIRECT --to-ports 20;OK
+-j REDIRECT --to-ports 42;;FAIL
diff --git a/extensions/libxt_REDIRECT.txlate b/extensions/libxt_REDIRECT.txlate
new file mode 100644
index 0000000000000..c7375c614f50a
--- /dev/null
+++ b/extensions/libxt_REDIRECT.txlate
@@ -0,0 +1,17 @@
+iptables-translate -t nat -A prerouting -p tcp --dport 80 -j REDIRECT
+nft add rule ip nat prerouting tcp dport 80 counter redirect
+
+iptables-translate -t nat -A prerouting -p tcp --dport 80 -j REDIRECT --to-ports 8080
+nft add rule ip nat prerouting tcp dport 80 counter redirect to :8080
+
+iptables-translate -t nat -A prerouting -p tcp --dport 80 -j REDIRECT --to-ports 8080 --random
+nft add rule ip nat prerouting tcp dport 80 counter redirect to :8080 random
+
+ip6tables-translate -t nat -A prerouting -p tcp --dport 80 -j REDIRECT
+nft add rule ip6 nat prerouting tcp dport 80 counter redirect
+
+ip6tables-translate -t nat -A prerouting -p tcp --dport 80 -j REDIRECT --to-ports 8080
+nft add rule ip6 nat prerouting tcp dport 80 counter redirect to :8080
+
+ip6tables-translate -t nat -A prerouting -p tcp --dport 80 -j REDIRECT --to-ports 8080 --random
+nft add rule ip6 nat prerouting tcp dport 80 counter redirect to :8080 random
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [iptables PATCH 9/9] extensions: DNAT: Support service names in all spots
  2022-03-30 15:58 [iptables PATCH 0/9] extensions: Merge *_DNAT and *_REDIRECT Phil Sutter
                   ` (7 preceding siblings ...)
  2022-03-30 15:58 ` [iptables PATCH 8/9] extensions: Merge REDIRECT into DNAT Phil Sutter
@ 2022-03-30 15:58 ` Phil Sutter
  2022-03-30 18:38   ` Jan Engelhardt
  8 siblings, 1 reply; 14+ messages in thread
From: Phil Sutter @ 2022-03-30 15:58 UTC (permalink / raw)
  To: Pablo Neira Ayuso; +Cc: netfilter-devel

When parsing (parts of) a port spec, if it doesn't start with a digit,
try to find the largest substring getservbyname() accepts.

Signed-off-by: Phil Sutter <phil@nwl.cc>
---
 extensions/libipt_DNAT.t    |  4 +++
 extensions/libxt_DNAT.c     | 70 +++++++++++++++++++++++++++----------
 extensions/libxt_REDIRECT.t |  2 ++
 3 files changed, 58 insertions(+), 18 deletions(-)

diff --git a/extensions/libipt_DNAT.t b/extensions/libipt_DNAT.t
index eb187bc91053b..c744dff3ec902 100644
--- a/extensions/libipt_DNAT.t
+++ b/extensions/libipt_DNAT.t
@@ -15,4 +15,8 @@
 -p tcp -j DNAT --to-destination 1.1.1.1:1000-2000/65536;;FAIL
 -p tcp -j DNAT --to-destination 1.1.1.1:ssh;-p tcp -j DNAT --to-destination 1.1.1.1:22;OK
 -p tcp -j DNAT --to-destination 1.1.1.1:ftp-data;-p tcp -j DNAT --to-destination 1.1.1.1:20;OK
+-p tcp -j DNAT --to-destination 1.1.1.1:ftp-data-ssh;-p tcp -j DNAT --to-destination 1.1.1.1:20-22;OK
+-p tcp -j DNAT --to-destination 1.1.1.1:echo-ftp-data;-p tcp -j DNAT --to-destination 1.1.1.1:7-20;OK
+-p tcp -j DNAT --to-destination 1.1.1.1:ftp-data-ssh/echo;-p tcp -j DNAT --to-destination 1.1.1.1:20-22/7;OK
+-p tcp -j DNAT --to-destination 1.1.1.1:echo-ftp-data/ssh;-p tcp -j DNAT --to-destination 1.1.1.1:7-20/22;OK
 -j DNAT;;FAIL
diff --git a/extensions/libxt_DNAT.c b/extensions/libxt_DNAT.c
index 754e244e0dbe6..70d2823568c7d 100644
--- a/extensions/libxt_DNAT.c
+++ b/extensions/libxt_DNAT.c
@@ -77,6 +77,49 @@ static const struct xt_option_entry REDIRECT_opts[] = {
 	XTOPT_TABLEEND,
 };
 
+static char *strrchrs(const char *s, const char *chrs)
+{
+	int i;
+
+	for (i = strlen(s) - 1; i >= 0; i--) {
+		if (strchr(chrs, s[i]))
+			return (char *)s + i;
+	}
+	return NULL;
+}
+
+static bool parse_port(const char *orig_s, char **end, unsigned int *value,
+		       unsigned int min, unsigned int max)
+{
+	char *s, *pos;
+	int port;
+
+	if (xtables_strtoui(orig_s, end, value, min, max))
+		return true;
+
+	s = xtables_strdup(orig_s);
+	port = xtables_service_to_port(s, NULL);
+	if (port >= min && port <= max)
+		goto found;
+
+	pos = strrchrs(s, "-:/");
+	while (pos) {
+		*pos = '\0';
+		port = xtables_service_to_port(s, NULL);
+		if (port >= min && port <= max)
+			goto found;
+
+		pos = strrchrs(s, "-:/");
+	}
+	free(s);
+	return false;
+found:
+	*end = (char *)orig_s + strlen(s);
+	*value = port;
+	free(s);
+	return true;
+}
+
 /* Parses ports */
 static void
 parse_ports(const char *arg, bool portok, struct nf_nat_range2 *range)
@@ -90,12 +133,9 @@ parse_ports(const char *arg, bool portok, struct nf_nat_range2 *range)
 
 	range->flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
 
-	if (!xtables_strtoui(arg, &end, &port, 1, UINT16_MAX)) {
-		port = xtables_service_to_port(arg, NULL);
-		if (port == (unsigned)-1)
-			xtables_error(PARAMETER_PROBLEM,
-				      "Port `%s' not valid", arg);
-	}
+	if (!parse_port(arg, &end, &port, 1, UINT16_MAX))
+		xtables_error(PARAMETER_PROBLEM,
+			      "Port `%s' not valid", arg);
 
 	switch (*end) {
 	case '\0':
@@ -114,12 +154,9 @@ parse_ports(const char *arg, bool portok, struct nf_nat_range2 *range)
 			      "Garbage after port value: `%s'", end);
 	}
 
-	if (!xtables_strtoui(arg, &end, &maxport, 1, UINT16_MAX)) {
-		maxport = xtables_service_to_port(arg, NULL);
-		if (maxport == (unsigned)-1)
-			xtables_error(PARAMETER_PROBLEM,
-				      "Port `%s' not valid", arg);
-	}
+	if (!parse_port(arg, &end, &maxport, 1, UINT16_MAX))
+		xtables_error(PARAMETER_PROBLEM,
+			      "Port `%s' not valid", arg);
 	if (maxport < port)
 		/* People are stupid. */
 		xtables_error(PARAMETER_PROBLEM,
@@ -139,12 +176,9 @@ parse_ports(const char *arg, bool portok, struct nf_nat_range2 *range)
 			      "Garbage after port range: `%s'", end);
 	}
 
-	if (!xtables_strtoui(arg, &end, &baseport, 1, UINT16_MAX)) {
-		baseport = xtables_service_to_port(arg, NULL);
-		if (baseport == (unsigned)-1)
-			xtables_error(PARAMETER_PROBLEM,
-				      "Port `%s' not valid", arg);
-	}
+	if (!parse_port(arg, &end, &baseport, 1, UINT16_MAX))
+		xtables_error(PARAMETER_PROBLEM,
+			      "Port `%s' not valid", arg);
 
 	range->flags |= NF_NAT_RANGE_PROTO_OFFSET;
 	range->base_proto.tcp.port = htons(baseport);
diff --git a/extensions/libxt_REDIRECT.t b/extensions/libxt_REDIRECT.t
index 3f0b8a6000445..a50ef257ec956 100644
--- a/extensions/libxt_REDIRECT.t
+++ b/extensions/libxt_REDIRECT.t
@@ -6,4 +6,6 @@
 -p tcp -j REDIRECT --to-ports 42-1234/567;;FAIL
 -p tcp -j REDIRECT --to-ports ssh;-p tcp -j REDIRECT --to-ports 22;OK
 -p tcp -j REDIRECT --to-ports ftp-data;-p tcp -j REDIRECT --to-ports 20;OK
+-p tcp -j REDIRECT --to-ports ftp-data-ssh;-p tcp -j REDIRECT --to-ports 20-22;OK
+-p tcp -j REDIRECT --to-ports echo-ftp-data;-p tcp -j REDIRECT --to-ports 7-20;OK
 -j REDIRECT --to-ports 42;;FAIL
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [iptables PATCH 9/9] extensions: DNAT: Support service names in all spots
  2022-03-30 15:58 ` [iptables PATCH 9/9] extensions: DNAT: Support service names in all spots Phil Sutter
@ 2022-03-30 18:38   ` Jan Engelhardt
  2022-03-30 20:57     ` Phil Sutter
  0 siblings, 1 reply; 14+ messages in thread
From: Jan Engelhardt @ 2022-03-30 18:38 UTC (permalink / raw)
  To: Phil Sutter; +Cc: Pablo Neira Ayuso, netfilter-devel


On Wednesday 2022-03-30 17:58, Phil Sutter wrote:

>When parsing (parts of) a port spec, if it doesn't start with a digit,
>try to find the largest substring getservbyname() accepts.

> -p tcp -j DNAT --to-destination 1.1.1.1:1000-2000/65536;;FAIL
> -p tcp -j DNAT --to-destination 1.1.1.1:ssh;-p tcp -j DNAT --to-destination 1.1.1.1:22;OK
> -p tcp -j DNAT --to-destination 1.1.1.1:ftp-data;-p tcp -j DNAT --to-destination 1.1.1.1:20;OK
>+-p tcp -j DNAT --to-destination 1.1.1.1:ftp-data-ssh;-p tcp -j DNAT --to-destination 1.1.1.1:20-22;OK
>+-p tcp -j DNAT --to-destination 1.1.1.1:echo-ftp-data;-p tcp -j DNAT --to-destination 1.1.1.1:7-20;OK
>+-p tcp -j DNAT --to-destination 1.1.1.1:ftp-data-ssh/echo;-p tcp -j DNAT --to-destination 1.1.1.1:20-22/7;OK
>+-p tcp -j DNAT --to-destination 1.1.1.1:echo-ftp-data/ssh;-p tcp -j DNAT --to-destination 1.1.1.1:7-20/22;OK
> -j DNAT;;FAIL

This looks dangerous. It is why I originally never allowed service names in
port ranges that use dash as the range character. a-b-c could mean a..b-c
today, and could mean a-b..c tomorrow, either because someone managed to
inject a-b into the service list.

The "solution" would be to use : as the range character, but that would require
a new --dport option for reasons of command-line compatibility.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [iptables PATCH 9/9] extensions: DNAT: Support service names in all spots
  2022-03-30 18:38   ` Jan Engelhardt
@ 2022-03-30 20:57     ` Phil Sutter
  2022-03-31  0:19       ` Jan Engelhardt
  0 siblings, 1 reply; 14+ messages in thread
From: Phil Sutter @ 2022-03-30 20:57 UTC (permalink / raw)
  To: Jan Engelhardt; +Cc: Pablo Neira Ayuso, netfilter-devel

Hi Jan,

On Wed, Mar 30, 2022 at 08:38:28PM +0200, Jan Engelhardt wrote:
> 
> On Wednesday 2022-03-30 17:58, Phil Sutter wrote:
> 
> >When parsing (parts of) a port spec, if it doesn't start with a digit,
> >try to find the largest substring getservbyname() accepts.
> 
> > -p tcp -j DNAT --to-destination 1.1.1.1:1000-2000/65536;;FAIL
> > -p tcp -j DNAT --to-destination 1.1.1.1:ssh;-p tcp -j DNAT --to-destination 1.1.1.1:22;OK
> > -p tcp -j DNAT --to-destination 1.1.1.1:ftp-data;-p tcp -j DNAT --to-destination 1.1.1.1:20;OK
> >+-p tcp -j DNAT --to-destination 1.1.1.1:ftp-data-ssh;-p tcp -j DNAT --to-destination 1.1.1.1:20-22;OK
> >+-p tcp -j DNAT --to-destination 1.1.1.1:echo-ftp-data;-p tcp -j DNAT --to-destination 1.1.1.1:7-20;OK
> >+-p tcp -j DNAT --to-destination 1.1.1.1:ftp-data-ssh/echo;-p tcp -j DNAT --to-destination 1.1.1.1:20-22/7;OK
> >+-p tcp -j DNAT --to-destination 1.1.1.1:echo-ftp-data/ssh;-p tcp -j DNAT --to-destination 1.1.1.1:7-20/22;OK
> > -j DNAT;;FAIL
> 
> This looks dangerous. It is why I originally never allowed service names in
> port ranges that use dash as the range character. a-b-c could mean a..b-c
> today, and could mean a-b..c tomorrow, either because someone managed to
> inject a-b into the service list.

Yes, it is a rather sloppy solution. I could at least do a shortest
substring first search in addition to check if the input is ambiguous.

Guess if someone is able to manipulate /etc/services, any service names
are problematic, not just in ranges.

Another potential problem I didn't have in mind though is that 'a-b'
could mean [a; b] or [a-b] assuming that all three exist. But I haven't
found a valid example in my /etc/services, yet. :)

> The "solution" would be to use : as the range character, but that would require
> a new --dport option for reasons of command-line compatibility.

Well, we could allow both (a-b with numeric a and b only) and use it in
output only if non-numeric was requested.

Maybe also just limit service names in DNAT to non-ranges. I wanted to
write "like with REDIRECT before the merge", but it looks like it
accepted them as upper boundary, e.g. '10-ftp-data'.

Hmm. I also noticed my series drops support for port 0 from REDIRECT
which commit 84d758b3bc312 ("extensions: REDIRECT: fix --to-ports
parser") explicitly allowed.

Thanks, Phil

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [iptables PATCH 9/9] extensions: DNAT: Support service names in all spots
  2022-03-30 20:57     ` Phil Sutter
@ 2022-03-31  0:19       ` Jan Engelhardt
  2022-03-31 10:04         ` Phil Sutter
  0 siblings, 1 reply; 14+ messages in thread
From: Jan Engelhardt @ 2022-03-31  0:19 UTC (permalink / raw)
  To: Phil Sutter; +Cc: Pablo Neira Ayuso, netfilter-devel


On Wednesday 2022-03-30 22:57, Phil Sutter wrote:
>> >+-p tcp -j DNAT --to-destination 1.1.1.1:echo-ftp-data/ssh;-p tcp -j DNAT --to-destination 1.1.1.1:7-20/22;OK
>> 
>> This looks dangerous. It is why I originally never allowed service names in
>> port ranges that use dash as the range character.
>
>Guess if someone is able to manipulate /etc/services, any service names
>are problematic, not just in ranges.

Well, pretty much anyone has a shot at manipulating this file. File a
port registration with IANA, and when your distro updates the file,
eventually there is a chance of messing up firewall configs,
worldwide.

Other than that, thanks to nsswitch, the service list can also be in
LDAP or so, and then all that's needed is a disgruntled LDAP admin
who dislikes the firewall admin.

>Another potential problem I didn't have in mind though is that 'a-b'
>could mean [a; b] or [a-b] assuming that all three exist. But I haven't
>found a valid example in my /etc/services, yet. :)

I found 25 at once!

914c :: 914c-g :: g-talk
ads :: ads-s :: s-openmail
bctp :: bctp-server :: server-find
cis :: cis-secure :: secure-mqtt
connect :: connect-server :: server-find
docker :: docker-s :: s-openmail
documentum :: documentum-s :: s-openmail
domain :: domain-s :: s-openmail
dtp :: dtp-net :: net-device
genie :: genie-lm :: lm-mon
linktest :: linktest-s :: s-openmail
mailbox :: mailbox-lm :: lm-mon
mbap :: mbap-s :: s-openmail
ns :: ns-server :: server-find
plato :: plato-lm :: lm-mon
rmonitor :: rmonitor-secure :: secure-mqtt
sentinel :: sentinel-lm :: lm-mon
sitewatch :: sitewatch-s :: s-openmail
spss :: spss-lm :: lm-mon
sql :: sql-net :: net-device
tacacs :: tacacs-ds :: ds-slp
tl1 :: tl1-lv :: lv-auth
trim :: trim-ice :: ice-router
wnn6 :: wnn6-ds :: ds-slp
wsdapi :: wsdapi-s :: s-openmail

To be read as: "wsdapi-s-openmail" is ambiguous, because
it allows for two interpretations (and all four port names are in
/etc/services):

	[wsdapi]-[s-openmail]
	[wsdapi-s]-[openmail]


>> The "solution" would be to use : as the range character, but that would require
>> a new --dport option for reasons of command-line compatibility.
>
>Well, we could allow both (a-b with numeric a and b only) and use it in
>output only if non-numeric was requested.

Given I'm seeing "914c" in the IANA list (leading digits always stand
out from the crowd, e.g. you don't normally see them in UNIX
usernames either), I won't hold my breath that no one would try to
register "914-915".

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [iptables PATCH 9/9] extensions: DNAT: Support service names in all spots
  2022-03-31  0:19       ` Jan Engelhardt
@ 2022-03-31 10:04         ` Phil Sutter
  0 siblings, 0 replies; 14+ messages in thread
From: Phil Sutter @ 2022-03-31 10:04 UTC (permalink / raw)
  To: Jan Engelhardt; +Cc: Pablo Neira Ayuso, netfilter-devel

On Thu, Mar 31, 2022 at 02:19:51AM +0200, Jan Engelhardt wrote:
> On Wednesday 2022-03-30 22:57, Phil Sutter wrote:
> >> >+-p tcp -j DNAT --to-destination 1.1.1.1:echo-ftp-data/ssh;-p tcp -j DNAT --to-destination 1.1.1.1:7-20/22;OK
> >> 
> >> This looks dangerous. It is why I originally never allowed service names in
> >> port ranges that use dash as the range character.
> >
> >Guess if someone is able to manipulate /etc/services, any service names
> >are problematic, not just in ranges.
> 
> Well, pretty much anyone has a shot at manipulating this file. File a
> port registration with IANA, and when your distro updates the file,
> eventually there is a chance of messing up firewall configs,
> worldwide.
> 
> Other than that, thanks to nsswitch, the service list can also be in
> LDAP or so, and then all that's needed is a disgruntled LDAP admin
> who dislikes the firewall admin.
> 
> >Another potential problem I didn't have in mind though is that 'a-b'
> >could mean [a; b] or [a-b] assuming that all three exist. But I haven't
> >found a valid example in my /etc/services, yet. :)
> 
> I found 25 at once!
> 
> 914c :: 914c-g :: g-talk
> ads :: ads-s :: s-openmail
> bctp :: bctp-server :: server-find
> cis :: cis-secure :: secure-mqtt
> connect :: connect-server :: server-find
> docker :: docker-s :: s-openmail
> documentum :: documentum-s :: s-openmail
> domain :: domain-s :: s-openmail
> dtp :: dtp-net :: net-device
> genie :: genie-lm :: lm-mon
> linktest :: linktest-s :: s-openmail
> mailbox :: mailbox-lm :: lm-mon
> mbap :: mbap-s :: s-openmail
> ns :: ns-server :: server-find
> plato :: plato-lm :: lm-mon
> rmonitor :: rmonitor-secure :: secure-mqtt
> sentinel :: sentinel-lm :: lm-mon
> sitewatch :: sitewatch-s :: s-openmail
> spss :: spss-lm :: lm-mon
> sql :: sql-net :: net-device
> tacacs :: tacacs-ds :: ds-slp
> tl1 :: tl1-lv :: lv-auth
> trim :: trim-ice :: ice-router
> wnn6 :: wnn6-ds :: ds-slp
> wsdapi :: wsdapi-s :: s-openmail

Your /etc/services seems to be much larger than mine, many of those
don't exist in my case.

> To be read as: "wsdapi-s-openmail" is ambiguous, because
> it allows for two interpretations (and all four port names are in
> /etc/services):
> 
> 	[wsdapi]-[s-openmail]
> 	[wsdapi-s]-[openmail]

OK. My code preferred the latter, checking if the former is possible is
a poor workaround. The only difference it makes is rules get rejected
instead of changing behaviour silently.

> >> The "solution" would be to use : as the range character, but that would require
> >> a new --dport option for reasons of command-line compatibility.
> >
> >Well, we could allow both (a-b with numeric a and b only) and use it in
> >output only if non-numeric was requested.
> 
> Given I'm seeing "914c" in the IANA list (leading digits always stand
> out from the crowd, e.g. you don't normally see them in UNIX
> usernames either), I won't hold my breath that no one would try to
> register "914-915".

That's an interesting point: "914c" is not usable in iptables. The code
tries strtoul which succeeds for "914", "c" is then rejected as illegal
remainder. To fix that, we had to consult getservbyname() first which is
a performance hit. Guess the whole "support users with bad number
memory" game is best effort, only.

I'll submit a v2 without the experimental "names in ranges" patch in a
minute.

Thanks, Phil

^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2022-03-31 10:04 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-03-30 15:58 [iptables PATCH 0/9] extensions: Merge *_DNAT and *_REDIRECT Phil Sutter
2022-03-30 15:58 ` [iptables PATCH 1/9] man: DNAT: Describe shifted port range feature Phil Sutter
2022-03-30 15:58 ` [iptables PATCH 2/9] Revert "libipt_[SD]NAT: avoid false error about multiple destinations specified" Phil Sutter
2022-03-30 15:58 ` [iptables PATCH 3/9] extensions: ipt_DNAT: Merge v1 and v2 parsers Phil Sutter
2022-03-30 15:58 ` [iptables PATCH 4/9] extensions: ipt_DNAT: Merge v1/v2 print/save code Phil Sutter
2022-03-30 15:58 ` [iptables PATCH 5/9] extensions: ipt_DNAT: Combine xlate functions also Phil Sutter
2022-03-30 15:58 ` [iptables PATCH 6/9] extensions: DNAT: Rename from libipt to libxt Phil Sutter
2022-03-30 15:58 ` [iptables PATCH 7/9] extensions: Merge IPv4 and IPv6 DNAT targets Phil Sutter
2022-03-30 15:58 ` [iptables PATCH 8/9] extensions: Merge REDIRECT into DNAT Phil Sutter
2022-03-30 15:58 ` [iptables PATCH 9/9] extensions: DNAT: Support service names in all spots Phil Sutter
2022-03-30 18:38   ` Jan Engelhardt
2022-03-30 20:57     ` Phil Sutter
2022-03-31  0:19       ` Jan Engelhardt
2022-03-31 10:04         ` Phil Sutter

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.