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

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