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