]> git.ipfire.org Git - thirdparty/iptables.git/commitdiff
xtables: add IPv6 support
authorTomasz Bursztyka <tomasz.bursztyka@linux.intel.com>
Sun, 13 Jan 2013 15:42:11 +0000 (16:42 +0100)
committerPablo Neira Ayuso <pablo@netfilter.org>
Mon, 30 Dec 2013 22:50:22 +0000 (23:50 +0100)
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>
iptables/nft.c
iptables/nft.h
iptables/xtables-restore.c
iptables/xtables-save.c
iptables/xtables.c

index 46c8c9482d100b5ed81a42664eee99d4dba93fa3..43b13debea596e07173e990d1147122cadd80e9c 100644 (file)
@@ -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_builtin_add(struct nft_handle *h, struct builtin_table *_t,
                                        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 @@ nft_chain_builtin_add(struct nft_handle *h, struct builtin_table *table,
                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 @@ int nft_table_add(struct nft_handle *h, const struct nft_table *t)
        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 @@ int nft_chain_add(struct nft_handle *h, const struct nft_chain *c)
        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 @@ int nft_table_wake_dormant(struct nft_handle *h, const char *table)
        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 @@ __nft_chain_set(struct nft_handle *h, const char *table,
                                        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 @@ static void add_counters(struct nft_rule *r, uint64_t packets, uint64_t bytes)
        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 @@ nft_rule_add(struct nft_handle *h, const char *chain, const char *table,
        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_add(struct nft_handle *h, const char *chain, const char *table,
        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);
-
-               if (cs->fw.ip.invflags & IPT_INV_VIA_IN)
-                       op = NFT_CMP_NEQ;
-               else
-                       op = NFT_CMP_EQ;
-
-               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);
+       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.outiface[0] != '\0')
+                       add_outiface(r, cs->fw.ip.outiface,
+                                    cs->fw.ip.invflags);
+
+               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);
+
+               ip_flags = cs->fw6.ipv6.flags;
 
-               add_cmp_u16(r, 0, op);
+               break;
        }
 
        for (matchp = cs->matches; matchp; matchp = matchp->next)
@@ -862,7 +932,7 @@ nft_rule_add(struct nft_handle *h, const char *chain, const char *table,
                        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 @@ nft_rule_add(struct nft_handle *h, const char *chain, const char *table,
        }
 
        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 const char *mask_to_str(uint32_t mask)
 }
 
 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;
@@ -1248,6 +1318,48 @@ nft_print_payload(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter)
        }
 }
 
+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 @@ static void nft_rule_print_save(struct nft_rule *r, bool counters)
                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 @@ static struct nft_chain_list *nft_chain_list_get(struct nft_handle *h)
                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 @@ static struct nft_rule_list *nft_rule_list_get(struct nft_handle *h)
                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_flush(struct nft_handle *h, const char *table, const char *chain)
        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 @@ int nft_chain_user_add(struct nft_handle *h, const char *chain, const char *tabl
        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 @@ static int __nft_chain_del(struct nft_handle *h, struct nft_chain *c)
        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 @@ int nft_chain_user_rename(struct nft_handle *h,const char *chain,
        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 @@ static struct nft_table_list *nft_table_list_get(struct nft_handle *h)
                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 @@ match_different(const struct xt_entry_match *a,
 }
 
 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 @@ is_same(const struct iptables_command_state *a, const struct iptables_command_st
 
 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 @@ nft_parse_meta(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter,
                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 @@ nft_parse_meta(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter,
 }
 
 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);
@@ -2084,6 +2262,56 @@ nft_parse_payload(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter,
        }
 }
 
+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 @@ nft_parse_counter(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter,
 
 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 @@ nft_parse_immediate(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter,
                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 @@ nft_rule_to_iptables_command_state(struct nft_rule *r,
 {
        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 @@ nft_rule_to_iptables_command_state(struct nft_rule *r,
                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 @@ __nft_rule_del(struct nft_handle *h, struct nft_rule *r)
        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_find(struct nft_rule_list *list, const char *chain, const char *table,
 
                        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)) {
@@ -2683,6 +2915,73 @@ print_match(struct nft_rule_expr *expr, int numeric)
        free(m);
 }
 
+static void
+print_ipv4_addr(const struct iptables_command_state *cs, unsigned int format)
+{
+       char buf[BUFSIZ];
+
+       fputc(cs->fw.ip.invflags & IPT_INV_SRCIP ? '!' : ' ', stdout);
+       if (cs->fw.ip.smsk.s_addr == 0L && !(format & FMT_NUMERIC))
+               printf(FMT("%-19s ","%s "), "anywhere");
+       else {
+               if (format & FMT_NUMERIC)
+                       strcpy(buf, xtables_ipaddr_to_numeric(&cs->fw.ip.src));
+               else
+                       strcpy(buf, xtables_ipaddr_to_anyname(&cs->fw.ip.src));
+               strcat(buf, xtables_ipmask_to_numeric(&cs->fw.ip.smsk));
+               printf(FMT("%-19s ","%s "), buf);
+       }
+
+       fputc(cs->fw.ip.invflags & IPT_INV_DSTIP ? '!' : ' ', stdout);
+       if (cs->fw.ip.dmsk.s_addr == 0L && !(format & FMT_NUMERIC))
+               printf(FMT("%-19s ","-> %s"), "anywhere");
+       else {
+               if (format & FMT_NUMERIC)
+                       strcpy(buf, xtables_ipaddr_to_numeric(&cs->fw.ip.dst));
+               else
+                       strcpy(buf, xtables_ipaddr_to_anyname(&cs->fw.ip.dst));
+               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)
@@ -2690,8 +2989,11 @@ print_firewall(const struct iptables_command_state *cs, struct nft_rule *r,
        const struct xtables_target *target = NULL;
        const char *targname = NULL;
        const void *targinfo = NULL;
-       uint8_t flags;
-       char buf[BUFSIZ];
+       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;
@@ -2709,8 +3011,10 @@ print_firewall(const struct iptables_command_state *cs, struct nft_rule *r,
                        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);
+                       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 =
@@ -2727,10 +3031,12 @@ print_firewall(const struct iptables_command_state *cs, struct nft_rule *r,
                                targname = "RETURN";
                                break;
                        case NFT_GOTO:
-                               targname = nft_rule_expr_get_str(expr, NFT_EXPR_IMM_CHAIN);
+                               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);
+                               targname = nft_rule_expr_get_str(expr,
+                                                       NFT_EXPR_IMM_CHAIN);
                        break;
                        }
                }
@@ -2738,7 +3044,24 @@ print_firewall(const struct iptables_command_state *cs, struct nft_rule *r,
        }
        nft_rule_expr_iter_destroy(iter);
 
-       flags = cs->fw.ip.flags;
+       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);
@@ -2751,82 +3074,68 @@ print_firewall(const struct iptables_command_state *cs, struct nft_rule *r,
        if (!(format & FMT_NOTARGET))
                printf(FMT("%-9s ", "%s "), targname ? targname : "");
 
-       fputc(cs->fw.ip.invflags & XT_INV_PROTO ? '!' : ' ', stdout);
+       fputc(invflags & XT_INV_PROTO ? '!' : ' ', stdout);
        {
                const char *pname =
-                       proto_to_name(cs->fw.ip.proto, format&FMT_NUMERIC);
+                       proto_to_name(proto, format&FMT_NUMERIC);
                if (pname)
                        printf(FMT("%-5s", "%s "), pname);
                else
-                       printf(FMT("%-5hu", "%hu "), cs->fw.ip.proto);
+                       printf(FMT("%-5hu", "%hu "), proto);
        }
 
        if (format & FMT_OPTIONS) {
                if (format & FMT_NOTABLE)
                        fputs("opt ", stdout);
-               fputc(cs->fw.ip.invflags & IPT_INV_FRAG ? '!' : '-', 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 (cs->fw.ip.invflags & IPT_INV_VIA_IN) {
+               if (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);
+               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 (cs->fw.ip.invflags & IPT_INV_VIA_OUT) {
+               if (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);
+               if (outiface[0] != '\0') {
+                       strcat(iface, 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))
-               printf(FMT("%-19s ","%s "), "anywhere");
-       else {
-               if (format & FMT_NUMERIC)
-                       strcpy(buf, xtables_ipaddr_to_numeric(&cs->fw.ip.src));
-               else
-                       strcpy(buf, xtables_ipaddr_to_anyname(&cs->fw.ip.src));
-               strcat(buf, xtables_ipmask_to_numeric(&cs->fw.ip.smsk));
-               printf(FMT("%-19s ","%s "), buf);
-       }
 
-       fputc(cs->fw.ip.invflags & IPT_INV_DSTIP ? '!' : ' ', stdout);
-       if (cs->fw.ip.dmsk.s_addr == 0L && !(format & FMT_NUMERIC))
-               printf(FMT("%-19s ","-> %s"), "anywhere");
-       else {
-               if (format & FMT_NUMERIC)
-                       strcpy(buf, xtables_ipaddr_to_numeric(&cs->fw.ip.dst));
-               else
-                       strcpy(buf, xtables_ipaddr_to_anyname(&cs->fw.ip.dst));
-               strcat(buf, xtables_ipmask_to_numeric(&cs->fw.ip.dmsk));
-               printf(FMT("%-19s ","-> %s"), buf);
+       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
 
index aed2498b7cf0c6bf7fa5fa47d1b9f5c6d956ec6e..1bd9ccce9876e47b7a069ce89043ac4b7d4844da 100644 (file)
@@ -4,6 +4,7 @@
 #include "xshared.h"
 
 struct nft_handle {
+       int                     family;
        struct mnl_socket       *nl;
        uint32_t                portid;
        uint32_t                seq;
index 30ea813c9eba99d994b8960886e0a481152ca807..e83eacc39506b50087e03ac808603db2ccb3d4e9 100644 (file)
@@ -161,7 +161,9 @@ static void add_param_to_argv(char *parsestart)
 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];
index 046c948dfc9c82e3e4b60a8f43ab56b259528e80..05d06b1e8688429c533f74c6aac10959440e4218 100644 (file)
@@ -76,7 +76,9 @@ int
 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";
index d604590644359600b87775969153903715fd9eca..9c59b7d8db2cfae63b856e6cf9747db2635cd468 100644 (file)
@@ -204,6 +204,20 @@ enum {
        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 @@ add_command(unsigned int *cmd, const int newcmd, const int othercmds,
  *     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 @@ static int
 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 @@ static int
 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 @@ replace_entry(const char *chain, const char *table,
 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 @@ delete_entry(const char *chain, const char *table,
 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 @@ static void command_match(struct iptables_command_state *cs)
 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 @@ int do_commandx(struct nft_handle *h, int argc, char *argv[], char **table)
        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 @@ int do_commandx(struct nft_handle *h, int argc, char *argv[], char **table)
                         * 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 @@ int do_commandx(struct nft_handle *h, int argc, char *argv[], char **table)
                                *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 @@ int do_commandx(struct nft_handle *h, int argc, char *argv[], char **table)
                                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 @@ int do_commandx(struct nft_handle *h, int argc, char *argv[], char **table)
                                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 @@ int do_commandx(struct nft_handle *h, int argc, char *argv[], char **table)
                        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 @@ int do_commandx(struct nft_handle *h, int argc, char *argv[], char **table)
                        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 @@ int do_commandx(struct nft_handle *h, int argc, char *argv[], char **table)
                        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 @@ int do_commandx(struct nft_handle *h, int argc, char *argv[], char **table)
 
                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 @@ int do_commandx(struct nft_handle *h, int argc, char *argv[], char **table)
                                        "-%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 @@ int do_commandx(struct nft_handle *h, int argc, char *argv[], char **table)
                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;
 
-       if (shostnetworkmask)
-               xtables_ipparse_multiple(shostnetworkmask, &saddrs,
-                                        &smasks, &nsaddrs);
+               strncpy(cs.fw.ip.iniface, iniface, IFNAMSIZ);
+               memcpy(cs.fw.ip.iniface_mask,
+                      iniface_mask, IFNAMSIZ*sizeof(unsigned char));
 
-       if (dhostnetworkmask)
-               xtables_ipparse_multiple(dhostnetworkmask, &daddrs,
-                                        &dmasks, &ndaddrs);
+               strncpy(cs.fw.ip.outiface, outiface, IFNAMSIZ);
+               memcpy(cs.fw.ip.outiface_mask,
+                      outiface_mask, IFNAMSIZ*sizeof(unsigned char));
 
-       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 (goto_set)
+                       cs.fw.ip.flags |= IPT_F_GOTO;
 
-       if (command == CMD_REPLACE && (nsaddrs != 1 || ndaddrs != 1))
+               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;
+       }
+
+       h->family = family;
+
+       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 @@ int do_commandx(struct nft_handle *h, int argc, char *argv[], char **table)
 
        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 @@ int do_commandx(struct nft_handle *h, int argc, char *argv[], char **table)
 
        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;