diff mbox series

[OpenWrt-Devel,firewall3] redirect & nat: add IPv6 NAT support

Message ID 1585060146-22054-1-git-send-email-alin.nastac@technicolor.com
State Superseded
Headers show
Series [OpenWrt-Devel,firewall3] redirect & nat: add IPv6 NAT support | expand

Commit Message

Alin Nastac March 24, 2020, 2:29 p.m. UTC
From: Alin Nastac <alin.nastac@gmail.com>

1) Remove hardcoded restrictions that disable redirect support on IPv6.
2) Allow usage of IP address lists in redirect and snat uci sections.
This is needed for 2 scenarios:
  - use the interface address that matches the redirect & nat family
    when dest_ip is set to an interface name
  - set redirect destination to a pair of IPv4/v6 addresses when DNAT
    redirection is needed on both families
To be documented that, although redirect & nat IP addresses are now
technically lists, only the first address of the respective family
will be used in the correspondent ip(6)tables rule.

This new feature has been tested with the following redirect:
  config redirect
        option name 'DNS-interception'
        option src 'lan'
        option dest 'lan'
        option family 'any'
        option proto 'tcpudp'
        option src_dport '53'
        option dest_ip 'lan'
        option target 'DNAT'

It was also tested on a build that did not supported IPv6 NAT (nat
was not present in /proc/net/ip6_tables_names), fw3 -d restart did
not signaled any error.

Signed-off-by: Alin Nastac <alin.nastac@gmail.com>
---
 defaults.c  |   4 +-
 options.h   |  12 ++---
 redirects.c | 155 ++++++++++++++++++++++++++++++++++++++----------------------
 redirects.h |   2 +-
 snats.c     |  78 +++++++++++++++++++-----------
 ubus.c      |  37 ++++++++++++++-
 utils.c     |  34 +++++++++++++
 utils.h     |   6 +++
 zones.c     |  11 +++--
 9 files changed, 242 insertions(+), 97 deletions(-)
diff mbox series

Patch

diff --git a/defaults.c b/defaults.c
index 60a4c81..e5369eb 100644
--- a/defaults.c
+++ b/defaults.c
@@ -29,8 +29,8 @@  static const struct fw3_chain_spec default_chains[] = {
 	C(ANY, FILTER, CUSTOM_CHAINS, "forwarding_rule"),
 	C(ANY, FILTER, SYN_FLOOD,     "syn_flood"),
 
-	C(V4,  NAT,    CUSTOM_CHAINS, "prerouting_rule"),
-	C(V4,  NAT,    CUSTOM_CHAINS, "postrouting_rule"),
+	C(ANY, NAT,    CUSTOM_CHAINS, "prerouting_rule"),
+	C(ANY, NAT,    CUSTOM_CHAINS, "postrouting_rule"),
 
 	{ }
 };
diff --git a/options.h b/options.h
index e20c89b..d48db74 100644
--- a/options.h
+++ b/options.h
@@ -420,14 +420,14 @@  struct fw3_redirect
 
 	struct list_head proto;
 
-	struct fw3_address ip_src;
+	struct list_head ip_src;
 	struct list_head mac_src;
 	struct fw3_port port_src;
 
-	struct fw3_address ip_dest;
+	struct list_head ip_dest;
 	struct fw3_port port_dest;
 
-	struct fw3_address ip_redir;
+	struct list_head ip_redir;
 	struct fw3_port port_redir;
 
 	struct fw3_limit limit;
@@ -462,13 +462,13 @@  struct fw3_snat
 
 	struct list_head proto;
 
-	struct fw3_address ip_src;
+	struct list_head ip_src;
 	struct fw3_port port_src;
 
-	struct fw3_address ip_dest;
+	struct list_head ip_dest;
 	struct fw3_port port_dest;
 
-	struct fw3_address ip_snat;
+	struct list_head ip_snat;
 	struct fw3_port port_snat;
 
 	struct fw3_limit limit;
diff --git a/redirects.c b/redirects.c
index b928287..a557679 100644
--- a/redirects.c
+++ b/redirects.c
@@ -33,14 +33,14 @@  const struct fw3_option fw3_redirect_opts[] = {
 
 	FW3_LIST("proto",              protocol,  redirect,     proto),
 
-	FW3_OPT("src_ip",              network,   redirect,     ip_src),
+	FW3_LIST("src_ip",             network,   redirect,     ip_src),
 	FW3_LIST("src_mac",            mac,       redirect,     mac_src),
 	FW3_OPT("src_port",            port,      redirect,     port_src),
 
-	FW3_OPT("src_dip",             network,   redirect,     ip_dest),
+	FW3_LIST("src_dip",            network,   redirect,     ip_dest),
 	FW3_OPT("src_dport",           port,      redirect,     port_dest),
 
-	FW3_OPT("dest_ip",             network,   redirect,     ip_redir),
+	FW3_LIST("dest_ip",            network,   redirect,     ip_redir),
 	FW3_OPT("dest_port",           port,      redirect,     port_redir),
 
 	FW3_OPT("extra",               string,    redirect,     extra),
@@ -68,7 +68,6 @@  const struct fw3_option fw3_redirect_opts[] = {
 	{ }
 };
 
-
 static bool
 check_families(struct uci_element *e, struct fw3_redirect *r)
 {
@@ -101,19 +100,19 @@  check_families(struct uci_element *e, struct fw3_redirect *r)
 		return false;
 	}
 
-	if (r->ip_src.family && r->ip_src.family != r->family)
+	if (!fw3_check_family_addr(&r->ip_src, r->family))
 	{
 		warn_elem(e, "uses source ip with different family");
 		return false;
 	}
 
-	if (r->ip_dest.family && r->ip_dest.family != r->family)
+	if (!fw3_check_family_addr(&r->ip_dest, r->family))
 	{
 		warn_elem(e, "uses destination ip with different family");
 		return false;
 	}
 
-	if (r->ip_redir.family && r->ip_redir.family != r->family)
+	if (!fw3_check_family_addr(&r->ip_redir, r->family))
 	{
 		warn_elem(e, "uses redirect ip with different family");
 		return false;
@@ -125,11 +124,21 @@  check_families(struct uci_element *e, struct fw3_redirect *r)
 static bool
 compare_addr(struct fw3_address *a, struct fw3_address *b)
 {
-	if (a->family != FW3_FAMILY_V4 || b->family != FW3_FAMILY_V4)
+	if (a->family != b->family)
 		return false;
 
-	return ((a->address.v4.s_addr & a->mask.v4.s_addr) ==
-	        (b->address.v4.s_addr & a->mask.v4.s_addr));
+	return (a->family == FW3_FAMILY_V4 &&
+			(a->address.v4.s_addr & a->mask.v4.s_addr) ==
+			(b->address.v4.s_addr & b->mask.v4.s_addr)) ||
+	       (a->family == FW3_FAMILY_V6 &&
+			(a->address.v6.s6_addr32[0] & a->mask.v6.s6_addr32[0]) ==
+			(b->address.v6.s6_addr32[0] & b->mask.v6.s6_addr32[0]) &&
+			(a->address.v6.s6_addr32[1] & a->mask.v6.s6_addr32[1]) ==
+			(b->address.v6.s6_addr32[1] & b->mask.v6.s6_addr32[1]) &&
+			(a->address.v6.s6_addr32[2] & a->mask.v6.s6_addr32[2]) ==
+			(b->address.v6.s6_addr32[2] & b->mask.v6.s6_addr32[2]) &&
+			(a->address.v6.s6_addr32[3] & a->mask.v6.s6_addr32[3]) ==
+			(b->address.v6.s6_addr32[3] & b->mask.v6.s6_addr32[3]));
 }
 
 static bool
@@ -139,8 +148,9 @@  resolve_dest(struct uci_element *e, struct fw3_redirect *redir,
 	struct fw3_zone *zone;
 	struct fw3_address *addr;
 	struct list_head *addrs;
+	struct fw3_address *ip_redir = list_first_entry(&redir->ip_redir, struct fw3_address, list);
 
-	if (!redir->ip_redir.set)
+	if (!ip_redir->set)
 		return false;
 
 	list_for_each_entry(zone, &state->zones, list)
@@ -152,7 +162,7 @@  resolve_dest(struct uci_element *e, struct fw3_redirect *redir,
 
 		list_for_each_entry(addr, addrs, list)
 		{
-			if (!compare_addr(addr, &redir->ip_redir))
+			if (!compare_addr(addr, ip_redir))
 				continue;
 
 			strncpy(redir->dest.name, zone->name, sizeof(redir->dest.name) - 1);
@@ -175,10 +185,12 @@  static bool
 check_local(struct uci_element *e, struct fw3_redirect *redir,
             struct fw3_state *state)
 {
+	struct fw3_address *ip_redir = list_first_entry(&redir->ip_redir, struct fw3_address, list);
+
 	if (redir->target != FW3_FLAG_DNAT)
 		return false;
 
-	if (!redir->ip_redir.set)
+	if (!ip_redir->set)
 		redir->local = true;
 
 	return redir->local;
@@ -300,6 +312,7 @@  check_redirect(struct fw3_state *state, struct fw3_redirect *redir, struct uci_e
 		else
 		{
 			set(redir->_src->flags, FW3_FAMILY_V4, redir->target);
+			set(redir->_src->flags, FW3_FAMILY_V6, redir->target);
 			valid = true;
 
 			if (!check_local(e, redir, state) && !redir->dest.set &&
@@ -318,7 +331,10 @@  check_redirect(struct fw3_state *state, struct fw3_redirect *redir, struct uci_e
 			}
 
 			if (redir->helper.ptr)
+			{
 				set(redir->_src->flags, FW3_FAMILY_V4, FW3_FLAG_HELPER);
+				set(redir->_src->flags, FW3_FAMILY_V6, FW3_FLAG_HELPER);
+			}
 		}
 	}
 	else
@@ -328,7 +344,7 @@  check_redirect(struct fw3_state *state, struct fw3_redirect *redir, struct uci_e
 					"must not have destination '*' for SNAT target");
 		else if (!redir->_dest)
 			warn_section("redirect", redir, e, "has no destination specified");
-		else if (!redir->ip_dest.set)
+		else if (list_empty(&redir->ip_dest))
 			warn_section("redirect", redir, e, "has no src_dip option specified");
 		else if (!list_empty(&redir->mac_src))
 			warn_section("redirect", redir, e, "must not use 'src_mac' option for SNAT target");
@@ -337,6 +353,7 @@  check_redirect(struct fw3_state *state, struct fw3_redirect *redir, struct uci_e
 		else
 		{
 			set(redir->_dest->flags, FW3_FAMILY_V4, redir->target);
+			set(redir->_dest->flags, FW3_FAMILY_V6, redir->target);
 			valid = true;
 		}
 	}
@@ -366,7 +383,10 @@  fw3_alloc_redirect(struct fw3_state *state)
 		return NULL;
 
 	INIT_LIST_HEAD(&redir->proto);
+	INIT_LIST_HEAD(&redir->ip_src);
 	INIT_LIST_HEAD(&redir->mac_src);
+	INIT_LIST_HEAD(&redir->ip_dest);
+	INIT_LIST_HEAD(&redir->ip_redir);
 	INIT_LIST_HEAD(&redir->reflection_zones);
 
 	redir->enabled = true;
@@ -477,13 +497,26 @@  static void
 set_snat_dnat(struct fw3_ipt_rule *r, enum fw3_flag target,
               struct fw3_address *addr, struct fw3_port *port)
 {
-	char buf[sizeof("255.255.255.255:65535-65535\0")];
+	char buf[sizeof("[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:65535-65535\0")];
 
 	buf[0] = '\0';
 
 	if (addr && addr->set)
 	{
-		inet_ntop(AF_INET, &addr->address.v4, buf, sizeof(buf));
+		if (addr->family == FW3_FAMILY_V4)
+			inet_ntop(AF_INET, &addr->address.v4, buf, sizeof(buf));
+		else
+		{
+			int len;
+
+			buf[0] = '[';
+			if (inet_ntop(AF_INET6, &addr->address.v6, buf + 1, sizeof(buf) - 2))
+				len = strlen(buf);
+			else
+				len = 1;
+			buf[len++]= ']';
+			buf[len] = '\0';
+		}
 	}
 
 	if (port && port->set)
@@ -508,17 +541,6 @@  set_snat_dnat(struct fw3_ipt_rule *r, enum fw3_flag target,
 }
 
 static void
-set_target_nat(struct fw3_ipt_rule *r, struct fw3_redirect *redir)
-{
-	if (redir->local)
-		set_redirect(r, &redir->port_redir);
-	else if (redir->target == FW3_FLAG_DNAT)
-		set_snat_dnat(r, redir->target, &redir->ip_redir, &redir->port_redir);
-	else
-		set_snat_dnat(r, redir->target, &redir->ip_dest, &redir->port_dest);
-}
-
-static void
 set_comment(struct fw3_ipt_rule *r, const char *name, int num, const char *suffix)
 {
 	if (name)
@@ -543,32 +565,41 @@  print_redirect(struct fw3_ipt_handle *h, struct fw3_state *state,
                struct fw3_protocol *proto, struct fw3_mac *mac)
 {
 	struct fw3_ipt_rule *r;
-	struct fw3_address *src, *dst;
-	struct fw3_port *spt, *dpt;
+	struct fw3_address *src, *dst, *rdr;
+	struct fw3_port *spt, *dpt, *rpt;
 
 	switch (h->table)
 	{
 	case FW3_TABLE_NAT:
-		src = &redir->ip_src;
-		dst = &redir->ip_dest;
-		spt = &redir->port_src;
-		dpt = &redir->port_dest;
-
-		if (redir->target == FW3_FLAG_SNAT)
+		src = fw3_first_family_addr(&redir->ip_src, h->family);
+		dst = fw3_first_family_addr(&redir->ip_dest, h->family);
+		rdr = fw3_first_family_addr(&redir->ip_redir, h->family);
+		if ((src == NULL && !list_empty(&redir->ip_src)) ||
+		    (dst == NULL && !list_empty(&redir->ip_dest)) ||
+		    (rdr == NULL && !list_empty(&redir->ip_redir)))
 		{
-			dst = &redir->ip_redir;
-			dpt = &redir->port_redir;
+			info("     ! Skipping due to different family of ip address");
+			return;
 		}
 
-		r = fw3_ipt_rule_create(h, proto, NULL, NULL, src, dst);
-		fw3_ipt_rule_sport_dport(r, spt, dpt);
+		spt = &redir->port_src;
+		dpt = &redir->port_dest;
+		rpt = &redir->port_redir;
+
+		r = fw3_ipt_rule_create(h, proto, NULL, NULL, src, redir->target == FW3_FLAG_SNAT ? rdr : dst);
+		fw3_ipt_rule_sport_dport(r, spt, redir->target == FW3_FLAG_SNAT ? rpt : dpt);
 		fw3_ipt_rule_mac(r, mac);
 		fw3_ipt_rule_ipset(r, &redir->ipset);
 		fw3_ipt_rule_helper(r, &redir->helper);
 		fw3_ipt_rule_limit(r, &redir->limit);
 		fw3_ipt_rule_time(r, &redir->time);
 		fw3_ipt_rule_mark(r, &redir->mark);
-		set_target_nat(r, redir);
+		if (redir->local)
+			set_redirect(r, rpt);
+		else if (redir->target == FW3_FLAG_DNAT)
+			set_snat_dnat(r, redir->target, rdr, rpt);
+		else
+			set_snat_dnat(r, redir->target, dst, dpt);
 		fw3_ipt_rule_extra(r, redir->extra);
 		set_comment(r, redir->name, num, NULL);
 		append_chain_nat(r, redir);
@@ -577,6 +608,15 @@  print_redirect(struct fw3_ipt_handle *h, struct fw3_state *state,
 	case FW3_TABLE_RAW:
 		if (redir->target == FW3_FLAG_DNAT && redir->helper.ptr)
 		{
+			src = fw3_first_family_addr(&redir->ip_src, h->family);
+			rdr = fw3_first_family_addr(&redir->ip_redir, h->family);
+			if ((src == NULL && !list_empty(&redir->ip_src)) ||
+			    (rdr == NULL && !list_empty(&redir->ip_redir)))
+			{
+				info("     ! Skipping due to different family of ip address");
+				return;
+			}
+
 			if (!fw3_cthelper_check_proto(redir->helper.ptr, proto))
 			{
 				info("     ! Skipping protocol %s since helper '%s' does not support it",
@@ -588,7 +628,7 @@  print_redirect(struct fw3_ipt_handle *h, struct fw3_state *state,
 				info("     - Auto-selected conntrack helper '%s' based on proto/port",
 				     redir->helper.ptr->name);
 
-			r = fw3_ipt_rule_create(h, proto, NULL, NULL, &redir->ip_src, &redir->ip_redir);
+			r = fw3_ipt_rule_create(h, proto, NULL, NULL, src, rdr);
 			fw3_ipt_rule_sport_dport(r, &redir->port_src, &redir->port_redir);
 			fw3_ipt_rule_mac(r, mac);
 			fw3_ipt_rule_ipset(r, &redir->ipset);
@@ -616,19 +656,26 @@  print_reflection(struct fw3_ipt_handle *h, struct fw3_state *state,
                  struct fw3_address *ia, struct fw3_address *ea, struct fw3_device *rz)
 {
 	struct fw3_ipt_rule *r;
+	struct fw3_address *rdr;
 
 	switch (h->table)
 	{
 	case FW3_TABLE_NAT:
+		rdr = fw3_first_family_addr(&redir->ip_redir, h->family);
+		if (rdr == NULL && !list_empty(&redir->ip_redir))
+		{
+			info("     ! Skipping reflection due to different family of dest_ip");
+			return;
+		}
 		r = fw3_ipt_rule_create(h, proto, NULL, NULL, ia, ea);
 		fw3_ipt_rule_sport_dport(r, NULL, &redir->port_dest);
 		fw3_ipt_rule_limit(r, &redir->limit);
 		fw3_ipt_rule_time(r, &redir->time);
 		set_comment(r, redir->name, num, "reflection");
-		set_snat_dnat(r, FW3_FLAG_DNAT, &redir->ip_redir, &redir->port_redir);
+		set_snat_dnat(r, FW3_FLAG_DNAT, rdr, &redir->port_redir);
 		fw3_ipt_rule_replace(r, "zone_%s_prerouting", rz->name);
 
-		r = fw3_ipt_rule_create(h, proto, NULL, NULL, ia, &redir->ip_redir);
+		r = fw3_ipt_rule_create(h, proto, NULL, NULL, ia, rdr);
 		fw3_ipt_rule_sport_dport(r, NULL, &redir->port_redir);
 		fw3_ipt_rule_limit(r, &redir->limit);
 		fw3_ipt_rule_time(r, &redir->time);
@@ -653,6 +700,9 @@  expand_redirect(struct fw3_ipt_handle *handle, struct fw3_state *state,
 	struct fw3_device *reflection_zone;
 	struct fw3_zone *zone;
 
+	if (!fw3_is_family(redir, handle->family))
+		return;
+
 	if (redir->name)
 		info("   * Redirect '%s'", redir->name);
 	else
@@ -665,15 +715,11 @@  expand_redirect(struct fw3_ipt_handle *handle, struct fw3_state *state,
 		return;
 	}
 
-	if (!fw3_is_family(&redir->ip_src, handle->family) ||
-	    !fw3_is_family(&redir->ip_dest, handle->family) ||
-		!fw3_is_family(&redir->ip_redir, handle->family))
+	if (!fw3_check_family_addr(&redir->ip_src, handle->family) ||
+	    !fw3_check_family_addr(&redir->ip_dest, handle->family) ||
+	    !fw3_check_family_addr(&redir->ip_redir, handle->family))
 	{
-		if (!redir->ip_src.resolved ||
-		    !redir->ip_dest.resolved ||
-		    !redir->ip_redir.resolved)
-			info("     ! Skipping due to different family of ip address");
-
+		info("     ! Skipping due to different family of ip address");
 		return;
 	}
 
@@ -701,13 +747,13 @@  expand_redirect(struct fw3_ipt_handle *handle, struct fw3_state *state,
 		print_redirect(handle, state, redir, num, proto, mac);
 
 	/* reflection rules */
-	if (redir->target != FW3_FLAG_DNAT || !redir->reflection || redir->local)
+	if (redir->target != FW3_FLAG_DNAT || !redir->reflection || redir->local || handle->family != FW3_FAMILY_V4)
 		return;
 
 	if (!redir->_dest || !redir->_src->masq)
 		return;
 
-	ext_addrs = fw3_resolve_zone_addresses(redir->_src, &redir->ip_dest);
+	ext_addrs = fw3_resolve_zone_addresses(redir->_src, fw3_first_family_addr(&redir->ip_dest, handle->family));
 
 	if (!ext_addrs)
 		goto out;
@@ -769,9 +815,6 @@  fw3_print_redirects(struct fw3_ipt_handle *handle, struct fw3_state *state)
 	int num = 0;
 	struct fw3_redirect *redir;
 
-	if (handle->family == FW3_FAMILY_V6)
-		return;
-
 	if (handle->table != FW3_TABLE_FILTER &&
 	    handle->table != FW3_TABLE_NAT &&
 	    handle->table != FW3_TABLE_RAW)
diff --git a/redirects.h b/redirects.h
index 0d46bd2..3e4db7b 100644
--- a/redirects.h
+++ b/redirects.h
@@ -23,7 +23,7 @@ 
 #include "zones.h"
 #include "ipsets.h"
 #include "helpers.h"
-#include "ubus.h"
+#include "utils.h"
 #include "iptables.h"
 
 extern const struct fw3_option fw3_redirect_opts[];
diff --git a/snats.c b/snats.c
index 1d78f93..00ac82a 100644
--- a/snats.c
+++ b/snats.c
@@ -32,13 +32,13 @@  const struct fw3_option fw3_snat_opts[] = {
 
 	FW3_LIST("proto",              protocol,  snat,     proto),
 
-	FW3_OPT("src_ip",              network,   snat,     ip_src),
+	FW3_LIST("src_ip",             network,   snat,     ip_src),
 	FW3_OPT("src_port",            port,      snat,     port_src),
 
-	FW3_OPT("snat_ip",             network,   snat,     ip_snat),
+	FW3_LIST("snat_ip",            network,   snat,     ip_snat),
 	FW3_OPT("snat_port",           port,      snat,     port_snat),
 
-	FW3_OPT("dest_ip",             network,   snat,     ip_dest),
+	FW3_LIST("dest_ip",            network,   snat,     ip_dest),
 	FW3_OPT("dest_port",           port,      snat,     port_dest),
 
 	FW3_OPT("extra",               string,    snat,     extra),
@@ -83,19 +83,19 @@  check_families(struct uci_element *e, struct fw3_snat *r)
 		return false;
 	}
 
-	if (r->ip_src.family && r->ip_src.family != r->family)
+	if (!fw3_check_family_addr(&r->ip_src, r->family))
 	{
 		warn_section("nat", r, e, "uses source ip with different family");
 		return false;
 	}
 
-	if (r->ip_dest.family && r->ip_dest.family != r->family)
+	if (!fw3_check_family_addr(&r->ip_dest, r->family))
 	{
 		warn_section("nat", r, e, "uses destination ip with different family");
 		return false;
 	}
 
-	if (r->ip_snat.family && r->ip_snat.family != r->family)
+	if (!fw3_check_family_addr(&r->ip_snat, r->family))
 	{
 		warn_section("nat", r, e, "uses snat ip with different family");
 		return false;
@@ -112,6 +112,9 @@  alloc_snat(struct fw3_state *state)
 
 	if (snat) {
 		INIT_LIST_HEAD(&snat->proto);
+		INIT_LIST_HEAD(&snat->ip_src);
+		INIT_LIST_HEAD(&snat->ip_dest);
+		INIT_LIST_HEAD(&snat->ip_snat);
 		list_add_tail(&snat->list, &state->snats);
 		snat->enabled = true;
 	}
@@ -164,12 +167,12 @@  check_snat(struct fw3_state *state, struct fw3_snat *snat, struct uci_element *e
 	}
 
 	if (snat->target == FW3_FLAG_SNAT &&
-			!snat->ip_snat.set && !snat->port_snat.set)
+			list_empty(&snat->ip_snat) && !snat->port_snat.set)
 	{
 		warn_section("nat", snat, e, "needs either 'snat_ip' or 'snat_port' for SNAT");
 		return false;
 	}
-	else if (snat->target != FW3_FLAG_SNAT && snat->ip_snat.set)
+	else if (snat->target != FW3_FLAG_SNAT && !list_empty(&snat->ip_snat))
 	{
 		warn_section("nat", snat, e, "must not use 'snat_ip' for non-SNAT");
 		return false;
@@ -187,7 +190,10 @@  check_snat(struct fw3_state *state, struct fw3_snat *snat, struct uci_element *e
 	}
 
 	if (snat->_src)
+	{
 		set(snat->_src->flags, FW3_FAMILY_V4, FW3_FLAG_SNAT);
+		set(snat->_src->flags, FW3_FAMILY_V6, FW3_FLAG_SNAT);
+	}
 
 	return true;
 }
@@ -262,18 +268,31 @@  append_chain(struct fw3_ipt_rule *r, struct fw3_snat *snat)
 }
 
 static void
-set_target(struct fw3_ipt_rule *r, struct fw3_snat *snat,
+set_target(struct fw3_ipt_rule *r, struct fw3_snat *snat, struct fw3_address *snat_addr,
            struct fw3_protocol *proto)
 {
-	char buf[sizeof("255.255.255.255:65535-65535\0")];
+	char buf[sizeof("[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:65535-65535\0")];
 
 	if (snat->target == FW3_FLAG_SNAT)
 	{
 		buf[0] = '\0';
 
-		if (snat->ip_snat.set)
+		if (snat_addr && snat_addr->set)
 		{
-			inet_ntop(AF_INET, &snat->ip_snat.address.v4, buf, sizeof(buf));
+			if (snat_addr->family == FW3_FAMILY_V4)
+				inet_ntop(AF_INET, &snat_addr->address.v4, buf, sizeof(buf));
+			else
+			{
+				int len;
+
+				buf[0] = '[';
+				if (inet_ntop(AF_INET6, &snat_addr->address.v6, buf + 1, sizeof(buf) - 2))
+					len = strlen(buf);
+				else
+					len = 1;
+				buf[len++]= ']';
+				buf[len] = '\0';
+			}
 		}
 
 		if (snat->port_snat.set && proto && !proto->any &&
@@ -323,14 +342,23 @@  print_snat(struct fw3_ipt_handle *h, struct fw3_state *state,
            struct fw3_snat *snat, int num, struct fw3_protocol *proto)
 {
 	struct fw3_ipt_rule *r;
-	struct fw3_address *src, *dst;
+	struct fw3_address *src, *dst, *snt;
 	struct fw3_port *spt, *dpt;
 
 	switch (h->table)
 	{
 	case FW3_TABLE_NAT:
-		src = &snat->ip_src;
-		dst = &snat->ip_dest;
+		src = fw3_first_family_addr(&snat->ip_src, h->family);
+		dst = fw3_first_family_addr(&snat->ip_dest, h->family);
+		snt = fw3_first_family_addr(&snat->ip_snat, h->family);
+		if ((src == NULL && !list_empty(&snat->ip_src)) ||
+		    (dst == NULL && !list_empty(&snat->ip_dest)) ||
+		    (snt == NULL && !list_empty(&snat->ip_snat)))
+		{
+			info("     ! Skipping due to different family of ip address");
+			return;
+		}
+
 		spt = &snat->port_src;
 		dpt = &snat->port_dest;
 
@@ -341,7 +369,7 @@  print_snat(struct fw3_ipt_handle *h, struct fw3_state *state,
 		fw3_ipt_rule_limit(r, &snat->limit);
 		fw3_ipt_rule_time(r, &snat->time);
 		fw3_ipt_rule_mark(r, &snat->mark);
-		set_target(r, snat, proto);
+		set_target(r, snat, snt, proto);
 		fw3_ipt_rule_extra(r, snat->extra);
 		set_comment(r, snat->name, num);
 		append_chain(r, snat);
@@ -358,6 +386,9 @@  expand_snat(struct fw3_ipt_handle *handle, struct fw3_state *state,
 {
 	struct fw3_protocol *proto;
 
+	if (!fw3_is_family(snat, handle->family))
+		return;
+
 	if (snat->name)
 		info("   * NAT '%s'", snat->name);
 	else
@@ -369,15 +400,11 @@  expand_snat(struct fw3_ipt_handle *handle, struct fw3_state *state,
 		return;
 	}
 
-	if (!fw3_is_family(&snat->ip_src, handle->family) ||
-	    !fw3_is_family(&snat->ip_dest, handle->family) ||
-		!fw3_is_family(&snat->ip_snat, handle->family))
+	if (!fw3_check_family_addr(&snat->ip_src, handle->family) ||
+	    !fw3_check_family_addr(&snat->ip_dest, handle->family) ||
+	    !fw3_check_family_addr(&snat->ip_snat, handle->family))
 	{
-		if (!snat->ip_src.resolved ||
-		    !snat->ip_dest.resolved ||
-		    !snat->ip_snat.resolved)
-			info("     ! Skipping due to different family of ip address");
-
+		info("     ! Skipping due to different family of ip address");
 		return;
 	}
 
@@ -410,9 +437,6 @@  fw3_print_snats(struct fw3_ipt_handle *handle, struct fw3_state *state)
 	int num = 0;
 	struct fw3_snat *snat;
 
-	if (handle->family == FW3_FAMILY_V6)
-		return;
-
 	if (handle->table != FW3_TABLE_NAT)
 		return;
 
diff --git a/ubus.c b/ubus.c
index cf5c8b1..0e47f00 100644
--- a/ubus.c
+++ b/ubus.c
@@ -133,6 +133,41 @@  parse_subnets(struct list_head *head, enum fw3_family family,
 	return n;
 }
 
+static int
+parse_prefix_assignments(struct list_head *head, enum fw3_family family,
+              struct blob_attr *list)
+{
+	struct blob_attr *pfx, *cur;
+	struct fw3_address *addr;
+	int rem, pfxlen, n = 0;
+
+	if (!list)
+		return 0;
+
+	rem = blobmsg_data_len(list);
+
+	__blob_for_each_attr(pfx, blobmsg_data(list), rem)
+	{
+		pfxlen = blobmsg_data_len(pfx);
+
+		__blob_for_each_attr(cur, blobmsg_data(pfx), pfxlen)
+		{
+			if (!strcmp(blobmsg_name(cur), "local-address"))
+			{
+				addr = parse_subnet(family, blobmsg_data(cur), blobmsg_data_len(cur));
+
+				if (addr)
+				{
+					list_add_tail(&addr->list, head);
+					n++;
+				}
+			}
+		}
+	}
+
+	return n;
+}
+
 struct fw3_device *
 fw3_ubus_device(const char *net)
 {
@@ -218,7 +253,7 @@  fw3_ubus_address(struct list_head *list, const char *net)
 
 		n += parse_subnets(list, FW3_FAMILY_V4, tb[ADDR_IPV4]);
 		n += parse_subnets(list, FW3_FAMILY_V6, tb[ADDR_IPV6]);
-		n += parse_subnets(list, FW3_FAMILY_V6, tb[ADDR_IPV6_PREFIX]);
+		n += parse_prefix_assignments(list, FW3_FAMILY_V6, tb[ADDR_IPV6_PREFIX]);
 	}
 
 	return n;
diff --git a/utils.c b/utils.c
index da65632..f1f9282 100644
--- a/utils.c
+++ b/utils.c
@@ -1047,3 +1047,37 @@  fw3_check_loopback_addr(struct fw3_address *addr)
 
 	return false;
 }
+
+struct fw3_address *
+fw3_first_family_addr(struct list_head *addrs, enum fw3_family family)
+{
+	struct fw3_address *addr;
+
+	if (family == FW3_FAMILY_ANY)
+		return NULL;
+
+	list_for_each_entry(addr, addrs, list)
+	{
+		if (addr->family == family)
+			return addr;
+	}
+
+	return NULL;
+}
+
+bool
+fw3_check_family_addr(struct list_head *addrs, enum fw3_family family)
+{
+	struct fw3_address *addr;
+
+	if (family == FW3_FAMILY_ANY || list_empty(addrs))
+		return true;
+
+	list_for_each_entry(addr, addrs, list)
+	{
+		if (addr->family == family)
+			return true;
+	}
+
+	return false;
+}
diff --git a/utils.h b/utils.h
index 254bea4..4ffcbe6 100644
--- a/utils.h
+++ b/utils.h
@@ -44,6 +44,7 @@ 
 
 extern bool fw3_pr_debug;
 
+enum fw3_family;
 struct fw3_address;
 
 void warn_elem(struct uci_element *e, const char *format, ...)
@@ -134,4 +135,9 @@  const char * fw3_protoname(void *proto);
 bool fw3_check_loopback_dev(const char *name);
 
 bool fw3_check_loopback_addr(struct fw3_address *addr);
+
+struct fw3_address * fw3_first_family_addr(struct list_head *addrs, enum fw3_family family);
+
+bool fw3_check_family_addr(struct list_head *addrs, enum fw3_family family);
+
 #endif
diff --git a/zones.c b/zones.c
index 01fb706..4e13c1b 100644
--- a/zones.c
+++ b/zones.c
@@ -37,8 +37,8 @@  static const struct fw3_chain_spec zone_chains[] = {
 	C(ANY, FILTER, REJECT,        "zone_%s_dest_REJECT"),
 	C(ANY, FILTER, DROP,          "zone_%s_dest_DROP"),
 
-	C(V4,  NAT,    SNAT,          "zone_%s_postrouting"),
-	C(V4,  NAT,    DNAT,          "zone_%s_prerouting"),
+	C(ANY, NAT,    SNAT,          "zone_%s_postrouting"),
+	C(ANY, NAT,    DNAT,          "zone_%s_prerouting"),
 
 	C(ANY, RAW,    HELPER,        "zone_%s_helper"),
 	C(ANY, RAW,    NOTRACK,       "zone_%s_notrack"),
@@ -47,8 +47,8 @@  static const struct fw3_chain_spec zone_chains[] = {
 	C(ANY, FILTER, CUSTOM_CHAINS, "output_%s_rule"),
 	C(ANY, FILTER, CUSTOM_CHAINS, "forwarding_%s_rule"),
 
-	C(V4,  NAT,    CUSTOM_CHAINS, "prerouting_%s_rule"),
-	C(V4,  NAT,    CUSTOM_CHAINS, "postrouting_%s_rule"),
+	C(ANY, NAT,    CUSTOM_CHAINS, "prerouting_%s_rule"),
+	C(ANY, NAT,    CUSTOM_CHAINS, "postrouting_%s_rule"),
 
 	{ }
 };
@@ -310,6 +310,9 @@  fw3_load_zones(struct fw3_state *state, struct uci_package *p)
 		{
 			fw3_setbit(zone->flags[0], FW3_FLAG_SNAT);
 			fw3_setbit(zone->flags[0], FW3_FLAG_DNAT);
+
+			fw3_setbit(zone->flags[1], FW3_FLAG_SNAT);
+			fw3_setbit(zone->flags[1], FW3_FLAG_DNAT);
 		}
 
 		resolve_cthelpers(state, e, zone);