Blame SOURCES/0001-conntrack-Support-IPv6-NAT.patch

7d290b
From c1874130f845e526de76d116639c044cb30fcc9a Mon Sep 17 00:00:00 2001
7d290b
From: Neil Wilson <neil@aldur.co.uk>
7d290b
Date: Thu, 16 Mar 2017 11:49:03 +0000
7d290b
Subject: [PATCH] conntrack: Support IPv6 NAT
7d290b
7d290b
Refactor and improve nat support to allow conntrack to manage IPv6
7d290b
NAT entries.
7d290b
7d290b
Refactor and improve conntrack nat tests to include IPv6 NAT.
7d290b
7d290b
Signed-off-by: Neil Wilson <neil@aldur.co.uk>
7d290b
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
7d290b
(cherry picked from commit 29b390a2122143997a651e6b25d7496e62ead2a1)
7d290b
---
7d290b
 src/conntrack.c                    | 213 ++++++++++++++++++++---------
7d290b
 tests/conntrack/testsuite/00create |   6 +
7d290b
 tests/conntrack/testsuite/03nat    |   8 ++
7d290b
 tests/conntrack/testsuite/07nat6   |  56 ++++++++
7d290b
 4 files changed, 216 insertions(+), 67 deletions(-)
7d290b
 create mode 100644 tests/conntrack/testsuite/07nat6
7d290b
7d290b
diff --git a/src/conntrack.c b/src/conntrack.c
7d290b
index ff030fe54e103..cbf03c7be8834 100644
7d290b
--- a/src/conntrack.c
7d290b
+++ b/src/conntrack.c
7d290b
@@ -43,6 +43,8 @@
7d290b
 #include <stdio.h>
7d290b
 #include <getopt.h>
7d290b
 #include <stdlib.h>
7d290b
+#include <ctype.h>
7d290b
+#include <limits.h>
7d290b
 #include <stdarg.h>
7d290b
 #include <errno.h>
7d290b
 #include <unistd.h>
7d290b
@@ -437,6 +439,9 @@ static const int opt2type[] = {
7d290b
 static const int opt2maskopt[] = {
7d290b
 	['s']	= '{',
7d290b
 	['d']	= '}',
7d290b
+	['g']   = 0,
7d290b
+	['j']   = 0,
7d290b
+	['n']   = 0,
7d290b
 	['r']	= 0, /* no netmask */
7d290b
 	['q']	= 0, /* support yet */
7d290b
 	['{']	= 0,
7d290b
@@ -448,6 +453,8 @@ static const int opt2maskopt[] = {
7d290b
 static const int opt2family_attr[][2] = {
7d290b
 	['s']	= { ATTR_ORIG_IPV4_SRC,	ATTR_ORIG_IPV6_SRC },
7d290b
 	['d']	= { ATTR_ORIG_IPV4_DST,	ATTR_ORIG_IPV6_DST },
7d290b
+	['g']   = { ATTR_DNAT_IPV4, ATTR_DNAT_IPV6 },
7d290b
+	['n']   = { ATTR_SNAT_IPV4, ATTR_SNAT_IPV6 },
7d290b
 	['r']	= { ATTR_REPL_IPV4_SRC, ATTR_REPL_IPV6_SRC },
7d290b
 	['q']	= { ATTR_REPL_IPV4_DST, ATTR_REPL_IPV6_DST },
7d290b
 	['{']	= { ATTR_ORIG_IPV4_SRC,	ATTR_ORIG_IPV6_SRC },
7d290b
@@ -459,6 +466,8 @@ static const int opt2family_attr[][2] = {
7d290b
 static const int opt2attr[] = {
7d290b
 	['s']	= ATTR_ORIG_L3PROTO,
7d290b
 	['d']	= ATTR_ORIG_L3PROTO,
7d290b
+	['g']	= ATTR_ORIG_L3PROTO,
7d290b
+	['n']	= ATTR_ORIG_L3PROTO,
7d290b
 	['r']	= ATTR_REPL_L3PROTO,
7d290b
 	['q']	= ATTR_REPL_L3PROTO,
7d290b
 	['{']	= ATTR_ORIG_L3PROTO,
7d290b
@@ -1094,58 +1103,85 @@ parse_addr(const char *cp, union ct_address *address, int *mask)
7d290b
 	return family;
7d290b
 }
7d290b
 
7d290b
-static void
7d290b
-nat_parse(char *arg, struct nf_conntrack *obj, int type)
7d290b
+static bool
7d290b
+valid_port(char *cursor)
7d290b
 {
7d290b
-	char *colon, *error;
7d290b
-	union ct_address parse;
7d290b
+	const char *str = cursor;
7d290b
+	/* Missing port number */
7d290b
+	if (!*str)
7d290b
+		return false;
7d290b
 
7d290b
-	colon = strchr(arg, ':');
7d290b
+	/* Must be entirely digits - no spaces or +/- */
7d290b
+	while (*cursor) {
7d290b
+		if (!isdigit(*cursor))
7d290b
+			return false;
7d290b
+		else
7d290b
+			++cursor;
7d290b
+	}
7d290b
 
7d290b
-	if (colon) {
7d290b
-		uint16_t port;
7d290b
+	/* Must be in range */
7d290b
+	errno = 0;
7d290b
+	long port = strtol(str, NULL, 10);
7d290b
 
7d290b
-		*colon = '\0';
7d290b
+	if ((errno == ERANGE && (port == LONG_MAX || port == LONG_MIN))
7d290b
+		|| (errno != 0 && port == 0) || (port > USHRT_MAX))
7d290b
+		return false;
7d290b
 
7d290b
-		port = (uint16_t)atoi(colon+1);
7d290b
-		if (port == 0) {
7d290b
-			if (strlen(colon+1) == 0) {
7d290b
-				exit_error(PARAMETER_PROBLEM,
7d290b
-					   "No port specified after `:'");
7d290b
-			} else {
7d290b
-				exit_error(PARAMETER_PROBLEM,
7d290b
-					   "Port `%s' not valid", colon+1);
7d290b
-			}
7d290b
-		}
7d290b
+	return true;
7d290b
+}
7d290b
+
7d290b
+static void
7d290b
+split_address_and_port(const char *arg, char **address, char **port_str)
7d290b
+{
7d290b
+	char *cursor = strchr(arg, '[');
7d290b
+
7d290b
+	if (cursor) {
7d290b
+		/* IPv6 address with port*/
7d290b
+		char *start = cursor + 1;
7d290b
 
7d290b
-		error = strchr(colon+1, ':');
7d290b
-		if (error)
7d290b
+		cursor = strchr(start, ']');
7d290b
+		if (start == cursor) {
7d290b
+			exit_error(PARAMETER_PROBLEM,
7d290b
+				   "No IPv6 address specified");
7d290b
+		} else if (!cursor) {
7d290b
 			exit_error(PARAMETER_PROBLEM,
7d290b
-				   "Invalid port:port syntax");
7d290b
-
7d290b
-		if (type == CT_OPT_SRC_NAT)
7d290b
-			nfct_set_attr_u16(tmpl.ct, ATTR_SNAT_PORT, ntohs(port));
7d290b
-		else if (type == CT_OPT_DST_NAT)
7d290b
-			nfct_set_attr_u16(tmpl.ct, ATTR_DNAT_PORT, ntohs(port));
7d290b
-		else if (type == CT_OPT_ANY_NAT) {
7d290b
-			nfct_set_attr_u16(tmpl.ct, ATTR_SNAT_PORT, ntohs(port));
7d290b
-			nfct_set_attr_u16(tmpl.ct, ATTR_DNAT_PORT, ntohs(port));
7d290b
+				   "No closing ']' around IPv6 address");
7d290b
 		}
7d290b
-	}
7d290b
+		size_t len = cursor - start;
7d290b
 
7d290b
-	if (parse_addr(arg, &parse, NULL) == AF_UNSPEC) {
7d290b
-		if (strlen(arg) == 0) {
7d290b
-			exit_error(PARAMETER_PROBLEM, "No IP specified");
7d290b
+		cursor = strchr(cursor, ':');
7d290b
+		if (cursor) {
7d290b
+			/* Copy address only if there is a port */
7d290b
+			*address = strndup(start, len);
7d290b
+		}
7d290b
+	} else {
7d290b
+		cursor = strchr(arg, ':');
7d290b
+		if (cursor && !strchr(cursor + 1, ':')) {
7d290b
+			/* IPv4 address with port */
7d290b
+			*address = strndup(arg, cursor - arg);
7d290b
 		} else {
7d290b
+			/* v6 address */
7d290b
+			cursor = NULL;
7d290b
+		}
7d290b
+	}
7d290b
+	if (cursor) {
7d290b
+		/* Parse port entry */
7d290b
+		cursor++;
7d290b
+		if (strlen(cursor) == 0) {
7d290b
 			exit_error(PARAMETER_PROBLEM,
7d290b
-					"Invalid IP address `%s'", arg);
7d290b
+				   "No port specified after `:'");
7d290b
 		}
7d290b
+		if (!valid_port(cursor)) {
7d290b
+			exit_error(PARAMETER_PROBLEM,
7d290b
+				   "Invalid port `%s'", cursor);
7d290b
+		}
7d290b
+		*port_str = strdup(cursor);
7d290b
+	} else {
7d290b
+		/* No port colon or more than one colon (ipv6)
7d290b
+		 * assume arg is straight IP address and no port
7d290b
+		 */
7d290b
+		*address = strdup(arg);
7d290b
 	}
7d290b
-
7d290b
-	if (type == CT_OPT_SRC_NAT || type == CT_OPT_ANY_NAT)
7d290b
-		nfct_set_attr_u32(tmpl.ct, ATTR_SNAT_IPV4, parse.v4);
7d290b
-	else if (type == CT_OPT_DST_NAT || type == CT_OPT_ANY_NAT)
7d290b
-		nfct_set_attr_u32(tmpl.ct, ATTR_DNAT_IPV4, parse.v4);
7d290b
 }
7d290b
 
7d290b
 static void
7d290b
@@ -1289,7 +1325,7 @@ nfct_ip6_net_cmp(const union ct_address *addr, const struct ct_network *net)
7d290b
 
7d290b
 static int
7d290b
 nfct_ip_net_cmp(int family, const union ct_address *addr,
7d290b
-                const struct ct_network *net)
7d290b
+		const struct ct_network *net)
7d290b
 {
7d290b
 	switch(family) {
7d290b
 	case AF_INET:
7d290b
@@ -2128,6 +2164,7 @@ static void merge_bitmasks(struct nfct_bitmask **current,
7d290b
 	nfct_bitmask_destroy(src);
7d290b
 }
7d290b
 
7d290b
+
7d290b
 static void
7d290b
 nfct_build_netmask(uint32_t *dst, int b, int n)
7d290b
 {
7d290b
@@ -2147,10 +2184,9 @@ nfct_build_netmask(uint32_t *dst, int b, int n)
7d290b
 }
7d290b
 
7d290b
 static void
7d290b
-nfct_set_addr_opt(int opt, struct nf_conntrack *ct, union ct_address *ad,
7d290b
-		  int l3protonum)
7d290b
+nfct_set_addr_only(const int opt, struct nf_conntrack *ct, union ct_address *ad,
7d290b
+		   const int l3protonum)
7d290b
 {
7d290b
-	options |= opt2type[opt];
7d290b
 	switch (l3protonum) {
7d290b
 	case AF_INET:
7d290b
 		nfct_set_attr_u32(ct,
7d290b
@@ -2163,24 +2199,33 @@ nfct_set_addr_opt(int opt, struct nf_conntrack *ct, union ct_address *ad,
7d290b
 		              &ad->v6);
7d290b
 		break;
7d290b
 	}
7d290b
+}
7d290b
+
7d290b
+static void
7d290b
+nfct_set_addr_opt(const int opt, struct nf_conntrack *ct, union ct_address *ad,
7d290b
+		  const int l3protonum)
7d290b
+{
7d290b
+	options |= opt2type[opt];
7d290b
+	nfct_set_addr_only(opt, ct, ad, l3protonum);
7d290b
 	nfct_set_attr_u8(ct, opt2attr[opt], l3protonum);
7d290b
 }
7d290b
 
7d290b
 static void
7d290b
-nfct_parse_addr_from_opt(int opt, struct nf_conntrack *ct,
7d290b
-                         struct nf_conntrack *ctmask,
7d290b
-                         union ct_address *ad, int *family)
7d290b
+nfct_parse_addr_from_opt(const int opt, const char *arg,
7d290b
+			 struct nf_conntrack *ct,
7d290b
+			 struct nf_conntrack *ctmask,
7d290b
+			 union ct_address *ad, int *family)
7d290b
 {
7d290b
-	int l3protonum, mask, maskopt;
7d290b
+	int mask, maskopt;
7d290b
 
7d290b
-	l3protonum = parse_addr(optarg, ad, &mask);
7d290b
+	const int l3protonum = parse_addr(arg, ad, &mask);
7d290b
 	if (l3protonum == AF_UNSPEC) {
7d290b
 		exit_error(PARAMETER_PROBLEM,
7d290b
-			   "Invalid IP address `%s'", optarg);
7d290b
+			   "Invalid IP address `%s'", arg);
7d290b
 	}
7d290b
 	set_family(family, l3protonum);
7d290b
 	maskopt = opt2maskopt[opt];
7d290b
-	if (!maskopt && mask != -1) {
7d290b
+	if (mask != -1 && !maskopt) {
7d290b
 		exit_error(PARAMETER_PROBLEM,
7d290b
 		           "CIDR notation unavailable"
7d290b
 		           " for `--%s'", get_long_opt(opt));
7d290b
@@ -2192,7 +2237,7 @@ nfct_parse_addr_from_opt(int opt, struct nf_conntrack *ct,
7d290b
 	nfct_set_addr_opt(opt, ct, ad, l3protonum);
7d290b
 
7d290b
 	/* bail if we don't have a netmask to set*/
7d290b
-	if (!maskopt || mask == -1 || ctmask == NULL)
7d290b
+	if (mask == -1 || !maskopt || ctmask == NULL)
7d290b
 		return;
7d290b
 
7d290b
 	switch(l3protonum) {
7d290b
@@ -2211,6 +2256,24 @@ nfct_parse_addr_from_opt(int opt, struct nf_conntrack *ct,
7d290b
 	nfct_set_addr_opt(maskopt, ctmask, ad, l3protonum);
7d290b
 }
7d290b
 
7d290b
+static void
7d290b
+nfct_set_nat_details(const int opt, struct nf_conntrack *ct,
7d290b
+		     union ct_address *ad, const char *port_str,
7d290b
+		     const int family)
7d290b
+{
7d290b
+	const int type = opt2type[opt];
7d290b
+
7d290b
+	nfct_set_addr_only(opt, ct, ad, family);
7d290b
+	if (port_str && type == CT_OPT_SRC_NAT) {
7d290b
+		nfct_set_attr_u16(ct, ATTR_SNAT_PORT,
7d290b
+				  ntohs((uint16_t)atoi(port_str)));
7d290b
+	} else if (port_str && type == CT_OPT_DST_NAT) {
7d290b
+		nfct_set_attr_u16(ct, ATTR_DNAT_PORT,
7d290b
+				  ntohs((uint16_t)atoi(port_str)));
7d290b
+	}
7d290b
+
7d290b
+}
7d290b
+
7d290b
 int main(int argc, char *argv[])
7d290b
 {
7d290b
 	int c, cmd;
7d290b
@@ -2289,17 +2352,18 @@ int main(int argc, char *argv[])
7d290b
 		case 'd':
7d290b
 		case 'r':
7d290b
 		case 'q':
7d290b
-			nfct_parse_addr_from_opt(c, tmpl.ct, tmpl.mask,
7d290b
-			                         &ad, &family);
7d290b
+			nfct_parse_addr_from_opt(c, optarg, tmpl.ct,
7d290b
+						 tmpl.mask, &ad, &family);
7d290b
 			break;
7d290b
 		case '[':
7d290b
 		case ']':
7d290b
-			nfct_parse_addr_from_opt(c, tmpl.exptuple, tmpl.mask,
7d290b
-			                         &ad, &family);
7d290b
+			nfct_parse_addr_from_opt(c, optarg, tmpl.exptuple,
7d290b
+						 tmpl.mask, &ad, &family);
7d290b
 			break;
7d290b
 		case '{':
7d290b
 		case '}':
7d290b
-			nfct_parse_addr_from_opt(c, tmpl.mask, NULL, &ad, &family);
7d290b
+			nfct_parse_addr_from_opt(c, optarg, tmpl.mask,
7d290b
+						 NULL, &ad, &family);
7d290b
 			break;
7d290b
 		case 'p':
7d290b
 			options |= CT_OPT_PROTO;
7d290b
@@ -2341,19 +2405,34 @@ int main(int argc, char *argv[])
7d290b
 			break;
7d290b
 		case 'n':
7d290b
 		case 'g':
7d290b
-		case 'j': {
7d290b
-			char *tmp = NULL;
7d290b
-
7d290b
+		case 'j':
7d290b
 			options |= opt2type[c];
7d290b
-
7d290b
-			tmp = get_optional_arg(argc, argv);
7d290b
-			if (tmp == NULL)
7d290b
-				continue;
7d290b
-
7d290b
-			set_family(&family, AF_INET);
7d290b
-			nat_parse(tmp, tmpl.ct, opt2type[c]);
7d290b
+			char *optional_arg = get_optional_arg(argc, argv);
7d290b
+
7d290b
+			if (optional_arg) {
7d290b
+				char *port_str = NULL;
7d290b
+				char *nat_address = NULL;
7d290b
+
7d290b
+				split_address_and_port(optional_arg,
7d290b
+						       &nat_address,
7d290b
+						       &port_str);
7d290b
+				nfct_parse_addr_from_opt(c, nat_address,
7d290b
+							 tmpl.ct, NULL,
7d290b
+							 &ad, &family);
7d290b
+				if (c == 'j') {
7d290b
+					/* Set details on both src and dst
7d290b
+					 * with any-nat
7d290b
+					 */
7d290b
+					nfct_set_nat_details('g', tmpl.ct, &ad,
7d290b
+							     port_str, family);
7d290b
+					nfct_set_nat_details('n', tmpl.ct, &ad,
7d290b
+							     port_str, family);
7d290b
+				} else {
7d290b
+					nfct_set_nat_details(c, tmpl.ct, &ad,
7d290b
+							     port_str, family);
7d290b
+				}
7d290b
+			}
7d290b
 			break;
7d290b
-		}
7d290b
 		case 'w':
7d290b
 		case '(':
7d290b
 		case ')':
7d290b
diff --git a/tests/conntrack/testsuite/00create b/tests/conntrack/testsuite/00create
7d290b
index 40e2c1952940c..afe4342e9b00d 100644
7d290b
--- a/tests/conntrack/testsuite/00create
7d290b
+++ b/tests/conntrack/testsuite/00create
7d290b
@@ -18,3 +18,9 @@
7d290b
 -I -r 2.2.2.2 -q 1.1.1.1 -p tcp --reply-port-src 11 --reply-port-dst 21 --state LISTEN -u SEEN_REPLY -t 50 ; OK
7d290b
 # delete reverse
7d290b
 -D -r 2.2.2.2 -q 1.1.1.1 -p tcp --reply-port-src 11 --reply-port-dst 21 ; OK
7d290b
+# create a v6 conntrack
7d290b
+-I -s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; OK
7d290b
+# delete v6 conntrack
7d290b
+-D -s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 -p tcp --sport 10 --dport 20 ; OK
7d290b
+# mismatched address family
7d290b
+-I -s 2001:DB8::1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; BAD
7d290b
diff --git a/tests/conntrack/testsuite/03nat b/tests/conntrack/testsuite/03nat
7d290b
index f94e8ffeb2f9f..014feb8e6e3ab 100644
7d290b
--- a/tests/conntrack/testsuite/03nat
7d290b
+++ b/tests/conntrack/testsuite/03nat
7d290b
@@ -36,5 +36,13 @@
7d290b
 -L --dst-nat 3.3.3.3:81 ; OK
7d290b
 # show
7d290b
 -L --dst-nat 1.1.1.1:80 ; OK
7d290b
+# badport
7d290b
+-L --dst-nat 1.1.1.1: ; BAD
7d290b
+# badport
7d290b
+-L --dst-nat 1.1.1.1::; BAD
7d290b
+# badport
7d290b
+-L --dst-nat 1.1.1.1:80:80; BAD
7d290b
+# badport
7d290b
+-L --dst-nat 1.1.1.1:65536; BAD
7d290b
 # delete
7d290b
 -D -s 1.1.1.1 ; OK
7d290b
diff --git a/tests/conntrack/testsuite/07nat6 b/tests/conntrack/testsuite/07nat6
7d290b
new file mode 100644
7d290b
index 0000000000000..8cecd8e9bdd88
7d290b
--- /dev/null
7d290b
+++ b/tests/conntrack/testsuite/07nat6
7d290b
@@ -0,0 +1,56 @@
7d290b
+# create dummy
7d290b
+-I -s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 --dst-nat 2001:DB8::3.3.3.3 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; OK
7d290b
+# show
7d290b
+-L --dst-nat ; OK
7d290b
+# show
7d290b
+-L --dst-nat 2001:DB8::3.3.3.3 ; OK
7d290b
+# show
7d290b
+-L --src-nat ; OK
7d290b
+# delete
7d290b
+-D -s 2001:DB8::1.1.1.1 ; OK
7d290b
+# create dummy again
7d290b
+-I -s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 --src-nat 2001:DB8::3.3.3.3 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; OK
7d290b
+# show
7d290b
+-L --src-nat ; OK
7d290b
+# show
7d290b
+-L --src-nat 2001:DB8::3.3.3.3 ; OK
7d290b
+# show
7d290b
+-L --dst-nat ; OK
7d290b
+# show any-nat
7d290b
+-L --any-nat ; OK
7d290b
+# delete
7d290b
+-D -s 2001:DB8::1.1.1.1 ; OK
7d290b
+# bad combination
7d290b
+-L --dst-nat --any-nat ; BAD
7d290b
+# bad combination
7d290b
+-L --src-nat --any-nat ; BAD
7d290b
+# bad combination
7d290b
+-L --src-nat --dst-nat --any-nat ; BAD
7d290b
+# create
7d290b
+-I -s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 --dst-nat [2001:DB8::3.3.3.3]:80 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; OK
7d290b
+# show
7d290b
+-L --dst-nat [2001:DB8::3.3.3.3]:80 ; OK
7d290b
+# show
7d290b
+-L --any-nat [2001:DB8::3.3.3.3]:80 ; OK
7d290b
+# show
7d290b
+-L --dst-nat [2001:DB8::3.3.3.3]:81 ; OK
7d290b
+# show
7d290b
+-L --dst-nat [2001:DB8::1.1.1.1]:80 ; OK
7d290b
+# noport
7d290b
+-L --dst-nat [2001:DB8::1.1.1.1]: ; BAD
7d290b
+# badport
7d290b
+-L --dst-nat [2001:DB8::1.1.1.1]:: ; BAD
7d290b
+# badport
7d290b
+-L --dst-nat [2001:DB8::1.1.1.1]:80:80 ; BAD
7d290b
+# badport
7d290b
+-L --dst-nat [2001:DB8::1.1.1.1]:65536 ; BAD
7d290b
+# delete
7d290b
+-D -s 2001:DB8::1.1.1.1 ; OK
7d290b
+# mismatched address family
7d290b
+-I -s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 --dst-nat 3.3.3.3 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; BAD
7d290b
+# mismatched address family
7d290b
+-I -s 1.1.1.1 -d 2.2.2.2 --dst-nat 2001:DB8::3.3.3.3 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; BAD
7d290b
+# create - brackets only for ports in nat
7d290b
+-I -s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 --dst-nat [2001:DB8::3.3.3.3] -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; BAD
7d290b
+# create - brackets rejected elsewhere
7d290b
+-I -s [2001:DB8::1.1.1.1] -d 2001:DB8::2.2.2.2 --dst-nat 2001:DB8::3.3.3.3 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; BAD
7d290b
-- 
7d290b
2.21.0
7d290b