xtables: add IPv6 support

Summary of changes to add IPv6 support to the xtables utility:

* modify all commands (add, delete, replace, check and listing) to
  support IPv6 addresses.

And for the internal nft library:

* add family to struct nft_handle and modify all caller to use this
  family instead of the hardcoded AF_INET.
* move code that we can re-use for IPv4 and IPv6 into helper functions.
* add IPv6 rule printing support.
* add support to parse IPv6 address.

Pablo added several improvements to this patch:

* added basic xtables-save and xtables-restore support (so it defaults
  to IPv4)
* fixed a couple of bugs found while testing
* added reference when -f is used to point to -m frag (until we can make
  this consistent with IPv4).

Note that we use one single xtables binary utility for IPv4 and IPv6.

Signed-off-by: Tomasz Bursztyka <tomasz.bursztyka@linux.intel.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
diff --git a/iptables/nft.c b/iptables/nft.c
index 46c8c94..43b13de 100644
--- a/iptables/nft.c
+++ b/iptables/nft.c
@@ -34,7 +34,9 @@
 #include <string.h>
 
 #include <linux/netfilter/x_tables.h>
-#include <linux/netfilter_ipv4/ip_tables.h>	/* FIXME: only IPV4 by now */
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <netinet/ip6.h>
 
 #include <linux/netlink.h>
 #include <linux/netfilter/nfnetlink.h>
@@ -248,7 +250,7 @@
 					NFT_TABLE_F_DORMANT);
 	}
 
-	nlh = nft_table_nlmsg_build_hdr(buf, NFT_MSG_NEWTABLE, AF_INET,
+	nlh = nft_table_nlmsg_build_hdr(buf, NFT_MSG_NEWTABLE, h->family,
 					NLM_F_ACK|NLM_F_EXCL, h->seq);
 	nft_table_nlmsg_build_payload(nlh, t);
 	nft_table_free(t);
@@ -294,7 +296,7 @@
 		return;
 
 	/* NLM_F_CREATE requests module autoloading */
-	nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_NEWCHAIN, AF_INET,
+	nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_NEWCHAIN, h->family,
 					NLM_F_ACK|NLM_F_EXCL|NLM_F_CREATE,
 					h->seq);
 	nft_chain_nlmsg_build_payload(nlh, c);
@@ -410,7 +412,7 @@
 	char buf[MNL_SOCKET_BUFFER_SIZE];
 	struct nlmsghdr *nlh;
 
-	nlh = nft_table_nlmsg_build_hdr(buf, NFT_MSG_NEWTABLE, AF_INET,
+	nlh = nft_table_nlmsg_build_hdr(buf, NFT_MSG_NEWTABLE, h->family,
 					NLM_F_ACK|NLM_F_EXCL, h->seq);
 	nft_table_nlmsg_build_payload(nlh, t);
 
@@ -422,7 +424,7 @@
 	char buf[MNL_SOCKET_BUFFER_SIZE];
 	struct nlmsghdr *nlh;
 
-	nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_NEWCHAIN, AF_INET,
+	nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_NEWCHAIN, h->family,
 					NLM_F_ACK|NLM_F_EXCL, h->seq);
 	nft_chain_nlmsg_build_payload(nlh, c);
 
@@ -464,7 +466,7 @@
 	nft_table_attr_set(t, NFT_TABLE_ATTR_NAME, (char *)table);
 	nft_table_attr_set_u32(t, NFT_TABLE_ATTR_FLAGS, 0);
 
-	nlh = nft_table_nlmsg_build_hdr(buf, NFT_MSG_NEWTABLE, AF_INET,
+	nlh = nft_table_nlmsg_build_hdr(buf, NFT_MSG_NEWTABLE, h->family,
 					NLM_F_ACK, h->seq);
 	nft_table_nlmsg_build_payload(nlh, t);
 	nft_table_free(t);
@@ -525,7 +527,7 @@
 					counters->pcnt);
 	}
 
-	nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_NEWCHAIN, AF_INET,
+	nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_NEWCHAIN, h->family,
 					NLM_F_ACK, h->seq);
 	nft_chain_nlmsg_build_payload(nlh, c);
 
@@ -741,6 +743,78 @@
 	nft_rule_add_expr(r, expr);
 }
 
+static void add_iniface(struct nft_rule *r, char *iface, int invflags)
+{
+	int iface_len;
+	uint32_t op;
+
+	iface_len = strlen(iface);
+
+	if (invflags & IPT_INV_VIA_IN)
+		op = NFT_CMP_NEQ;
+	else
+		op = NFT_CMP_EQ;
+
+	if (iface[iface_len - 1] == '+') {
+		add_meta(r, NFT_META_IIFNAME);
+		add_cmp_ptr(r, op, iface, iface_len - 1);
+	} else {
+		add_meta(r, NFT_META_IIF);
+		add_cmp_u32(r, if_nametoindex(iface), op);
+	}
+}
+
+static void add_outiface(struct nft_rule *r, char *iface, int invflags)
+{
+	int iface_len;
+	uint32_t op;
+
+	iface_len = strlen(iface);
+
+	if (invflags & IPT_INV_VIA_OUT)
+		op = NFT_CMP_NEQ;
+	else
+		op = NFT_CMP_EQ;
+
+	if (iface[iface_len - 1] == '+') {
+		add_meta(r, NFT_META_OIFNAME);
+		add_cmp_ptr(r, op, iface, iface_len - 1);
+	} else {
+		add_meta(r, NFT_META_OIF);
+		add_cmp_u32(r, if_nametoindex(iface), op);
+	}
+}
+
+static void add_addr(struct nft_rule *r, int offset,
+			void *data, size_t len, int invflags)
+{
+	uint32_t op;
+
+	add_payload(r, offset, len);
+
+	if (invflags & IPT_INV_SRCIP || invflags & IPT_INV_DSTIP)
+		op = NFT_CMP_NEQ;
+	else
+		op = NFT_CMP_EQ;
+
+	add_cmp_ptr(r, op, data, len);
+}
+
+static void add_proto(struct nft_rule *r, int offset, size_t len,
+		      uint32_t proto, int invflags)
+{
+	uint32_t op;
+
+	add_payload(r, offset, len);
+
+	if (invflags & XT_INV_PROTO)
+		op = NFT_CMP_NEQ;
+	else
+		op = NFT_CMP_EQ;
+
+	add_cmp_u32(r, proto, op);
+}
+
 int
 nft_rule_add(struct nft_handle *h, const char *chain, const char *table,
 	     struct iptables_command_state *cs,
@@ -753,6 +827,7 @@
 	int ret = 1;
 	uint32_t op;
 	int flags = append ? NLM_F_APPEND : 0;
+	int ip_flags = 0;
 
 	/* If built-in chains don't exist for this table, create them */
 	nft_chain_builtin_init(h, table, chain, NF_ACCEPT);
@@ -768,77 +843,72 @@
 	nft_rule_attr_set(r, NFT_RULE_ATTR_TABLE, (char *)table);
 	nft_rule_attr_set(r, NFT_RULE_ATTR_CHAIN, (char *)chain);
 
-	if (cs->fw.ip.iniface[0] != '\0') {
-		int iface_len = strlen(cs->fw.ip.iniface);
+	switch (h->family) {
+	case AF_INET:
+		if (cs->fw.ip.iniface[0] != '\0')
+			add_iniface(r, cs->fw.ip.iniface, cs->fw.ip.invflags);
 
-		if (cs->fw.ip.invflags & IPT_INV_VIA_IN)
-			op = NFT_CMP_NEQ;
-		else
-			op = NFT_CMP_EQ;
+		if (cs->fw.ip.outiface[0] != '\0')
+			add_outiface(r, cs->fw.ip.outiface,
+				     cs->fw.ip.invflags);
 
-		if (cs->fw.ip.iniface[iface_len - 1] == '+') {
-			add_meta(r, NFT_META_IIFNAME);
-			add_cmp_ptr(r, op, cs->fw.ip.iniface, iface_len - 1);
-		} else {
-			add_meta(r, NFT_META_IIF);
-			add_cmp_u32(r, if_nametoindex(cs->fw.ip.iniface), op);
+		if (cs->fw.ip.src.s_addr != 0)
+			add_addr(r, offsetof(struct iphdr, saddr),
+				 &cs->fw.ip.src.s_addr, 4,
+				 cs->fw.ip.invflags);
+
+		if (cs->fw.ip.dst.s_addr != 0)
+			add_addr(r, offsetof(struct iphdr, daddr),
+				 &cs->fw.ip.dst.s_addr, 4,
+				 cs->fw.ip.invflags);
+
+		if (cs->fw.ip.proto != 0)
+			add_proto(r, offsetof(struct iphdr, protocol), 1,
+				  cs->fw.ip.proto, cs->fw.ip.invflags);
+
+		if (cs->fw.ip.flags & IPT_F_FRAG) {
+			add_payload(r, offsetof(struct iphdr, frag_off), 2);
+			/* get the 13 bits that contain the fragment offset */
+			add_bitwise_u16(r, 0x1fff, !0x1fff);
+
+			/* if offset is non-zero, this is a fragment */
+			if (cs->fw.ip.invflags & IPT_INV_FRAG)
+				op = NFT_CMP_EQ;
+			else
+				op = NFT_CMP_NEQ;
+
+			add_cmp_u16(r, 0, op);
 		}
-	}
-	if (cs->fw.ip.outiface[0] != '\0') {
-		int iface_len = strlen(cs->fw.ip.outiface);
 
-		if (cs->fw.ip.invflags & IPT_INV_VIA_OUT)
-			op = NFT_CMP_NEQ;
-		else
-			op = NFT_CMP_EQ;
+		ip_flags = cs->fw.ip.flags;
 
-		if (cs->fw.ip.outiface[iface_len - 1] == '+') {
-			add_meta(r, NFT_META_OIFNAME);
-			add_cmp_ptr(r, op, cs->fw.ip.outiface, iface_len - 1);
-		} else {
-			add_meta(r, NFT_META_OIF);
-			add_cmp_u32(r, if_nametoindex(cs->fw.ip.outiface), op);
-		}
-	}
-	if (cs->fw.ip.src.s_addr != 0) {
-		add_payload(r, offsetof(struct iphdr, saddr), 4);
-		if (cs->fw.ip.invflags & IPT_INV_SRCIP)
-			op = NFT_CMP_NEQ;
-		else
-			op = NFT_CMP_EQ;
+		break;
+	case AF_INET6:
+		if (cs->fw6.ipv6.iniface[0] != '\0')
+			add_iniface(r, cs->fw6.ipv6.iniface,
+				    cs->fw6.ipv6.invflags);
 
-		add_cmp_u32(r, cs->fw.ip.src.s_addr, op);
-	}
-	if (cs->fw.ip.dst.s_addr != 0) {
-		add_payload(r, offsetof(struct iphdr, daddr), 4);
-		if (cs->fw.ip.invflags & IPT_INV_DSTIP)
-			op = NFT_CMP_NEQ;
-		else
-			op = NFT_CMP_EQ;
+		if (cs->fw6.ipv6.outiface[0] != '\0')
+			add_outiface(r, cs->fw6.ipv6.outiface,
+				    cs->fw6.ipv6.invflags);
 
-		add_cmp_u32(r, cs->fw.ip.dst.s_addr, op);
-	}
-	if (cs->fw.ip.proto != 0) {
-		add_payload(r, offsetof(struct iphdr, protocol), 1);
-		if (cs->fw.ip.invflags & XT_INV_PROTO)
-			op = NFT_CMP_NEQ;
-		else
-			op = NFT_CMP_EQ;
+		if (!IN6_IS_ADDR_UNSPECIFIED(&cs->fw6.ipv6.src))
+			add_addr(r, offsetof(struct ip6_hdr, ip6_src),
+				 &cs->fw6.ipv6.src, 16,
+				 cs->fw6.ipv6.invflags);
 
-		add_cmp_u32(r, cs->fw.ip.proto, op);
-	}
-	if (cs->fw.ip.flags & IPT_F_FRAG) {
-		add_payload(r, offsetof(struct iphdr, frag_off), 2);
-		/* get the 13 bits that contain the fragment offset */
-		add_bitwise_u16(r, 0x1fff, !0x1fff);
+		if (!IN6_IS_ADDR_UNSPECIFIED(&cs->fw6.ipv6.dst))
+			add_addr(r, offsetof(struct ip6_hdr, ip6_dst),
+				 &cs->fw6.ipv6.dst, 16,
+				 cs->fw6.ipv6.invflags);
 
-		/* if offset is non-zero, this is a fragment */
-		if (cs->fw.ip.invflags & IPT_INV_FRAG)
-			op = NFT_CMP_EQ;
-		else
-			op = NFT_CMP_NEQ;
+		if (cs->fw6.ipv6.proto != 0)
+			add_proto(r, offsetof(struct ip6_hdr, ip6_nxt), 1,
+				  cs->fw6.ipv6.proto, cs->fw6.ipv6.invflags);
 
-		add_cmp_u16(r, 0, op);
+		ip_flags = cs->fw6.ipv6.flags;
+
+		break;
 	}
 
 	for (matchp = cs->matches; matchp; matchp = matchp->next)
@@ -862,7 +932,7 @@
 			add_target(r, cs->target->t);
 	} else if (strlen(cs->jumpto) > 0) {
 		/* Not standard, then it's a go / jump to chain */
-		if (cs->fw.ip.flags & IPT_F_GOTO)
+		if (ip_flags & IPT_F_GOTO)
 			add_jumpto(r, cs->jumpto, NFT_GOTO);
 		else
 			add_jumpto(r, cs->jumpto, NFT_JUMP);
@@ -877,7 +947,7 @@
 	}
 
 	nlh = nft_rule_nlmsg_build_hdr(buf, NFT_MSG_NEWRULE,
-				       AF_INET, flags, h->seq);
+				       h->family, flags, h->seq);
 
 	nft_rule_nlmsg_build_payload(nlh, r);
 
@@ -1205,7 +1275,7 @@
 }
 
 static void
-nft_print_payload(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter)
+nft_print_payload_ipv4(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter)
 {
 	uint32_t offset;
 	bool inv;
@@ -1249,6 +1319,48 @@
 }
 
 static void
+nft_print_payload_ipv6(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter)
+{
+	uint32_t offset;
+	bool inv;
+
+	offset = nft_rule_expr_get_u32(e, NFT_EXPR_PAYLOAD_OFFSET);
+
+	switch (offset) {
+	char addr_str[INET6_ADDRSTRLEN];
+	struct in6_addr addr;
+	uint8_t proto;
+	case offsetof(struct ip6_hdr, ip6_src):
+		get_cmp_data(iter, &addr, sizeof(addr), &inv);
+		inet_ntop(AF_INET6, &addr, addr_str, INET6_ADDRSTRLEN);
+
+		if (inv)
+			printf("! -s %s ", addr_str);
+		else
+			printf("-s %s ", addr_str);
+
+		break;
+	case offsetof(struct ip6_hdr, ip6_dst):
+		get_cmp_data(iter, &addr, sizeof(addr), &inv);
+		inet_ntop(AF_INET6, &addr, addr_str, INET6_ADDRSTRLEN);
+
+		if (inv)
+			printf("! -d %s ", addr_str);
+		else
+			printf("-d %s ", addr_str);
+
+		break;
+	case offsetof(struct ip6_hdr, ip6_nxt):
+		get_cmp_data(iter, &proto, sizeof(proto), &inv);
+		print_proto(proto, inv);
+		break;
+	default:
+		DEBUGP("unknown payload offset %d\n", offset);
+		break;
+	}
+}
+
+static void
 nft_print_counters(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter,
 		   bool counters)
 {
@@ -1279,7 +1391,10 @@
 		if (strcmp(name, "counter") == 0) {
 			nft_print_counters(expr, iter, counters);
 		} else if (strcmp(name, "payload") == 0) {
-			nft_print_payload(expr, iter);
+			if (nft_rule_attr_get_u8(r, NFT_RULE_ATTR_FAMILY) == AF_INET)
+				nft_print_payload_ipv4(expr, iter);
+			else
+				nft_print_payload_ipv6(expr, iter);
 		} else if (strcmp(name, "meta") == 0) {
 			nft_print_meta(expr, iter);
 		} else if (strcmp(name, "match") == 0) {
@@ -1334,7 +1449,7 @@
 		return 0;
 	}
 
-	nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_GETCHAIN, AF_INET,
+	nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_GETCHAIN, h->family,
 					NLM_F_DUMP, h->seq);
 
 	ret = mnl_talk(h, nlh, nft_chain_list_cb, list);
@@ -1446,7 +1561,7 @@
 		return 0;
 	}
 
-	nlh = nft_rule_nlmsg_build_hdr(buf, NFT_MSG_GETRULE, AF_INET,
+	nlh = nft_rule_nlmsg_build_hdr(buf, NFT_MSG_GETRULE, h->family,
 					NLM_F_DUMP, h->seq);
 
 	ret = mnl_talk(h, nlh, nft_rule_list_cb, list);
@@ -1512,7 +1627,7 @@
 	nft_rule_attr_set(r, NFT_RULE_ATTR_CHAIN, (char *)chain);
 
 	/* Delete all rules in this table + chain */
-	nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_DELRULE, AF_INET,
+	nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_DELRULE, h->family,
 					NLM_F_ACK, h->seq);
 	nft_rule_nlmsg_build_payload(nlh, r);
 	nft_rule_free(r);
@@ -1589,7 +1704,7 @@
 	nft_chain_attr_set(c, NFT_CHAIN_ATTR_TABLE, (char *)table);
 	nft_chain_attr_set(c, NFT_CHAIN_ATTR_NAME, (char *)chain);
 
-	nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_NEWCHAIN, AF_INET,
+	nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_NEWCHAIN, h->family,
 					NLM_F_ACK|NLM_F_EXCL, h->seq);
 	nft_chain_nlmsg_build_payload(nlh, c);
 	nft_chain_free(c);
@@ -1610,7 +1725,7 @@
 	struct nlmsghdr *nlh;
 	int ret;
 
-	nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_DELCHAIN, AF_INET,
+	nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_DELCHAIN, h->family,
 					NLM_F_ACK, h->seq);
 	nft_chain_nlmsg_build_payload(nlh, c);
 
@@ -1756,7 +1871,7 @@
 	nft_chain_attr_set(c, NFT_CHAIN_ATTR_NAME, (char *)newname);
 	nft_chain_attr_set_u64(c, NFT_CHAIN_ATTR_HANDLE, handle);
 
-	nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_NEWCHAIN, AF_INET,
+	nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_NEWCHAIN, h->family,
 					NLM_F_ACK, h->seq);
 	nft_chain_nlmsg_build_payload(nlh, c);
 	nft_chain_free(c);
@@ -1809,7 +1924,7 @@
 		return 0;
 	}
 
-	nlh = nft_rule_nlmsg_build_hdr(buf, NFT_MSG_GETTABLE, AF_INET,
+	nlh = nft_rule_nlmsg_build_hdr(buf, NFT_MSG_GETTABLE, h->family,
 					NLM_F_DUMP, h->seq);
 
 	ret = mnl_talk(h, nlh, nft_table_list_cb, list);
@@ -1919,39 +2034,86 @@
 }
 
 static bool
-is_same(const struct iptables_command_state *a, const struct iptables_command_state *b)
+is_same(int family, const struct iptables_command_state *a,
+	const struct iptables_command_state *b)
 {
 	unsigned int i;
+	const char *a_outiface, *a_iniface;
+	unsigned const char *a_iniface_mask, *a_outiface_mask;
+	const char *b_outiface, *b_iniface;
+	unsigned const char *b_iniface_mask, *b_outiface_mask;
 
 	/* Always compare head structures: ignore mask here. */
-	if (a->fw.ip.src.s_addr != b->fw.ip.src.s_addr
-	    || a->fw.ip.dst.s_addr != b->fw.ip.dst.s_addr
-	    || a->fw.ip.smsk.s_addr != b->fw.ip.smsk.s_addr
-	    || a->fw.ip.dmsk.s_addr != b->fw.ip.dmsk.s_addr
-	    || a->fw.ip.proto != b->fw.ip.proto
-	    || a->fw.ip.flags != b->fw.ip.flags
-	    || a->fw.ip.invflags != b->fw.ip.invflags) {
-		DEBUGP("different src/dst/proto/flags/invflags\n");
+	switch (family) {
+	case AF_INET:
+		if (a->fw.ip.src.s_addr != b->fw.ip.src.s_addr
+		    || a->fw.ip.dst.s_addr != b->fw.ip.dst.s_addr
+		    || a->fw.ip.smsk.s_addr != b->fw.ip.smsk.s_addr
+		    || a->fw.ip.dmsk.s_addr != b->fw.ip.dmsk.s_addr
+		    || a->fw.ip.proto != b->fw.ip.proto
+		    || a->fw.ip.flags != b->fw.ip.flags
+		    || a->fw.ip.invflags != b->fw.ip.invflags) {
+			DEBUGP("different src/dst/proto/flags/invflags\n");
+			return false;
+		}
+
+		a_iniface_mask = a->fw.ip.iniface_mask;
+		a_iniface = a->fw.ip.iniface;
+		a_outiface_mask = a->fw.ip.outiface_mask;
+		a_outiface = a->fw.ip.outiface;
+
+		b_iniface_mask = b->fw.ip.iniface_mask;
+		b_iniface = b->fw.ip.iniface;
+		b_outiface_mask = b->fw.ip.outiface_mask;
+		b_outiface = b->fw.ip.outiface;
+
+		break;
+	case AF_INET6:
+		if (memcmp(a->fw6.ipv6.src.s6_addr,
+			   b->fw6.ipv6.src.s6_addr,
+			   sizeof(struct in6_addr)) != 0	||
+		    memcmp(a->fw6.ipv6.dst.s6_addr,
+			   b->fw6.ipv6.dst.s6_addr,
+			   sizeof(struct in6_addr)) != 0	||
+		    a->fw6.ipv6.proto != b->fw6.ipv6.proto	||
+		    a->fw6.ipv6.flags != b->fw6.ipv6.flags	||
+		    a->fw6.ipv6.invflags != b->fw6.ipv6.invflags) {
+			DEBUGP("different src/dst/proto/flags/invflags\n");
+			return false;
+		}
+
+		a_iniface_mask = a->fw6.ipv6.iniface_mask;
+		a_iniface = a->fw6.ipv6.iniface;
+		a_outiface_mask = a->fw6.ipv6.outiface_mask;
+		a_outiface = a->fw6.ipv6.outiface;
+
+		b_iniface_mask = b->fw6.ipv6.iniface_mask;
+		b_iniface = b->fw6.ipv6.iniface;
+		b_outiface_mask = b->fw6.ipv6.outiface_mask;
+		b_outiface = b->fw6.ipv6.outiface;
+
+		break;
+	default:
 		return false;
 	}
 
 	for (i = 0; i < IFNAMSIZ; i++) {
-		if (a->fw.ip.iniface_mask[i] != b->fw.ip.iniface_mask[i]) {
+		if (a_iniface_mask[i] != b_iniface_mask[i]) {
 			DEBUGP("different iniface mask %x, %x (%d)\n",
-			a->fw.ip.iniface_mask[i] & 0xff, b->fw.ip.iniface_mask[i] & 0xff, i);
+			a_iniface_mask[i] & 0xff, b_iniface_mask[i] & 0xff, i);
 			return false;
 		}
-		if ((a->fw.ip.iniface[i] & a->fw.ip.iniface_mask[i])
-		    != (b->fw.ip.iniface[i] & b->fw.ip.iniface_mask[i])) {
+		if ((a_iniface[i] & a_iniface_mask[i])
+		    != (b_iniface[i] & b_iniface_mask[i])) {
 			DEBUGP("different iniface\n");
 			return false;
 		}
-		if (a->fw.ip.outiface_mask[i] != b->fw.ip.outiface_mask[i]) {
+		if (a_outiface_mask[i] != b_outiface_mask[i]) {
 			DEBUGP("different outiface mask\n");
 			return false;
 		}
-		if ((a->fw.ip.outiface[i] & a->fw.ip.outiface_mask[i])
-		    != (b->fw.ip.outiface[i] & b->fw.ip.outiface_mask[i])) {
+		if ((a_outiface[i] & a_outiface_mask[i])
+		    != (b_outiface[i] & b_outiface_mask[i])) {
 			DEBUGP("different outiface\n");
 			return false;
 		}
@@ -1962,13 +2124,16 @@
 
 static void
 nft_parse_meta(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter,
-	       struct iptables_command_state *cs)
+	       int family, struct iptables_command_state *cs)
 {
 	uint8_t key = nft_rule_expr_get_u8(e, NFT_EXPR_META_KEY);
 	uint32_t value;
 	const char *name;
 	const void *ifname;
+	char *iniface, *outiface;
+	unsigned char *iniface_mask, *outiface_mask;
 	size_t len;
+	uint8_t *invflags;
 
 	e = nft_rule_expr_iter_next(iter);
 	if (e == NULL)
@@ -1980,58 +2145,75 @@
 		return;
 	}
 
+	switch (family) {
+	case AF_INET:
+		iniface = cs->fw.ip.iniface;
+		outiface = cs->fw.ip.outiface;
+		iniface_mask = cs->fw.ip.iniface_mask;
+		outiface_mask = cs->fw.ip.outiface_mask;
+		invflags = &cs->fw.ip.invflags;
+		break;
+	case AF_INET6:
+		iniface = cs->fw6.ipv6.iniface;
+		outiface = cs->fw6.ipv6.outiface;
+		iniface_mask = cs->fw6.ipv6.iniface_mask;
+		outiface_mask = cs->fw6.ipv6.outiface_mask;
+		invflags = &cs->fw6.ipv6.invflags;
+		break;
+	default:
+		return;
+	}
+
 	switch(key) {
 	case NFT_META_IIF:
 		value = nft_rule_expr_get_u32(e, NFT_EXPR_CMP_DATA);
 		if (nft_rule_expr_get_u8(e, NFT_EXPR_CMP_OP) == NFT_CMP_NEQ)
-			cs->fw.ip.invflags |= IPT_INV_VIA_IN;
+			*invflags |= IPT_INV_VIA_IN;
 
-		if_indextoname(value, cs->fw.ip.iniface);
+		if_indextoname(value, iniface);
 
-		memset(cs->fw.ip.iniface_mask, 0xff,
-		       strlen(cs->fw.ip.iniface)+1);
+		memset(iniface_mask, 0xff, strlen(iniface)+1);
 		break;
 	case NFT_META_OIF:
 		value = nft_rule_expr_get_u32(e, NFT_EXPR_CMP_DATA);
 		if (nft_rule_expr_get_u8(e, NFT_EXPR_CMP_OP) == NFT_CMP_NEQ)
-			cs->fw.ip.invflags |= IPT_INV_VIA_OUT;
+			*invflags |= IPT_INV_VIA_OUT;
 
-		if_indextoname(value, cs->fw.ip.outiface);
+		if_indextoname(value, outiface);
 
-		memset(cs->fw.ip.outiface_mask, 0xff,
-		       strlen(cs->fw.ip.outiface)+1);
+		memset(outiface_mask, 0xff, strlen(outiface)+1);
 		break;
 	case NFT_META_IIFNAME:
 		ifname = nft_rule_expr_get(e, NFT_EXPR_CMP_DATA, &len);
 		if (nft_rule_expr_get_u8(e, NFT_EXPR_CMP_OP) == NFT_CMP_NEQ)
-			cs->fw.ip.invflags |= IPT_INV_VIA_IN;
+			*invflags |= IPT_INV_VIA_IN;
 
-		memcpy(cs->fw.ip.iniface, ifname, len);
-		cs->fw.ip.iniface[len] = '\0';
+		memcpy(iniface, ifname, len);
+		iniface[len] = '\0';
 
 		/* If zero, then this is an interface mask */
-		if (if_nametoindex(cs->fw.ip.iniface) == 0) {
-			cs->fw.ip.iniface[len] = '+';
-			cs->fw.ip.iniface[len+1] = '\0';
+		if (if_nametoindex(iniface) == 0) {
+			iniface[len] = '+';
+			iniface[len+1] = '\0';
 		}
 
-		memset(cs->fw.ip.iniface_mask, 0xff, len);
+		memset(iniface_mask, 0xff, len);
 		break;
 	case NFT_META_OIFNAME:
 		ifname = nft_rule_expr_get(e, NFT_EXPR_CMP_DATA, &len);
 		if (nft_rule_expr_get_u8(e, NFT_EXPR_CMP_OP) == NFT_CMP_NEQ)
-			cs->fw.ip.invflags |= IPT_INV_VIA_OUT;
+			*invflags |= IPT_INV_VIA_OUT;
 
-		memcpy(cs->fw.ip.outiface, ifname, len);
-		cs->fw.ip.outiface[len] = '\0';
+		memcpy(outiface, ifname, len);
+		outiface[len] = '\0';
 
 		/* If zero, then this is an interface mask */
-		if (if_nametoindex(cs->fw.ip.outiface) == 0) {
-			cs->fw.ip.outiface[len] = '+';
-			cs->fw.ip.outiface[len+1] = '\0';
+		if (if_nametoindex(outiface) == 0) {
+			outiface[len] = '+';
+			outiface[len+1] = '\0';
 		}
 
-		memset(cs->fw.ip.outiface_mask, 0xff, len);
+		memset(outiface_mask, 0xff, len);
 		break;
 	default:
 		DEBUGP("unknown meta key %d\n", key);
@@ -2040,17 +2222,13 @@
 }
 
 static void
-nft_parse_payload(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter,
-		  struct iptables_command_state *cs)
+nft_parse_payload_ipv4(uint32_t offset, struct nft_rule_expr_iter *iter,
+		       struct iptables_command_state *cs)
 {
-	uint32_t offset;
-	bool inv;
-
-	offset = nft_rule_expr_get_u32(e, NFT_EXPR_PAYLOAD_OFFSET);
-
 	switch(offset) {
 	struct in_addr addr;
 	uint8_t proto;
+	bool inv;
 
 	case offsetof(struct iphdr, saddr):
 		get_cmp_data(iter, &addr, sizeof(addr), &inv);
@@ -2085,6 +2263,56 @@
 }
 
 static void
+nft_parse_payload_ipv6(uint32_t offset, struct nft_rule_expr_iter *iter,
+		       struct iptables_command_state *cs)
+{
+	switch (offset) {
+	struct in6_addr addr;
+	uint8_t proto;
+	bool inv;
+
+	case offsetof(struct ip6_hdr, ip6_src):
+		get_cmp_data(iter, &addr, sizeof(addr), &inv);
+		memcpy(cs->fw6.ipv6.src.s6_addr, &addr, sizeof(addr));
+		if (inv)
+			cs->fw6.ipv6.invflags |= IPT_INV_SRCIP;
+		break;
+	case offsetof(struct ip6_hdr, ip6_dst):
+		get_cmp_data(iter, &addr, sizeof(addr), &inv);
+		memcpy(cs->fw6.ipv6.dst.s6_addr, &addr, sizeof(addr));
+		if (inv)
+			cs->fw6.ipv6.invflags |= IPT_INV_DSTIP;
+		break;
+	case offsetof(struct ip6_hdr, ip6_nxt):
+		get_cmp_data(iter, &proto, sizeof(proto), &inv);
+		cs->fw6.ipv6.proto = proto;
+		if (inv)
+			cs->fw6.ipv6.invflags |= IPT_INV_PROTO;
+	default:
+		DEBUGP("unknown payload offset %d\n", offset);
+		break;
+	}
+}
+
+static void
+nft_parse_payload(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter,
+		  int family, struct iptables_command_state *cs)
+{
+	uint32_t offset;
+
+	offset = nft_rule_expr_get_u32(e, NFT_EXPR_PAYLOAD_OFFSET);
+
+	switch (family) {
+	case AF_INET:
+		nft_parse_payload_ipv4(offset, iter, cs);
+		break;
+	case AF_INET6:
+		nft_parse_payload_ipv6(offset, iter, cs);
+		break;
+	}
+}
+
+static void
 nft_parse_counter(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter,
 		  struct xt_counters *counters)
 {
@@ -2094,7 +2322,7 @@
 
 static void
 nft_parse_immediate(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter,
-		    struct iptables_command_state *cs)
+		    int family, struct iptables_command_state *cs)
 {
 	int verdict = nft_rule_expr_get_u32(e, NFT_EXPR_IMM_VERDICT);
 	const char *chain = nft_rule_expr_get_str(e, NFT_EXPR_IMM_CHAIN);
@@ -2111,7 +2339,10 @@
 		cs->jumpto = "RETURN";
 		return;
 	case NFT_GOTO:
-		cs->fw.ip.flags |= IPT_F_GOTO;
+		if (family == AF_INET)
+			cs->fw.ip.flags |= IPT_F_GOTO;
+		else
+			cs->fw6.ipv6.flags |= IPT_F_GOTO;
 	case NFT_JUMP:
 		cs->jumpto = chain;
 		return;
@@ -2124,6 +2355,7 @@
 {
 	struct nft_rule_expr_iter *iter;
 	struct nft_rule_expr *expr;
+	int family = nft_rule_attr_get_u8(r, NFT_RULE_ATTR_FAMILY);
 
 	iter = nft_rule_expr_iter_create(r);
 	if (iter == NULL)
@@ -2137,11 +2369,11 @@
 		if (strcmp(name, "counter") == 0) {
 			nft_parse_counter(expr, iter, &cs->counters);
 		} else if (strcmp(name, "payload") == 0) {
-			nft_parse_payload(expr, iter, cs);
+			nft_parse_payload(expr, iter, family, cs);
 		} else if (strcmp(name, "meta") == 0) {
-			nft_parse_meta(expr, iter, cs);
+			nft_parse_meta(expr, iter, family, cs);
 		} else if (strcmp(name, "immediate") == 0) {
-			nft_parse_immediate(expr, iter, cs);
+			nft_parse_immediate(expr, iter, family, cs);
 		}
 
 		expr = nft_rule_expr_iter_next(iter);
@@ -2348,7 +2580,7 @@
 	struct nlmsghdr *nlh;
 	int ret;
 
-	nlh = nft_rule_nlmsg_build_hdr(buf, NFT_MSG_DELRULE, AF_INET,
+	nlh = nft_rule_nlmsg_build_hdr(buf, NFT_MSG_DELRULE, h->family,
 					NLM_F_ACK, h->seq);
 	nft_rule_nlmsg_build_payload(nlh, r);
 
@@ -2421,7 +2653,7 @@
 
 			nft_rule_to_iptables_command_state(r, &this);
 
-			if (!is_same(cs, &this))
+			if (!is_same(nft_rule_attr_get_u8(r, NFT_RULE_ATTR_FAMILY), cs, &this))
 				goto next;
 
 			if (!find_matches(cs->matches, r)) {
@@ -2684,119 +2916,9 @@
 }
 
 static void
-print_firewall(const struct iptables_command_state *cs, struct nft_rule *r,
-	       unsigned int num, unsigned int format)
+print_ipv4_addr(const struct iptables_command_state *cs, unsigned int format)
 {
-	const struct xtables_target *target = NULL;
-	const char *targname = NULL;
-	const void *targinfo = NULL;
-	uint8_t flags;
 	char buf[BUFSIZ];
-	struct nft_rule_expr_iter *iter;
-	struct nft_rule_expr *expr;
-	struct xt_entry_target *t;
-	size_t target_len = 0;
-
-	iter = nft_rule_expr_iter_create(r);
-	if (iter == NULL) {
-		DEBUGP("cannot allocate rule list iterator\n");
-		return;
-	}
-
-	expr = nft_rule_expr_iter_next(iter);
-	while (expr != NULL) {
-		const char *name =
-			nft_rule_expr_get_str(expr, NFT_RULE_EXPR_ATTR_NAME);
-
-		if (strcmp(name, "target") == 0) {
-			targname = nft_rule_expr_get_str(expr, NFT_EXPR_TG_NAME);
-			targinfo = nft_rule_expr_get(expr, NFT_EXPR_TG_INFO, &target_len);
-			break;
-		} else if (strcmp(name, "immediate") == 0) {
-			uint32_t verdict =
-			nft_rule_expr_get_u32(expr, NFT_EXPR_IMM_VERDICT);
-
-			switch(verdict) {
-			case NF_ACCEPT:
-				targname = "ACCEPT";
-				break;
-			case NF_DROP:
-				targname = "DROP";
-				break;
-			case NFT_RETURN:
-				targname = "RETURN";
-				break;
-			case NFT_GOTO:
-				targname = nft_rule_expr_get_str(expr, NFT_EXPR_IMM_CHAIN);
-				break;
-			case NFT_JUMP:
-				targname = nft_rule_expr_get_str(expr, NFT_EXPR_IMM_CHAIN);
-			break;
-			}
-		}
-		expr = nft_rule_expr_iter_next(iter);
-	}
-	nft_rule_expr_iter_destroy(iter);
-
-	flags = cs->fw.ip.flags;
-
-	if (format & FMT_LINENUMBERS)
-		printf(FMT("%-4u ", "%u "), num);
-
-	if (!(format & FMT_NOCOUNTS)) {
-		print_num(cs->counters.pcnt, format);
-		print_num(cs->counters.bcnt, format);
-	}
-
-	if (!(format & FMT_NOTARGET))
-		printf(FMT("%-9s ", "%s "), targname ? targname : "");
-
-	fputc(cs->fw.ip.invflags & XT_INV_PROTO ? '!' : ' ', stdout);
-	{
-		const char *pname =
-			proto_to_name(cs->fw.ip.proto, format&FMT_NUMERIC);
-		if (pname)
-			printf(FMT("%-5s", "%s "), pname);
-		else
-			printf(FMT("%-5hu", "%hu "), cs->fw.ip.proto);
-	}
-
-	if (format & FMT_OPTIONS) {
-		if (format & FMT_NOTABLE)
-			fputs("opt ", stdout);
-		fputc(cs->fw.ip.invflags & IPT_INV_FRAG ? '!' : '-', stdout);
-		fputc(flags & IPT_F_FRAG ? 'f' : '-', stdout);
-		fputc(' ', stdout);
-	}
-
-	if (format & FMT_VIA) {
-		char iface[IFNAMSIZ+2];
-		if (cs->fw.ip.invflags & IPT_INV_VIA_IN) {
-			iface[0] = '!';
-			iface[1] = '\0';
-		}
-		else iface[0] = '\0';
-
-		if (cs->fw.ip.iniface[0] != '\0') {
-			strcat(iface, cs->fw.ip.iniface);
-		}
-		else if (format & FMT_NUMERIC) strcat(iface, "*");
-		else strcat(iface, "any");
-		printf(FMT(" %-6s ","in %s "), iface);
-
-		if (cs->fw.ip.invflags & IPT_INV_VIA_OUT) {
-			iface[0] = '!';
-			iface[1] = '\0';
-		}
-		else iface[0] = '\0';
-
-		if (cs->fw.ip.outiface[0] != '\0') {
-			strcat(iface, cs->fw.ip.outiface);
-		}
-		else if (format & FMT_NUMERIC) strcat(iface, "*");
-		else strcat(iface, "any");
-		printf(FMT("%-6s ","out %s "), iface);
-	}
 
 	fputc(cs->fw.ip.invflags & IPT_INV_SRCIP ? '!' : ' ', stdout);
 	if (cs->fw.ip.smsk.s_addr == 0L && !(format & FMT_NUMERIC))
@@ -2821,12 +2943,199 @@
 		strcat(buf, xtables_ipmask_to_numeric(&cs->fw.ip.dmsk));
 		printf(FMT("%-19s ","-> %s"), buf);
 	}
+}
+
+static void
+print_ipv6_addr(const struct iptables_command_state *cs, unsigned int format)
+{
+	char buf[BUFSIZ];
+
+	fputc(cs->fw6.ipv6.invflags & IPT_INV_SRCIP ? '!' : ' ', stdout);
+	if (IN6_IS_ADDR_UNSPECIFIED(&cs->fw6.ipv6.src)
+	    && !(format & FMT_NUMERIC))
+		printf(FMT("%-19s ","%s "), "anywhere");
+	else {
+		if (format & FMT_NUMERIC)
+			strcpy(buf,
+			       xtables_ip6addr_to_numeric(&cs->fw6.ipv6.src));
+		else
+			strcpy(buf,
+			       xtables_ip6addr_to_anyname(&cs->fw6.ipv6.src));
+		strcat(buf, xtables_ip6mask_to_numeric(&cs->fw6.ipv6.smsk));
+		printf(FMT("%-19s ","%s "), buf);
+	}
+
+
+	fputc(cs->fw6.ipv6.invflags & IPT_INV_DSTIP ? '!' : ' ', stdout);
+	if (IN6_IS_ADDR_UNSPECIFIED(&cs->fw6.ipv6.dst)
+	    && !(format & FMT_NUMERIC))
+		printf(FMT("%-19s ","-> %s"), "anywhere");
+	else {
+		if (format & FMT_NUMERIC)
+			strcpy(buf,
+			       xtables_ip6addr_to_numeric(&cs->fw6.ipv6.dst));
+		else
+			strcpy(buf,
+			       xtables_ip6addr_to_anyname(&cs->fw6.ipv6.dst));
+		strcat(buf, xtables_ip6mask_to_numeric(&cs->fw6.ipv6.dmsk));
+		printf(FMT("%-19s ","-> %s"), buf);
+	}
+}
+
+static void
+print_firewall(const struct iptables_command_state *cs, struct nft_rule *r,
+	       unsigned int num, unsigned int format)
+{
+	const struct xtables_target *target = NULL;
+	const char *targname = NULL;
+	const void *targinfo = NULL;
+	int family;
+	uint8_t flags = 0;
+	uint8_t invflags = 0;
+	uint8_t proto = 0;
+	const char *iniface = NULL, *outiface = NULL;
+	struct nft_rule_expr_iter *iter;
+	struct nft_rule_expr *expr;
+	struct xt_entry_target *t;
+	size_t target_len = 0;
+
+	iter = nft_rule_expr_iter_create(r);
+	if (iter == NULL) {
+		DEBUGP("cannot allocate rule list iterator\n");
+		return;
+	}
+
+	expr = nft_rule_expr_iter_next(iter);
+	while (expr != NULL) {
+		const char *name =
+			nft_rule_expr_get_str(expr, NFT_RULE_EXPR_ATTR_NAME);
+
+		if (strcmp(name, "target") == 0) {
+			targname = nft_rule_expr_get_str(expr,
+							 NFT_EXPR_TG_NAME);
+			targinfo = nft_rule_expr_get(expr, NFT_EXPR_TG_INFO,
+						     &target_len);
+			break;
+		} else if (strcmp(name, "immediate") == 0) {
+			uint32_t verdict =
+			nft_rule_expr_get_u32(expr, NFT_EXPR_IMM_VERDICT);
+
+			switch(verdict) {
+			case NF_ACCEPT:
+				targname = "ACCEPT";
+				break;
+			case NF_DROP:
+				targname = "DROP";
+				break;
+			case NFT_RETURN:
+				targname = "RETURN";
+				break;
+			case NFT_GOTO:
+				targname = nft_rule_expr_get_str(expr,
+							NFT_EXPR_IMM_CHAIN);
+				break;
+			case NFT_JUMP:
+				targname = nft_rule_expr_get_str(expr,
+							NFT_EXPR_IMM_CHAIN);
+			break;
+			}
+		}
+		expr = nft_rule_expr_iter_next(iter);
+	}
+	nft_rule_expr_iter_destroy(iter);
+
+	family = nft_rule_attr_get_u8(r, NFT_RULE_ATTR_FAMILY);
+
+	switch (family) {
+	case AF_INET:
+		flags = cs->fw.ip.flags;
+		invflags = flags = cs->fw.ip.invflags;
+		proto = cs->fw.ip.proto;
+		iniface = cs->fw.ip.iniface;
+		outiface = cs->fw.ip.outiface;
+		break;
+	case AF_INET6:
+		flags = cs->fw6.ipv6.flags;
+		invflags = cs->fw6.ipv6.invflags;
+		proto = cs->fw6.ipv6.proto;
+		iniface = cs->fw6.ipv6.iniface;
+		outiface = cs->fw6.ipv6.outiface;
+		break;
+	}
+
+	if (format & FMT_LINENUMBERS)
+		printf(FMT("%-4u ", "%u "), num);
+
+	if (!(format & FMT_NOCOUNTS)) {
+		print_num(cs->counters.pcnt, format);
+		print_num(cs->counters.bcnt, format);
+	}
+
+	if (!(format & FMT_NOTARGET))
+		printf(FMT("%-9s ", "%s "), targname ? targname : "");
+
+	fputc(invflags & XT_INV_PROTO ? '!' : ' ', stdout);
+	{
+		const char *pname =
+			proto_to_name(proto, format&FMT_NUMERIC);
+		if (pname)
+			printf(FMT("%-5s", "%s "), pname);
+		else
+			printf(FMT("%-5hu", "%hu "), proto);
+	}
+
+	if (format & FMT_OPTIONS) {
+		if (format & FMT_NOTABLE)
+			fputs("opt ", stdout);
+		fputc(invflags & IPT_INV_FRAG ? '!' : '-', stdout);
+		fputc(flags & IPT_F_FRAG ? 'f' : '-', stdout);
+		fputc(' ', stdout);
+	}
+
+	if (format & FMT_VIA) {
+		char iface[IFNAMSIZ+2];
+		if (invflags & IPT_INV_VIA_IN) {
+			iface[0] = '!';
+			iface[1] = '\0';
+		}
+		else iface[0] = '\0';
+
+		if (iniface[0] != '\0') {
+			strcat(iface, iniface);
+		}
+		else if (format & FMT_NUMERIC) strcat(iface, "*");
+		else strcat(iface, "any");
+		printf(FMT(" %-6s ","in %s "), iface);
+
+		if (invflags & IPT_INV_VIA_OUT) {
+			iface[0] = '!';
+			iface[1] = '\0';
+		}
+		else iface[0] = '\0';
+
+		if (outiface[0] != '\0') {
+			strcat(iface, outiface);
+		}
+		else if (format & FMT_NUMERIC) strcat(iface, "*");
+		else strcat(iface, "any");
+		printf(FMT("%-6s ","out %s "), iface);
+	}
+
+
+	switch (family) {
+	case AF_INET:
+		print_ipv4_addr(cs, format);
+		break;
+	case AF_INET6:
+		print_ipv6_addr(cs, format);
+		break;
+	}
 
 	if (format & FMT_NOTABLE)
 		fputs("  ", stdout);
 
 #ifdef IPT_F_GOTO
-	if(cs->fw.ip.flags & IPT_F_GOTO)
+	if(flags & IPT_F_GOTO)
 		printf("[goto] ");
 #endif
 
diff --git a/iptables/nft.h b/iptables/nft.h
index aed2498..1bd9ccc 100644
--- a/iptables/nft.h
+++ b/iptables/nft.h
@@ -4,6 +4,7 @@
 #include "xshared.h"
 
 struct nft_handle {
+	int			family;
 	struct mnl_socket	*nl;
 	uint32_t		portid;
 	uint32_t		seq;
diff --git a/iptables/xtables-restore.c b/iptables/xtables-restore.c
index 30ea813..e83eacc 100644
--- a/iptables/xtables-restore.c
+++ b/iptables/xtables-restore.c
@@ -161,7 +161,9 @@
 int
 xtables_restore_main(int argc, char *argv[])
 {
-	struct nft_handle h;
+	struct nft_handle h = {
+		.family = AF_INET,	/* default to IPv4 */
+	};
 	char buffer[10240];
 	int c;
 	char curtable[XT_TABLE_MAXNAMELEN + 1];
diff --git a/iptables/xtables-save.c b/iptables/xtables-save.c
index 046c948..05d06b1 100644
--- a/iptables/xtables-save.c
+++ b/iptables/xtables-save.c
@@ -76,7 +76,9 @@
 xtables_save_main(int argc, char *argv[])
 {
 	const char *tablename = NULL;
-	struct nft_handle h;
+	struct nft_handle h = {
+		.family	= AF_INET,	/* default to AF_INET */
+	};
 	int c;
 
 	xtables_globals.program_name = "xtables-save";
diff --git a/iptables/xtables.c b/iptables/xtables.c
index d604590..9c59b7d 100644
--- a/iptables/xtables.c
+++ b/iptables/xtables.c
@@ -204,6 +204,20 @@
 	IPT_DOTTED_MASK
 };
 
+struct addr_mask {
+	union {
+		struct in_addr	*v4;
+		struct in6_addr *v6;
+	} addr;
+
+	unsigned int naddrs;
+
+	union {
+		struct in_addr	*v4;
+		struct in6_addr *v6;
+	} mask;
+};
+
 static void __attribute__((noreturn))
 exit_tryhelp(int status)
 {
@@ -370,6 +384,15 @@
  *	return global static data.
 */
 
+/* These are invalid numbers as upper layer protocol */
+static int is_exthdr(uint16_t proto)
+{
+	return (proto == IPPROTO_ROUTING ||
+		proto == IPPROTO_FRAGMENT ||
+		proto == IPPROTO_AH ||
+		proto == IPPROTO_DSTOPTS);
+}
+
 /* Christophe Burki wants `-p 6' to imply `-m tcp'.  */
 /* Can't be zero. */
 static int
@@ -430,26 +453,38 @@
 add_entry(const char *chain,
 	  const char *table,
 	  struct iptables_command_state *cs,
-	  unsigned int nsaddrs,
-	  const struct in_addr saddrs[],
-	  const struct in_addr smasks[],
-	  unsigned int ndaddrs,
-	  const struct in_addr daddrs[],
-	  const struct in_addr dmasks[],
+	  int family,
+	  const struct addr_mask s,
+	  const struct addr_mask d,
 	  bool verbose, struct nft_handle *h, bool append)
 {
 	unsigned int i, j;
 	int ret = 1;
 
-	for (i = 0; i < nsaddrs; i++) {
-		cs->fw.ip.src.s_addr = saddrs[i].s_addr;
-		cs->fw.ip.smsk.s_addr = smasks[i].s_addr;
-		for (j = 0; j < ndaddrs; j++) {
-			cs->fw.ip.dst.s_addr = daddrs[j].s_addr;
-			cs->fw.ip.dmsk.s_addr = dmasks[j].s_addr;
+	for (i = 0; i < s.naddrs; i++) {
+		if (family == AF_INET) {
+			cs->fw.ip.src.s_addr = s.addr.v4[i].s_addr;
+			cs->fw.ip.smsk.s_addr = s.mask.v4[i].s_addr;
+			for (j = 0; j < d.naddrs; j++) {
+				cs->fw.ip.dst.s_addr = d.addr.v4[j].s_addr;
+				cs->fw.ip.dmsk.s_addr = d.mask.v4[j].s_addr;
 
-			ret = nft_rule_add(h, chain, table,
-					   cs, append, 0, verbose);
+				ret = nft_rule_add(h, chain, table,
+						   cs, append, 0, verbose);
+			}
+		} else if (family == AF_INET6) {
+			memcpy(&cs->fw6.ipv6.src,
+			       &s.addr.v6[i], sizeof(struct in6_addr));
+			memcpy(&cs->fw6.ipv6.smsk,
+			       &s.mask.v6[i], sizeof(struct in6_addr));
+			for (j = 0; j < d.naddrs; j++) {
+				memcpy(&cs->fw6.ipv6.dst,
+				       &d.addr.v6[j], sizeof(struct in6_addr));
+				memcpy(&cs->fw6.ipv6.dmsk,
+				       &d.mask.v6[j], sizeof(struct in6_addr));
+				ret = nft_rule_add(h, chain, table,
+						   cs, append, 0, verbose);
+			}
 		}
 	}
 
@@ -460,14 +495,23 @@
 replace_entry(const char *chain, const char *table,
 	      struct iptables_command_state *cs,
 	      unsigned int rulenum,
-	      const struct in_addr *saddr, const struct in_addr *smask,
-	      const struct in_addr *daddr, const struct in_addr *dmask,
+	      int family,
+	      const struct addr_mask s,
+	      const struct addr_mask d,
 	      bool verbose, struct nft_handle *h)
 {
-	cs->fw.ip.src.s_addr = saddr->s_addr;
-	cs->fw.ip.dst.s_addr = daddr->s_addr;
-	cs->fw.ip.smsk.s_addr = smask->s_addr;
-	cs->fw.ip.dmsk.s_addr = dmask->s_addr;
+	if (family == AF_INET) {
+		cs->fw.ip.src.s_addr = s.addr.v4->s_addr;
+		cs->fw.ip.dst.s_addr = d.addr.v4->s_addr;
+		cs->fw.ip.smsk.s_addr = s.mask.v4->s_addr;
+		cs->fw.ip.dmsk.s_addr = d.mask.v4->s_addr;
+	} else if (family == AF_INET6) {
+		memcpy(&cs->fw6.ipv6.src, s.addr.v6, sizeof(struct in6_addr));
+		memcpy(&cs->fw6.ipv6.dst, d.addr.v6, sizeof(struct in6_addr));
+		memcpy(&cs->fw6.ipv6.smsk, s.mask.v6, sizeof(struct in6_addr));
+		memcpy(&cs->fw6.ipv6.dmsk, d.mask.v6, sizeof(struct in6_addr));
+	} else
+		return 1;
 
 	return nft_rule_replace(h, chain, table, cs, rulenum, verbose);
 }
@@ -475,25 +519,38 @@
 static int
 delete_entry(const char *chain, const char *table,
 	     struct iptables_command_state *cs,
-	     unsigned int nsaddrs,
-	     const struct in_addr saddrs[],
-	     const struct in_addr smasks[],
-	     unsigned int ndaddrs,
-	     const struct in_addr daddrs[],
-	     const struct in_addr dmasks[],
+	     int family,
+	     const struct addr_mask s,
+	     const struct addr_mask d,
 	     bool verbose,
 	     struct nft_handle *h)
 {
 	unsigned int i, j;
 	int ret = 1;
 
-	for (i = 0; i < nsaddrs; i++) {
-		cs->fw.ip.src.s_addr = saddrs[i].s_addr;
-		cs->fw.ip.smsk.s_addr = smasks[i].s_addr;
-		for (j = 0; j < ndaddrs; j++) {
-			cs->fw.ip.dst.s_addr = daddrs[j].s_addr;
-			cs->fw.ip.dmsk.s_addr = dmasks[j].s_addr;
-			ret = nft_rule_delete(h, chain, table, cs, verbose);
+	for (i = 0; i < s.naddrs; i++) {
+		if (family == AF_INET) {
+			cs->fw.ip.src.s_addr = s.addr.v4[i].s_addr;
+			cs->fw.ip.smsk.s_addr = s.mask.v4[i].s_addr;
+			for (j = 0; j < d.naddrs; j++) {
+				cs->fw.ip.dst.s_addr = d.addr.v4[j].s_addr;
+				cs->fw.ip.dmsk.s_addr = d.mask.v4[j].s_addr;
+				ret = nft_rule_delete(h, chain,
+						      table, cs, verbose);
+			}
+		} else if (family == AF_INET6) {
+			memcpy(&cs->fw6.ipv6.src,
+			       &s.addr.v6[i], sizeof(struct in6_addr));
+			memcpy(&cs->fw6.ipv6.smsk,
+			       &s.mask.v6[i], sizeof(struct in6_addr));
+			for (j = 0; j < d.naddrs; j++) {
+				memcpy(&cs->fw6.ipv6.dst,
+				       &d.addr.v6[j], sizeof(struct in6_addr));
+				memcpy(&cs->fw6.ipv6.dmsk,
+				       &d.mask.v6[j], sizeof(struct in6_addr));
+				ret = nft_rule_delete(h, chain,
+						      table, cs, verbose);
+			}
 		}
 	}
 
@@ -503,21 +560,37 @@
 static int
 check_entry(const char *chain, const char *table,
 	    struct iptables_command_state *cs,
-	    unsigned int nsaddrs, const struct in_addr *saddrs,
-	    const struct in_addr *smasks, unsigned int ndaddrs,
-	    const struct in_addr *daddrs, const struct in_addr *dmasks,
+	    int family,
+	    const struct addr_mask s,
+	    const struct addr_mask d,
 	    bool verbose, struct nft_handle *h)
 {
 	unsigned int i, j;
 	int ret = 1;
 
-	for (i = 0; i < nsaddrs; i++) {
-		cs->fw.ip.src.s_addr = saddrs[i].s_addr;
-		cs->fw.ip.smsk.s_addr = smasks[i].s_addr;
-		for (j = 0; j < ndaddrs; j++) {
-			cs->fw.ip.dst.s_addr = daddrs[j].s_addr;
-			cs->fw.ip.dmsk.s_addr = dmasks[j].s_addr;
-			ret = nft_rule_check(h, chain, table, cs, verbose);
+	for (i = 0; i < s.naddrs; i++) {
+		if (family == AF_INET) {
+			cs->fw.ip.src.s_addr = s.addr.v4[i].s_addr;
+			cs->fw.ip.smsk.s_addr = s.mask.v4[i].s_addr;
+			for (j = 0; j < d.naddrs; j++) {
+				cs->fw.ip.dst.s_addr = d.addr.v4[j].s_addr;
+				cs->fw.ip.dmsk.s_addr = d.mask.v4[j].s_addr;
+				ret = nft_rule_check(h, chain,
+						     table, cs, verbose);
+			}
+		} else if (family == AF_INET6) {
+			memcpy(&cs->fw6.ipv6.src,
+			       &s.addr.v6[i], sizeof(struct in6_addr));
+			memcpy(&cs->fw6.ipv6.smsk,
+			       &s.mask.v6[i], sizeof(struct in6_addr));
+			for (j = 0; j < d.naddrs; j++) {
+				memcpy(&cs->fw6.ipv6.dst,
+				       &d.addr.v6[j], sizeof(struct in6_addr));
+				memcpy(&cs->fw6.ipv6.dmsk,
+				       &d.mask.v6[j], sizeof(struct in6_addr));
+				ret = nft_rule_check(h, chain,
+						     table, cs, verbose);
+			}
 		}
 	}
 
@@ -673,9 +746,8 @@
 int do_commandx(struct nft_handle *h, int argc, char *argv[], char **table)
 {
 	struct iptables_command_state cs;
-	unsigned int nsaddrs = 0, ndaddrs = 0;
-	struct in_addr *saddrs = NULL, *smasks = NULL;
-	struct in_addr *daddrs = NULL, *dmasks = NULL;
+	struct addr_mask s;
+	struct addr_mask d;
 
 	int verbose = 0;
 	const char *chain = NULL;
@@ -687,7 +759,21 @@
 	struct xtables_match *m;
 	struct xtables_rule_match *matchp;
 	struct xtables_target *t;
-	unsigned long long cnt;
+	unsigned long long pcnt_cnt = 0, bcnt_cnt = 0;
+
+	int family = AF_INET;
+	u_int16_t proto = 0;
+	u_int8_t flags = 0, invflags = 0;
+	char iniface[IFNAMSIZ], outiface[IFNAMSIZ];
+	unsigned char iniface_mask[IFNAMSIZ], outiface_mask[IFNAMSIZ];
+	bool goto_set = false;
+
+	memset(&s, 0, sizeof(s));
+	memset(&d, 0, sizeof(d));
+	memset(iniface, 0, sizeof(iniface));
+	memset(outiface, 0, sizeof(outiface));
+	memset(iniface_mask, 0, sizeof(iniface_mask));
+	memset(outiface_mask, 0, sizeof(outiface_mask));
 
 	memset(&cs, 0, sizeof(cs));
 	cs.jumpto = "";
@@ -877,7 +963,7 @@
 			 * Option selection
 			 */
 		case 'p':
-			set_option(&cs.options, OPT_PROTOCOL, &cs.fw.ip.invflags,
+			set_option(&cs.options, OPT_PROTOCOL, &invflags,
 				   cs.invert);
 
 			/* Canonicalize into lower case */
@@ -885,31 +971,30 @@
 				*cs.protocol = tolower(*cs.protocol);
 
 			cs.protocol = optarg;
-			cs.fw.ip.proto = xtables_parse_protocol(cs.protocol);
+			proto = xtables_parse_protocol(cs.protocol);
 
-			if (cs.fw.ip.proto == 0
-			    && (cs.fw.ip.invflags & XT_INV_PROTO))
+			if (proto == 0 && (invflags & XT_INV_PROTO))
 				xtables_error(PARAMETER_PROBLEM,
 					   "rule would never match protocol");
 			break;
 
 		case 's':
-			set_option(&cs.options, OPT_SOURCE, &cs.fw.ip.invflags,
+			set_option(&cs.options, OPT_SOURCE, &invflags,
 				   cs.invert);
 			shostnetworkmask = optarg;
 			break;
 
 		case 'd':
-			set_option(&cs.options, OPT_DESTINATION, &cs.fw.ip.invflags,
+			set_option(&cs.options, OPT_DESTINATION, &invflags,
 				   cs.invert);
 			dhostnetworkmask = optarg;
 			break;
 
 #ifdef IPT_F_GOTO
 		case 'g':
-			set_option(&cs.options, OPT_JUMP, &cs.fw.ip.invflags,
+			set_option(&cs.options, OPT_JUMP, &invflags,
 				   cs.invert);
-			cs.fw.ip.flags |= IPT_F_GOTO;
+			goto_set = true;
 			cs.jumpto = parse_target(optarg);
 			break;
 #endif
@@ -924,11 +1009,11 @@
 				xtables_error(PARAMETER_PROBLEM,
 					"Empty interface is likely to be "
 					"undesired");
-			set_option(&cs.options, OPT_VIANAMEIN, &cs.fw.ip.invflags,
+			set_option(&cs.options, OPT_VIANAMEIN, &invflags,
 				   cs.invert);
 			xtables_parse_interface(optarg,
-					cs.fw.ip.iniface,
-					cs.fw.ip.iniface_mask);
+						iniface,
+						iniface_mask);
 			break;
 
 		case 'o':
@@ -936,23 +1021,28 @@
 				xtables_error(PARAMETER_PROBLEM,
 					"Empty interface is likely to be "
 					"undesired");
-			set_option(&cs.options, OPT_VIANAMEOUT, &cs.fw.ip.invflags,
+			set_option(&cs.options, OPT_VIANAMEOUT, &invflags,
 				   cs.invert);
 			xtables_parse_interface(optarg,
-					cs.fw.ip.outiface,
-					cs.fw.ip.outiface_mask);
+						outiface,
+						outiface_mask);
 			break;
 
 		case 'f':
-			set_option(&cs.options, OPT_FRAGMENT, &cs.fw.ip.invflags,
+			if (family == AF_INET6) {
+				xtables_error(PARAMETER_PROBLEM,
+					"`-f' is not supported in IPv6, "
+					"use -m frag instead");
+			}
+			set_option(&cs.options, OPT_FRAGMENT, &invflags,
 				   cs.invert);
-			cs.fw.ip.flags |= IPT_F_FRAG;
+			flags |= IPT_F_FRAG;
 			break;
 
 		case 'v':
 			if (!verbose)
 				set_option(&cs.options, OPT_VERBOSE,
-					   &cs.fw.ip.invflags, cs.invert);
+					   &invflags, cs.invert);
 			verbose++;
 			break;
 
@@ -961,7 +1051,7 @@
 			break;
 
 		case 'n':
-			set_option(&cs.options, OPT_NUMERIC, &cs.fw.ip.invflags,
+			set_option(&cs.options, OPT_NUMERIC, &invflags,
 				   cs.invert);
 			break;
 
@@ -973,7 +1063,7 @@
 			break;
 
 		case 'x':
-			set_option(&cs.options, OPT_EXPANDED, &cs.fw.ip.invflags,
+			set_option(&cs.options, OPT_EXPANDED, &invflags,
 				   cs.invert);
 			break;
 
@@ -986,7 +1076,7 @@
 			exit(0);
 
 		case '0':
-			set_option(&cs.options, OPT_LINENUMBERS, &cs.fw.ip.invflags,
+			set_option(&cs.options, OPT_LINENUMBERS, &invflags,
 				   cs.invert);
 			break;
 
@@ -996,7 +1086,7 @@
 
 		case 'c':
 
-			set_option(&cs.options, OPT_COUNTERS, &cs.fw.ip.invflags,
+			set_option(&cs.options, OPT_COUNTERS, &invflags,
 				   cs.invert);
 			pcnt = optarg;
 			bcnt = strchr(pcnt + 1, ',');
@@ -1010,29 +1100,25 @@
 					"-%c requires packet and byte counter",
 					opt2char(OPT_COUNTERS));
 
-			if (sscanf(pcnt, "%llu", &cnt) != 1)
+			if (sscanf(pcnt, "%llu", &pcnt_cnt) != 1)
 				xtables_error(PARAMETER_PROBLEM,
 					"-%c packet counter not numeric",
 					opt2char(OPT_COUNTERS));
-			cs.counters.pcnt = cnt;
 
-			if (sscanf(bcnt, "%llu", &cnt) != 1)
+			if (sscanf(bcnt, "%llu", &bcnt_cnt) != 1)
 				xtables_error(PARAMETER_PROBLEM,
 					"-%c byte counter not numeric",
 					opt2char(OPT_COUNTERS));
-			cs.counters.bcnt = cnt;
 			break;
 
 		case '4':
-			/* This is indeed the IPv4 iptables */
+			if (family != AF_INET)
+				exit_tryhelp(2);
 			break;
 
 		case '6':
-			/* This is not the IPv6 ip6tables */
-			if (line != -1)
-				return 1; /* success: line ignored */
-			fprintf(stderr, "This is the IPv4 version of iptables.\n");
-			exit_tryhelp(2);
+			family = AF_INET6;
+			break;
 
 		case 1: /* non option */
 			if (optarg[0] == '!' && optarg[1] == '\0') {
@@ -1079,27 +1165,109 @@
 		xtables_error(PARAMETER_PROBLEM,
 			   "nothing appropriate following !");
 
-	if (command & (CMD_REPLACE | CMD_INSERT | CMD_DELETE | CMD_APPEND | CMD_CHECK)) {
-		if (!(cs.options & OPT_DESTINATION))
-			dhostnetworkmask = "0.0.0.0/0";
-		if (!(cs.options & OPT_SOURCE))
-			shostnetworkmask = "0.0.0.0/0";
+	switch (family) {
+	case AF_INET:
+		cs.fw.ip.proto = proto;
+		cs.fw.ip.invflags = invflags;
+		cs.fw.ip.flags = flags;
+
+		strncpy(cs.fw.ip.iniface, iniface, IFNAMSIZ);
+		memcpy(cs.fw.ip.iniface_mask,
+		       iniface_mask, IFNAMSIZ*sizeof(unsigned char));
+
+		strncpy(cs.fw.ip.outiface, outiface, IFNAMSIZ);
+		memcpy(cs.fw.ip.outiface_mask,
+		       outiface_mask, IFNAMSIZ*sizeof(unsigned char));
+
+		if (goto_set)
+			cs.fw.ip.flags |= IPT_F_GOTO;
+
+		cs.counters.pcnt = pcnt_cnt;
+		cs.counters.bcnt = bcnt_cnt;
+
+		if (command & (CMD_REPLACE | CMD_INSERT |
+				CMD_DELETE | CMD_APPEND | CMD_CHECK)) {
+			if (!(cs.options & OPT_DESTINATION))
+				dhostnetworkmask = "0.0.0.0/0";
+			if (!(cs.options & OPT_SOURCE))
+				shostnetworkmask = "0.0.0.0/0";
+		}
+
+		if (shostnetworkmask)
+			xtables_ipparse_multiple(shostnetworkmask, &s.addr.v4,
+						 &s.mask.v4, &s.naddrs);
+		if (dhostnetworkmask)
+			xtables_ipparse_multiple(dhostnetworkmask, &d.addr.v4,
+						 &d.mask.v4, &d.naddrs);
+
+		if ((s.naddrs > 1 || d.naddrs > 1) &&
+		    (cs.fw.ip.invflags & (IPT_INV_SRCIP | IPT_INV_DSTIP)))
+			xtables_error(PARAMETER_PROBLEM,
+				      "! not allowed with multiple"
+				      " source or destination IP addresses");
+		break;
+	case AF_INET6:
+		if (proto != 0)
+			flags |= IP6T_F_PROTO;
+
+		cs.fw6.ipv6.proto = proto;
+		cs.fw6.ipv6.invflags = invflags;
+		cs.fw6.ipv6.flags = flags;
+
+		if (flags & IPT_F_FRAG)
+			xtables_error(PARAMETER_PROBLEM,
+				      "-f is not valid on IPv6");
+
+		if (is_exthdr(cs.fw6.ipv6.proto)
+		    && (cs.fw6.ipv6.invflags & XT_INV_PROTO) == 0)
+			fprintf(stderr,
+				"Warning: never matched protocol: %s. "
+				"use extension match instead.\n",
+				cs.protocol);
+
+		strncpy(cs.fw6.ipv6.iniface, iniface, IFNAMSIZ);
+		memcpy(cs.fw6.ipv6.iniface_mask,
+		       iniface_mask, IFNAMSIZ*sizeof(unsigned char));
+
+		strncpy(cs.fw6.ipv6.outiface, outiface, IFNAMSIZ);
+		memcpy(cs.fw6.ipv6.outiface_mask,
+		       outiface_mask, IFNAMSIZ*sizeof(unsigned char));
+
+		if (goto_set)
+			cs.fw6.ipv6.flags |= IP6T_F_GOTO;
+
+		cs.fw6.counters.pcnt = pcnt_cnt;
+		cs.fw6.counters.bcnt = bcnt_cnt;
+
+		if (command & (CMD_REPLACE | CMD_INSERT |
+				CMD_DELETE | CMD_APPEND | CMD_CHECK)) {
+			if (!(cs.options & OPT_DESTINATION))
+				dhostnetworkmask = "::0/0";
+			if (!(cs.options & OPT_SOURCE))
+				shostnetworkmask = "::0/0";
+		}
+
+		if (shostnetworkmask)
+			xtables_ip6parse_multiple(shostnetworkmask, &s.addr.v6,
+						  &s.mask.v6, &s.naddrs);
+		if (dhostnetworkmask)
+			xtables_ip6parse_multiple(dhostnetworkmask, &d.addr.v6,
+						  &d.mask.v6, &d.naddrs);
+
+		if ((s.naddrs > 1 || d.naddrs > 1) &&
+		    (cs.fw6.ipv6.invflags & (IP6T_INV_SRCIP | IP6T_INV_DSTIP)))
+			xtables_error(PARAMETER_PROBLEM,
+				      "! not allowed with multiple"
+				      " source or destination IP addresses");
+		break;
+	default:
+		exit_tryhelp(2);
+		break;
 	}
 
-	if (shostnetworkmask)
-		xtables_ipparse_multiple(shostnetworkmask, &saddrs,
-					 &smasks, &nsaddrs);
+	h->family = family;
 
-	if (dhostnetworkmask)
-		xtables_ipparse_multiple(dhostnetworkmask, &daddrs,
-					 &dmasks, &ndaddrs);
-
-	if ((nsaddrs > 1 || ndaddrs > 1) &&
-	    (cs.fw.ip.invflags & (IPT_INV_SRCIP | IPT_INV_DSTIP)))
-		xtables_error(PARAMETER_PROBLEM, "! not allowed with multiple"
-			   " source or destination IP addresses");
-
-	if (command == CMD_REPLACE && (nsaddrs != 1 || ndaddrs != 1))
+	if (command == CMD_REPLACE && (s.naddrs != 1 || d.naddrs != 1))
 		xtables_error(PARAMETER_PROBLEM, "Replacement rule does not "
 			   "specify a unique address");
 
@@ -1144,39 +1312,30 @@
 
 	switch (command) {
 	case CMD_APPEND:
-		ret = add_entry(chain, *table, &cs,
-				nsaddrs, saddrs, smasks,
-				ndaddrs, daddrs, dmasks,
-				cs.options&OPT_VERBOSE,
+		ret = add_entry(chain, *table, &cs, family,
+				s, d, cs.options&OPT_VERBOSE,
 				h, true);
 		break;
 	case CMD_DELETE:
-		ret = delete_entry(chain, *table, &cs,
-				   nsaddrs, saddrs, smasks,
-				   ndaddrs, daddrs, dmasks,
-				   cs.options&OPT_VERBOSE, h);
+		ret = delete_entry(chain, *table, &cs, family,
+				   s, d, cs.options&OPT_VERBOSE, h);
 		break;
 	case CMD_DELETE_NUM:
 		ret = nft_rule_delete_num(h, chain, *table, rulenum - 1, verbose);
 		break;
 	case CMD_CHECK:
-		ret = check_entry(chain, *table, &cs,
-				   nsaddrs, saddrs, smasks,
-				   ndaddrs, daddrs, dmasks,
-				   cs.options&OPT_VERBOSE, h);
+		ret = check_entry(chain, *table, &cs, family,
+				   s, d, cs.options&OPT_VERBOSE, h);
 		break;
 	case CMD_REPLACE:
 		/* FIXME replace at rulenum */
 		ret = replace_entry(chain, *table, &cs, rulenum - 1,
-				    saddrs, smasks, daddrs, dmasks,
-				    cs.options&OPT_VERBOSE, h);
+				    family, s, d, cs.options&OPT_VERBOSE, h);
 		break;
 	case CMD_INSERT:
 		/* FIXME insert at rulenum */
-		ret = add_entry(chain, *table, &cs,
-				nsaddrs, saddrs, smasks,
-				ndaddrs, daddrs, dmasks,
-				cs.options&OPT_VERBOSE, h, false);
+		ret = add_entry(chain, *table, &cs, family,
+				s, d, cs.options&OPT_VERBOSE, h, false);
 		break;
 	case CMD_FLUSH:
 		ret = nft_rule_flush(h, chain, *table);
@@ -1242,10 +1401,17 @@
 
 	clear_rule_matches(&cs.matches);
 
-	free(saddrs);
-	free(smasks);
-	free(daddrs);
-	free(dmasks);
+	if (family == AF_INET) {
+		free(s.addr.v4);
+		free(s.mask.v4);
+		free(d.addr.v4);
+		free(d.mask.v4);
+	} else if (family == AF_INET6) {
+		free(s.addr.v6);
+		free(s.mask.v6);
+		free(d.addr.v6);
+		free(d.mask.v6);
+	}
 	xtables_free_opts(1);
 
 	return ret;