Merge branch 'nft-compat'

This merges the branch that contains the iptables over nftables
compatibility layer into master.
diff --git a/configure.ac b/configure.ac
index e83304c..2521ccc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -60,6 +60,9 @@
 AC_ARG_WITH([pkgconfigdir], AS_HELP_STRING([--with-pkgconfigdir=PATH],
 	[Path to the pkgconfig directory [[LIBDIR/pkgconfig]]]),
 	[pkgconfigdir="$withval"], [pkgconfigdir='${libdir}/pkgconfig'])
+AC_ARG_ENABLE([nftables],
+	AS_HELP_STRING([--disable-nftables], [Do not build nftables compat]),
+	[enable_nftables="$enableval"], [enable_nftables="yes"])
 
 libiptc_LDFLAGS2="";
 AX_CHECK_LINKER_FLAGS([-Wl,--no-as-needed],
@@ -106,6 +109,7 @@
 AM_CONDITIONAL([ENABLE_LIBIPQ], [test "$enable_libipq" = "yes"])
 AM_CONDITIONAL([ENABLE_BPFC], [test "$enable_bpfc" = "yes"])
 AM_CONDITIONAL([ENABLE_SYNCONF], [test "$enable_nfsynproxy" = "yes"])
+AM_CONDITIONAL([ENABLE_NFTABLES], [test "$enable_nftables" = "yes"])
 
 if test "x$enable_bpfc" = "xyes" || test "x$enable_nfsynproxy" = "xyes"; then
 	AC_CHECK_LIB(pcap, pcap_compile,, AC_MSG_ERROR(missing libpcap library required by bpf compiler or nfsynproxy tool))
@@ -115,6 +119,46 @@
 	[nfnetlink=1], [nfnetlink=0])
 AM_CONDITIONAL([HAVE_LIBNFNETLINK], [test "$nfnetlink" = 1])
 
+if test "x$enable_nftables" = "xyes"; then
+	PKG_CHECK_MODULES([libmnl], [libmnl >= 1.0], [mnl=1], [mnl=0])
+
+	PKG_CHECK_MODULES([libnftnl], [libnftnl >= 1.0], [nftables=1], [nftables=0])
+
+	AM_PROG_LEX
+	AC_PROG_YACC
+
+	if test -z "$ac_cv_prog_YACC"
+	then
+		echo "*** Error: No suitable bison/yacc found. ***"
+		echo "    Please install the 'bison' package."
+		exit 1
+	fi
+	if test -z "$ac_cv_prog_LEX"
+	then
+	        echo "*** Error: No suitable flex/lex found. ***"
+	        echo "    Please install the 'flex' package."
+	        exit 1
+	fi
+
+	AC_MSG_CHECKING(flex version)
+	flex_version=`$ac_cv_prog_LEX --version | sed 's/version//g' | awk '/flex/ {print $2}'`
+	flex_major=`echo $flex_version| cut -d . -f 1`
+	flex_minor=`echo $flex_version| cut -d . -f 2`
+	flex_rev=`echo $flex_version| cut -d . -f 3`
+
+	if test "$flex_major" -eq "2" && test "$flex_minor" -eq "5" && test "$flex_rev" -ge "33"; then
+		AC_MSG_RESULT([$flex_version. OK])
+	else
+		AC_MSG_WARN([flex version $flex_version found.
+		Version 2.5.33 or greater is required. You may experience problems
+		while compilating the nftables compatibility layer for iptables.
+		Please, consider to upgrade flex.])
+	fi
+fi
+
+AM_CONDITIONAL([HAVE_LIBMNL], [test "$mnl" = 1])
+AM_CONDITIONAL([HAVE_LIBNFTNL], [test "$nftables" = 1])
+
 regular_CFLAGS="-Wall -Waggregate-return -Wmissing-declarations \
 	-Wmissing-prototypes -Wredundant-decls -Wshadow -Wstrict-prototypes \
 	-Winline -pipe";
@@ -182,6 +226,7 @@
   Large file support:			${enable_largefile}
   BPF utils support:			${enable_bpfc}
   nfsynproxy util support:		${enable_nfsynproxy}
+  nftables support:			${enable_nftables}
 
 Build parameters:
   Put plugins into executable (static):	${enable_static}
diff --git a/etc/xtables.conf b/etc/xtables.conf
new file mode 100644
index 0000000..d37b0d7
--- /dev/null
+++ b/etc/xtables.conf
@@ -0,0 +1,75 @@
+family ipv4 {
+	table raw {
+		chain PREROUTING hook NF_INET_PRE_ROUTING prio -300
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio -300
+	}
+
+	table mangle {
+		chain PREROUTING hook NF_INET_PRE_ROUTING prio -150
+		chain INPUT hook NF_INET_LOCAL_IN prio -150
+		chain FORWARD hook NF_INET_FORWARD prio -150
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio -150
+		chain POSTROUTING hook NF_INET_POST_ROUTING prio -150
+	}
+
+	table filter {
+		chain INPUT hook NF_INET_LOCAL_IN prio 0
+		chain FORWARD hook NF_INET_FORWARD prio 0
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio 0
+	}
+
+	table nat {
+		chain PREROUTING hook NF_INET_PRE_ROUTING prio -100
+		chain INPUT hook NF_INET_LOCAL_IN prio -100
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio 100
+		chain POSTROUTING hook NF_INET_POST_ROUTING prio 100
+	}
+
+	table security {
+		chain INPUT hook NF_INET_LOCAL_IN prio 50
+		chain FORWARD hook NF_INET_FORWARD prio 50
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio 50
+	}
+}
+
+family ipv6 {
+	table raw {
+		chain PREROUTING hook NF_INET_PRE_ROUTING prio -300
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio -300
+	}
+
+	table mangle {
+		chain PREROUTING hook NF_INET_PRE_ROUTING prio -150
+		chain INPUT hook NF_INET_LOCAL_IN prio -150
+		chain FORWARD hook NF_INET_FORWARD prio -150
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio -150
+		chain POSTROUTING hook NF_INET_POST_ROUTING prio -150
+	}
+
+	table filter {
+		chain INPUT hook NF_INET_LOCAL_IN prio 0
+		chain FORWARD hook NF_INET_FORWARD prio 0
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio 0
+	}
+
+	table nat {
+		chain PREROUTING hook NF_INET_PRE_ROUTING prio -100
+		chain INPUT hook NF_INET_LOCAL_IN prio -100
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio 100
+		chain POSTROUTING hook NF_INET_POST_ROUTING prio 100
+	}
+
+	table security {
+		chain INPUT hook NF_INET_LOCAL_IN prio 50
+		chain FORWARD hook NF_INET_FORWARD prio 50
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio 50
+	}
+}
+
+family arp {
+	table filter {
+		chain INPUT hook NF_ARP_IN prio 0
+		chain FORWARD hook NF_ARP_FORWARD prio 0
+		chain OUTPUT hook NF_ARP_OUT prio 0
+	}
+}
\ No newline at end of file
diff --git a/extensions/libxt_mangle.c b/extensions/libxt_mangle.c
new file mode 100644
index 0000000..4b20feb
--- /dev/null
+++ b/extensions/libxt_mangle.c
@@ -0,0 +1,388 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Authors:
+ * 	Libarptc code from: Bart De Schuymer <bdschuym@pandora.be>
+ * 	Port to libxtables: Tomasz Bursztyka <tomasz.bursztyka@linux.intel.com>
+ */
+
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <getopt.h>
+#include <errno.h>
+#include <netinet/ether.h>
+
+#include <xtables.h>
+#include <linux/netfilter_arp/arpt_mangle.h>
+
+static void mangle_help(void)
+{
+	printf(
+"mangle target options:\n"
+"--mangle-ip-s IP address\n"
+"--mangle-ip-d IP address\n"
+"--mangle-mac-s MAC address\n"
+"--mangle-mac-d MAC address\n"
+"--mangle-target target (DROP, CONTINUE or ACCEPT -- default is ACCEPT)\n"
+	);
+}
+
+enum {
+	MANGLE_IPS    = 0,
+	MANGLE_IPT    = 1,
+	MANGLE_DEVS   = 2,
+	MANGLE_DEVT   = 3,
+	MANGLE_TARGET = 4,
+};
+
+static const struct xt_option_entry mangle_opts[] = {
+	{ .name = "mangle-ip-s", .id = MANGLE_IPS, .type = XTTYPE_STRING },
+	{ .name = "mangle-ip-d", .id = MANGLE_IPT, .type = XTTYPE_STRING },
+	{ .name = "mangle-mac-s", .id = MANGLE_DEVS, .type = XTTYPE_STRING },
+	{ .name = "mangle-mac-d", .id = MANGLE_DEVT, .type = XTTYPE_STRING },
+	{ .name = "mangle-target", .id = MANGLE_TARGET,
+	  .type = XTTYPE_STRING },
+	XTOPT_TABLEEND,
+};
+
+
+static struct in_addr *network_to_addr(const char *name)
+{
+	struct netent *net;
+	static struct in_addr addr;
+
+	if ((net = getnetbyname(name)) != NULL) {
+		if (net->n_addrtype != AF_INET)
+			return (struct in_addr *) NULL;
+		addr.s_addr = htonl((unsigned long) net->n_net);
+		return &addr;
+	}
+
+	return (struct in_addr *) NULL;
+}
+
+static void inaddrcpy(struct in_addr *dst, struct in_addr *src)
+{
+	dst->s_addr = src->s_addr;
+}
+
+static struct in_addr *host_to_addr(const char *name, unsigned int *naddr)
+{
+	struct hostent *host;
+	struct in_addr *addr;
+	unsigned int i;
+
+	*naddr = 0;
+	if ((host = gethostbyname(name)) != NULL) {
+		if (host->h_addrtype != AF_INET ||
+			host->h_length != sizeof(struct in_addr))
+			return (struct in_addr *) NULL;
+
+		while (host->h_addr_list[*naddr] != (char *) NULL)
+			(*naddr)++;
+		addr = xtables_calloc(*naddr, sizeof(struct in_addr));
+		for (i = 0; i < *naddr; i++)
+			inaddrcpy(&(addr[i]),
+				  (struct in_addr *) host->h_addr_list[i]);
+		return addr;
+	}
+
+	return (struct in_addr *) NULL;
+}
+
+static int string_to_number(const char *s, unsigned int min,
+			    unsigned int max, unsigned int *ret)
+{
+	long number;
+	char *end;
+
+	/* Handle hex, octal, etc. */
+	errno = 0;
+	number = strtol(s, &end, 0);
+	if (*end == '\0' && end != s) {
+		/* we parsed a number, let's see if we want this */
+		if (errno != ERANGE && min <= number && number <= max) {
+			*ret = number;
+			return 0;
+		}
+	}
+	return -1;
+}
+
+static struct in_addr *dotted_to_addr(const char *dotted)
+{
+	static struct in_addr addr;
+	unsigned char *addrp;
+	char *p, *q;
+	unsigned int onebyte;
+	int i;
+	char buf[20];
+
+	/* copy dotted string, because we need to modify it */
+	strncpy(buf, dotted, sizeof(buf) - 1);
+	addrp = (unsigned char *) &(addr.s_addr);
+
+	p = buf;
+	for (i = 0; i < 3; i++) {
+		if ((q = strchr(p, '.')) == NULL)
+			return (struct in_addr *) NULL;
+
+		*q = '\0';
+		if (string_to_number(p, 0, 255, &onebyte) == -1)
+			return (struct in_addr *) NULL;
+
+		addrp[i] = (unsigned char) onebyte;
+		p = q + 1;
+	}
+
+	/* we've checked 3 bytes, now we check the last one */
+	if (string_to_number(p, 0, 255, &onebyte) == -1)
+		return (struct in_addr *) NULL;
+
+	addrp[3] = (unsigned char) onebyte;
+
+	return &addr;
+}
+
+static struct in_addr *parse_hostnetwork(const char *name,
+					 unsigned int *naddrs)
+{
+	struct in_addr *addrp, *addrptmp;
+
+	if ((addrptmp = dotted_to_addr(name)) != NULL ||
+		(addrptmp = network_to_addr(name)) != NULL) {
+		addrp = xtables_malloc(sizeof(struct in_addr));
+		inaddrcpy(addrp, addrptmp);
+		*naddrs = 1;
+		return addrp;
+	}
+	if ((addrp = host_to_addr(name, naddrs)) != NULL)
+		return addrp;
+
+	xtables_error(PARAMETER_PROBLEM, "host/network `%s' not found", name);
+}
+
+static void mangle_parse(struct xt_option_call *cb)
+{
+	const struct arpt_entry *e = cb->xt_entry;
+	struct arpt_mangle *mangle =  cb->data;
+	struct in_addr *ipaddr;
+	struct ether_addr *macaddr;
+
+	/* mangle target is by default "ACCEPT". Setting it here,
+	 * since original arpt_mangle.c init() no longer exists*/
+	mangle->target = NF_ACCEPT;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case MANGLE_IPS:
+/*
+		if (e->arp.arpln_mask == 0)
+			xtables_error(PARAMETER_PROBLEM, "no pln defined");
+
+		if (e->arp.invflags & ARPT_INV_ARPPLN)
+			xtables_error(PARAMETER_PROBLEM,
+				   "! pln not allowed for --mangle-ip-s");
+*/
+/*
+		if (e->arp.arpln != 4)
+			xtables_error(PARAMETER_PROBLEM, "only pln=4 supported");
+*/
+		{
+			unsigned int nr;
+			ipaddr = parse_hostnetwork(cb->arg, &nr);
+		}
+		mangle->u_s.src_ip.s_addr = ipaddr->s_addr;
+		free(ipaddr);
+		mangle->flags |= ARPT_MANGLE_SIP;
+		break;
+	case MANGLE_IPT:
+/*
+		if (e->arp.arpln_mask == 0)
+			xtables_error(PARAMETER_PROBLEM, "no pln defined");
+
+		if (e->arp.invflags & ARPT_INV_ARPPLN)
+			xtables_error(PARAMETER_PROBLEM,
+				   "! pln not allowed for --mangle-ip-d");
+*/
+/*
+		if (e->arp.arpln != 4)
+			xtables_error(PARAMETER_PROBLEM, "only pln=4 supported");
+*/
+		{
+			unsigned int nr;
+			ipaddr = parse_hostnetwork(cb->arg, &nr);
+		}
+		mangle->u_t.tgt_ip.s_addr = ipaddr->s_addr;
+		free(ipaddr);
+		mangle->flags |= ARPT_MANGLE_TIP;
+		break;
+	case MANGLE_DEVS:
+		if (e->arp.arhln_mask == 0)
+			xtables_error(PARAMETER_PROBLEM,
+				      "no --h-length defined");
+		if (e->arp.invflags & ARPT_INV_ARPHLN)
+			xtables_error(PARAMETER_PROBLEM,
+				      "! --h-length not allowed for "
+				      "--mangle-mac-s");
+		if (e->arp.arhln != 6)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only --h-length 6 supported");
+		macaddr = ether_aton(cb->arg);
+		if (macaddr == NULL)
+			xtables_error(PARAMETER_PROBLEM, "invalid source MAC");
+		memcpy(mangle->src_devaddr, macaddr, e->arp.arhln);
+		mangle->flags |= ARPT_MANGLE_SDEV;
+		break;
+	case MANGLE_DEVT:
+		if (e->arp.arhln_mask == 0)
+			xtables_error(PARAMETER_PROBLEM,
+				      "no --h-length defined");
+		if (e->arp.invflags & ARPT_INV_ARPHLN)
+			xtables_error(PARAMETER_PROBLEM,
+				      "! hln not allowed for --mangle-mac-d");
+		if (e->arp.arhln != 6)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only --h-length 6 supported");
+		macaddr = ether_aton(cb->arg);
+		if (macaddr == NULL)
+			xtables_error(PARAMETER_PROBLEM, "invalid target MAC");
+		memcpy(mangle->tgt_devaddr, macaddr, e->arp.arhln);
+		mangle->flags |= ARPT_MANGLE_TDEV;
+		break;
+	case MANGLE_TARGET:
+		if (!strcmp(cb->arg, "DROP"))
+			mangle->target = NF_DROP;
+		else if (!strcmp(cb->arg, "ACCEPT"))
+			mangle->target = NF_ACCEPT;
+		else if (!strcmp(cb->arg, "CONTINUE"))
+			mangle->target = ARPT_CONTINUE;
+		else
+			xtables_error(PARAMETER_PROBLEM,
+				      "bad target for --mangle-target");
+		break;
+	}
+}
+
+static void mangle_fcheck(struct xt_fcheck_call *cb)
+{
+}
+
+static char *addr_to_dotted(const struct in_addr *addrp)
+{
+	static char buf[20];
+	const unsigned char *bytep;
+
+	bytep = (const unsigned char *) &(addrp->s_addr);
+	sprintf(buf, "%d.%d.%d.%d", bytep[0], bytep[1], bytep[2], bytep[3]);
+	return buf;
+}
+
+static char *addr_to_host(const struct in_addr *addr)
+{
+	struct hostent *host;
+
+	if ((host = gethostbyaddr((char *) addr,
+				  sizeof(struct in_addr), AF_INET)) != NULL)
+		return (char *) host->h_name;
+
+	return (char *) NULL;
+}
+
+static char *addr_to_network(const struct in_addr *addr)
+{
+	struct netent *net;
+
+	if ((net = getnetbyaddr((long) ntohl(addr->s_addr), AF_INET)) != NULL)
+		return (char *) net->n_name;
+
+	return (char *) NULL;
+}
+
+static char *addr_to_anyname(const struct in_addr *addr)
+{
+	char *name;
+
+	if ((name = addr_to_host(addr)) != NULL ||
+		(name = addr_to_network(addr)) != NULL)
+		return name;
+
+	return addr_to_dotted(addr);
+}
+
+static void print_mac(const unsigned char *mac, int l)
+{
+	int j;
+
+	for (j = 0; j < l; j++)
+		printf("%02x%s", mac[j],
+			(j==l-1) ? "" : ":");
+}
+
+static void mangle_print(const void *ip, const struct xt_entry_target *target,
+			 int numeric)
+{
+	const struct arpt_mangle *m = (const void *)target;
+	char buf[100];
+
+	if (m->flags & ARPT_MANGLE_SIP) {
+		if (numeric)
+			sprintf(buf, "%s", addr_to_dotted(&(m->u_s.src_ip)));
+		else
+			sprintf(buf, "%s", addr_to_anyname(&(m->u_s.src_ip)));
+		printf("--mangle-ip-s %s ", buf);
+	}
+	if (m->flags & ARPT_MANGLE_SDEV) {
+		printf("--mangle-mac-s ");
+		print_mac((unsigned char *)m->src_devaddr, 6);
+		printf(" ");
+	}
+	if (m->flags & ARPT_MANGLE_TIP) {
+		if (numeric)
+			sprintf(buf, "%s", addr_to_dotted(&(m->u_t.tgt_ip)));
+		else
+			sprintf(buf, "%s", addr_to_anyname(&(m->u_t.tgt_ip)));
+		printf("--mangle-ip-d %s ", buf);
+	}
+	if (m->flags & ARPT_MANGLE_TDEV) {
+		printf("--mangle-mac-d ");
+		print_mac((unsigned char *)m->tgt_devaddr, 6);
+		printf(" ");
+	}
+	if (m->target != NF_ACCEPT) {
+		printf("--mangle-target ");
+		if (m->target == NF_DROP)
+			printf("DROP ");
+		else
+			printf("CONTINUE ");
+	}
+}
+
+static void mangle_save(const void *ip, const struct xt_entry_target *target)
+{
+}
+
+static struct xtables_target mangle_tg_reg = {
+	.family		= NFPROTO_ARP,
+	.name		= "mangle",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct arpt_mangle)),
+	.userspacesize	= XT_ALIGN(sizeof(struct arpt_mangle)),
+	.help		= mangle_help,
+	.x6_parse	= mangle_parse,
+	.x6_fcheck	= mangle_fcheck,
+	.print		= mangle_print,
+	.save		= mangle_save,
+	.x6_options	= mangle_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&mangle_tg_reg);
+}
diff --git a/include/iptables.h b/include/iptables.h
index ac9dc0e..78c10ab 100644
--- a/include/iptables.h
+++ b/include/iptables.h
@@ -20,4 +20,6 @@
 
 extern struct xtables_globals iptables_globals;
 
+extern struct xtables_globals xtables_globals;
+
 #endif /*_IPTABLES_USER_H*/
diff --git a/include/linux/netfilter/nf_tables.h b/include/linux/netfilter/nf_tables.h
new file mode 100644
index 0000000..fbfd229
--- /dev/null
+++ b/include/linux/netfilter/nf_tables.h
@@ -0,0 +1,718 @@
+#ifndef _LINUX_NF_TABLES_H
+#define _LINUX_NF_TABLES_H
+
+#define NFT_CHAIN_MAXNAMELEN 32
+
+enum nft_registers {
+	NFT_REG_VERDICT,
+	NFT_REG_1,
+	NFT_REG_2,
+	NFT_REG_3,
+	NFT_REG_4,
+	__NFT_REG_MAX
+};
+#define NFT_REG_MAX	(__NFT_REG_MAX - 1)
+
+/**
+ * enum nft_verdicts - nf_tables internal verdicts
+ *
+ * @NFT_CONTINUE: continue evaluation of the current rule
+ * @NFT_BREAK: terminate evaluation of the current rule
+ * @NFT_JUMP: push the current chain on the jump stack and jump to a chain
+ * @NFT_GOTO: jump to a chain without pushing the current chain on the jump stack
+ * @NFT_RETURN: return to the topmost chain on the jump stack
+ *
+ * The nf_tables verdicts share their numeric space with the netfilter verdicts.
+ */
+enum nft_verdicts {
+	NFT_CONTINUE	= -1,
+	NFT_BREAK	= -2,
+	NFT_JUMP	= -3,
+	NFT_GOTO	= -4,
+	NFT_RETURN	= -5,
+};
+
+/**
+ * enum nf_tables_msg_types - nf_tables netlink message types
+ *
+ * @NFT_MSG_NEWTABLE: create a new table (enum nft_table_attributes)
+ * @NFT_MSG_GETTABLE: get a table (enum nft_table_attributes)
+ * @NFT_MSG_DELTABLE: delete a table (enum nft_table_attributes)
+ * @NFT_MSG_NEWCHAIN: create a new chain (enum nft_chain_attributes)
+ * @NFT_MSG_GETCHAIN: get a chain (enum nft_chain_attributes)
+ * @NFT_MSG_DELCHAIN: delete a chain (enum nft_chain_attributes)
+ * @NFT_MSG_NEWRULE: create a new rule (enum nft_rule_attributes)
+ * @NFT_MSG_GETRULE: get a rule (enum nft_rule_attributes)
+ * @NFT_MSG_DELRULE: delete a rule (enum nft_rule_attributes)
+ * @NFT_MSG_NEWSET: create a new set (enum nft_set_attributes)
+ * @NFT_MSG_GETSET: get a set (enum nft_set_attributes)
+ * @NFT_MSG_DELSET: delete a set (enum nft_set_attributes)
+ * @NFT_MSG_NEWSETELEM: create a new set element (enum nft_set_elem_attributes)
+ * @NFT_MSG_GETSETELEM: get a set element (enum nft_set_elem_attributes)
+ * @NFT_MSG_DELSETELEM: delete a set element (enum nft_set_elem_attributes)
+ */
+enum nf_tables_msg_types {
+	NFT_MSG_NEWTABLE,
+	NFT_MSG_GETTABLE,
+	NFT_MSG_DELTABLE,
+	NFT_MSG_NEWCHAIN,
+	NFT_MSG_GETCHAIN,
+	NFT_MSG_DELCHAIN,
+	NFT_MSG_NEWRULE,
+	NFT_MSG_GETRULE,
+	NFT_MSG_DELRULE,
+	NFT_MSG_NEWSET,
+	NFT_MSG_GETSET,
+	NFT_MSG_DELSET,
+	NFT_MSG_NEWSETELEM,
+	NFT_MSG_GETSETELEM,
+	NFT_MSG_DELSETELEM,
+	NFT_MSG_MAX,
+};
+
+/**
+ * enum nft_list_attributes - nf_tables generic list netlink attributes
+ *
+ * @NFTA_LIST_ELEM: list element (NLA_NESTED)
+ */
+enum nft_list_attributes {
+	NFTA_LIST_UNPEC,
+	NFTA_LIST_ELEM,
+	__NFTA_LIST_MAX
+};
+#define NFTA_LIST_MAX		(__NFTA_LIST_MAX - 1)
+
+/**
+ * enum nft_hook_attributes - nf_tables netfilter hook netlink attributes
+ *
+ * @NFTA_HOOK_HOOKNUM: netfilter hook number (NLA_U32)
+ * @NFTA_HOOK_PRIORITY: netfilter hook priority (NLA_U32)
+ */
+enum nft_hook_attributes {
+	NFTA_HOOK_UNSPEC,
+	NFTA_HOOK_HOOKNUM,
+	NFTA_HOOK_PRIORITY,
+	__NFTA_HOOK_MAX
+};
+#define NFTA_HOOK_MAX		(__NFTA_HOOK_MAX - 1)
+
+/**
+ * enum nft_table_flags - nf_tables table flags
+ *
+ * @NFT_TABLE_F_DORMANT: this table is not active
+ */
+enum nft_table_flags {
+	NFT_TABLE_F_DORMANT	= 0x1,
+};
+
+/**
+ * enum nft_table_attributes - nf_tables table netlink attributes
+ *
+ * @NFTA_TABLE_NAME: name of the table (NLA_STRING)
+ * @NFTA_TABLE_FLAGS: bitmask of enum nft_table_flags (NLA_U32)
+ */
+enum nft_table_attributes {
+	NFTA_TABLE_UNSPEC,
+	NFTA_TABLE_NAME,
+	NFTA_TABLE_FLAGS,
+	__NFTA_TABLE_MAX
+};
+#define NFTA_TABLE_MAX		(__NFTA_TABLE_MAX - 1)
+
+/**
+ * enum nft_chain_attributes - nf_tables chain netlink attributes
+ *
+ * @NFTA_CHAIN_TABLE: name of the table containing the chain (NLA_STRING)
+ * @NFTA_CHAIN_HANDLE: numeric handle of the chain (NLA_U64)
+ * @NFTA_CHAIN_NAME: name of the chain (NLA_STRING)
+ * @NFTA_CHAIN_HOOK: hook specification for basechains (NLA_NESTED: nft_hook_attributes)
+ * @NFTA_CHAIN_POLICY: numeric policy of the chain (NLA_U32)
+ * @NFTA_CHAIN_USE: number of references to this chain (NLA_U32)
+ * @NFTA_CHAIN_TYPE: type name of the string (NLA_NUL_STRING)
+ * @NFTA_CHAIN_COUNTERS: counter specification of the chain (NLA_NESTED: nft_counter_attributes)
+ */
+enum nft_chain_attributes {
+	NFTA_CHAIN_UNSPEC,
+	NFTA_CHAIN_TABLE,
+	NFTA_CHAIN_HANDLE,
+	NFTA_CHAIN_NAME,
+	NFTA_CHAIN_HOOK,
+	NFTA_CHAIN_POLICY,
+	NFTA_CHAIN_USE,
+	NFTA_CHAIN_TYPE,
+	NFTA_CHAIN_COUNTERS,
+	__NFTA_CHAIN_MAX
+};
+#define NFTA_CHAIN_MAX		(__NFTA_CHAIN_MAX - 1)
+
+/**
+ * enum nft_rule_attributes - nf_tables rule netlink attributes
+ *
+ * @NFTA_RULE_TABLE: name of the table containing the rule (NLA_STRING)
+ * @NFTA_RULE_CHAIN: name of the chain containing the rule (NLA_STRING)
+ * @NFTA_RULE_HANDLE: numeric handle of the rule (NLA_U64)
+ * @NFTA_RULE_EXPRESSIONS: list of expressions (NLA_NESTED: nft_expr_attributes)
+ * @NFTA_RULE_COMPAT: compatibility specifications of the rule (NLA_NESTED: nft_rule_compat_attributes)
+ * @NFTA_RULE_POSITION: numeric handle of the previous rule (NLA_U64)
+ */
+enum nft_rule_attributes {
+	NFTA_RULE_UNSPEC,
+	NFTA_RULE_TABLE,
+	NFTA_RULE_CHAIN,
+	NFTA_RULE_HANDLE,
+	NFTA_RULE_EXPRESSIONS,
+	NFTA_RULE_COMPAT,
+	NFTA_RULE_POSITION,
+	__NFTA_RULE_MAX
+};
+#define NFTA_RULE_MAX		(__NFTA_RULE_MAX - 1)
+
+/**
+ * enum nft_rule_compat_flags - nf_tables rule compat flags
+ *
+ * @NFT_RULE_COMPAT_F_INV: invert the check result
+ */
+enum nft_rule_compat_flags {
+	NFT_RULE_COMPAT_F_INV	= (1 << 1),
+	NFT_RULE_COMPAT_F_MASK	= NFT_RULE_COMPAT_F_INV,
+};
+
+/**
+ * enum nft_rule_compat_attributes - nf_tables rule compat attributes
+ *
+ * @NFTA_RULE_COMPAT_PROTO: numerice value of handled protocol (NLA_U32)
+ * @NFTA_RULE_COMPAT_FLAGS: bitmask of enum nft_rule_compat_flags (NLA_U32)
+ */
+enum nft_rule_compat_attributes {
+	NFTA_RULE_COMPAT_UNSPEC,
+	NFTA_RULE_COMPAT_PROTO,
+	NFTA_RULE_COMPAT_FLAGS,
+	__NFTA_RULE_COMPAT_MAX
+};
+#define NFTA_RULE_COMPAT_MAX	(__NFTA_RULE_COMPAT_MAX - 1)
+
+/**
+ * enum nft_set_flags - nf_tables set flags
+ *
+ * @NFT_SET_ANONYMOUS: name allocation, automatic cleanup on unlink
+ * @NFT_SET_CONSTANT: set contents may not change while bound
+ * @NFT_SET_INTERVAL: set contains intervals
+ * @NFT_SET_MAP: set is used as a dictionary
+ */
+enum nft_set_flags {
+	NFT_SET_ANONYMOUS		= 0x1,
+	NFT_SET_CONSTANT		= 0x2,
+	NFT_SET_INTERVAL		= 0x4,
+	NFT_SET_MAP			= 0x8,
+};
+
+/**
+ * enum nft_set_attributes - nf_tables set netlink attributes
+ *
+ * @NFTA_SET_TABLE: table name (NLA_STRING)
+ * @NFTA_SET_NAME: set name (NLA_STRING)
+ * @NFTA_SET_FLAGS: bitmask of enum nft_set_flags (NLA_U32)
+ * @NFTA_SET_KEY_TYPE: key data type, informational purpose only (NLA_U32)
+ * @NFTA_SET_KEY_LEN: key data length (NLA_U32)
+ * @NFTA_SET_DATA_TYPE: mapping data type (NLA_U32)
+ * @NFTA_SET_DATA_LEN: mapping data length (NLA_U32)
+ */
+enum nft_set_attributes {
+	NFTA_SET_UNSPEC,
+	NFTA_SET_TABLE,
+	NFTA_SET_NAME,
+	NFTA_SET_FLAGS,
+	NFTA_SET_KEY_TYPE,
+	NFTA_SET_KEY_LEN,
+	NFTA_SET_DATA_TYPE,
+	NFTA_SET_DATA_LEN,
+	__NFTA_SET_MAX
+};
+#define NFTA_SET_MAX		(__NFTA_SET_MAX - 1)
+
+/**
+ * enum nft_set_elem_flags - nf_tables set element flags
+ *
+ * @NFT_SET_ELEM_INTERVAL_END: element ends the previous interval
+ */
+enum nft_set_elem_flags {
+	NFT_SET_ELEM_INTERVAL_END	= 0x1,
+};
+
+/**
+ * enum nft_set_elem_attributes - nf_tables set element netlink attributes
+ *
+ * @NFTA_SET_ELEM_KEY: key value (NLA_NESTED: nft_data)
+ * @NFTA_SET_ELEM_DATA: data value of mapping (NLA_NESTED: nft_data_attributes)
+ * @NFTA_SET_ELEM_FLAGS: bitmask of nft_set_elem_flags (NLA_U32)
+ */
+enum nft_set_elem_attributes {
+	NFTA_SET_ELEM_UNSPEC,
+	NFTA_SET_ELEM_KEY,
+	NFTA_SET_ELEM_DATA,
+	NFTA_SET_ELEM_FLAGS,
+	__NFTA_SET_ELEM_MAX
+};
+#define NFTA_SET_ELEM_MAX	(__NFTA_SET_ELEM_MAX - 1)
+
+/**
+ * enum nft_set_elem_list_attributes - nf_tables set element list netlink attributes
+ *
+ * @NFTA_SET_ELEM_LIST_TABLE: table of the set to be changed (NLA_STRING)
+ * @NFTA_SET_ELEM_LIST_SET: name of the set to be changed (NLA_STRING)
+ * @NFTA_SET_ELEM_LIST_ELEMENTS: list of set elements (NLA_NESTED: nft_set_elem_attributes)
+ */
+enum nft_set_elem_list_attributes {
+	NFTA_SET_ELEM_LIST_UNSPEC,
+	NFTA_SET_ELEM_LIST_TABLE,
+	NFTA_SET_ELEM_LIST_SET,
+	NFTA_SET_ELEM_LIST_ELEMENTS,
+	__NFTA_SET_ELEM_LIST_MAX
+};
+#define NFTA_SET_ELEM_LIST_MAX	(__NFTA_SET_ELEM_LIST_MAX - 1)
+
+/**
+ * enum nft_data_types - nf_tables data types
+ *
+ * @NFT_DATA_VALUE: generic data
+ * @NFT_DATA_VERDICT: netfilter verdict
+ *
+ * The type of data is usually determined by the kernel directly and is not
+ * explicitly specified by userspace. The only difference are sets, where
+ * userspace specifies the key and mapping data types.
+ *
+ * The values 0xffffff00-0xffffffff are reserved for internally used types.
+ * The remaining range can be freely used by userspace to encode types, all
+ * values are equivalent to NFT_DATA_VALUE.
+ */
+enum nft_data_types {
+	NFT_DATA_VALUE,
+	NFT_DATA_VERDICT	= 0xffffff00U,
+};
+
+#define NFT_DATA_RESERVED_MASK	0xffffff00U
+
+/**
+ * enum nft_data_attributes - nf_tables data netlink attributes
+ *
+ * @NFTA_DATA_VALUE: generic data (NLA_BINARY)
+ * @NFTA_DATA_VERDICT: nf_tables verdict (NLA_NESTED: nft_verdict_attributes)
+ */
+enum nft_data_attributes {
+	NFTA_DATA_UNSPEC,
+	NFTA_DATA_VALUE,
+	NFTA_DATA_VERDICT,
+	__NFTA_DATA_MAX
+};
+#define NFTA_DATA_MAX		(__NFTA_DATA_MAX - 1)
+
+/**
+ * enum nft_verdict_attributes - nf_tables verdict netlink attributes
+ *
+ * @NFTA_VERDICT_CODE: nf_tables verdict (NLA_U32: enum nft_verdicts)
+ * @NFTA_VERDICT_CHAIN: jump target chain name (NLA_STRING)
+ */
+enum nft_verdict_attributes {
+	NFTA_VERDICT_UNSPEC,
+	NFTA_VERDICT_CODE,
+	NFTA_VERDICT_CHAIN,
+	__NFTA_VERDICT_MAX
+};
+#define NFTA_VERDICT_MAX	(__NFTA_VERDICT_MAX - 1)
+
+/**
+ * enum nft_expr_attributes - nf_tables expression netlink attributes
+ *
+ * @NFTA_EXPR_NAME: name of the expression type (NLA_STRING)
+ * @NFTA_EXPR_DATA: type specific data (NLA_NESTED)
+ */
+enum nft_expr_attributes {
+	NFTA_EXPR_UNSPEC,
+	NFTA_EXPR_NAME,
+	NFTA_EXPR_DATA,
+	__NFTA_EXPR_MAX
+};
+#define NFTA_EXPR_MAX		(__NFTA_EXPR_MAX - 1)
+
+/**
+ * enum nft_immediate_attributes - nf_tables immediate expression netlink attributes
+ *
+ * @NFTA_IMMEDIATE_DREG: destination register to load data into (NLA_U32)
+ * @NFTA_IMMEDIATE_DATA: data to load (NLA_NESTED: nft_data_attributes)
+ */
+enum nft_immediate_attributes {
+	NFTA_IMMEDIATE_UNSPEC,
+	NFTA_IMMEDIATE_DREG,
+	NFTA_IMMEDIATE_DATA,
+	__NFTA_IMMEDIATE_MAX
+};
+#define NFTA_IMMEDIATE_MAX	(__NFTA_IMMEDIATE_MAX - 1)
+
+/**
+ * enum nft_bitwise_attributes - nf_tables bitwise expression netlink attributes
+ *
+ * @NFTA_BITWISE_SREG: source register (NLA_U32: nft_registers)
+ * @NFTA_BITWISE_DREG: destination register (NLA_U32: nft_registers)
+ * @NFTA_BITWISE_LEN: length of operands (NLA_U32)
+ * @NFTA_BITWISE_MASK: mask value (NLA_NESTED: nft_data_attributes)
+ * @NFTA_BITWISE_XOR: xor value (NLA_NESTED: nft_data_attributes)
+ *
+ * The bitwise expression performs the following operation:
+ *
+ * dreg = (sreg & mask) ^ xor
+ *
+ * which allow to express all bitwise operations:
+ *
+ * 		mask	xor
+ * NOT:		1	1
+ * OR:		0	x
+ * XOR:		1	x
+ * AND:		x	0
+ */
+enum nft_bitwise_attributes {
+	NFTA_BITWISE_UNSPEC,
+	NFTA_BITWISE_SREG,
+	NFTA_BITWISE_DREG,
+	NFTA_BITWISE_LEN,
+	NFTA_BITWISE_MASK,
+	NFTA_BITWISE_XOR,
+	__NFTA_BITWISE_MAX
+};
+#define NFTA_BITWISE_MAX	(__NFTA_BITWISE_MAX - 1)
+
+/**
+ * enum nft_byteorder_ops - nf_tables byteorder operators
+ *
+ * @NFT_BYTEORDER_NTOH: network to host operator
+ * @NFT_BYTEORDER_HTON: host to network opertaor
+ */
+enum nft_byteorder_ops {
+	NFT_BYTEORDER_NTOH,
+	NFT_BYTEORDER_HTON,
+};
+
+/**
+ * enum nft_byteorder_attributes - nf_tables byteorder expression netlink attributes
+ *
+ * @NFTA_BYTEORDER_SREG: source register (NLA_U32: nft_registers)
+ * @NFTA_BYTEORDER_DREG: destination register (NLA_U32: nft_registers)
+ * @NFTA_BYTEORDER_OP: operator (NLA_U32: enum nft_byteorder_ops)
+ * @NFTA_BYTEORDER_LEN: length of the data (NLA_U32)
+ * @NFTA_BYTEORDER_SIZE: data size in bytes (NLA_U32: 2 or 4)
+ */
+enum nft_byteorder_attributes {
+	NFTA_BYTEORDER_UNSPEC,
+	NFTA_BYTEORDER_SREG,
+	NFTA_BYTEORDER_DREG,
+	NFTA_BYTEORDER_OP,
+	NFTA_BYTEORDER_LEN,
+	NFTA_BYTEORDER_SIZE,
+	__NFTA_BYTEORDER_MAX
+};
+#define NFTA_BYTEORDER_MAX	(__NFTA_BYTEORDER_MAX - 1)
+
+/**
+ * enum nft_cmp_ops - nf_tables relational operator
+ *
+ * @NFT_CMP_EQ: equal
+ * @NFT_CMP_NEQ: not equal
+ * @NFT_CMP_LT: less than
+ * @NFT_CMP_LTE: less than or equal to
+ * @NFT_CMP_GT: greater than
+ * @NFT_CMP_GTE: greater than or equal to
+ */
+enum nft_cmp_ops {
+	NFT_CMP_EQ,
+	NFT_CMP_NEQ,
+	NFT_CMP_LT,
+	NFT_CMP_LTE,
+	NFT_CMP_GT,
+	NFT_CMP_GTE,
+};
+
+/**
+ * enum nft_cmp_attributes - nf_tables cmp expression netlink attributes
+ *
+ * @NFTA_CMP_SREG: source register of data to compare (NLA_U32: nft_registers)
+ * @NFTA_CMP_OP: cmp operation (NLA_U32: nft_cmp_ops)
+ * @NFTA_CMP_DATA: data to compare against (NLA_NESTED: nft_data_attributes)
+ */
+enum nft_cmp_attributes {
+	NFTA_CMP_UNSPEC,
+	NFTA_CMP_SREG,
+	NFTA_CMP_OP,
+	NFTA_CMP_DATA,
+	__NFTA_CMP_MAX
+};
+#define NFTA_CMP_MAX		(__NFTA_CMP_MAX - 1)
+
+/**
+ * enum nft_lookup_attributes - nf_tables set lookup expression netlink attributes
+ *
+ * @NFTA_LOOKUP_SET: name of the set where to look for (NLA_STRING)
+ * @NFTA_LOOKUP_SREG: source register of the data to look for (NLA_U32: nft_registers)
+ * @NFTA_LOOKUP_DREG: destination register (NLA_U32: nft_registers)
+ */
+enum nft_lookup_attributes {
+	NFTA_LOOKUP_UNSPEC,
+	NFTA_LOOKUP_SET,
+	NFTA_LOOKUP_SREG,
+	NFTA_LOOKUP_DREG,
+	__NFTA_LOOKUP_MAX
+};
+#define NFTA_LOOKUP_MAX		(__NFTA_LOOKUP_MAX - 1)
+
+/**
+ * enum nft_payload_bases - nf_tables payload expression offset bases
+ *
+ * @NFT_PAYLOAD_LL_HEADER: link layer header
+ * @NFT_PAYLOAD_NETWORK_HEADER: network header
+ * @NFT_PAYLOAD_TRANSPORT_HEADER: transport header
+ */
+enum nft_payload_bases {
+	NFT_PAYLOAD_LL_HEADER,
+	NFT_PAYLOAD_NETWORK_HEADER,
+	NFT_PAYLOAD_TRANSPORT_HEADER,
+};
+
+/**
+ * enum nft_payload_attributes - nf_tables payload expression netlink attributes
+ *
+ * @NFTA_PAYLOAD_DREG: destination register to load data into (NLA_U32: nft_registers)
+ * @NFTA_PAYLOAD_BASE: payload base (NLA_U32: nft_payload_bases)
+ * @NFTA_PAYLOAD_OFFSET: payload offset relative to base (NLA_U32)
+ * @NFTA_PAYLOAD_LEN: payload length (NLA_U32)
+ */
+enum nft_payload_attributes {
+	NFTA_PAYLOAD_UNSPEC,
+	NFTA_PAYLOAD_DREG,
+	NFTA_PAYLOAD_BASE,
+	NFTA_PAYLOAD_OFFSET,
+	NFTA_PAYLOAD_LEN,
+	__NFTA_PAYLOAD_MAX
+};
+#define NFTA_PAYLOAD_MAX	(__NFTA_PAYLOAD_MAX - 1)
+
+/**
+ * enum nft_exthdr_attributes - nf_tables IPv6 extension header expression netlink attributes
+ *
+ * @NFTA_EXTHDR_DREG: destination register (NLA_U32: nft_registers)
+ * @NFTA_EXTHDR_TYPE: extension header type (NLA_U8)
+ * @NFTA_EXTHDR_OFFSET: extension header offset (NLA_U32)
+ * @NFTA_EXTHDR_LEN: extension header length (NLA_U32)
+ */
+enum nft_exthdr_attributes {
+	NFTA_EXTHDR_UNSPEC,
+	NFTA_EXTHDR_DREG,
+	NFTA_EXTHDR_TYPE,
+	NFTA_EXTHDR_OFFSET,
+	NFTA_EXTHDR_LEN,
+	__NFTA_EXTHDR_MAX
+};
+#define NFTA_EXTHDR_MAX		(__NFTA_EXTHDR_MAX - 1)
+
+/**
+ * enum nft_meta_keys - nf_tables meta expression keys
+ *
+ * @NFT_META_LEN: packet length (skb->len)
+ * @NFT_META_PROTOCOL: packet ethertype protocol (skb->protocol), invalid in OUTPUT
+ * @NFT_META_PRIORITY: packet priority (skb->priority)
+ * @NFT_META_MARK: packet mark (skb->mark)
+ * @NFT_META_IIF: packet input interface index (dev->ifindex)
+ * @NFT_META_OIF: packet output interface index (dev->ifindex)
+ * @NFT_META_IIFNAME: packet input interface name (dev->name)
+ * @NFT_META_OIFNAME: packet output interface name (dev->name)
+ * @NFT_META_IIFTYPE: packet input interface type (dev->type)
+ * @NFT_META_OIFTYPE: packet output interface type (dev->type)
+ * @NFT_META_SKUID: originating socket UID (fsuid)
+ * @NFT_META_SKGID: originating socket GID (fsgid)
+ * @NFT_META_NFTRACE: packet nftrace bit
+ * @NFT_META_RTCLASSID: realm value of packet's route (skb->dst->tclassid)
+ * @NFT_META_SECMARK: packet secmark (skb->secmark)
+ */
+enum nft_meta_keys {
+	NFT_META_LEN,
+	NFT_META_PROTOCOL,
+	NFT_META_PRIORITY,
+	NFT_META_MARK,
+	NFT_META_IIF,
+	NFT_META_OIF,
+	NFT_META_IIFNAME,
+	NFT_META_OIFNAME,
+	NFT_META_IIFTYPE,
+	NFT_META_OIFTYPE,
+	NFT_META_SKUID,
+	NFT_META_SKGID,
+	NFT_META_NFTRACE,
+	NFT_META_RTCLASSID,
+	NFT_META_SECMARK,
+};
+
+/**
+ * enum nft_meta_attributes - nf_tables meta expression netlink attributes
+ *
+ * @NFTA_META_DREG: destination register (NLA_U32)
+ * @NFTA_META_KEY: meta data item to load (NLA_U32: nft_meta_keys)
+ */
+enum nft_meta_attributes {
+	NFTA_META_UNSPEC,
+	NFTA_META_DREG,
+	NFTA_META_KEY,
+	__NFTA_META_MAX
+};
+#define NFTA_META_MAX		(__NFTA_META_MAX - 1)
+
+/**
+ * enum nft_ct_keys - nf_tables ct expression keys
+ *
+ * @NFT_CT_STATE: conntrack state (bitmask of enum ip_conntrack_info)
+ * @NFT_CT_DIRECTION: conntrack direction (enum ip_conntrack_dir)
+ * @NFT_CT_STATUS: conntrack status (bitmask of enum ip_conntrack_status)
+ * @NFT_CT_MARK: conntrack mark value
+ * @NFT_CT_SECMARK: conntrack secmark value
+ * @NFT_CT_EXPIRATION: relative conntrack expiration time in ms
+ * @NFT_CT_HELPER: connection tracking helper assigned to conntrack
+ * @NFT_CT_L3PROTOCOL: conntrack layer 3 protocol
+ * @NFT_CT_SRC: conntrack layer 3 protocol source (IPv4/IPv6 address)
+ * @NFT_CT_DST: conntrack layer 3 protocol destination (IPv4/IPv6 address)
+ * @NFT_CT_PROTOCOL: conntrack layer 4 protocol
+ * @NFT_CT_PROTO_SRC: conntrack layer 4 protocol source
+ * @NFT_CT_PROTO_DST: conntrack layer 4 protocol destination
+ */
+enum nft_ct_keys {
+	NFT_CT_STATE,
+	NFT_CT_DIRECTION,
+	NFT_CT_STATUS,
+	NFT_CT_MARK,
+	NFT_CT_SECMARK,
+	NFT_CT_EXPIRATION,
+	NFT_CT_HELPER,
+	NFT_CT_L3PROTOCOL,
+	NFT_CT_SRC,
+	NFT_CT_DST,
+	NFT_CT_PROTOCOL,
+	NFT_CT_PROTO_SRC,
+	NFT_CT_PROTO_DST,
+};
+
+/**
+ * enum nft_ct_attributes - nf_tables ct expression netlink attributes
+ *
+ * @NFTA_CT_DREG: destination register (NLA_U32)
+ * @NFTA_CT_KEY: conntrack data item to load (NLA_U32: nft_ct_keys)
+ * @NFTA_CT_DIRECTION: direction in case of directional keys (NLA_U8)
+ */
+enum nft_ct_attributes {
+	NFTA_CT_UNSPEC,
+	NFTA_CT_DREG,
+	NFTA_CT_KEY,
+	NFTA_CT_DIRECTION,
+	__NFTA_CT_MAX
+};
+#define NFTA_CT_MAX		(__NFTA_CT_MAX - 1)
+
+/**
+ * enum nft_limit_attributes - nf_tables limit expression netlink attributes
+ *
+ * @NFTA_LIMIT_RATE: refill rate (NLA_U64)
+ * @NFTA_LIMIT_UNIT: refill unit (NLA_U64)
+ */
+enum nft_limit_attributes {
+	NFTA_LIMIT_UNSPEC,
+	NFTA_LIMIT_RATE,
+	NFTA_LIMIT_UNIT,
+	__NFTA_LIMIT_MAX
+};
+#define NFTA_LIMIT_MAX		(__NFTA_LIMIT_MAX - 1)
+
+/**
+ * enum nft_counter_attributes - nf_tables counter expression netlink attributes
+ *
+ * @NFTA_COUNTER_BYTES: number of bytes (NLA_U64)
+ * @NFTA_COUNTER_PACKETS: number of packets (NLA_U64)
+ */
+enum nft_counter_attributes {
+	NFTA_COUNTER_UNSPEC,
+	NFTA_COUNTER_BYTES,
+	NFTA_COUNTER_PACKETS,
+	__NFTA_COUNTER_MAX
+};
+#define NFTA_COUNTER_MAX	(__NFTA_COUNTER_MAX - 1)
+
+/**
+ * enum nft_log_attributes - nf_tables log expression netlink attributes
+ *
+ * @NFTA_LOG_GROUP: netlink group to send messages to (NLA_U32)
+ * @NFTA_LOG_PREFIX: prefix to prepend to log messages (NLA_STRING)
+ * @NFTA_LOG_SNAPLEN: length of payload to include in netlink message (NLA_U32)
+ * @NFTA_LOG_QTHRESHOLD: queue threshold (NLA_U32)
+ */
+enum nft_log_attributes {
+	NFTA_LOG_UNSPEC,
+	NFTA_LOG_GROUP,
+	NFTA_LOG_PREFIX,
+	NFTA_LOG_SNAPLEN,
+	NFTA_LOG_QTHRESHOLD,
+	__NFTA_LOG_MAX
+};
+#define NFTA_LOG_MAX		(__NFTA_LOG_MAX - 1)
+
+/**
+ * enum nft_reject_types - nf_tables reject expression reject types
+ *
+ * @NFT_REJECT_ICMP_UNREACH: reject using ICMP unreachable
+ * @NFT_REJECT_TCP_RST: reject using TCP RST
+ */
+enum nft_reject_types {
+	NFT_REJECT_ICMP_UNREACH,
+	NFT_REJECT_TCP_RST,
+};
+
+/**
+ * enum nft_reject_attributes - nf_tables reject expression netlink attributes
+ *
+ * @NFTA_REJECT_TYPE: packet type to use (NLA_U32: nft_reject_types)
+ * @NFTA_REJECT_ICMP_CODE: ICMP code to use (NLA_U8)
+ */
+enum nft_reject_attributes {
+	NFTA_REJECT_UNSPEC,
+	NFTA_REJECT_TYPE,
+	NFTA_REJECT_ICMP_CODE,
+	__NFTA_REJECT_MAX
+};
+#define NFTA_REJECT_MAX		(__NFTA_REJECT_MAX - 1)
+
+/**
+ * enum nft_nat_types - nf_tables nat expression NAT types
+ *
+ * @NFT_NAT_SNAT: source NAT
+ * @NFT_NAT_DNAT: destination NAT
+ */
+enum nft_nat_types {
+	NFT_NAT_SNAT,
+	NFT_NAT_DNAT,
+};
+
+/**
+ * enum nft_nat_attributes - nf_tables nat expression netlink attributes
+ *
+ * @NFTA_NAT_TYPE: NAT type (NLA_U32: nft_nat_types)
+ * @NFTA_NAT_FAMILY: NAT family (NLA_U32)
+ * @NFTA_NAT_REG_ADDR_MIN: source register of address range start (NLA_U32: nft_registers)
+ * @NFTA_NAT_REG_ADDR_MAX: source register of address range end (NLA_U32: nft_registers)
+ * @NFTA_NAT_REG_PROTO_MIN: source register of proto range start (NLA_U32: nft_registers)
+ * @NFTA_NAT_REG_PROTO_MAX: source register of proto range end (NLA_U32: nft_registers)
+ */
+enum nft_nat_attributes {
+	NFTA_NAT_UNSPEC,
+	NFTA_NAT_TYPE,
+	NFTA_NAT_FAMILY,
+	NFTA_NAT_REG_ADDR_MIN,
+	NFTA_NAT_REG_ADDR_MAX,
+	NFTA_NAT_REG_PROTO_MIN,
+	NFTA_NAT_REG_PROTO_MAX,
+	__NFTA_NAT_MAX
+};
+#define NFTA_NAT_MAX		(__NFTA_NAT_MAX - 1)
+
+#endif /* _LINUX_NF_TABLES_H */
diff --git a/include/linux/netfilter/nf_tables_compat.h b/include/linux/netfilter/nf_tables_compat.h
new file mode 100644
index 0000000..36fb81d
--- /dev/null
+++ b/include/linux/netfilter/nf_tables_compat.h
@@ -0,0 +1,20 @@
+#ifndef _NFT_COMPAT_NFNETLINK_H_
+#define _NFT_COMPAT_NFNETLINK_H_
+
+#define NFT_COMPAT_NAME_MAX	32
+
+enum {
+	NFNL_MSG_COMPAT_GET,
+	NFNL_MSG_COMPAT_MAX
+};
+
+enum {
+	NFTA_COMPAT_UNSPEC = 0,
+	NFTA_COMPAT_NAME,
+	NFTA_COMPAT_REV,
+	NFTA_COMPAT_TYPE,
+	__NFTA_COMPAT_MAX,
+};
+#define NFTA_COMPAT_MAX (__NFTA_COMPAT_MAX - 1)
+
+#endif
diff --git a/include/linux/netfilter/nfnetlink.h b/include/linux/netfilter/nfnetlink.h
new file mode 100644
index 0000000..06eea26
--- /dev/null
+++ b/include/linux/netfilter/nfnetlink.h
@@ -0,0 +1,64 @@
+#ifndef _NFNETLINK_H
+#define _NFNETLINK_H
+#include <linux/types.h>
+#include <linux/netfilter/nfnetlink_compat.h>
+
+enum nfnetlink_groups {
+	NFNLGRP_NONE,
+#define NFNLGRP_NONE			NFNLGRP_NONE
+	NFNLGRP_CONNTRACK_NEW,
+#define NFNLGRP_CONNTRACK_NEW		NFNLGRP_CONNTRACK_NEW
+	NFNLGRP_CONNTRACK_UPDATE,
+#define NFNLGRP_CONNTRACK_UPDATE	NFNLGRP_CONNTRACK_UPDATE
+	NFNLGRP_CONNTRACK_DESTROY,
+#define NFNLGRP_CONNTRACK_DESTROY	NFNLGRP_CONNTRACK_DESTROY
+	NFNLGRP_CONNTRACK_EXP_NEW,
+#define	NFNLGRP_CONNTRACK_EXP_NEW	NFNLGRP_CONNTRACK_EXP_NEW
+	NFNLGRP_CONNTRACK_EXP_UPDATE,
+#define NFNLGRP_CONNTRACK_EXP_UPDATE	NFNLGRP_CONNTRACK_EXP_UPDATE
+	NFNLGRP_CONNTRACK_EXP_DESTROY,
+#define NFNLGRP_CONNTRACK_EXP_DESTROY	NFNLGRP_CONNTRACK_EXP_DESTROY
+	NFNLGRP_NFTABLES,
+#define NFNLGRP_NFTABLES		NFNLGRP_NFTABLES
+	__NFNLGRP_MAX,
+};
+#define NFNLGRP_MAX	(__NFNLGRP_MAX - 1)
+
+/* General form of address family dependent message.
+ */
+struct nfgenmsg {
+	__u8  nfgen_family;		/* AF_xxx */
+	__u8  version;		/* nfnetlink version */
+	__be16    res_id;		/* resource id */
+};
+
+#define NFNETLINK_V0	0
+
+/* netfilter netlink message types are split in two pieces:
+ * 8 bit subsystem, 8bit operation.
+ */
+
+#define NFNL_SUBSYS_ID(x)	((x & 0xff00) >> 8)
+#define NFNL_MSG_TYPE(x)	(x & 0x00ff)
+
+/* No enum here, otherwise __stringify() trick of MODULE_ALIAS_NFNL_SUBSYS()
+ * won't work anymore */
+#define NFNL_SUBSYS_NONE 		0
+#define NFNL_SUBSYS_CTNETLINK		1
+#define NFNL_SUBSYS_CTNETLINK_EXP	2
+#define NFNL_SUBSYS_QUEUE		3
+#define NFNL_SUBSYS_ULOG		4
+#define NFNL_SUBSYS_OSF			5
+#define NFNL_SUBSYS_IPSET		6
+#define NFNL_SUBSYS_ACCT		7
+#define NFNL_SUBSYS_CTNETLINK_TIMEOUT	8
+#define NFNL_SUBSYS_CTHELPER		9
+#define NFNL_SUBSYS_NFTABLES		10
+#define NFNL_SUBSYS_NFT_COMPAT		11
+#define NFNL_SUBSYS_COUNT		12
+
+/* Reserved control nfnetlink messages */
+#define NFNL_MSG_BATCH_BEGIN		NLMSG_MIN_TYPE
+#define NFNL_MSG_BATCH_END		NLMSG_MIN_TYPE+1
+
+#endif /* _NFNETLINK_H */
diff --git a/include/linux/netfilter_arp.h b/include/linux/netfilter_arp.h
new file mode 100644
index 0000000..92bc6dd
--- /dev/null
+++ b/include/linux/netfilter_arp.h
@@ -0,0 +1,19 @@
+#ifndef __LINUX_ARP_NETFILTER_H
+#define __LINUX_ARP_NETFILTER_H
+
+/* ARP-specific defines for netfilter.
+ * (C)2002 Rusty Russell IBM -- This code is GPL.
+ */
+
+#include <linux/netfilter.h>
+
+/* There is no PF_ARP. */
+#define NF_ARP		0
+
+/* ARP Hooks */
+#define NF_ARP_IN	0
+#define NF_ARP_OUT	1
+#define NF_ARP_FORWARD	2
+#define NF_ARP_NUMHOOKS	3
+
+#endif /* __LINUX_ARP_NETFILTER_H */
diff --git a/include/linux/netfilter_arp/arp_tables.h b/include/linux/netfilter_arp/arp_tables.h
new file mode 100644
index 0000000..bb1ec64
--- /dev/null
+++ b/include/linux/netfilter_arp/arp_tables.h
@@ -0,0 +1,204 @@
+/*
+ * 	Format of an ARP firewall descriptor
+ *
+ * 	src, tgt, src_mask, tgt_mask, arpop, arpop_mask are always stored in
+ *	network byte order.
+ * 	flags are stored in host byte order (of course).
+ */
+
+#ifndef _ARPTABLES_H
+#define _ARPTABLES_H
+
+#include <linux/types.h>
+
+#include <linux/netfilter_arp.h>
+
+#include <linux/netfilter/x_tables.h>
+
+#define ARPT_FUNCTION_MAXNAMELEN XT_FUNCTION_MAXNAMELEN
+#define ARPT_TABLE_MAXNAMELEN XT_TABLE_MAXNAMELEN
+#define arpt_entry_target xt_entry_target
+#define arpt_standard_target xt_standard_target
+#define arpt_error_target xt_error_target
+#define ARPT_CONTINUE XT_CONTINUE
+#define ARPT_RETURN XT_RETURN
+#define arpt_counters_info xt_counters_info
+#define arpt_counters xt_counters
+#define ARPT_STANDARD_TARGET XT_STANDARD_TARGET
+#define ARPT_ERROR_TARGET XT_ERROR_TARGET
+#define ARPT_ENTRY_ITERATE(entries, size, fn, args...) \
+	XT_ENTRY_ITERATE(struct arpt_entry, entries, size, fn, ## args)
+
+#define ARPT_DEV_ADDR_LEN_MAX 16
+
+struct arpt_devaddr_info {
+	char addr[ARPT_DEV_ADDR_LEN_MAX];
+	char mask[ARPT_DEV_ADDR_LEN_MAX];
+};
+
+/* Yes, Virginia, you have to zero the padding. */
+struct arpt_arp {
+	/* Source and target IP addr */
+	struct in_addr src, tgt;
+	/* Mask for src and target IP addr */
+	struct in_addr smsk, tmsk;
+
+	/* Device hw address length, src+target device addresses */
+	__u8 arhln, arhln_mask;
+	struct arpt_devaddr_info src_devaddr;
+	struct arpt_devaddr_info tgt_devaddr;
+
+	/* ARP operation code. */
+	__be16 arpop, arpop_mask;
+
+	/* ARP hardware address and protocol address format. */
+	__be16 arhrd, arhrd_mask;
+	__be16 arpro, arpro_mask;
+
+	/* The protocol address length is only accepted if it is 4
+	 * so there is no use in offering a way to do filtering on it.
+	 */
+
+	char iniface[IFNAMSIZ], outiface[IFNAMSIZ];
+	unsigned char iniface_mask[IFNAMSIZ], outiface_mask[IFNAMSIZ];
+
+	/* Flags word */
+	__u8 flags;
+	/* Inverse flags */
+	__u16 invflags;
+};
+
+/* Values for "flag" field in struct arpt_ip (general arp structure).
+ * No flags defined yet.
+ */
+#define ARPT_F_MASK		0x00	/* All possible flag bits mask. */
+
+/* Values for "inv" field in struct arpt_arp. */
+#define ARPT_INV_VIA_IN		0x0001	/* Invert the sense of IN IFACE. */
+#define ARPT_INV_VIA_OUT	0x0002	/* Invert the sense of OUT IFACE */
+#define ARPT_INV_SRCIP		0x0004	/* Invert the sense of SRC IP. */
+#define ARPT_INV_TGTIP		0x0008	/* Invert the sense of TGT IP. */
+#define ARPT_INV_SRCDEVADDR	0x0010	/* Invert the sense of SRC DEV ADDR. */
+#define ARPT_INV_TGTDEVADDR	0x0020	/* Invert the sense of TGT DEV ADDR. */
+#define ARPT_INV_ARPOP		0x0040	/* Invert the sense of ARP OP. */
+#define ARPT_INV_ARPHRD		0x0080	/* Invert the sense of ARP HRD. */
+#define ARPT_INV_ARPPRO		0x0100	/* Invert the sense of ARP PRO. */
+#define ARPT_INV_ARPHLN		0x0200	/* Invert the sense of ARP HLN. */
+#define ARPT_INV_MASK		0x03FF	/* All possible flag bits mask. */
+
+/* This structure defines each of the firewall rules.  Consists of 3
+   parts which are 1) general ARP header stuff 2) match specific
+   stuff 3) the target to perform if the rule matches */
+struct arpt_entry
+{
+	struct arpt_arp arp;
+
+	/* Size of arpt_entry + matches */
+	__u16 target_offset;
+	/* Size of arpt_entry + matches + target */
+	__u16 next_offset;
+
+	/* Back pointer */
+	unsigned int comefrom;
+
+	/* Packet and byte counters. */
+	struct xt_counters counters;
+
+	/* The matches (if any), then the target. */
+	unsigned char elems[0];
+};
+
+/*
+ * New IP firewall options for [gs]etsockopt at the RAW IP level.
+ * Unlike BSD Linux inherits IP options so you don't have to use a raw
+ * socket for this. Instead we check rights in the calls.
+ *
+ * ATTENTION: check linux/in.h before adding new number here.
+ */
+#define ARPT_BASE_CTL		96
+
+#define ARPT_SO_SET_REPLACE		(ARPT_BASE_CTL)
+#define ARPT_SO_SET_ADD_COUNTERS	(ARPT_BASE_CTL + 1)
+#define ARPT_SO_SET_MAX			ARPT_SO_SET_ADD_COUNTERS
+
+#define ARPT_SO_GET_INFO		(ARPT_BASE_CTL)
+#define ARPT_SO_GET_ENTRIES		(ARPT_BASE_CTL + 1)
+/* #define ARPT_SO_GET_REVISION_MATCH	(APRT_BASE_CTL + 2) */
+#define ARPT_SO_GET_REVISION_TARGET	(ARPT_BASE_CTL + 3)
+#define ARPT_SO_GET_MAX			(ARPT_SO_GET_REVISION_TARGET)
+
+/* The argument to ARPT_SO_GET_INFO */
+struct arpt_getinfo {
+	/* Which table: caller fills this in. */
+	char name[XT_TABLE_MAXNAMELEN];
+
+	/* Kernel fills these in. */
+	/* Which hook entry points are valid: bitmask */
+	unsigned int valid_hooks;
+
+	/* Hook entry points: one per netfilter hook. */
+	unsigned int hook_entry[NF_ARP_NUMHOOKS];
+
+	/* Underflow points. */
+	unsigned int underflow[NF_ARP_NUMHOOKS];
+
+	/* Number of entries */
+	unsigned int num_entries;
+
+	/* Size of entries. */
+	unsigned int size;
+};
+
+/* The argument to ARPT_SO_SET_REPLACE. */
+struct arpt_replace {
+	/* Which table. */
+	char name[XT_TABLE_MAXNAMELEN];
+
+	/* Which hook entry points are valid: bitmask.  You can't
+           change this. */
+	unsigned int valid_hooks;
+
+	/* Number of entries */
+	unsigned int num_entries;
+
+	/* Total size of new entries */
+	unsigned int size;
+
+	/* Hook entry points. */
+	unsigned int hook_entry[NF_ARP_NUMHOOKS];
+
+	/* Underflow points. */
+	unsigned int underflow[NF_ARP_NUMHOOKS];
+
+	/* Information about old entries: */
+	/* Number of counters (must be equal to current number of entries). */
+	unsigned int num_counters;
+	/* The old entries' counters. */
+	struct xt_counters *counters;
+
+	/* The entries (hang off end: not really an array). */
+	struct arpt_entry entries[0];
+};
+
+/* The argument to ARPT_SO_GET_ENTRIES. */
+struct arpt_get_entries {
+	/* Which table: user fills this in. */
+	char name[XT_TABLE_MAXNAMELEN];
+
+	/* User fills this in: total entry size. */
+	unsigned int size;
+
+	/* The entries. */
+	struct arpt_entry entrytable[0];
+};
+
+/* Helper functions */
+static __inline__ struct xt_entry_target *arpt_get_target(struct arpt_entry *e)
+{
+	return (void *)e + e->target_offset;
+}
+
+/*
+ *	Main firewall chains definitions and global var's definitions.
+ */
+#endif /* _ARPTABLES_H */
diff --git a/include/linux/netfilter_arp/arpt_mangle.h b/include/linux/netfilter_arp/arpt_mangle.h
new file mode 100644
index 0000000..250f502
--- /dev/null
+++ b/include/linux/netfilter_arp/arpt_mangle.h
@@ -0,0 +1,26 @@
+#ifndef _ARPT_MANGLE_H
+#define _ARPT_MANGLE_H
+#include <linux/netfilter_arp/arp_tables.h>
+
+#define ARPT_MANGLE_ADDR_LEN_MAX sizeof(struct in_addr)
+struct arpt_mangle
+{
+	char src_devaddr[ARPT_DEV_ADDR_LEN_MAX];
+	char tgt_devaddr[ARPT_DEV_ADDR_LEN_MAX];
+	union {
+		struct in_addr src_ip;
+	} u_s;
+	union {
+		struct in_addr tgt_ip;
+	} u_t;
+	u_int8_t flags;
+	int target;
+};
+
+#define ARPT_MANGLE_SDEV 0x01
+#define ARPT_MANGLE_TDEV 0x02
+#define ARPT_MANGLE_SIP 0x04
+#define ARPT_MANGLE_TIP 0x08
+#define ARPT_MANGLE_MASK 0x0f
+
+#endif /* _ARPT_MANGLE_H */
diff --git a/include/xtables.h b/include/xtables.h
index 0217267..bad11a8 100644
--- a/include/xtables.h
+++ b/include/xtables.h
@@ -401,6 +401,7 @@
 	struct option *orig_opts;
 	struct option *opts;
 	void (*exit_err)(enum xtables_exittype status, const char *msg, ...) __attribute__((noreturn, format(printf,2,3)));
+	int (*compat_rev)(const char *name, uint8_t rev, int opt);
 };
 
 #define XT_GETOPT_TABLEEND {.name = NULL, .has_arg = false}
@@ -432,6 +433,8 @@
 	enum xtables_tryload, struct xtables_rule_match **match);
 extern struct xtables_target *xtables_find_target(const char *name,
 	enum xtables_tryload);
+extern int xtables_compatible_revision(const char *name, uint8_t revision,
+				       int opt);
 
 extern void xtables_rule_matches_free(struct xtables_rule_match **matches);
 
diff --git a/iptables/.gitignore b/iptables/.gitignore
index 31baf7d..6c0ade1 100644
--- a/iptables/.gitignore
+++ b/iptables/.gitignore
@@ -11,5 +11,8 @@
 /iptables-static
 /iptables-xml
 /xtables-multi
+/xtables-config-parser.c
+/xtables-config-parser.h
+/xtables-config-syntax.c
 
 /xtables.pc
diff --git a/iptables/Makefile.am b/iptables/Makefile.am
index a4246eb..41bca7c 100644
--- a/iptables/Makefile.am
+++ b/iptables/Makefile.am
@@ -1,7 +1,8 @@
 # -*- Makefile -*-
 
 AM_CFLAGS        = ${regular_CFLAGS}
-AM_CPPFLAGS      = ${regular_CPPFLAGS} -I${top_builddir}/include -I${top_srcdir}/include ${kinclude_CPPFLAGS}
+AM_CPPFLAGS      = ${regular_CPPFLAGS} -I${top_builddir}/include -I${top_srcdir}/include ${kinclude_CPPFLAGS} ${libmnl_CFLAGS} ${libnftnl_CFLAGS}
+AM_YFLAGS = -d
 
 xtables_multi_SOURCES  = xtables-multi.c iptables-xml.c
 xtables_multi_CFLAGS   = ${AM_CFLAGS}
@@ -24,11 +25,37 @@
 xtables_multi_SOURCES += xshared.c
 xtables_multi_LDADD   += ../libxtables/libxtables.la -lm
 
+# nftables compatibility layer
+if ENABLE_NFTABLES
+xtables_compat_multi_SOURCES  = xtables-compat-multi.c iptables-xml.c
+xtables_compat_multi_CFLAGS   = ${AM_CFLAGS}
+xtables_compat_multi_LDADD    = ../extensions/libext.a
+if ENABLE_STATIC
+xtables_compat_multi_CFLAGS  += -DALL_INCLUSIVE
+endif
+xtables_compat_multi_CFLAGS  += -DENABLE_NFTABLES -DENABLE_IPV4 -DENABLE_IPV6
+xtables_compat_multi_SOURCES += xtables-config-parser.y xtables-config-syntax.l
+xtables_compat_multi_SOURCES += xtables-save.c xtables-restore.c \
+				xtables-standalone.c xtables.c nft.c \
+				nft-shared.c nft-ipv4.c nft-ipv6.c nft-arp.c \
+				xtables-config.c xtables-events.c \
+				xtables-arp-standalone.c xtables-arp.c
+xtables_compat_multi_LDADD   += ${libmnl_LIBS} ${libnftnl_LIBS}
+# yacc and lex generate dirty code
+xtables_compat_multi-xtables-config-parser.o xtables_compat_multi-xtables-config-syntax.o: AM_CFLAGS += -Wno-missing-prototypes -Wno-missing-declarations -Wno-implicit-function-declaration -Wno-nested-externs -Wno-undef -Wno-redundant-decls
+xtables_compat_multi_SOURCES += xshared.c
+xtables_compat_multi_LDADD   += ../libxtables/libxtables.la -lm
+endif
+
 sbin_PROGRAMS    = xtables-multi
+if ENABLE_NFTABLES
+sbin_PROGRAMS	+= xtables-compat-multi
+endif
 man_MANS         = iptables.8 iptables-restore.8 iptables-save.8 \
                    iptables-xml.1 ip6tables.8 ip6tables-restore.8 \
                    ip6tables-save.8 iptables-extensions.8
-CLEANFILES       = iptables.8
+CLEANFILES       = iptables.8 \
+		   xtables-config-parser.c xtables-config-syntax.c
 
 vx_bin_links   = iptables-xml
 if ENABLE_IPV4
@@ -37,6 +64,11 @@
 if ENABLE_IPV6
 v6_sbin_links  = ip6tables ip6tables-restore ip6tables-save
 endif
+if ENABLE_NFTABLES
+x_sbin_links  = iptables-compat iptables-compat-restore iptables-compat-save \
+		ip6tables-compat ip6tables-compat-restore ip6tables-compat-save \
+		arptables-compat xtables-config xtables-events
+endif
 
 iptables-extensions.8: iptables-extensions.8.tmpl ../extensions/matches.man ../extensions/targets.man
 	${AM_VERBOSE_GEN} sed \
@@ -52,3 +84,4 @@
 	for i in ${vx_bin_links}; do ${LN_S} -f "${sbindir}/xtables-multi" "${DESTDIR}${bindir}/$$i"; done;
 	for i in ${v4_sbin_links}; do ${LN_S} -f xtables-multi "${DESTDIR}${sbindir}/$$i"; done;
 	for i in ${v6_sbin_links}; do ${LN_S} -f xtables-multi "${DESTDIR}${sbindir}/$$i"; done;
+	for i in ${x_sbin_links}; do ${LN_S} -f xtables-compat-multi "${DESTDIR}${sbindir}/$$i"; done;
diff --git a/iptables/ip6tables.c b/iptables/ip6tables.c
index a5199d5..2ebfd6c 100644
--- a/iptables/ip6tables.c
+++ b/iptables/ip6tables.c
@@ -121,6 +121,7 @@
 	.program_version = IPTABLES_VERSION,
 	.orig_opts = original_opts,
 	.exit_err = ip6tables_exit_error,
+	.compat_rev = xtables_compatible_revision,
 };
 
 /* Table of legal combinations of commands and options.  If any of the
diff --git a/iptables/iptables.c b/iptables/iptables.c
index 5cd2596..471bff0 100644
--- a/iptables/iptables.c
+++ b/iptables/iptables.c
@@ -120,6 +120,7 @@
 	.program_version = IPTABLES_VERSION,
 	.orig_opts = original_opts,
 	.exit_err = iptables_exit_error,
+	.compat_rev = xtables_compatible_revision,
 };
 
 /* Table of legal combinations of commands and options.  If any of the
diff --git a/iptables/nft-arp.c b/iptables/nft-arp.c
new file mode 100644
index 0000000..1710136
--- /dev/null
+++ b/iptables/nft-arp.c
@@ -0,0 +1,649 @@
+/*
+ * (C) 2013 by Pablo Neira Ayuso <pablo@netfilter.org>
+ * (C) 2013 by Giuseppe Longo <giuseppelng@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This code has been sponsored by Sophos Astaro <http://www.sophos.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netdb.h>
+#include <net/if_arp.h>
+
+#include <xtables.h>
+#include <libiptc/libxtc.h>
+#include <net/if_arp.h>
+#include <netinet/if_ether.h>
+
+#include <linux/netfilter_arp/arp_tables.h>
+#include <linux/netfilter/nf_tables.h>
+
+#include "nft-shared.h"
+#include "nft.h"
+
+/* a few names */
+char *opcodes[] =
+{
+	"Request",
+	"Reply",
+	"Request_Reverse",
+	"Reply_Reverse",
+	"DRARP_Request",
+	"DRARP_Reply",
+	"DRARP_Error",
+	"InARP_Request",
+	"ARP_NAK",
+};
+
+static char *
+addr_to_dotted(const struct in_addr *addrp)
+{
+	static char buf[20];
+	const unsigned char *bytep;
+
+	bytep = (const unsigned char *) &(addrp->s_addr);
+	sprintf(buf, "%d.%d.%d.%d", bytep[0], bytep[1], bytep[2], bytep[3]);
+	return buf;
+}
+
+static char *
+addr_to_host(const struct in_addr *addr)
+{
+	struct hostent *host;
+
+	if ((host = gethostbyaddr((char *) addr,
+					sizeof(struct in_addr), AF_INET)) != NULL)
+		return (char *) host->h_name;
+
+	return (char *) NULL;
+}
+
+static char *
+addr_to_network(const struct in_addr *addr)
+{
+	struct netent *net;
+
+	if ((net = getnetbyaddr((long) ntohl(addr->s_addr), AF_INET)) != NULL)
+		return (char *) net->n_name;
+
+	return (char *) NULL;
+}
+
+static char *
+addr_to_anyname(const struct in_addr *addr)
+{
+	char *name;
+
+	if ((name = addr_to_host(addr)) != NULL ||
+		(name = addr_to_network(addr)) != NULL)
+		return name;
+
+	return addr_to_dotted(addr);
+}
+
+static char *
+mask_to_dotted(const struct in_addr *mask)
+{
+	int i;
+	static char buf[20];
+	u_int32_t maskaddr, bits;
+
+	maskaddr = ntohl(mask->s_addr);
+
+	if (maskaddr == 0xFFFFFFFFL)
+		/* we don't want to see "/32" */
+		return "";
+
+	i = 32;
+	bits = 0xFFFFFFFEL;
+	while (--i >= 0 && maskaddr != bits)
+		bits <<= 1;
+	if (i >= 0)
+		sprintf(buf, "/%d", i);
+	else
+		/* mask was not a decent combination of 1's and 0's */
+		sprintf(buf, "/%s", addr_to_dotted(mask));
+
+	return buf;
+}
+
+static void print_mac(const unsigned char *mac, int l)
+{
+	int j;
+
+	for (j = 0; j < l; j++)
+		printf("%02x%s", mac[j],
+			(j==l-1) ? "" : ":");
+}
+
+static void print_mac_and_mask(const unsigned char *mac, const unsigned char *mask, int l)
+{
+	int i;
+
+	print_mac(mac, l);
+	for (i = 0; i < l ; i++)
+		if (mask[i] != 255)
+			break;
+	if (i == l)
+		return;
+	printf("/");
+	print_mac(mask, l);
+}
+
+static uint8_t arpt_to_ipt_flags(uint16_t invflags)
+{
+	uint8_t result = 0;
+
+	if (invflags & ARPT_INV_VIA_IN)
+		result |= IPT_INV_VIA_IN;
+
+	if (invflags & ARPT_INV_VIA_OUT)
+		result |= IPT_INV_VIA_OUT;
+
+	if (invflags & ARPT_INV_SRCIP)
+		result |= IPT_INV_SRCIP;
+
+	if (invflags & ARPT_INV_TGTIP)
+		result |= IPT_INV_DSTIP;
+
+	if (invflags & ARPT_INV_ARPPRO)
+		result |= IPT_INV_PROTO;
+
+	if (invflags & ARPT_INV_MASK)
+		result |= IPT_INV_MASK;
+
+	return result;
+}
+
+static int nft_arp_add(struct nft_rule *r, void *data)
+{
+	struct arpt_entry *fw = data;
+	uint8_t flags = arpt_to_ipt_flags(fw->arp.invflags);
+	struct xt_entry_target *t;
+	char *targname;
+	int ret;
+
+	if (fw->arp.iniface[0] != '\0')
+		add_iniface(r, fw->arp.iniface, flags);
+
+	if (fw->arp.outiface[0] != '\0')
+		add_outiface(r, fw->arp.outiface, flags);
+
+	if (fw->arp.arhrd != 0) {
+		add_payload(r, offsetof(struct arphdr, ar_hrd), 2);
+		add_cmp_u16(r, fw->arp.arhrd, NFT_CMP_EQ);
+	}
+
+	if (fw->arp.arpro != 0) {
+	        add_payload(r, offsetof(struct arphdr, ar_pro), 2);
+		add_cmp_u16(r, fw->arp.arpro, NFT_CMP_EQ);
+	}
+
+	if (fw->arp.arhln != 0)
+		add_proto(r, offsetof(struct arphdr, ar_hln), 1,
+			  fw->arp.arhln, flags);
+
+	add_proto(r, offsetof(struct arphdr, ar_pln), 1, 4, 0);
+
+	if (fw->arp.arpop != 0) {
+		add_payload(r, offsetof(struct arphdr, ar_op), 2);
+		add_cmp_u16(r, fw->arp.arpop, NFT_CMP_EQ);
+	}
+
+	if (fw->arp.src_devaddr.addr[0] != '\0') {
+		add_payload(r, sizeof(struct arphdr), fw->arp.arhln);
+		add_cmp_ptr(r, NFT_CMP_EQ, fw->arp.src_devaddr.addr, fw->arp.arhln);
+	}
+
+	if (fw->arp.src.s_addr != 0)
+		add_addr(r, sizeof(struct arphdr) + fw->arp.arhln,
+			 &fw->arp.src.s_addr, 4, flags);
+
+	if (fw->arp.tgt_devaddr.addr[0] != '\0') {
+		add_payload(r, sizeof(struct arphdr) + fw->arp.arhln + 4, fw->arp.arhln);
+		add_cmp_ptr(r, NFT_CMP_EQ, fw->arp.tgt_devaddr.addr, fw->arp.arhln);
+	}
+
+	if (fw->arp.tgt.s_addr != 0)
+		add_addr(r, sizeof(struct arphdr) + fw->arp.arhln + sizeof(struct in_addr),
+			 &fw->arp.tgt.s_addr, 4, flags);
+
+	/* Counters need to me added before the target, otherwise they are
+	 * increased for each rule because of the way nf_tables works.
+	 */
+	if (add_counters(r, fw->counters.pcnt, fw->counters.bcnt) < 0)
+		return -1;
+
+	t = nft_arp_get_target(fw);
+	targname = t->u.user.name;
+
+	/* Standard target? */
+	if (strcmp(targname, XTC_LABEL_ACCEPT) == 0)
+		ret = add_verdict(r, NF_ACCEPT);
+	else if (strcmp(targname, XTC_LABEL_DROP) == 0)
+		ret = add_verdict(r, NF_DROP);
+	else if (strcmp(targname, XTC_LABEL_RETURN) == 0)
+		ret = add_verdict(r, NFT_RETURN);
+	else if (xtables_find_target(targname, XTF_TRY_LOAD) != NULL)
+		ret = add_target(r, t);
+	else
+		ret = add_jumpto(r, targname, NFT_JUMP);
+
+	return ret;
+}
+
+static uint16_t ipt_to_arpt_flags(uint8_t invflags)
+{
+	uint16_t result = 0;
+
+	if (invflags & IPT_INV_VIA_IN)
+		result |= ARPT_INV_VIA_IN;
+
+	if (invflags & IPT_INV_VIA_OUT)
+		result |= ARPT_INV_VIA_OUT;
+
+	if (invflags & IPT_INV_SRCIP)
+		result |= ARPT_INV_SRCIP;
+
+	if (invflags & IPT_INV_DSTIP)
+		result |= ARPT_INV_TGTIP;
+
+	if (invflags & IPT_INV_PROTO)
+		result |= ARPT_INV_ARPPRO;
+
+	if (invflags & IPT_INV_MASK)
+		result |= ARPT_INV_MASK;
+
+	return result;
+}
+
+static void nft_arp_parse_meta(struct nft_rule_expr *e, uint8_t key,
+			       void *data)
+{
+	struct arpt_entry *fw = data;
+	uint8_t flags = 0;
+
+	parse_meta(e, key, fw->arp.iniface, fw->arp.iniface_mask,
+		   fw->arp.outiface, fw->arp.outiface_mask,
+		   &flags);
+
+	fw->arp.invflags |= ipt_to_arpt_flags(flags);
+}
+
+static void nft_arp_parse_target(struct xtables_target *target, void *data)
+{
+	struct arpt_entry *fw = data;
+	struct xt_entry_target **t;
+
+	fw->target_offset = offsetof(struct arpt_entry, elems);
+	fw->next_offset = fw->target_offset + target->t->u.target_size;
+
+	t = (void *) &fw->elems;
+	*t = target->t;
+}
+
+static void nft_arp_parse_immediate(const char *jumpto, bool nft_goto,
+				    void *data)
+{
+	struct xtables_target *target;
+	size_t size;
+
+	target = xtables_find_target(XT_STANDARD_TARGET,
+				     XTF_LOAD_MUST_SUCCEED);
+
+	size = XT_ALIGN(sizeof(struct xt_entry_target)) + target->size;
+
+	target->t = xtables_calloc(1, size);
+	target->t->u.target_size = size;
+	strcpy(target->t->u.user.name, jumpto);
+	target->t->u.user.revision = target->revision;
+
+	nft_arp_parse_target(target, data);
+}
+
+static void nft_arp_parse_payload(struct nft_rule_expr_iter *iter,
+				  uint32_t offset, void *data)
+{
+	struct arpt_entry *fw = data;
+	struct in_addr addr;
+	unsigned short int ar_hrd, ar_pro, ar_op, ar_hln;
+	bool inv;
+
+	switch (offset) {
+	case offsetof(struct arphdr, ar_hrd):
+		get_cmp_data(iter, &ar_hrd, sizeof(ar_hrd), &inv);
+		fw->arp.arhrd = ar_hrd;
+		fw->arp.arhrd_mask = 0xffff;
+		if (inv)
+			fw->arp.invflags |= ARPT_INV_ARPHRD;
+		break;
+	case offsetof(struct arphdr, ar_pro):
+		get_cmp_data(iter, &ar_pro, sizeof(ar_pro), &inv);
+		fw->arp.arpro = ar_pro;
+		fw->arp.arpro_mask = 0xffff;
+		if (inv)
+			fw->arp.invflags |= ARPT_INV_ARPPRO;
+		break;
+	case offsetof(struct arphdr, ar_op):
+		get_cmp_data(iter, &ar_op, sizeof(ar_op), &inv);
+		fw->arp.arpop = ar_op;
+		fw->arp.arpop_mask = 0xffff;
+		if (inv)
+			fw->arp.invflags |= ARPT_INV_ARPOP;
+		break;
+	case offsetof(struct arphdr, ar_hln):
+		get_cmp_data(iter, &ar_hln, sizeof(ar_op), &inv);
+		fw->arp.arhln = ar_hln;
+		fw->arp.arhln_mask = 0xff;
+		if (inv)
+			fw->arp.invflags |= ARPT_INV_ARPOP;
+		break;
+	default:
+		if (!fw->arp.arhln)
+			break;
+
+		if (offset == sizeof(struct arphdr) + fw->arp.arhln) {
+			get_cmp_data(iter, &addr, sizeof(addr), &inv);
+			fw->arp.src.s_addr = addr.s_addr;
+			fw->arp.smsk.s_addr = 0xffffffff;
+			if (inv)
+				fw->arp.invflags |= ARPT_INV_SRCIP;
+		} else if (offset == sizeof(struct arphdr) + fw->arp.arhln + sizeof(struct in_addr)) {
+			get_cmp_data(iter, &addr, sizeof(addr), &inv);
+			fw->arp.tgt.s_addr = addr.s_addr;
+			fw->arp.tmsk.s_addr = 0xffffffff;
+			if (inv)
+				fw->arp.invflags |= ARPT_INV_TGTIP;
+		}
+		break;
+	}
+}
+
+void nft_rule_to_arpt_entry(struct nft_rule *r, struct arpt_entry *fw)
+{
+	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)
+		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, "counter") == 0)
+			nft_parse_counter(expr, iter, &fw->counters);
+		else if (strcmp(name, "payload") == 0)
+			nft_parse_payload(expr, iter, family, fw);
+		else if (strcmp(name, "meta") == 0)
+			nft_parse_meta(expr, iter, family, fw);
+		else if (strcmp(name, "immediate") == 0)
+			nft_parse_immediate(expr, iter, family, fw);
+		else if (strcmp(name, "target") == 0)
+			nft_parse_target(expr, iter, family, fw);
+
+		expr = nft_rule_expr_iter_next(iter);
+	}
+
+	nft_rule_expr_iter_destroy(iter);
+}
+
+static void
+nft_arp_print_firewall(struct nft_rule *r, unsigned int num,
+		       unsigned int format)
+{
+	struct arpt_entry fw = {};
+	const char *targname;
+	struct xtables_target *target = NULL;
+	const struct xt_entry_target *t;
+	char buf[BUFSIZ];
+	int i;
+	char iface[IFNAMSIZ+2];
+	int print_iface = 0;
+
+	nft_rule_to_arpt_entry(r, &fw);
+
+	if (format & FMT_LINENUMBERS)
+		printf("%u ", num);
+
+	if (fw.target_offset) {
+		t = nft_arp_get_target(&fw);
+		targname = t->u.user.name;
+		target = xtables_find_target(targname, XTF_TRY_LOAD);
+		if (!(format & FMT_NOTARGET))
+			printf("-j %s ", targname);
+	}
+
+	iface[0] = '\0';
+
+	if (fw.arp.iniface[0] != '\0') {
+		strcat(iface, fw.arp.iniface);
+		print_iface = 1;
+	}
+	else if (format & FMT_VIA) {
+		print_iface = 1;
+		if (format & FMT_NUMERIC) strcat(iface, "*");
+		else strcat(iface, "any");
+	}
+	if (print_iface)
+		printf("%s-i %s ", fw.arp.invflags & ARPT_INV_VIA_IN ? "! ": "", iface);
+
+	print_iface = 0;
+	iface[0] = '\0';
+
+	if (fw.arp.outiface[0] != '\0') {
+		strcat(iface, fw.arp.outiface);
+		print_iface = 1;
+	}
+	else if (format & FMT_VIA) {
+		print_iface = 1;
+		if (format & FMT_NUMERIC) strcat(iface, "*");
+		else strcat(iface, "any");
+	}
+	if (print_iface)
+		printf("%s-o %s ", fw.arp.invflags & ARPT_INV_VIA_OUT ? "! " : "", iface);
+
+	if (fw.arp.smsk.s_addr != 0L) {
+		printf("%s", fw.arp.invflags & ARPT_INV_SRCIP
+			? "! " : "");
+		if (format & FMT_NUMERIC)
+			sprintf(buf, "%s", addr_to_dotted(&(fw.arp.src)));
+		else
+			sprintf(buf, "%s", addr_to_anyname(&(fw.arp.src)));
+		strncat(buf, mask_to_dotted(&(fw.arp.smsk)),
+			sizeof(buf) - strlen(buf) - 1);
+		printf("-s %s ", buf);
+	}
+
+	for (i = 0; i < ARPT_DEV_ADDR_LEN_MAX; i++)
+		if (fw.arp.src_devaddr.mask[i] != 0)
+			break;
+	if (i == ARPT_DEV_ADDR_LEN_MAX)
+		goto after_devsrc;
+	printf("%s", fw.arp.invflags & ARPT_INV_SRCDEVADDR
+		? "! " : "");
+	printf("--src-mac ");
+	print_mac_and_mask((unsigned char *)fw.arp.src_devaddr.addr,
+		(unsigned char *)fw.arp.src_devaddr.mask, ETH_ALEN);
+	printf(" ");
+after_devsrc:
+
+	if (fw.arp.tmsk.s_addr != 0L) {
+		printf("%s",fw.arp.invflags & ARPT_INV_TGTIP
+			? "! " : "");
+		if (format & FMT_NUMERIC)
+			sprintf(buf, "%s", addr_to_dotted(&(fw.arp.tgt)));
+		else
+			sprintf(buf, "%s", addr_to_anyname(&(fw.arp.tgt)));
+		strncat(buf, mask_to_dotted(&(fw.arp.tmsk)),
+			sizeof(buf) - strlen(buf) - 1);
+		printf("-d %s ", buf);
+	}
+
+	for (i = 0; i <ARPT_DEV_ADDR_LEN_MAX; i++)
+		if (fw.arp.tgt_devaddr.mask[i] != 0)
+			break;
+	if (i == ARPT_DEV_ADDR_LEN_MAX)
+		goto after_devdst;
+	printf("%s",fw.arp.invflags & ARPT_INV_TGTDEVADDR
+		? "! " : "");
+	printf("--dst-mac ");
+	print_mac_and_mask((unsigned char *)fw.arp.tgt_devaddr.addr,
+		(unsigned char *)fw.arp.tgt_devaddr.mask, ETH_ALEN);
+	printf(" ");
+after_devdst:
+
+	if (fw.arp.arhln_mask != 0) {
+		printf("%s",fw.arp.invflags & ARPT_INV_ARPHLN
+			? "! " : "");
+		printf("--h-length %d", fw.arp.arhln);
+		if (fw.arp.arhln_mask != 255)
+			printf("/%d", fw.arp.arhln_mask);
+		printf(" ");
+	}
+
+	if (fw.arp.arpop_mask != 0) {
+		int tmp = ntohs(fw.arp.arpop);
+
+		printf("%s",fw.arp.invflags & ARPT_INV_ARPOP
+			? "! " : "");
+		if (tmp <= NUMOPCODES && !(format & FMT_NUMERIC))
+			printf("--opcode %s", opcodes[tmp-1]);
+		else
+			printf("--opcode %d", tmp);
+		if (fw.arp.arpop_mask != 65535)
+			printf("/%d", ntohs(fw.arp.arpop_mask));
+		printf(" ");
+	}
+
+	if (fw.arp.arhrd_mask != 0) {
+		uint16_t tmp = ntohs(fw.arp.arhrd);
+
+		printf("%s", fw.arp.invflags & ARPT_INV_ARPHRD
+			? "! " : "");
+		if (tmp == 1 && !(format & FMT_NUMERIC))
+			printf("--h-type %s", "Ethernet");
+		else
+			printf("--h-type %u", tmp);
+		if (fw.arp.arhrd_mask != 65535)
+			printf("/%d", ntohs(fw.arp.arhrd_mask));
+		printf(" ");
+	}
+
+	if (fw.arp.arpro_mask != 0) {
+		int tmp = ntohs(fw.arp.arpro);
+
+		printf("%s", fw.arp.invflags & ARPT_INV_ARPPRO
+			? "! " : "");
+		if (tmp == 0x0800 && !(format & FMT_NUMERIC))
+			printf("--proto-type %s", "IPv4");
+		else
+			printf("--proto-type 0x%x", tmp);
+		if (fw.arp.arpro_mask != 65535)
+			printf("/%x", ntohs(fw.arp.arpro_mask));
+		printf(" ");
+	}
+
+	if (target) {
+		if (target->print)
+			/* Print the target information. */
+			target->print(&fw.arp, t, format & FMT_NUMERIC);
+	}
+
+	if (!(format & FMT_NOCOUNTS)) {
+		printf(", pcnt=");
+		xtables_print_num(fw.counters.pcnt, format);
+		printf("-- bcnt=");
+		xtables_print_num(fw.counters.bcnt, format);
+	}
+
+	if (!(format & FMT_NONEWLINE))
+		fputc('\n', stdout);
+}
+
+static bool nft_arp_is_same(const void *data_a,
+			    const void *data_b)
+{
+	const struct arpt_entry *a = data_a;
+	const struct arpt_entry *b = data_b;
+
+	if (a->arp.src.s_addr != b->arp.src.s_addr
+	    || a->arp.tgt.s_addr != b->arp.tgt.s_addr
+	    || a->arp.smsk.s_addr != b->arp.tmsk.s_addr
+	    || a->arp.arpro != b->arp.arpro
+	    || a->arp.flags != b->arp.flags
+	    || a->arp.invflags != b->arp.invflags) {
+		DEBUGP("different src/dst/proto/flags/invflags\n");
+		return false;
+	}
+
+	return is_same_interfaces(a->arp.src_devaddr.addr,
+				  a->arp.tgt_devaddr.addr,
+				  (unsigned char*)a->arp.src_devaddr.mask,
+				  (unsigned char*)a->arp.tgt_devaddr.mask,
+				  b->arp.src_devaddr.addr,
+				  a->arp.tgt_devaddr.addr,
+				  (unsigned char*)b->arp.src_devaddr.mask,
+				  (unsigned char*)b->arp.tgt_devaddr.mask);
+}
+
+static bool nft_arp_rule_find(struct nft_family_ops *ops, struct nft_rule *r,
+			      void *data)
+{
+	struct arpt_entry *fw = data;
+	struct xt_entry_target *t_fw, *t_this;
+	char *targname_fw, *targname_this;
+	struct arpt_entry this = {};
+
+	/* Delete by matching rule case */
+	nft_rule_to_arpt_entry(r, &this);
+
+	if (!ops->is_same(fw, &this))
+		return false;
+
+	t_fw = nft_arp_get_target(fw);
+	t_this = nft_arp_get_target(&this);
+
+	targname_fw = t_fw->u.user.name;
+	targname_this = t_this->u.user.name;
+
+	if (!strcmp(targname_fw, targname_this) &&
+	    (!strcmp(targname_fw, "mangle") ||
+	    !strcmp(targname_fw, "CLASSIFY"))) {
+		if (memcmp(t_fw->data, t_this->data,
+		    t_fw->u.user.target_size - sizeof(*t_fw)) != 0) {
+			DEBUGP("Different target\n");
+			return false;
+		}
+		return true;
+	}
+
+	if (strcmp(targname_fw, targname_this) != 0) {
+		DEBUGP("Different verdict\n");
+		return false;
+	}
+
+	return true;
+}
+
+struct nft_family_ops nft_family_ops_arp = {
+	.add			= nft_arp_add,
+	.is_same		= nft_arp_is_same,
+	.print_payload		= NULL,
+	.parse_meta		= nft_arp_parse_meta,
+	.parse_payload		= nft_arp_parse_payload,
+	.parse_immediate	= nft_arp_parse_immediate,
+	.print_firewall		= nft_arp_print_firewall,
+	.post_parse		= NULL,
+	.rule_find		= nft_arp_rule_find,
+	.parse_target		= nft_arp_parse_target,
+};
diff --git a/iptables/nft-ipv4.c b/iptables/nft-ipv4.c
new file mode 100644
index 0000000..d05e80e
--- /dev/null
+++ b/iptables/nft-ipv4.c
@@ -0,0 +1,424 @@
+/*
+ * (C) 2012-2013 by Pablo Neira Ayuso <pablo@netfilter.org>
+ * (C) 2013 by Tomasz Bursztyka <tomasz.bursztyka@linux.intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This code has been sponsored by Sophos Astaro <http://www.sophos.com>
+ */
+
+#include <string.h>
+#include <stdio.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netinet/ip.h>
+
+#include <xtables.h>
+
+#include <linux/netfilter/nf_tables.h>
+
+#include "nft.h"
+#include "nft-shared.h"
+
+static int nft_ipv4_add(struct nft_rule *r, void *data)
+{
+	struct iptables_command_state *cs = data;
+	struct xtables_rule_match *matchp;
+	uint32_t op;
+
+	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);
+	}
+
+	add_compat(r, cs->fw.ip.proto, cs->fw.ip.invflags);
+
+	for (matchp = cs->matches; matchp; matchp = matchp->next) {
+		if (add_match(r, matchp->match->m) < 0)
+			break;
+	}
+
+	/* Counters need to me added before the target, otherwise they are
+	 * increased for each rule because of the way nf_tables works.
+	 */
+	if (add_counters(r, cs->counters.pcnt, cs->counters.bcnt) < 0)
+		return -1;
+
+	return add_action(r, cs, !!(cs->fw.ip.flags & IPT_F_GOTO));
+}
+
+static bool nft_ipv4_is_same(const void *data_a,
+			     const void *data_b)
+{
+	const struct iptables_command_state *a = data_a;
+	const struct iptables_command_state *b = data_b;
+
+	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;
+	}
+
+	return is_same_interfaces(a->fw.ip.iniface, a->fw.ip.outiface,
+				  a->fw.ip.iniface_mask, a->fw.ip.outiface_mask,
+				  b->fw.ip.iniface, b->fw.ip.outiface,
+				  b->fw.ip.iniface_mask, b->fw.ip.outiface_mask);
+}
+
+static void get_frag(struct nft_rule_expr_iter *iter, bool *inv)
+{
+	struct nft_rule_expr *e;
+	const char *name;
+	uint8_t op;
+
+	e = nft_rule_expr_iter_next(iter);
+	if (e == NULL)
+		return;
+
+	/* we assume correct mask and xor */
+	name = nft_rule_expr_get_str(e, NFT_RULE_EXPR_ATTR_NAME);
+	if (strcmp(name, "bitwise") != 0) {
+		DEBUGP("skipping no bitwise after payload\n");
+		return;
+	}
+
+	/* Now check for cmp */
+	e = nft_rule_expr_iter_next(iter);
+	if (e == NULL)
+		return;
+
+	/* we assume correct data */
+	name = nft_rule_expr_get_str(e, NFT_RULE_EXPR_ATTR_NAME);
+	if (strcmp(name, "cmp") != 0) {
+		DEBUGP("skipping no cmp after payload\n");
+		return;
+	}
+
+	op = nft_rule_expr_get_u32(e, NFT_EXPR_CMP_OP);
+	if (op == NFT_CMP_EQ)
+		*inv = true;
+	else
+		*inv = false;
+}
+
+static const char *mask_to_str(uint32_t mask)
+{
+	static char mask_str[sizeof("255.255.255.255")];
+	uint32_t bits, hmask = ntohl(mask);
+	struct in_addr mask_addr = {
+		.s_addr = mask,
+	};
+	int i;
+
+	if (mask == 0xFFFFFFFFU) {
+		sprintf(mask_str, "32");
+		return mask_str;
+	}
+
+	i    = 32;
+	bits = 0xFFFFFFFEU;
+	while (--i >= 0 && hmask != bits)
+		bits <<= 1;
+	if (i >= 0)
+		sprintf(mask_str, "%u", i);
+	else
+		sprintf(mask_str, "%s", inet_ntoa(mask_addr));
+
+	return mask_str;
+}
+
+static void nft_ipv4_parse_meta(struct nft_rule_expr *e, uint8_t key,
+				void *data)
+{
+	struct iptables_command_state *cs = data;
+
+	parse_meta(e, key, cs->fw.ip.iniface, cs->fw.ip.iniface_mask,
+		   cs->fw.ip.outiface, cs->fw.ip.outiface_mask,
+		   &cs->fw.ip.invflags);
+}
+
+static void nft_ipv4_parse_payload(struct nft_rule_expr_iter *iter,
+				   uint32_t offset, void *data)
+{
+	struct iptables_command_state *cs = data;
+
+	switch(offset) {
+	struct in_addr addr;
+	uint8_t proto;
+	bool inv;
+
+	case offsetof(struct iphdr, saddr):
+		get_cmp_data(iter, &addr, sizeof(addr), &inv);
+		cs->fw.ip.src.s_addr = addr.s_addr;
+		cs->fw.ip.smsk.s_addr = 0xffffffff;
+		if (inv)
+			cs->fw.ip.invflags |= IPT_INV_SRCIP;
+		break;
+	case offsetof(struct iphdr, daddr):
+		get_cmp_data(iter, &addr, sizeof(addr), &inv);
+		cs->fw.ip.dst.s_addr = addr.s_addr;
+		cs->fw.ip.dmsk.s_addr = 0xffffffff;
+		if (inv)
+			cs->fw.ip.invflags |= IPT_INV_DSTIP;
+		break;
+	case offsetof(struct iphdr, protocol):
+		get_cmp_data(iter, &proto, sizeof(proto), &inv);
+		cs->fw.ip.proto = proto;
+		if (inv)
+			cs->fw.ip.invflags |= IPT_INV_PROTO;
+		break;
+	case offsetof(struct iphdr, frag_off):
+		cs->fw.ip.flags |= IPT_F_FRAG;
+		get_frag(iter, &inv);
+		if (inv)
+			cs->fw.ip.invflags |= IPT_INV_FRAG;
+		break;
+	default:
+		DEBUGP("unknown payload offset %d\n", offset);
+		break;
+	}
+}
+
+static void nft_ipv4_parse_immediate(const char *jumpto, bool nft_goto,
+				     void *data)
+{
+	struct iptables_command_state *cs = data;
+
+	cs->jumpto = jumpto;
+
+	if (nft_goto)
+		cs->fw.ip.flags |= IPT_F_GOTO;
+}
+
+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_fragment(unsigned int flags, unsigned int invflags,
+			   unsigned int format)
+{
+	if (!(format & FMT_OPTIONS))
+		return;
+
+	if (format & FMT_NOTABLE)
+		fputs("opt ", stdout);
+	fputc(invflags & IPT_INV_FRAG ? '!' : '-', stdout);
+	fputc(flags & IPT_F_FRAG ? 'f' : '-', stdout);
+	fputc(' ', stdout);
+}
+
+static void nft_ipv4_print_firewall(struct nft_rule *r, unsigned int num,
+				    unsigned int format)
+{
+	struct iptables_command_state cs = {};
+
+	nft_rule_to_iptables_command_state(r, &cs);
+
+	print_firewall_details(&cs, cs.jumpto, cs.fw.ip.flags,
+			       cs.fw.ip.invflags, cs.fw.ip.proto,
+			       num, format);
+	print_fragment(cs.fw.ip.flags, cs.fw.ip.invflags, format);
+	print_ifaces(cs.fw.ip.iniface, cs.fw.ip.outiface, cs.fw.ip.invflags,
+		     format);
+	print_ipv4_addr(&cs, format);
+
+	if (format & FMT_NOTABLE)
+		fputs("  ", stdout);
+
+#ifdef IPT_F_GOTO
+	if (cs.fw.ip.flags & IPT_F_GOTO)
+		printf("[goto] ");
+#endif
+
+	print_matches_and_target(&cs, format);
+
+	if (!(format & FMT_NONEWLINE))
+		fputc('\n', stdout);
+}
+
+static void save_ipv4_addr(char letter, const struct in_addr *addr,
+			   uint32_t mask, int invert)
+{
+	if (!mask && !invert && !addr->s_addr)
+		return;
+
+	printf("%s-%c %s/%s ", invert ? "! " : "", letter, inet_ntoa(*addr),
+	       mask_to_str(mask));
+}
+
+static void nft_ipv4_save_firewall(const void *data, unsigned int format)
+{
+	const struct iptables_command_state *cs = data;
+
+	save_firewall_details(cs, cs->fw.ip.invflags, cs->fw.ip.proto,
+			      cs->fw.ip.iniface, cs->fw.ip.iniface_mask,
+			      cs->fw.ip.outiface, cs->fw.ip.outiface_mask,
+			      format);
+
+	if (cs->fw.ip.flags & IPT_F_FRAG) {
+		if (cs->fw.ip.invflags & IPT_INV_FRAG)
+			printf("! ");
+		printf("-f ");
+	}
+
+	save_ipv4_addr('s', &cs->fw.ip.src, cs->fw.ip.smsk.s_addr,
+		       cs->fw.ip.invflags & IPT_INV_SRCIP);
+	save_ipv4_addr('d', &cs->fw.ip.dst, cs->fw.ip.dmsk.s_addr,
+		       cs->fw.ip.invflags & IPT_INV_DSTIP);
+
+	save_matches_and_target(cs->matches, cs->target,
+				cs->jumpto, cs->fw.ip.flags, &cs->fw);
+
+	if (cs->target == NULL && strlen(cs->jumpto) > 0) {
+		printf("-%c %s", cs->fw.ip.flags & IPT_F_GOTO ? 'g' : 'j',
+		       cs->jumpto);
+	}
+	printf("\n");
+}
+
+static void nft_ipv4_proto_parse(struct iptables_command_state *cs,
+				 struct xtables_args *args)
+{
+	cs->fw.ip.proto = args->proto;
+	cs->fw.ip.invflags = args->invflags;
+}
+
+static void nft_ipv4_post_parse(int command,
+				struct iptables_command_state *cs,
+				struct xtables_args *args)
+{
+	cs->fw.ip.flags = args->flags;
+	/* We already set invflags in proto_parse, but we need to refresh it
+	 * to include new parsed options.
+	 */
+	cs->fw.ip.invflags = args->invflags;
+
+	strncpy(cs->fw.ip.iniface, args->iniface, IFNAMSIZ);
+	memcpy(cs->fw.ip.iniface_mask,
+	       args->iniface_mask, IFNAMSIZ*sizeof(unsigned char));
+
+	strncpy(cs->fw.ip.outiface, args->outiface, IFNAMSIZ);
+	memcpy(cs->fw.ip.outiface_mask,
+	       args->outiface_mask, IFNAMSIZ*sizeof(unsigned char));
+
+	if (args->goto_set)
+		cs->fw.ip.flags |= IPT_F_GOTO;
+
+	cs->counters.pcnt = args->pcnt_cnt;
+	cs->counters.bcnt = args->bcnt_cnt;
+
+	if (command & (CMD_REPLACE | CMD_INSERT |
+			CMD_DELETE | CMD_APPEND | CMD_CHECK)) {
+		if (!(cs->options & OPT_DESTINATION))
+			args->dhostnetworkmask = "0.0.0.0/0";
+		if (!(cs->options & OPT_SOURCE))
+			args->shostnetworkmask = "0.0.0.0/0";
+	}
+
+	if (args->shostnetworkmask)
+		xtables_ipparse_multiple(args->shostnetworkmask,
+					 &args->s.addr.v4, &args->s.mask.v4,
+					 &args->s.naddrs);
+	if (args->dhostnetworkmask)
+		xtables_ipparse_multiple(args->dhostnetworkmask,
+					 &args->d.addr.v4, &args->d.mask.v4,
+					 &args->d.naddrs);
+
+	if ((args->s.naddrs > 1 || args->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");
+}
+
+static void nft_ipv4_parse_target(struct xtables_target *t, void *data)
+{
+	struct iptables_command_state *cs = data;
+
+	cs->target = t;
+}
+
+static bool nft_ipv4_rule_find(struct nft_family_ops *ops,
+			       struct nft_rule *r, void *data)
+{
+	struct iptables_command_state *cs = data;
+
+	return nft_ipv46_rule_find(ops, r, cs);
+}
+
+struct nft_family_ops nft_family_ops_ipv4 = {
+	.add			= nft_ipv4_add,
+	.is_same		= nft_ipv4_is_same,
+	.parse_meta		= nft_ipv4_parse_meta,
+	.parse_payload		= nft_ipv4_parse_payload,
+	.parse_immediate	= nft_ipv4_parse_immediate,
+	.print_firewall		= nft_ipv4_print_firewall,
+	.save_firewall		= nft_ipv4_save_firewall,
+	.proto_parse		= nft_ipv4_proto_parse,
+	.post_parse		= nft_ipv4_post_parse,
+	.parse_target		= nft_ipv4_parse_target,
+	.rule_find		= nft_ipv4_rule_find,
+};
diff --git a/iptables/nft-ipv6.c b/iptables/nft-ipv6.c
new file mode 100644
index 0000000..f08598a
--- /dev/null
+++ b/iptables/nft-ipv6.c
@@ -0,0 +1,345 @@
+/*
+ * (C) 2012-2013 by Pablo Neira Ayuso <pablo@netfilter.org>
+ * (C) 2013 by Tomasz Bursztyka <tomasz.bursztyka@linux.intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This code has been sponsored by Sophos Astaro <http://www.sophos.com>
+ */
+
+#include <string.h>
+#include <stdio.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/ip6.h>
+
+#include <xtables.h>
+
+#include "nft.h"
+#include "nft-shared.h"
+
+static int nft_ipv6_add(struct nft_rule *r, void *data)
+{
+	struct iptables_command_state *cs = data;
+	struct xtables_rule_match *matchp;
+
+	if (cs->fw6.ipv6.iniface[0] != '\0')
+		add_iniface(r, cs->fw6.ipv6.iniface, cs->fw6.ipv6.invflags);
+
+	if (cs->fw6.ipv6.outiface[0] != '\0')
+		add_outiface(r, cs->fw6.ipv6.outiface, cs->fw6.ipv6.invflags);
+
+	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);
+
+	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 (cs->fw6.ipv6.proto != 0)
+		add_proto(r, offsetof(struct ip6_hdr, ip6_nxt), 1,
+			  cs->fw6.ipv6.proto, cs->fw6.ipv6.invflags);
+
+	add_compat(r, cs->fw6.ipv6.proto, cs->fw6.ipv6.invflags);
+
+	for (matchp = cs->matches; matchp; matchp = matchp->next) {
+		if (add_match(r, matchp->match->m) < 0)
+			break;
+	}
+
+	/* Counters need to me added before the target, otherwise they are
+	 * increased for each rule because of the way nf_tables works.
+	 */
+	if (add_counters(r, cs->counters.pcnt, cs->counters.bcnt) < 0)
+		return -1;
+
+	return add_action(r, cs, !!(cs->fw6.ipv6.flags & IP6T_F_GOTO));
+}
+
+static bool nft_ipv6_is_same(const void *data_a,
+			     const void *data_b)
+{
+	const struct iptables_command_state *a = data_a;
+	const struct iptables_command_state *b = data_b;
+
+	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;
+	}
+
+	return is_same_interfaces(a->fw6.ipv6.iniface, a->fw6.ipv6.outiface,
+				  a->fw6.ipv6.iniface_mask,
+				  a->fw6.ipv6.outiface_mask,
+				  b->fw6.ipv6.iniface, b->fw6.ipv6.outiface,
+				  b->fw6.ipv6.iniface_mask,
+				  b->fw6.ipv6.outiface_mask);
+}
+
+static void nft_ipv6_parse_meta(struct nft_rule_expr *e, uint8_t key,
+				void *data)
+{
+	struct iptables_command_state *cs = data;
+
+	parse_meta(e, key, cs->fw6.ipv6.iniface,
+		   cs->fw6.ipv6.iniface_mask, cs->fw6.ipv6.outiface,
+		   cs->fw6.ipv6.outiface_mask, &cs->fw6.ipv6.invflags);
+}
+
+static void nft_ipv6_parse_payload(struct nft_rule_expr_iter *iter,
+				   uint32_t offset, void *data)
+{
+	struct iptables_command_state *cs = data;
+	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.flags |= IP6T_F_PROTO;
+		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_ipv6_parse_immediate(const char *jumpto, bool nft_goto,
+				     void *data)
+{
+	struct iptables_command_state *cs = data;
+
+	cs->jumpto = jumpto;
+
+	if (nft_goto)
+		cs->fw6.ipv6.flags |= IP6T_F_GOTO;
+}
+
+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 nft_ipv6_print_firewall(struct nft_rule *r, unsigned int num,
+				    unsigned int format)
+{
+	struct iptables_command_state cs = {};
+
+	nft_rule_to_iptables_command_state(r, &cs);
+
+	print_firewall_details(&cs, cs.jumpto, cs.fw6.ipv6.flags,
+			       cs.fw6.ipv6.invflags, cs.fw6.ipv6.proto,
+			       num, format);
+	print_ifaces(cs.fw6.ipv6.iniface, cs.fw6.ipv6.outiface,
+		     cs.fw6.ipv6.invflags, format);
+	print_ipv6_addr(&cs, format);
+
+	if (format & FMT_NOTABLE)
+		fputs("  ", stdout);
+
+	if (cs.fw6.ipv6.flags & IP6T_F_GOTO)
+		printf("[goto] ");
+
+	print_matches_and_target(&cs, format);
+
+	if (!(format & FMT_NONEWLINE))
+		fputc('\n', stdout);
+}
+
+static void save_ipv6_addr(char letter, const struct in6_addr *addr,
+			   int invert)
+{
+	char addr_str[INET6_ADDRSTRLEN];
+
+	if (!invert && IN6_IS_ADDR_UNSPECIFIED(addr))
+		return;
+
+	inet_ntop(AF_INET6, addr, addr_str, INET6_ADDRSTRLEN);
+	printf("%s-%c %s ", invert ? "! " : "", letter, addr_str);
+}
+
+static void nft_ipv6_save_firewall(const void *data, unsigned int format)
+{
+	const struct iptables_command_state *cs = data;
+
+	save_firewall_details(cs, cs->fw6.ipv6.invflags, cs->fw6.ipv6.proto,
+			      cs->fw6.ipv6.iniface, cs->fw6.ipv6.iniface_mask,
+			      cs->fw6.ipv6.outiface, cs->fw6.ipv6.outiface_mask,
+			      format);
+
+	save_ipv6_addr('s', &cs->fw6.ipv6.src,
+		       cs->fw6.ipv6.invflags & IPT_INV_SRCIP);
+	save_ipv6_addr('d', &cs->fw6.ipv6.dst,
+		       cs->fw6.ipv6.invflags & IPT_INV_DSTIP);
+
+	save_matches_and_target(cs->matches, cs->target,
+				cs->jumpto, cs->fw6.ipv6.flags, &cs->fw6);
+
+	if (cs->target == NULL && strlen(cs->jumpto) > 0) {
+		printf("-%c %s", cs->fw6.ipv6.flags & IP6T_F_GOTO ? 'g' : 'j',
+		       cs->jumpto);
+	}
+	printf("\n");
+}
+
+/* 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);
+}
+
+static void nft_ipv6_proto_parse(struct iptables_command_state *cs,
+				 struct xtables_args *args)
+{
+	cs->fw6.ipv6.proto = args->proto;
+	cs->fw6.ipv6.invflags = args->invflags;
+
+	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);
+}
+
+static void nft_ipv6_post_parse(int command, struct iptables_command_state *cs,
+				struct xtables_args *args)
+{
+	if (args->proto != 0)
+		args->flags |= IP6T_F_PROTO;
+
+	cs->fw6.ipv6.flags = args->flags;
+	/* We already set invflags in proto_parse, but we need to refresh it
+	 * to include new parsed options.
+	 */
+	cs->fw6.ipv6.invflags = args->invflags;
+
+	strncpy(cs->fw6.ipv6.iniface, args->iniface, IFNAMSIZ);
+	memcpy(cs->fw6.ipv6.iniface_mask,
+	       args->iniface_mask, IFNAMSIZ*sizeof(unsigned char));
+
+	strncpy(cs->fw6.ipv6.outiface, args->outiface, IFNAMSIZ);
+	memcpy(cs->fw6.ipv6.outiface_mask,
+	       args->outiface_mask, IFNAMSIZ*sizeof(unsigned char));
+
+	if (args->goto_set)
+		cs->fw6.ipv6.flags |= IP6T_F_GOTO;
+
+	cs->fw6.counters.pcnt = args->pcnt_cnt;
+	cs->fw6.counters.bcnt = args->bcnt_cnt;
+
+	if (command & (CMD_REPLACE | CMD_INSERT |
+			CMD_DELETE | CMD_APPEND | CMD_CHECK)) {
+		if (!(cs->options & OPT_DESTINATION))
+			args->dhostnetworkmask = "::0/0";
+		if (!(cs->options & OPT_SOURCE))
+			args->shostnetworkmask = "::0/0";
+	}
+
+	if (args->shostnetworkmask)
+		xtables_ip6parse_multiple(args->shostnetworkmask,
+					  &args->s.addr.v6,
+					  &args->s.mask.v6,
+					  &args->s.naddrs);
+	if (args->dhostnetworkmask)
+		xtables_ip6parse_multiple(args->dhostnetworkmask,
+					  &args->d.addr.v6,
+					  &args->d.mask.v6,
+					  &args->d.naddrs);
+
+	if ((args->s.naddrs > 1 || args->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");
+}
+
+static void nft_ipv6_parse_target(struct xtables_target *t, void *data)
+{
+	struct iptables_command_state *cs = data;
+
+	cs->target = t;
+}
+
+static bool nft_ipv6_rule_find(struct nft_family_ops *ops,
+			       struct nft_rule *r, void *data)
+{
+	struct iptables_command_state *cs = data;
+
+	return nft_ipv46_rule_find(ops, r, cs);
+}
+
+struct nft_family_ops nft_family_ops_ipv6 = {
+	.add			= nft_ipv6_add,
+	.is_same		= nft_ipv6_is_same,
+	.parse_meta		= nft_ipv6_parse_meta,
+	.parse_payload		= nft_ipv6_parse_payload,
+	.parse_immediate	= nft_ipv6_parse_immediate,
+	.print_firewall		= nft_ipv6_print_firewall,
+	.save_firewall		= nft_ipv6_save_firewall,
+	.proto_parse		= nft_ipv6_proto_parse,
+	.post_parse		= nft_ipv6_post_parse,
+	.parse_target		= nft_ipv6_parse_target,
+	.rule_find		= nft_ipv6_rule_find,
+};
diff --git a/iptables/nft-shared.c b/iptables/nft-shared.c
new file mode 100644
index 0000000..ada71e6
--- /dev/null
+++ b/iptables/nft-shared.c
@@ -0,0 +1,773 @@
+/*
+ * (C) 2012-2013 by Pablo Neira Ayuso <pablo@netfilter.org>
+ * (C) 2013 by Tomasz Bursztyka <tomasz.bursztyka@linux.intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This code has been sponsored by Sophos Astaro <http://www.sophos.com>
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <netdb.h>
+#include <errno.h>
+
+#include <xtables.h>
+
+#include <linux/netfilter/nf_tables.h>
+
+#include <libmnl/libmnl.h>
+#include <libnftnl/rule.h>
+#include <libnftnl/expr.h>
+
+#include "nft-shared.h"
+#include "xshared.h"
+#include "nft.h"
+
+extern struct nft_family_ops nft_family_ops_ipv4;
+extern struct nft_family_ops nft_family_ops_ipv6;
+extern struct nft_family_ops nft_family_ops_arp;
+
+void add_meta(struct nft_rule *r, uint32_t key)
+{
+	struct nft_rule_expr *expr;
+
+	expr = nft_rule_expr_alloc("meta");
+	if (expr == NULL)
+		return;
+
+	nft_rule_expr_set_u32(expr, NFT_EXPR_META_KEY, key);
+	nft_rule_expr_set_u32(expr, NFT_EXPR_META_DREG, NFT_REG_1);
+
+	nft_rule_add_expr(r, expr);
+}
+
+void add_payload(struct nft_rule *r, int offset, int len)
+{
+	struct nft_rule_expr *expr;
+
+	expr = nft_rule_expr_alloc("payload");
+	if (expr == NULL)
+		return;
+
+	nft_rule_expr_set_u32(expr, NFT_EXPR_PAYLOAD_BASE,
+			      NFT_PAYLOAD_NETWORK_HEADER);
+	nft_rule_expr_set_u32(expr, NFT_EXPR_PAYLOAD_DREG, NFT_REG_1);
+	nft_rule_expr_set_u32(expr, NFT_EXPR_PAYLOAD_OFFSET, offset);
+	nft_rule_expr_set_u32(expr, NFT_EXPR_PAYLOAD_LEN, len);
+
+	nft_rule_add_expr(r, expr);
+}
+
+/* bitwise operation is = sreg & mask ^ xor */
+void add_bitwise_u16(struct nft_rule *r, int mask, int xor)
+{
+	struct nft_rule_expr *expr;
+
+	expr = nft_rule_expr_alloc("bitwise");
+	if (expr == NULL)
+		return;
+
+	nft_rule_expr_set_u32(expr, NFT_EXPR_BITWISE_SREG, NFT_REG_1);
+	nft_rule_expr_set_u32(expr, NFT_EXPR_BITWISE_DREG, NFT_REG_1);
+	nft_rule_expr_set_u32(expr, NFT_EXPR_BITWISE_LEN, sizeof(uint16_t));
+	nft_rule_expr_set(expr, NFT_EXPR_BITWISE_MASK, &mask, sizeof(uint16_t));
+	nft_rule_expr_set(expr, NFT_EXPR_BITWISE_XOR, &xor, sizeof(uint16_t));
+
+	nft_rule_add_expr(r, expr);
+}
+
+void add_cmp_ptr(struct nft_rule *r, uint32_t op, void *data, size_t len)
+{
+	struct nft_rule_expr *expr;
+
+	expr = nft_rule_expr_alloc("cmp");
+	if (expr == NULL)
+		return;
+
+	nft_rule_expr_set_u32(expr, NFT_EXPR_CMP_SREG, NFT_REG_1);
+	nft_rule_expr_set_u32(expr, NFT_EXPR_CMP_OP, op);
+	nft_rule_expr_set(expr, NFT_EXPR_CMP_DATA, data, len);
+
+	nft_rule_add_expr(r, expr);
+}
+
+void add_cmp_u8(struct nft_rule *r, uint8_t val, uint32_t op)
+{
+	add_cmp_ptr(r, op, &val, sizeof(val));
+}
+
+void add_cmp_u16(struct nft_rule *r, uint16_t val, uint32_t op)
+{
+	add_cmp_ptr(r, op, &val, sizeof(val));
+}
+
+void add_cmp_u32(struct nft_rule *r, uint32_t val, uint32_t op)
+{
+	add_cmp_ptr(r, op, &val, sizeof(val));
+}
+
+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;
+
+	add_meta(r, NFT_META_IIFNAME);
+	if (iface[iface_len - 1] == '+')
+		add_cmp_ptr(r, op, iface, iface_len - 1);
+	else
+		add_cmp_ptr(r, op, iface, iface_len + 1);
+}
+
+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;
+
+	add_meta(r, NFT_META_OIFNAME);
+	if (iface[iface_len - 1] == '+')
+		add_cmp_ptr(r, op, iface, iface_len - 1);
+	else
+		add_cmp_ptr(r, op, iface, iface_len + 1);
+}
+
+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);
+}
+
+void add_proto(struct nft_rule *r, int offset, size_t len,
+	       uint8_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_u8(r, proto, op);
+}
+
+bool is_same_interfaces(const char *a_iniface, const char *a_outiface,
+			unsigned const char *a_iniface_mask,
+			unsigned const char *a_outiface_mask,
+			const char *b_iniface, const char *b_outiface,
+			unsigned const char *b_iniface_mask,
+			unsigned const char *b_outiface_mask)
+{
+	int i;
+
+	for (i = 0; i < IFNAMSIZ; i++) {
+		if (a_iniface_mask[i] != b_iniface_mask[i]) {
+			DEBUGP("different iniface mask %x, %x (%d)\n",
+			a_iniface_mask[i] & 0xff, b_iniface_mask[i] & 0xff, i);
+			return false;
+		}
+		if ((a_iniface[i] & a_iniface_mask[i])
+		    != (b_iniface[i] & b_iniface_mask[i])) {
+			DEBUGP("different iniface\n");
+			return false;
+		}
+		if (a_outiface_mask[i] != b_outiface_mask[i]) {
+			DEBUGP("different outiface mask\n");
+			return false;
+		}
+		if ((a_outiface[i] & a_outiface_mask[i])
+		    != (b_outiface[i] & b_outiface_mask[i])) {
+			DEBUGP("different outiface\n");
+			return false;
+		}
+	}
+
+	return true;
+}
+
+void parse_meta(struct nft_rule_expr *e, uint8_t key, char *iniface,
+		unsigned char *iniface_mask, char *outiface,
+		unsigned char *outiface_mask, uint8_t *invflags)
+{
+	uint32_t value;
+	const void *ifname;
+	uint32_t len;
+
+	switch(key) {
+	case NFT_META_IIF:
+		value = nft_rule_expr_get_u32(e, NFT_EXPR_CMP_DATA);
+		if (nft_rule_expr_get_u32(e, NFT_EXPR_CMP_OP) == NFT_CMP_NEQ)
+			*invflags |= IPT_INV_VIA_IN;
+
+		if_indextoname(value, iniface);
+
+		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_u32(e, NFT_EXPR_CMP_OP) == NFT_CMP_NEQ)
+			*invflags |= IPT_INV_VIA_OUT;
+
+		if_indextoname(value, outiface);
+
+		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_u32(e, NFT_EXPR_CMP_OP) == NFT_CMP_NEQ)
+			*invflags |= IPT_INV_VIA_IN;
+
+		memcpy(iniface, ifname, len);
+
+		if (iniface[len] == '\0')
+			memset(iniface_mask, 0xff, len);
+		else {
+			iniface[len] = '+';
+			iniface[len+1] = '\0';
+			memset(iniface_mask, 0xff, len + 1);
+		}
+		break;
+	case NFT_META_OIFNAME:
+		ifname = nft_rule_expr_get(e, NFT_EXPR_CMP_DATA, &len);
+		if (nft_rule_expr_get_u32(e, NFT_EXPR_CMP_OP) == NFT_CMP_NEQ)
+			*invflags |= IPT_INV_VIA_OUT;
+
+		memcpy(outiface, ifname, len);
+
+		if (outiface[len] == '\0')
+			memset(outiface_mask, 0xff, len);
+		else {
+			outiface[len] = '+';
+			outiface[len+1] = '\0';
+			memset(outiface_mask, 0xff, len + 1);
+		}
+		break;
+	default:
+		DEBUGP("unknown meta key %d\n", key);
+		break;
+	}
+}
+
+void nft_parse_target(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter,
+		 int family, void *data)
+{
+	uint32_t tg_len;
+	const char *targname = nft_rule_expr_get_str(e, NFT_EXPR_TG_NAME);
+	const void *targinfo = nft_rule_expr_get(e, NFT_EXPR_TG_INFO, &tg_len);
+	struct xtables_target *target;
+	struct xt_entry_target *t;
+	struct nft_family_ops *ops;
+	size_t size;
+
+	target = xtables_find_target(targname, XTF_TRY_LOAD);
+	if (target == NULL)
+		return;
+
+	size = XT_ALIGN(sizeof(struct xt_entry_target)) + tg_len;
+
+	t = calloc(1, size);
+	if (t == NULL) {
+		fprintf(stderr, "OOM");
+		exit(EXIT_FAILURE);
+	}
+	memcpy(&t->data, targinfo, tg_len);
+	t->u.target_size = size;
+	t->u.user.revision = nft_rule_expr_get_u32(e, NFT_EXPR_TG_REV);
+	strcpy(t->u.user.name, target->name);
+
+	target->t = t;
+
+	ops = nft_family_ops_lookup(family);
+	ops->parse_target(target, data);
+}
+
+static void
+nft_parse_match(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter,
+		struct iptables_command_state *cs)
+{
+	uint32_t mt_len;
+	const char *mt_name = nft_rule_expr_get_str(e, NFT_EXPR_MT_NAME);
+	const void *mt_info = nft_rule_expr_get(e, NFT_EXPR_MT_INFO, &mt_len);
+	struct xtables_match *match;
+	struct xt_entry_match *m;
+
+	match = xtables_find_match(mt_name, XTF_TRY_LOAD, &cs->matches);
+	if (match == NULL)
+		return;
+
+	m = calloc(1, sizeof(struct xt_entry_match) + mt_len);
+	if (m == NULL) {
+		fprintf(stderr, "OOM");
+		exit(EXIT_FAILURE);
+	}
+
+	memcpy(&m->data, mt_info, mt_len);
+	m->u.match_size = mt_len + XT_ALIGN(sizeof(struct xt_entry_match));
+	m->u.user.revision = nft_rule_expr_get_u32(e, NFT_EXPR_TG_REV);
+	strcpy(m->u.user.name, match->name);
+
+	match->m = m;
+}
+
+void print_proto(uint16_t proto, int invert)
+{
+	const struct protoent *pent = getprotobynumber(proto);
+
+	if (invert)
+		printf("! ");
+
+	if (pent) {
+		printf("-p %s ", pent->p_name);
+		return;
+	}
+
+	printf("-p %u ", proto);
+}
+
+void get_cmp_data(struct nft_rule_expr_iter *iter,
+		  void *data, size_t dlen, bool *inv)
+{
+	struct nft_rule_expr *e;
+	const char *name;
+	uint32_t len;
+	uint8_t op;
+
+	e = nft_rule_expr_iter_next(iter);
+	if (e == NULL)
+		return;
+
+	name = nft_rule_expr_get_str(e, NFT_RULE_EXPR_ATTR_NAME);
+	if (strcmp(name, "cmp") != 0) {
+		DEBUGP("skipping no cmp after meta\n");
+		return;
+	}
+
+	memcpy(data, nft_rule_expr_get(e, NFT_EXPR_CMP_DATA, &len), dlen);
+	op = nft_rule_expr_get_u32(e, NFT_EXPR_CMP_OP);
+	if (op == NFT_CMP_NEQ)
+		*inv = true;
+	else
+		*inv = false;
+}
+
+void
+nft_parse_meta(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter,
+	       int family, void *data)
+{
+	uint8_t key = nft_rule_expr_get_u32(e, NFT_EXPR_META_KEY);
+	struct nft_family_ops *ops = nft_family_ops_lookup(family);
+	const char *name;
+
+	e = nft_rule_expr_iter_next(iter);
+	if (e == NULL)
+		return;
+
+	name = nft_rule_expr_get_str(e, NFT_RULE_EXPR_ATTR_NAME);
+	if (strcmp(name, "cmp") != 0) {
+		DEBUGP("skipping no cmp after meta\n");
+		return;
+	}
+
+	ops->parse_meta(e, key, data);
+}
+
+void
+nft_parse_payload(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter,
+		  int family, void *data)
+{
+	struct nft_family_ops *ops = nft_family_ops_lookup(family);
+	uint32_t offset;
+
+	offset = nft_rule_expr_get_u32(e, NFT_EXPR_PAYLOAD_OFFSET);
+
+	ops->parse_payload(iter, offset, data);
+}
+
+void
+nft_parse_counter(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter,
+		  struct xt_counters *counters)
+{
+	counters->pcnt = nft_rule_expr_get_u64(e, NFT_EXPR_CTR_PACKETS);
+	counters->bcnt = nft_rule_expr_get_u64(e, NFT_EXPR_CTR_BYTES);
+}
+
+void
+nft_parse_immediate(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter,
+		    int family, void *data)
+{
+	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);
+	struct nft_family_ops *ops;
+	const char *jumpto = NULL;
+	bool nft_goto = false;
+
+	/* Standard target? */
+	switch(verdict) {
+	case NF_ACCEPT:
+		jumpto = "ACCEPT";
+		break;
+	case NF_DROP:
+		jumpto = "DROP";
+		break;
+	case NFT_RETURN:
+		jumpto = "RETURN";
+		break;;
+	case NFT_GOTO:
+		nft_goto = true;
+	case NFT_JUMP:
+		jumpto = chain;
+		break;
+	}
+
+	ops = nft_family_ops_lookup(family);
+	ops->parse_immediate(jumpto, nft_goto, data);
+}
+
+void nft_rule_to_iptables_command_state(struct nft_rule *r,
+					struct iptables_command_state *cs)
+{
+	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)
+		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, "counter") == 0)
+			nft_parse_counter(expr, iter, &cs->counters);
+		else if (strcmp(name, "payload") == 0)
+			nft_parse_payload(expr, iter, family, cs);
+		else if (strcmp(name, "meta") == 0)
+			nft_parse_meta(expr, iter, family, cs);
+		else if (strcmp(name, "immediate") == 0)
+			nft_parse_immediate(expr, iter, family, cs);
+		else if (strcmp(name, "match") == 0)
+			nft_parse_match(expr, iter, cs);
+		else if (strcmp(name, "target") == 0)
+			nft_parse_target(expr, iter, family, cs);
+
+		expr = nft_rule_expr_iter_next(iter);
+	}
+
+	nft_rule_expr_iter_destroy(iter);
+
+	if (cs->target != NULL)
+		cs->jumpto = cs->target->name;
+	else if (cs->jumpto != NULL)
+		cs->target = xtables_find_target(cs->jumpto, XTF_TRY_LOAD);
+	else
+		cs->jumpto = "";
+}
+
+void print_firewall_details(const struct iptables_command_state *cs,
+			    const char *targname, uint8_t flags,
+			    uint8_t invflags, uint8_t proto,
+			    unsigned int num, unsigned int format)
+{
+	if (format & FMT_LINENUMBERS)
+		printf(FMT("%-4u ", "%u "), num);
+
+	if (!(format & FMT_NOCOUNTS)) {
+		xtables_print_num(cs->counters.pcnt, format);
+		xtables_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);
+	}
+}
+
+void print_ifaces(const char *iniface, const char *outiface, uint8_t invflags,
+		  unsigned int format)
+{
+	char iface[IFNAMSIZ+2];
+
+	if (!(format & FMT_VIA))
+		return;
+
+	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);
+}
+
+static void
+print_iface(char letter, const char *iface, const unsigned char *mask, int inv)
+{
+	unsigned int i;
+
+	if (mask[0] == 0)
+		return;
+
+	printf("%s-%c ", inv ? "! " : "", letter);
+
+	for (i = 0; i < IFNAMSIZ; i++) {
+		if (mask[i] != 0) {
+			if (iface[i] != '\0')
+				printf("%c", iface[i]);
+			} else {
+				if (iface[i-1] != '\0')
+					printf("+");
+				break;
+		}
+	}
+
+	printf(" ");
+}
+
+void save_firewall_details(const struct iptables_command_state *cs,
+			   uint8_t invflags, uint16_t proto,
+			   const char *iniface,
+			   unsigned const char *iniface_mask,
+			   const char *outiface,
+			   unsigned const char *outiface_mask,
+			   unsigned int format)
+{
+	if (!(format & FMT_NOCOUNTS)) {
+		printf("-c ");
+		xtables_print_num(cs->counters.pcnt, format);
+		xtables_print_num(cs->counters.bcnt, format);
+	}
+
+	if (iniface != NULL) {
+		print_iface('i', iniface, iniface_mask,
+			    invflags & IPT_INV_VIA_IN);
+	}
+	if (outiface != NULL) {
+		print_iface('o', outiface, outiface_mask,
+			    invflags & IPT_INV_VIA_OUT);
+	}
+
+	if (proto > 0) {
+		const struct protoent *pent = getprotobynumber(proto);
+
+		if (invflags & XT_INV_PROTO)
+			printf("! ");
+
+		if (pent)
+			printf("-p %s ", pent->p_name);
+		else
+			printf("-p %u ", proto);
+	}
+}
+
+void save_matches_and_target(struct xtables_rule_match *m,
+			     struct xtables_target *target,
+			     const char *jumpto, uint8_t flags, const void *fw)
+{
+	struct xtables_rule_match *matchp;
+
+	for (matchp = m; matchp; matchp = matchp->next) {
+		if (matchp->match->alias) {
+			printf("-m %s",
+			       matchp->match->alias(matchp->match->m));
+		} else
+			printf("-m %s", matchp->match->name);
+
+		if (matchp->match->save != NULL) {
+			/* cs->fw union makes the trick */
+			matchp->match->save(fw, matchp->match->m);
+		}
+		printf(" ");
+	}
+
+	if (target != NULL) {
+		if (target->alias) {
+			printf("-j %s", target->alias(target->t));
+		} else
+			printf("-j %s", jumpto);
+
+		if (target->save != NULL)
+			target->save(fw, target->t);
+	}
+}
+
+void print_matches_and_target(struct iptables_command_state *cs,
+			      unsigned int format)
+{
+	struct xtables_rule_match *matchp;
+
+	for (matchp = cs->matches; matchp; matchp = matchp->next) {
+		if (matchp->match->print != NULL) {
+			matchp->match->print(&cs->fw, matchp->match->m,
+					     format & FMT_NUMERIC);
+		}
+	}
+
+	if (cs->target != NULL) {
+		if (cs->target->print != NULL) {
+			cs->target->print(&cs->fw, cs->target->t,
+					  format & FMT_NUMERIC);
+		}
+	}
+}
+
+struct nft_family_ops *nft_family_ops_lookup(int family)
+{
+	switch (family) {
+	case AF_INET:
+		return &nft_family_ops_ipv4;
+	case AF_INET6:
+		return &nft_family_ops_ipv6;
+	case NFPROTO_ARP:
+		return &nft_family_ops_arp;
+	default:
+		break;
+	}
+
+	return NULL;
+}
+
+static bool
+compare_matches(struct xtables_rule_match *mt1, struct xtables_rule_match *mt2)
+{
+	struct xtables_rule_match *mp1;
+	struct xtables_rule_match *mp2;
+
+	for (mp1 = mt1, mp2 = mt2; mp1 && mp2; mp1 = mp1->next, mp2 = mp2->next) {
+		struct xt_entry_match *m1 = mp1->match->m;
+		struct xt_entry_match *m2 = mp2->match->m;
+
+		if (strcmp(m1->u.user.name, m2->u.user.name) != 0) {
+			DEBUGP("mismatching match name\n");
+			return false;
+		}
+
+		if (m1->u.user.match_size != m2->u.user.match_size) {
+			DEBUGP("mismatching match size\n");
+			return false;
+		}
+
+		if (memcmp(m1->data, m2->data,
+			   mp1->match->userspacesize) != 0) {
+			DEBUGP("mismatch match data\n");
+			return false;
+		}
+	}
+
+	/* Both cursors should be NULL */
+	if (mp1 != mp2) {
+		DEBUGP("mismatch matches amount\n");
+		return false;
+	}
+
+	return true;
+}
+
+bool compare_targets(struct xtables_target *tg1, struct xtables_target *tg2)
+{
+	if (tg1 == NULL && tg2 == NULL)
+		return true;
+
+	if ((tg1 == NULL && tg2 != NULL) || (tg1 != NULL && tg2 == NULL))
+		return false;
+
+	if (strcmp(tg1->t->u.user.name, tg2->t->u.user.name) != 0)
+		return false;
+
+	if (memcmp(tg1->t->data, tg2->t->data, tg1->userspacesize) != 0)
+		return false;
+
+	return true;
+}
+
+bool nft_ipv46_rule_find(struct nft_family_ops *ops,
+			 struct nft_rule *r, struct iptables_command_state *cs)
+{
+	struct iptables_command_state this = {};
+
+	nft_rule_to_iptables_command_state(r, &this);
+
+	DEBUGP("comparing with... ");
+#ifdef DEBUG_DEL
+	nft_rule_print_save(&this, r, NFT_RULE_APPEND, 0);
+#endif
+	if (!ops->is_same(cs, &this))
+		return false;
+
+	if (!compare_matches(cs->matches, this.matches)) {
+		DEBUGP("Different matches\n");
+		return false;
+	}
+
+	if (!compare_targets(cs->target, this.target)) {
+		DEBUGP("Different target\n");
+		return false;
+	}
+
+	if (strcmp(cs->jumpto, this.jumpto) != 0) {
+		DEBUGP("Different verdict\n");
+		return false;
+	}
+
+	return true;
+}
diff --git a/iptables/nft-shared.h b/iptables/nft-shared.h
new file mode 100644
index 0000000..145f19d
--- /dev/null
+++ b/iptables/nft-shared.h
@@ -0,0 +1,195 @@
+#ifndef _NFT_SHARED_H_
+#define _NFT_SHARED_H_
+
+#include <stdbool.h>
+
+#include <libnftnl/rule.h>
+#include <libnftnl/expr.h>
+
+#include "xshared.h"
+
+#if 0
+#define DEBUGP(x, args...) fprintf(stdout, x, ## args)
+#define NLDEBUG
+#define DEBUG_DEL
+#else
+#define DEBUGP(x, args...)
+#endif
+
+/*
+ * iptables print output emulation
+ */
+
+#define FMT_NUMERIC	0x0001
+#define FMT_NOCOUNTS	0x0002
+#define FMT_KILOMEGAGIGA 0x0004
+#define FMT_OPTIONS	0x0008
+#define FMT_NOTABLE	0x0010
+#define FMT_NOTARGET	0x0020
+#define FMT_VIA		0x0040
+#define FMT_NONEWLINE	0x0080
+#define FMT_LINENUMBERS 0x0100
+
+#define FMT_PRINT_RULE (FMT_NOCOUNTS | FMT_OPTIONS | FMT_VIA \
+			| FMT_NUMERIC | FMT_NOTABLE)
+#define FMT(tab,notab) ((format) & FMT_NOTABLE ? (notab) : (tab))
+
+struct xtables_args;
+
+struct nft_family_ops {
+	int (*add)(struct nft_rule *r, void *data);
+	bool (*is_same)(const void *data_a,
+			const void *data_b);
+	void (*print_payload)(struct nft_rule_expr *e,
+			      struct nft_rule_expr_iter *iter);
+	void (*parse_meta)(struct nft_rule_expr *e, uint8_t key,
+			   void *data);
+	void (*parse_payload)(struct nft_rule_expr_iter *iter,
+			      uint32_t offset, void *data);
+	void (*parse_immediate)(const char *jumpto, bool nft_goto, void *data);
+	void (*print_firewall)(struct nft_rule *r, unsigned int num,
+			       unsigned int format);
+	void (*save_firewall)(const void *data, unsigned int format);
+	void (*proto_parse)(struct iptables_command_state *cs,
+			    struct xtables_args *args);
+	void (*post_parse)(int command, struct iptables_command_state *cs,
+			   struct xtables_args *args);
+	void (*parse_target)(struct xtables_target *t, void *data);
+	bool (*rule_find)(struct nft_family_ops *ops, struct nft_rule *r,
+			  void *data);
+};
+
+void add_meta(struct nft_rule *r, uint32_t key);
+void add_payload(struct nft_rule *r, int offset, int len);
+void add_bitwise_u16(struct nft_rule *r, int mask, int xor);
+void add_cmp_ptr(struct nft_rule *r, uint32_t op, void *data, size_t len);
+void add_cmp_u8(struct nft_rule *r, uint8_t val, uint32_t op);
+void add_cmp_u16(struct nft_rule *r, uint16_t val, uint32_t op);
+void add_cmp_u32(struct nft_rule *r, uint32_t val, uint32_t op);
+void add_iniface(struct nft_rule *r, char *iface, int invflags);
+void add_outiface(struct nft_rule *r, char *iface, int invflags);
+void add_addr(struct nft_rule *r, int offset,
+	      void *data, size_t len, int invflags);
+void add_proto(struct nft_rule *r, int offset, size_t len,
+	       uint8_t proto, int invflags);
+void add_compat(struct nft_rule *r, uint32_t proto, bool inv);
+
+bool is_same_interfaces(const char *a_iniface, const char *a_outiface,
+			unsigned const char *a_iniface_mask,
+			unsigned const char *a_outiface_mask,
+			const char *b_iniface, const char *b_outiface,
+			unsigned const char *b_iniface_mask,
+			unsigned const char *b_outiface_mask);
+
+void parse_meta(struct nft_rule_expr *e, uint8_t key, char *iniface,
+		unsigned char *iniface_mask, char *outiface,
+		unsigned char *outiface_mask, uint8_t *invflags);
+void print_proto(uint16_t proto, int invert);
+void get_cmp_data(struct nft_rule_expr_iter *iter,
+		  void *data, size_t dlen, bool *inv);
+void nft_parse_target(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter,
+		      int family, void *data);
+void nft_parse_meta(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter,
+		    int family, void *data);
+void nft_parse_payload(struct nft_rule_expr *e,
+		       struct nft_rule_expr_iter *iter,
+		       int family, void *data);
+void nft_parse_counter(struct nft_rule_expr *e,
+		       struct nft_rule_expr_iter *iter,
+		       struct xt_counters *counters);
+void nft_parse_immediate(struct nft_rule_expr *e,
+			 struct nft_rule_expr_iter *iter,
+			 int family, void *data);
+void nft_rule_to_iptables_command_state(struct nft_rule *r,
+					struct iptables_command_state *cs);
+void print_firewall_details(const struct iptables_command_state *cs,
+			    const char *targname, uint8_t flags,
+			    uint8_t invflags, uint8_t proto,
+			    unsigned int num, unsigned int format);
+void print_ifaces(const char *iniface, const char *outiface, uint8_t invflags,
+		  unsigned int format);
+void print_matches_and_target(struct iptables_command_state *cs,
+			      unsigned int format);
+void save_firewall_details(const struct iptables_command_state *cs,
+			   uint8_t invflags, uint16_t proto,
+			   const char *iniface,
+			   unsigned const char *iniface_mask,
+			   const char *outiface,
+			   unsigned const char *outiface_mask,
+			   unsigned int format);
+void save_matches_and_target(struct xtables_rule_match *m,
+			     struct xtables_target *target,
+			     const char *jumpto,
+			     uint8_t flags, const void *fw);
+
+struct nft_family_ops *nft_family_ops_lookup(int family);
+
+struct nft_handle;
+bool nft_ipv46_rule_find(struct nft_family_ops *ops, struct nft_rule *r,
+			 struct iptables_command_state *cs);
+
+bool compare_targets(struct xtables_target *tg1, struct xtables_target *tg2);
+
+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;
+};
+
+struct xtables_args {
+	int		family;
+	uint16_t	proto;
+	uint8_t		flags;
+	uint8_t		invflags;
+	char		iniface[IFNAMSIZ], outiface[IFNAMSIZ];
+	unsigned char	iniface_mask[IFNAMSIZ], outiface_mask[IFNAMSIZ];
+	bool		goto_set;
+	const char	*shostnetworkmask, *dhostnetworkmask;
+	const char	*pcnt, *bcnt;
+	struct addr_mask s, d;
+	unsigned long long pcnt_cnt, bcnt_cnt;
+};
+
+#define CMD_NONE		0x0000U
+#define CMD_INSERT		0x0001U
+#define CMD_DELETE		0x0002U
+#define CMD_DELETE_NUM		0x0004U
+#define CMD_REPLACE		0x0008U
+#define CMD_APPEND		0x0010U
+#define CMD_LIST		0x0020U
+#define CMD_FLUSH		0x0040U
+#define CMD_ZERO		0x0080U
+#define CMD_NEW_CHAIN		0x0100U
+#define CMD_DELETE_CHAIN	0x0200U
+#define CMD_SET_POLICY		0x0400U
+#define CMD_RENAME_CHAIN	0x0800U
+#define CMD_LIST_RULES		0x1000U
+#define CMD_ZERO_NUM		0x2000U
+#define CMD_CHECK		0x4000U
+
+/*
+ * ARP
+ */
+extern char *opcodes[];
+#define NUMOPCODES 9
+
+#include <linux/netfilter_arp/arp_tables.h>
+
+static inline struct xt_entry_target *nft_arp_get_target(struct arpt_entry *fw)
+{
+	struct xt_entry_target **target;
+
+	target = (void *) &fw->elems;
+
+	return *target;
+}
+
+#endif
diff --git a/iptables/nft.c b/iptables/nft.c
new file mode 100644
index 0000000..1237659
--- /dev/null
+++ b/iptables/nft.c
@@ -0,0 +1,2542 @@
+/*
+ * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This code has been sponsored by Sophos Astaro <http://www.sophos.com>
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <netdb.h>	/* getprotobynumber */
+#include <time.h>
+#include <stdarg.h>
+#include <inttypes.h>
+
+#include <xtables.h>
+#include <libiptc/libxtc.h>
+#include <libiptc/xtcshared.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <linux/netfilter/x_tables.h>
+#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>
+#include <linux/netfilter/nf_tables.h>
+#include <linux/netfilter/nf_tables_compat.h>
+
+#include <libmnl/libmnl.h>
+#include <libnftnl/table.h>
+#include <libnftnl/chain.h>
+#include <libnftnl/rule.h>
+#include <libnftnl/expr.h>
+
+#include <netinet/in.h>	/* inet_ntoa */
+#include <arpa/inet.h>
+
+#include "nft.h"
+#include "xshared.h" /* proto_to_name */
+#include "nft-shared.h"
+#include "xtables-config-parser.h"
+
+static void *nft_fn;
+
+int mnl_talk(struct nft_handle *h, struct nlmsghdr *nlh,
+	     int (*cb)(const struct nlmsghdr *nlh, void *data),
+	     void *data)
+{
+	int ret;
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+
+	if (mnl_socket_sendto(h->nl, nlh, nlh->nlmsg_len) < 0) {
+		perror("mnl_socket_send");
+		return -1;
+	}
+
+	ret = mnl_socket_recvfrom(h->nl, buf, sizeof(buf));
+	while (ret > 0) {
+		ret = mnl_cb_run(buf, ret, h->seq, h->portid, cb, data);
+		if (ret <= 0)
+			break;
+
+		ret = mnl_socket_recvfrom(h->nl, buf, sizeof(buf));
+	}
+	if (ret == -1) {
+		return -1;
+	}
+
+	return 0;
+}
+
+static LIST_HEAD(batch_page_list);
+static int batch_num_pages;
+
+struct batch_page {
+	struct list_head	head;
+	struct mnl_nlmsg_batch	*batch;
+};
+
+/* selected batch page is 256 Kbytes long to load ruleset of
+ * half a million rules without hitting -EMSGSIZE due to large
+ * iovec.
+ */
+#define BATCH_PAGE_SIZE getpagesize() * 32
+
+static struct mnl_nlmsg_batch *mnl_nft_batch_alloc(void)
+{
+	static char *buf;
+
+	/* libmnl needs higher buffer to handle batch overflows */
+	buf = malloc(BATCH_PAGE_SIZE + getpagesize());
+	if (buf == NULL)
+		return NULL;
+
+	return mnl_nlmsg_batch_start(buf, BATCH_PAGE_SIZE);
+}
+
+static struct mnl_nlmsg_batch *
+mnl_nft_batch_page_add(struct mnl_nlmsg_batch *batch)
+{
+	struct batch_page *batch_page;
+
+	batch_page = malloc(sizeof(struct batch_page));
+	if (batch_page == NULL)
+		return NULL;
+
+	batch_page->batch = batch;
+	list_add_tail(&batch_page->head, &batch_page_list);
+	batch_num_pages++;
+
+	return mnl_nft_batch_alloc();
+}
+
+static int nlbuffsiz;
+
+static void mnl_nft_set_sndbuffer(const struct mnl_socket *nl)
+{
+	int newbuffsiz;
+
+	if (batch_num_pages * BATCH_PAGE_SIZE <= nlbuffsiz)
+		return;
+
+	newbuffsiz = batch_num_pages * BATCH_PAGE_SIZE;
+
+	/* Rise sender buffer length to avoid hitting -EMSGSIZE */
+	if (setsockopt(mnl_socket_get_fd(nl), SOL_SOCKET, SO_SNDBUFFORCE,
+		       &newbuffsiz, sizeof(socklen_t)) < 0)
+		return;
+
+	nlbuffsiz = newbuffsiz;
+}
+
+static ssize_t mnl_nft_socket_sendmsg(const struct mnl_socket *nl)
+{
+	static const struct sockaddr_nl snl = {
+		.nl_family = AF_NETLINK
+	};
+	struct iovec iov[batch_num_pages];
+	struct msghdr msg = {
+		.msg_name	= (struct sockaddr *) &snl,
+		.msg_namelen	= sizeof(snl),
+		.msg_iov	= iov,
+		.msg_iovlen	= batch_num_pages,
+	};
+	struct batch_page *batch_page, *next;
+	int i = 0;
+
+	mnl_nft_set_sndbuffer(nl);
+
+	list_for_each_entry_safe(batch_page, next, &batch_page_list, head) {
+		iov[i].iov_base = mnl_nlmsg_batch_head(batch_page->batch);
+		iov[i].iov_len = mnl_nlmsg_batch_size(batch_page->batch);
+		i++;
+#ifdef NL_DEBUG
+		mnl_nlmsg_fprintf(stdout,
+				  mnl_nlmsg_batch_head(batch_page->batch),
+				  mnl_nlmsg_batch_size(batch_page->batch),
+				  sizeof(struct nfgenmsg));
+#endif
+		list_del(&batch_page->head);
+		free(batch_page->batch);
+		free(batch_page);
+		batch_num_pages--;
+	}
+
+	return sendmsg(mnl_socket_get_fd(nl), &msg, 0);
+}
+
+static int cb_err(const struct nlmsghdr *nlh, void *data)
+{
+	/* We can provide better error reporting than iptables-restore */
+	errno = EINVAL;
+	return MNL_CB_ERROR;
+}
+
+static mnl_cb_t cb_ctl_array[NLMSG_MIN_TYPE] = {
+	[NLMSG_ERROR] = cb_err,
+};
+
+static int mnl_nft_batch_talk(struct nft_handle *h)
+{
+	int ret, fd = mnl_socket_get_fd(h->nl);
+	char rcv_buf[MNL_SOCKET_BUFFER_SIZE];
+	fd_set readfds;
+	struct timeval tv = {
+		.tv_sec		= 0,
+		.tv_usec	= 0
+	};
+	int err = 0;
+
+	ret = mnl_nft_socket_sendmsg(h->nl);
+	if (ret == -1) {
+		perror("mnl_socket_sendmsg");
+		return -1;
+	}
+
+	FD_ZERO(&readfds);
+	FD_SET(fd, &readfds);
+
+	/* receive and digest all the acknowledgments from the kernel. */
+	ret = select(fd+1, &readfds, NULL, NULL, &tv);
+	if (ret == -1) {
+		perror("select");
+		return -1;
+	}
+	while (ret > 0 && FD_ISSET(fd, &readfds)) {
+		ret = mnl_socket_recvfrom(h->nl, rcv_buf, sizeof(rcv_buf));
+		if (ret == -1) {
+			perror("mnl_socket_recvfrom");
+			return -1;
+		}
+
+		ret = mnl_cb_run2(rcv_buf, ret, 0, h->portid,
+				  NULL, NULL, cb_ctl_array,
+				  MNL_ARRAY_SIZE(cb_ctl_array));
+		/* Continue on error, make sure we get all acknoledgments */
+		if (ret == -1)
+			err = errno;
+
+		ret = select(fd+1, &readfds, NULL, NULL, &tv);
+		if (ret == -1) {
+			perror("select");
+			return -1;
+		}
+		FD_ZERO(&readfds);
+		FD_SET(fd, &readfds);
+	}
+	return err ? -1 : 0;
+}
+
+static void mnl_nft_batch_put(struct mnl_nlmsg_batch *batch, int type,
+			      uint32_t seq)
+{
+	struct nlmsghdr *nlh;
+	struct nfgenmsg *nfg;
+
+	nlh = mnl_nlmsg_put_header(mnl_nlmsg_batch_current(batch));
+	nlh->nlmsg_type = type;
+	nlh->nlmsg_flags = NLM_F_REQUEST;
+	nlh->nlmsg_seq = seq;
+
+	nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg));
+	nfg->nfgen_family = AF_INET;
+	nfg->version = NFNETLINK_V0;
+	nfg->res_id = NFNL_SUBSYS_NFTABLES;
+
+	if (!mnl_nlmsg_batch_next(batch))
+		mnl_nft_batch_page_add(batch);
+}
+
+static void mnl_nft_batch_begin(struct mnl_nlmsg_batch *batch, uint32_t seq)
+{
+	mnl_nft_batch_put(batch, NFNL_MSG_BATCH_BEGIN, seq);
+}
+
+static void mnl_nft_batch_end(struct mnl_nlmsg_batch *batch, uint32_t seq)
+{
+	mnl_nft_batch_put(batch, NFNL_MSG_BATCH_END, seq);
+}
+
+struct builtin_table xtables_ipv4[TABLES_MAX] = {
+	[RAW] = {
+		.name	= "raw",
+		.chains = {
+			{
+				.name	= "PREROUTING",
+				.type	= "filter",
+				.prio	= -300,	/* NF_IP_PRI_RAW */
+				.hook	= NF_INET_PRE_ROUTING,
+			},
+			{
+				.name	= "OUTPUT",
+				.type	= "filter",
+				.prio	= -300,	/* NF_IP_PRI_RAW */
+				.hook	= NF_INET_LOCAL_OUT,
+			},
+		},
+	},
+	[MANGLE] = {
+		.name	= "mangle",
+		.chains = {
+			{
+				.name	= "PREROUTING",
+				.type	= "filter",
+				.prio	= -150,	/* NF_IP_PRI_MANGLE */
+				.hook	= NF_INET_PRE_ROUTING,
+			},
+			{
+				.name	= "INPUT",
+				.type	= "filter",
+				.prio	= -150,	/* NF_IP_PRI_MANGLE */
+				.hook	= NF_INET_LOCAL_IN,
+			},
+			{
+				.name	= "FORWARD",
+				.type	= "filter",
+				.prio	= -150,	/* NF_IP_PRI_MANGLE */
+				.hook	= NF_INET_FORWARD,
+			},
+			{
+				.name	= "OUTPUT",
+				.type	= "route",
+				.prio	= -150,	/* NF_IP_PRI_MANGLE */
+				.hook	= NF_INET_LOCAL_OUT,
+			},
+			{
+				.name	= "POSTROUTING",
+				.type	= "filter",
+				.prio	= -150,	/* NF_IP_PRI_MANGLE */
+				.hook	= NF_INET_POST_ROUTING,
+			},
+		},
+	},
+	[FILTER] = {
+		.name	= "filter",
+		.chains = {
+			{
+				.name	= "INPUT",
+				.type	= "filter",
+				.prio	= 0,	/* NF_IP_PRI_FILTER */
+				.hook	= NF_INET_LOCAL_IN,
+			},
+			{
+				.name	= "FORWARD",
+				.type	= "filter",
+				.prio	= 0,	/* NF_IP_PRI_FILTER */
+				.hook	= NF_INET_FORWARD,
+			},
+			{
+				.name	= "OUTPUT",
+				.type	= "filter",
+				.prio	= 0,	/* NF_IP_PRI_FILTER */
+				.hook	= NF_INET_LOCAL_OUT,
+			},
+		},
+	},
+	[SECURITY] = {
+		.name	= "security",
+		.chains = {
+			{
+				.name	= "INPUT",
+				.type	= "filter",
+				.prio	= 150,	/* NF_IP_PRI_SECURITY */
+				.hook	= NF_INET_LOCAL_IN,
+			},
+			{
+				.name	= "FORWARD",
+				.type	= "filter",
+				.prio	= 150,	/* NF_IP_PRI_SECURITY */
+				.hook	= NF_INET_FORWARD,
+			},
+			{
+				.name	= "OUTPUT",
+				.type	= "filter",
+				.prio	= 150,	/* NF_IP_PRI_SECURITY */
+				.hook	= NF_INET_LOCAL_OUT,
+			},
+		},
+	},
+	[NAT] = {
+		.name	= "nat",
+		.chains = {
+			{
+				.name	= "PREROUTING",
+				.type	= "nat",
+				.prio	= -100, /* NF_IP_PRI_NAT_DST */
+				.hook	= NF_INET_PRE_ROUTING,
+			},
+			{
+				.name	= "INPUT",
+				.type	= "nat",
+				.prio	= 100, /* NF_IP_PRI_NAT_SRC */
+				.hook	= NF_INET_LOCAL_IN,
+			},
+			{
+				.name	= "POSTROUTING",
+				.type	= "nat",
+				.prio	= 100, /* NF_IP_PRI_NAT_SRC */
+				.hook	= NF_INET_POST_ROUTING,
+			},
+			{
+				.name	= "OUTPUT",
+				.type	= "nat",
+				.prio	= -100, /* NF_IP_PRI_NAT_DST */
+				.hook	= NF_INET_LOCAL_OUT,
+			},
+		},
+	},
+};
+
+#include <linux/netfilter_arp.h>
+
+struct builtin_table xtables_arp[TABLES_MAX] = {
+	[FILTER] = {
+	.name   = "filter",
+	.chains = {
+			{
+				.name   = "INPUT",
+				.type   = "filter",
+				.prio   = NF_IP_PRI_FILTER,
+				.hook   = NF_ARP_IN,
+			},
+			{
+				.name   = "FORWARD",
+				.type   = "filter",
+				.prio   = NF_IP_PRI_FILTER,
+				.hook   = NF_ARP_FORWARD,
+			},
+			{
+				.name   = "OUTPUT",
+				.type   = "filter",
+				.prio   = NF_IP_PRI_FILTER,
+				.hook   = NF_ARP_OUT,
+			},
+		},
+	},
+};
+
+int
+nft_table_builtin_add(struct nft_handle *h, struct builtin_table *_t,
+			bool dormant)
+{
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh;
+	struct nft_table *t;
+	int ret;
+
+	if (_t->initialized)
+		return 0;
+
+	t = nft_table_alloc();
+	if (t == NULL)
+		return -1;
+
+	nft_table_attr_set(t, NFT_TABLE_ATTR_NAME, (char *)_t->name);
+	if (dormant) {
+		nft_table_attr_set_u32(t, NFT_TABLE_ATTR_FLAGS,
+					NFT_TABLE_F_DORMANT);
+	}
+
+	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);
+
+#ifdef NLDEBUG
+	char tmp[1024];
+
+	nft_table_snprintf(tmp, sizeof(tmp), t, 0, 0);
+	printf("DEBUG: table: %s\n", tmp);
+	mnl_nlmsg_fprintf(stdout, nlh, nlh->nlmsg_len, sizeof(struct nfgenmsg));
+#endif
+
+	ret = mnl_talk(h, nlh, NULL, NULL);
+	if (ret == 0 || errno == EEXIST)
+		_t->initialized = true;
+
+	return ret;
+}
+
+struct nft_chain *
+nft_chain_builtin_alloc(struct builtin_table *table,
+			struct builtin_chain *chain, int policy)
+{
+	struct nft_chain *c;
+
+	c = nft_chain_alloc();
+	if (c == NULL)
+		return NULL;
+
+	nft_chain_attr_set(c, NFT_CHAIN_ATTR_TABLE, (char *)table->name);
+	nft_chain_attr_set(c, NFT_CHAIN_ATTR_NAME, (char *)chain->name);
+	nft_chain_attr_set_u32(c, NFT_CHAIN_ATTR_HOOKNUM, chain->hook);
+	nft_chain_attr_set_u32(c, NFT_CHAIN_ATTR_PRIO, chain->prio);
+	nft_chain_attr_set_u32(c, NFT_CHAIN_ATTR_POLICY, policy);
+	nft_chain_attr_set(c, NFT_CHAIN_ATTR_TYPE, (char *)chain->type);
+
+	return c;
+}
+
+void
+nft_chain_builtin_add(struct nft_handle *h, struct builtin_table *table,
+		      struct builtin_chain *chain, int policy)
+{
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh;
+	struct nft_chain *c;
+
+	c = nft_chain_builtin_alloc(table, chain, policy);
+	if (c == NULL)
+		return;
+
+	/* NLM_F_CREATE requests module autoloading */
+	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);
+	nft_chain_free(c);
+
+	mnl_talk(h, nlh, NULL, NULL);
+}
+
+/* find if built-in table already exists */
+struct builtin_table *
+nft_table_builtin_find(struct nft_handle *h, const char *table)
+{
+	int i;
+	bool found = false;
+
+	for (i=0; i<TABLES_MAX; i++) {
+		if (h->tables[i].name == NULL)
+			break;
+
+		if (strcmp(h->tables[i].name, table) != 0)
+			continue;
+
+		found = true;
+		break;
+	}
+
+	return found ? &h->tables[i] : NULL;
+}
+
+/* find if built-in chain already exists */
+struct builtin_chain *
+nft_chain_builtin_find(struct builtin_table *t, const char *chain)
+{
+	int i;
+	bool found = false;
+
+	for (i=0; i<NF_IP_NUMHOOKS && t->chains[i].name != NULL; i++) {
+		if (strcmp(t->chains[i].name, chain) != 0)
+			continue;
+
+		found = true;
+		break;
+	}
+	return found ? &t->chains[i] : NULL;
+}
+
+static void
+__nft_chain_builtin_init(struct nft_handle *h,
+			 struct builtin_table *table, const char *chain,
+			 int policy)
+{
+	int i, default_policy;
+
+	/* Initialize all built-in chains. Exception, for e one received as
+	 * parameter, set the default policy as requested.
+	 */
+	for (i=0; i<NF_IP_NUMHOOKS && table->chains[i].name != NULL; i++) {
+		if (chain && strcmp(table->chains[i].name, chain) == 0)
+			default_policy = policy;
+		else
+			default_policy = NF_ACCEPT;
+
+		nft_chain_builtin_add(h, table, &table->chains[i],
+					default_policy);
+	}
+}
+
+int
+nft_chain_builtin_init(struct nft_handle *h, const char *table,
+		       const char *chain, int policy)
+{
+	int ret = 0;
+	struct builtin_table *t;
+
+	t = nft_table_builtin_find(h, table);
+	if (t == NULL) {
+		ret = -1;
+		goto out;
+	}
+	if (nft_table_builtin_add(h, t, false) < 0) {
+		/* Built-in table already initialized, skip. */
+		if (errno == EEXIST)
+			goto out;
+	}
+	__nft_chain_builtin_init(h, t, chain, policy);
+out:
+	return ret;
+}
+
+static bool nft_chain_builtin(struct nft_chain *c)
+{
+	/* Check if this chain has hook number, in that case is built-in.
+	 * Should we better export the flags to user-space via nf_tables?
+	 */
+	return nft_chain_attr_get(c, NFT_CHAIN_ATTR_HOOKNUM) != NULL;
+}
+
+int nft_init(struct nft_handle *h, struct builtin_table *t)
+{
+	h->nl = mnl_socket_open(NETLINK_NETFILTER);
+	if (h->nl == NULL) {
+		perror("mnl_socket_open");
+		return -1;
+	}
+
+	if (mnl_socket_bind(h->nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+		perror("mnl_socket_bind");
+		return -1;
+	}
+	h->portid = mnl_socket_get_portid(h->nl);
+	h->tables = t;
+
+	INIT_LIST_HEAD(&h->rule_list);
+
+	h->batch = mnl_nft_batch_alloc();
+
+	return 0;
+}
+
+void nft_fini(struct nft_handle *h)
+{
+	mnl_socket_close(h->nl);
+	free(mnl_nlmsg_batch_head(h->batch));
+	mnl_nlmsg_batch_stop(h->batch);
+}
+
+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, h->family,
+					NLM_F_ACK|NLM_F_EXCL, h->seq);
+	nft_table_nlmsg_build_payload(nlh, t);
+
+	return mnl_talk(h, nlh, NULL, NULL);
+}
+
+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, h->family,
+					NLM_F_ACK|NLM_F_EXCL, h->seq);
+	nft_chain_nlmsg_build_payload(nlh, c);
+
+	return mnl_talk(h, nlh, NULL, NULL);
+}
+
+int nft_table_set_dormant(struct nft_handle *h, const char *table)
+{
+	int ret = 0, i;
+	struct builtin_table *t;
+
+	t = nft_table_builtin_find(h, table);
+	if (t == NULL) {
+		ret = -1;
+		goto out;
+	}
+	/* Add this table as dormant */
+	if (nft_table_builtin_add(h, t, true) < 0) {
+		/* Built-in table already initialized, skip. */
+		if (errno == EEXIST)
+			goto out;
+	}
+	for (i=0; t->chains[i].name != NULL && i<NF_INET_NUMHOOKS; i++)
+		__nft_chain_builtin_init(h, t, t->chains[i].name, NF_ACCEPT);
+out:
+	return ret;
+}
+
+int nft_table_wake_dormant(struct nft_handle *h, const char *table)
+{
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh;
+	struct nft_table *t;
+
+	t = nft_table_alloc();
+	if (t == NULL)
+		return -1;
+
+	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, h->family,
+					NLM_F_ACK, h->seq);
+	nft_table_nlmsg_build_payload(nlh, t);
+	nft_table_free(t);
+
+	return mnl_talk(h, nlh, NULL, NULL);
+}
+
+static void nft_chain_print_debug(struct nft_chain *c, struct nlmsghdr *nlh)
+{
+#ifdef NLDEBUG
+	char tmp[1024];
+
+	nft_chain_snprintf(tmp, sizeof(tmp), c, 0, 0);
+	printf("DEBUG: chain: %s\n", tmp);
+	mnl_nlmsg_fprintf(stdout, nlh, nlh->nlmsg_len, sizeof(struct nfgenmsg));
+#endif
+}
+
+static int
+__nft_chain_set(struct nft_handle *h, const char *table,
+		const char *chain, int policy,
+		const struct xt_counters *counters)
+{
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh;
+	struct nft_chain *c;
+	struct builtin_table *_t;
+	struct builtin_chain *_c;
+
+	_t = nft_table_builtin_find(h, table);
+	/* if this built-in table does not exists, create it */
+	if (_t != NULL)
+		nft_table_builtin_add(h, _t, false);
+
+	_c = nft_chain_builtin_find(_t, chain);
+	if (_c != NULL) {
+		/* This is a built-in chain */
+		c = nft_chain_builtin_alloc(_t, _c, policy);
+		if (c == NULL)
+			return -1;
+	} else {
+		errno = ENOENT;
+		return -1;
+	}
+
+	if (counters) {
+		nft_chain_attr_set_u64(c, NFT_CHAIN_ATTR_BYTES,
+					counters->bcnt);
+		nft_chain_attr_set_u64(c, NFT_CHAIN_ATTR_PACKETS,
+					counters->pcnt);
+	}
+
+	nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_NEWCHAIN, h->family,
+					h->restore ? NLM_F_ACK|NLM_F_CREATE :
+					NLM_F_ACK, h->seq);
+	nft_chain_nlmsg_build_payload(nlh, c);
+
+	nft_chain_print_debug(c, nlh);
+
+	nft_chain_free(c);
+
+	return mnl_talk(h, nlh, NULL, NULL);
+}
+
+int nft_chain_set(struct nft_handle *h, const char *table,
+		  const char *chain, const char *policy,
+		  const struct xt_counters *counters)
+{
+	int ret = -1;
+
+	nft_fn = nft_chain_set;
+
+	if (strcmp(policy, "DROP") == 0)
+		ret = __nft_chain_set(h, table, chain, NF_DROP, counters);
+	else if (strcmp(policy, "ACCEPT") == 0)
+		ret = __nft_chain_set(h, table, chain, NF_ACCEPT, counters);
+
+	/* the core expects 1 for success and 0 for error */
+	return ret == 0 ? 1 : 0;
+}
+
+static int __add_match(struct nft_rule_expr *e, struct xt_entry_match *m)
+{
+	void *info;
+
+	nft_rule_expr_set(e, NFT_EXPR_MT_NAME, m->u.user.name, strlen(m->u.user.name));
+	nft_rule_expr_set_u32(e, NFT_EXPR_MT_REV, m->u.user.revision);
+
+	info = calloc(1, m->u.match_size);
+	if (info == NULL)
+		return -ENOMEM;
+
+	memcpy(info, m->data, m->u.match_size - sizeof(*m));
+	nft_rule_expr_set(e, NFT_EXPR_MT_INFO, info, m->u.match_size - sizeof(*m));
+
+	return 0;
+}
+
+int add_match(struct nft_rule *r, struct xt_entry_match *m)
+{
+	struct nft_rule_expr *expr;
+	int ret;
+
+	expr = nft_rule_expr_alloc("match");
+	if (expr == NULL)
+		return -ENOMEM;
+
+	ret = __add_match(expr, m);
+	nft_rule_add_expr(r, expr);
+
+	return ret;
+}
+
+static int __add_target(struct nft_rule_expr *e, struct xt_entry_target *t)
+{
+	void *info;
+
+	nft_rule_expr_set(e, NFT_EXPR_TG_NAME, t->u.user.name,
+			  strlen(t->u.user.name));
+	nft_rule_expr_set_u32(e, NFT_EXPR_TG_REV, t->u.user.revision);
+
+	info = calloc(1, t->u.target_size);
+	if (info == NULL)
+		return -ENOMEM;
+
+	memcpy(info, t->data, t->u.target_size - sizeof(*t));
+	nft_rule_expr_set(e, NFT_EXPR_TG_INFO, info, t->u.target_size - sizeof(*t));
+
+	return 0;
+}
+
+int add_target(struct nft_rule *r, struct xt_entry_target *t)
+{
+	struct nft_rule_expr *expr;
+	int ret;
+
+	expr = nft_rule_expr_alloc("target");
+	if (expr == NULL)
+		return -ENOMEM;
+
+	ret = __add_target(expr, t);
+	nft_rule_add_expr(r, expr);
+
+	return ret;
+}
+
+int add_jumpto(struct nft_rule *r, const char *name, int verdict)
+{
+	struct nft_rule_expr *expr;
+
+	expr = nft_rule_expr_alloc("immediate");
+	if (expr == NULL)
+		return -ENOMEM;
+
+	nft_rule_expr_set_u32(expr, NFT_EXPR_IMM_DREG, NFT_REG_VERDICT);
+	nft_rule_expr_set_u32(expr, NFT_EXPR_IMM_VERDICT, verdict);
+	nft_rule_expr_set_str(expr, NFT_EXPR_IMM_CHAIN, (char *)name);
+	nft_rule_add_expr(r, expr);
+
+	return 0;
+}
+
+int add_verdict(struct nft_rule *r, int verdict)
+{
+	struct nft_rule_expr *expr;
+
+	expr = nft_rule_expr_alloc("immediate");
+	if (expr == NULL)
+		return -ENOMEM;
+
+	nft_rule_expr_set_u32(expr, NFT_EXPR_IMM_DREG, NFT_REG_VERDICT);
+	nft_rule_expr_set_u32(expr, NFT_EXPR_IMM_VERDICT, verdict);
+	nft_rule_add_expr(r, expr);
+
+	return 0;
+}
+
+int add_action(struct nft_rule *r, struct iptables_command_state *cs,
+	       bool goto_set)
+{
+       int ret = 0;
+
+       /* If no target at all, add nothing (default to continue) */
+       if (cs->target != NULL) {
+	       /* Standard target? */
+	       if (strcmp(cs->jumpto, XTC_LABEL_ACCEPT) == 0)
+		       ret = add_verdict(r, NF_ACCEPT);
+	       else if (strcmp(cs->jumpto, XTC_LABEL_DROP) == 0)
+		       ret = add_verdict(r, NF_DROP);
+	       else if (strcmp(cs->jumpto, XTC_LABEL_RETURN) == 0)
+		       ret = add_verdict(r, NFT_RETURN);
+	       else
+		       ret = add_target(r, cs->target->t);
+       } else if (strlen(cs->jumpto) > 0) {
+	       /* Not standard, then it's a go / jump to chain */
+	       if (goto_set)
+		       ret = add_jumpto(r, cs->jumpto, NFT_GOTO);
+	       else
+		       ret = add_jumpto(r, cs->jumpto, NFT_JUMP);
+       }
+       return ret;
+}
+
+static void nft_rule_print_debug(struct nft_rule *r, struct nlmsghdr *nlh)
+{
+#ifdef NLDEBUG
+	char tmp[1024];
+
+	nft_rule_snprintf(tmp, sizeof(tmp), r, 0, 0);
+	printf("DEBUG: rule: %s\n", tmp);
+	mnl_nlmsg_fprintf(stdout, nlh, nlh->nlmsg_len, sizeof(struct nfgenmsg));
+#endif
+}
+
+int add_counters(struct nft_rule *r, uint64_t packets, uint64_t bytes)
+{
+	struct nft_rule_expr *expr;
+
+	expr = nft_rule_expr_alloc("counter");
+	if (expr == NULL)
+		return -ENOMEM;
+
+	nft_rule_expr_set_u64(expr, NFT_EXPR_CTR_BYTES, packets);
+	nft_rule_expr_set_u64(expr, NFT_EXPR_CTR_PACKETS, bytes);
+
+	nft_rule_add_expr(r, expr);
+
+	return 0;
+}
+
+void add_compat(struct nft_rule *r, uint32_t proto, bool inv)
+{
+	nft_rule_attr_set_u32(r, NFT_RULE_ATTR_COMPAT_PROTO, proto);
+	nft_rule_attr_set_u32(r, NFT_RULE_ATTR_COMPAT_FLAGS,
+			      inv ? NFT_RULE_COMPAT_F_INV : 0);
+}
+
+static struct nft_rule *
+nft_rule_new(struct nft_handle *h, const char *chain, const char *table,
+	     void *data)
+{
+	struct nft_rule *r;
+
+	r = nft_rule_alloc();
+	if (r == NULL)
+		return NULL;
+
+	nft_rule_attr_set_u32(r, NFT_RULE_ATTR_FAMILY, h->family);
+	nft_rule_attr_set(r, NFT_RULE_ATTR_TABLE, (char *)table);
+	nft_rule_attr_set(r, NFT_RULE_ATTR_CHAIN, (char *)chain);
+
+	if (h->ops->add(r, data) < 0)
+		goto err;
+
+	return r;
+err:
+	nft_rule_free(r);
+	return NULL;
+}
+
+enum rule_update_type {
+       NFT_DO_APPEND,
+       NFT_DO_INSERT,
+       NFT_DO_REPLACE,
+       NFT_DO_DELETE,
+       NFT_DO_FLUSH,
+       NFT_DO_COMMIT,
+       NFT_DO_ABORT,
+};
+
+struct rule_update {
+       struct list_head        head;
+       enum rule_update_type   type;
+       struct nft_rule	       *rule;
+};
+
+static int rule_update_add(struct nft_handle *h, enum rule_update_type type,
+			  struct nft_rule *r)
+{
+       struct rule_update *rupd;
+
+       rupd = calloc(1, sizeof(struct rule_update));
+       if (rupd == NULL)
+	       return -1;
+
+       rupd->rule = r;
+       rupd->type = type;
+       list_add_tail(&rupd->head, &h->rule_list);
+       h->rule_list_num++;
+
+       return 0;
+}
+
+int
+nft_rule_append(struct nft_handle *h, const char *chain, const char *table,
+		void *data, uint64_t handle, bool verbose)
+{
+	struct nft_rule *r;
+	int type;
+
+	/* If built-in chains don't exist for this table, create them */
+	if (nft_xtables_config_load(h, XTABLES_CONFIG_DEFAULT, 0) < 0)
+		nft_chain_builtin_init(h, table, chain, NF_ACCEPT);
+
+	nft_fn = nft_rule_append;
+
+	r = nft_rule_new(h, chain, table, data);
+	if (r == NULL)
+		return 0;
+
+	if (handle > 0) {
+		nft_rule_attr_set(r, NFT_RULE_ATTR_HANDLE, &handle);
+		type = NFT_DO_REPLACE;
+	} else
+		type = NFT_DO_APPEND;
+
+	if (rule_update_add(h, type, r) < 0)
+		nft_rule_free(r);
+
+	return 1;
+}
+
+void
+nft_rule_print_save(const void *data,
+		    struct nft_rule *r, enum nft_rule_print type,
+		    unsigned int format)
+{
+	const char *chain = nft_rule_attr_get_str(r, NFT_RULE_ATTR_CHAIN);
+	int family = nft_rule_attr_get_u8(r, NFT_RULE_ATTR_FAMILY);
+	struct nft_family_ops *ops;
+
+	/* print chain name */
+	switch(type) {
+	case NFT_RULE_APPEND:
+		printf("-A %s ", chain);
+		break;
+	case NFT_RULE_DEL:
+		printf("-D %s ", chain);
+		break;
+	}
+
+	ops = nft_family_ops_lookup(family);
+
+	if (ops->save_firewall)
+		ops->save_firewall(data, format);
+
+}
+
+static int nft_chain_list_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct nft_chain *c;
+	struct nft_chain_list *list = data;
+
+	c = nft_chain_alloc();
+	if (c == NULL) {
+		perror("OOM");
+		goto err;
+	}
+
+	if (nft_chain_nlmsg_parse(nlh, c) < 0) {
+		perror("nft_rule_nlmsg_parse");
+		goto out;
+	}
+
+	nft_chain_list_add_tail(c, list);
+
+	return MNL_CB_OK;
+out:
+	nft_chain_free(c);
+err:
+	return MNL_CB_OK;
+}
+
+static struct nft_chain_list *nft_chain_list_get(struct nft_handle *h)
+{
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh;
+	struct nft_chain_list *list;
+
+	list = nft_chain_list_alloc();
+	if (list == NULL) {
+		errno = ENOMEM;
+		return NULL;
+	}
+
+	nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_GETCHAIN, h->family,
+					NLM_F_DUMP, h->seq);
+
+	mnl_talk(h, nlh, nft_chain_list_cb, list);
+
+	return list;
+}
+
+struct nft_chain_list *nft_chain_dump(struct nft_handle *h)
+{
+	return nft_chain_list_get(h);
+}
+
+static const char *policy_name[NF_ACCEPT+1] = {
+	[NF_DROP] = "DROP",
+	[NF_ACCEPT] = "ACCEPT",
+};
+
+static void nft_chain_print_save(struct nft_chain *c, bool basechain)
+{
+	const char *chain = nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_NAME);
+	uint64_t pkts = nft_chain_attr_get_u64(c, NFT_CHAIN_ATTR_PACKETS);
+	uint64_t bytes = nft_chain_attr_get_u64(c, NFT_CHAIN_ATTR_BYTES);
+
+	/* print chain name */
+	if (basechain) {
+		uint32_t pol = NF_ACCEPT;
+
+		/* no default chain policy? don't crash, display accept */
+		if (nft_chain_attr_get(c, NFT_CHAIN_ATTR_POLICY))
+			pol = nft_chain_attr_get_u32(c, NFT_CHAIN_ATTR_POLICY);
+
+		printf(":%s %s [%"PRIu64":%"PRIu64"]\n", chain, policy_name[pol],
+					     pkts, bytes);
+	} else
+		printf(":%s - [%"PRIu64":%"PRIu64"]\n", chain, pkts, bytes);
+}
+
+int nft_chain_save(struct nft_handle *h, struct nft_chain_list *list,
+		   const char *table)
+{
+	struct nft_chain_list_iter *iter;
+	struct nft_chain *c;
+
+	iter = nft_chain_list_iter_create(list);
+	if (iter == NULL)
+		return 0;
+
+	c = nft_chain_list_iter_next(iter);
+	while (c != NULL) {
+		const char *chain_table =
+			nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_TABLE);
+		bool basechain = false;
+
+		if (strcmp(table, chain_table) != 0)
+			goto next;
+
+		basechain = nft_chain_builtin(c);
+		nft_chain_print_save(c, basechain);
+next:
+		c = nft_chain_list_iter_next(iter);
+	}
+
+	nft_chain_list_iter_destroy(iter);
+	nft_chain_list_free(list);
+
+	return 1;
+}
+
+static int nft_rule_list_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct nft_rule *r;
+	struct nft_rule_list *list = data;
+
+	r = nft_rule_alloc();
+	if (r == NULL) {
+		perror("OOM");
+		goto err;
+	}
+
+	if (nft_rule_nlmsg_parse(nlh, r) < 0) {
+		perror("nft_rule_nlmsg_parse");
+		goto out;
+	}
+
+	nft_rule_list_add_tail(r, list);
+
+	return MNL_CB_OK;
+out:
+	nft_rule_free(r);
+	nft_rule_list_free(list);
+err:
+	return MNL_CB_OK;
+}
+
+static struct nft_rule_list *nft_rule_list_get(struct nft_handle *h)
+{
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh;
+	struct nft_rule_list *list;
+	int ret;
+
+	list = nft_rule_list_alloc();
+	if (list == NULL)
+		return 0;
+
+	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);
+	if (ret < 0) {
+		nft_rule_list_free(list);
+		return NULL;
+	}
+
+	return list;
+}
+
+int nft_rule_save(struct nft_handle *h, const char *table, bool counters)
+{
+	struct nft_rule_list *list;
+	struct nft_rule_list_iter *iter;
+	struct nft_rule *r;
+
+	list = nft_rule_list_get(h);
+	if (list == NULL)
+		return 0;
+
+	iter = nft_rule_list_iter_create(list);
+	if (iter == NULL)
+		return 0;
+
+	r = nft_rule_list_iter_next(iter);
+	while (r != NULL) {
+		const char *rule_table =
+			nft_rule_attr_get_str(r, NFT_RULE_ATTR_TABLE);
+		struct iptables_command_state cs = {};
+
+		if (strcmp(table, rule_table) != 0)
+			goto next;
+
+		nft_rule_to_iptables_command_state(r, &cs);
+
+		nft_rule_print_save(&cs, r, NFT_RULE_APPEND,
+				    counters ? 0 : FMT_NOCOUNTS);
+
+next:
+		r = nft_rule_list_iter_next(iter);
+	}
+
+	nft_rule_list_iter_destroy(iter);
+	nft_rule_list_free(list);
+
+	/* the core expects 1 for success and 0 for error */
+	return 1;
+}
+
+static void
+__nft_rule_flush(struct nft_handle *h, const char *table, const char *chain)
+{
+	struct nft_rule *r;
+
+	r = nft_rule_alloc();
+	if (r == NULL)
+		return;
+
+	nft_rule_attr_set(r, NFT_RULE_ATTR_TABLE, (char *)table);
+	nft_rule_attr_set(r, NFT_RULE_ATTR_CHAIN, (char *)chain);
+
+	if (rule_update_add(h, NFT_DO_FLUSH, r) < 0)
+		nft_rule_free(r);
+}
+
+int nft_rule_flush(struct nft_handle *h, const char *chain, const char *table)
+{
+	int ret;
+	struct nft_chain_list *list;
+	struct nft_chain_list_iter *iter;
+	struct nft_chain *c;
+
+	nft_fn = nft_rule_flush;
+
+	list = nft_chain_list_get(h);
+	if (list == NULL) {
+		ret = 0;
+		goto err;
+	}
+
+	iter = nft_chain_list_iter_create(list);
+	if (iter == NULL)
+		goto err;
+
+	c = nft_chain_list_iter_next(iter);
+	while (c != NULL) {
+		const char *table_name =
+			nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_TABLE);
+		const char *chain_name =
+			nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_NAME);
+
+		if (strcmp(table, table_name) != 0)
+			goto next;
+
+		if (chain != NULL && strcmp(chain, chain_name) != 0)
+			goto next;
+
+		__nft_rule_flush(h, table_name, chain_name);
+
+		if (chain != NULL)
+			break;
+next:
+		c = nft_chain_list_iter_next(iter);
+	}
+
+	nft_chain_list_iter_destroy(iter);
+err:
+	nft_chain_list_free(list);
+
+	/* the core expects 1 for success and 0 for error */
+	return ret == 0 ? 1 : 0;
+}
+
+int nft_chain_user_add(struct nft_handle *h, const char *chain, const char *table)
+{
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh;
+	struct nft_chain *c;
+	int ret;
+
+	/* If built-in chains don't exist for this table, create them */
+	if (nft_xtables_config_load(h, XTABLES_CONFIG_DEFAULT, 0) < 0)
+		nft_chain_builtin_init(h, table, NULL, NF_ACCEPT);
+
+	c = nft_chain_alloc();
+	if (c == NULL)
+		return 0;
+
+	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, h->family,
+					NLM_F_ACK|NLM_F_EXCL, h->seq);
+	nft_chain_nlmsg_build_payload(nlh, c);
+	nft_chain_free(c);
+
+	ret = mnl_talk(h, nlh, NULL, NULL);
+
+	/* the core expects 1 for success and 0 for error */
+	return ret == 0 ? 1 : 0;
+}
+
+static int __nft_chain_del(struct nft_handle *h, struct nft_chain *c)
+{
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh;
+
+	nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_DELCHAIN, h->family,
+					NLM_F_ACK, h->seq);
+	nft_chain_nlmsg_build_payload(nlh, c);
+
+	return mnl_talk(h, nlh, NULL, NULL);
+}
+
+int nft_chain_user_del(struct nft_handle *h, const char *chain, const char *table)
+{
+	struct nft_chain_list *list;
+	struct nft_chain_list_iter *iter;
+	struct nft_chain *c;
+	int ret = 0;
+	int deleted_ctr = 0;
+
+	list = nft_chain_list_get(h);
+	if (list == NULL)
+		goto err;
+
+	iter = nft_chain_list_iter_create(list);
+	if (iter == NULL)
+		goto err;
+
+	c = nft_chain_list_iter_next(iter);
+	while (c != NULL) {
+		const char *table_name =
+			nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_TABLE);
+		const char *chain_name =
+			nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_NAME);
+
+		/* don't delete built-in chain */
+		if (nft_chain_builtin(c))
+			goto next;
+
+		if (strcmp(table, table_name) != 0)
+			goto next;
+
+		if (chain != NULL && strcmp(chain, chain_name) != 0)
+			goto next;
+
+		ret = __nft_chain_del(h, c);
+		if (ret < 0)
+			break;
+
+		deleted_ctr++;
+
+		if (chain != NULL)
+			break;
+next:
+		c = nft_chain_list_iter_next(iter);
+	}
+
+	nft_chain_list_iter_destroy(iter);
+err:
+	nft_chain_list_free(list);
+
+	/* chain not found */
+	if (ret < 0 && deleted_ctr == 0)
+		errno = ENOENT;
+
+	/* the core expects 1 for success and 0 for error */
+	return ret == 0 ? 1 : 0;
+}
+
+struct nft_chain *
+nft_chain_list_find(struct nft_chain_list *list,
+		    const char *table, const char *chain)
+{
+	struct nft_chain_list_iter *iter;
+	struct nft_chain *c;
+
+	iter = nft_chain_list_iter_create(list);
+	if (iter == NULL)
+		return NULL;
+
+	c = nft_chain_list_iter_next(iter);
+	while (c != NULL) {
+		const char *table_name =
+			nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_TABLE);
+		const char *chain_name =
+			nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_NAME);
+
+		if (strcmp(table, table_name) != 0)
+			goto next;
+
+		if (strcmp(chain, chain_name) != 0)
+			goto next;
+
+		nft_chain_list_iter_destroy(iter);
+		return c;
+next:
+		c = nft_chain_list_iter_next(iter);
+	}
+	nft_chain_list_iter_destroy(iter);
+	return NULL;
+}
+
+static struct nft_chain *
+nft_chain_find(struct nft_handle *h, const char *table, const char *chain)
+{
+	struct nft_chain_list *list;
+
+	list = nft_chain_list_get(h);
+	if (list == NULL)
+		return NULL;
+
+	return nft_chain_list_find(list, table, chain);
+}
+
+int nft_chain_user_rename(struct nft_handle *h,const char *chain,
+			  const char *table, const char *newname)
+{
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh;
+	struct nft_chain *c;
+	uint64_t handle;
+	int ret;
+
+	/* If built-in chains don't exist for this table, create them */
+	if (nft_xtables_config_load(h, XTABLES_CONFIG_DEFAULT, 0) < 0)
+		nft_chain_builtin_init(h, table, NULL, NF_ACCEPT);
+
+	/* Find the old chain to be renamed */
+	c = nft_chain_find(h, table, chain);
+	if (c == NULL) {
+		errno = ENOENT;
+		return -1;
+	}
+	handle = nft_chain_attr_get_u64(c, NFT_CHAIN_ATTR_HANDLE);
+
+	/* Now prepare the new name for the chain */
+	c = nft_chain_alloc();
+	if (c == NULL)
+		return -1;
+
+	nft_chain_attr_set(c, NFT_CHAIN_ATTR_TABLE, (char *)table);
+	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, h->family,
+					NLM_F_ACK, h->seq);
+	nft_chain_nlmsg_build_payload(nlh, c);
+	nft_chain_free(c);
+
+	ret = mnl_talk(h, nlh, NULL, NULL);
+
+	/* the core expects 1 for success and 0 for error */
+	return ret == 0 ? 1 : 0;
+}
+
+static int nft_table_list_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct nft_table *t;
+	struct nft_table_list *list = data;
+
+	t = nft_table_alloc();
+	if (t == NULL) {
+		perror("OOM");
+		goto err;
+	}
+
+	if (nft_table_nlmsg_parse(nlh, t) < 0) {
+		perror("nft_rule_nlmsg_parse");
+		goto out;
+	}
+
+	nft_table_list_add_tail(t, list);
+
+	return MNL_CB_OK;
+out:
+	nft_table_free(t);
+err:
+	return MNL_CB_OK;
+}
+
+static struct nft_table_list *nft_table_list_get(struct nft_handle *h)
+{
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh;
+	struct nft_table_list *list;
+
+	list = nft_table_list_alloc();
+	if (list == NULL)
+		return 0;
+
+	nlh = nft_rule_nlmsg_build_hdr(buf, NFT_MSG_GETTABLE, h->family,
+					NLM_F_DUMP, h->seq);
+
+	mnl_talk(h, nlh, nft_table_list_cb, list);
+
+	return list;
+}
+
+bool nft_table_find(struct nft_handle *h, const char *tablename)
+{
+	struct nft_table_list *list;
+	struct nft_table_list_iter *iter;
+	struct nft_table *t;
+	bool ret = false;
+
+	list = nft_table_list_get(h);
+	if (list == NULL)
+		goto err;
+
+	iter = nft_table_list_iter_create(list);
+	if (iter == NULL)
+		goto err;
+
+	t = nft_table_list_iter_next(iter);
+	while (t != NULL) {
+		const char *this_tablename =
+			nft_table_attr_get(t, NFT_TABLE_ATTR_NAME);
+
+		if (strcmp(tablename, this_tablename) == 0)
+			return true;
+
+		t = nft_table_list_iter_next(iter);
+	}
+
+	nft_table_list_free(list);
+
+err:
+	return ret;
+}
+
+int nft_for_each_table(struct nft_handle *h,
+		       int (*func)(struct nft_handle *h, const char *tablename, bool counters),
+		       bool counters)
+{
+	int ret = 1;
+	struct nft_table_list *list;
+	struct nft_table_list_iter *iter;
+	struct nft_table *t;
+
+	list = nft_table_list_get(h);
+	if (list == NULL) {
+		ret = 0;
+		goto err;
+	}
+
+	iter = nft_table_list_iter_create(list);
+	if (iter == NULL)
+		return 0;
+
+	t = nft_table_list_iter_next(iter);
+	while (t != NULL) {
+		const char *tablename =
+			nft_table_attr_get(t, NFT_TABLE_ATTR_NAME);
+
+		func(h, tablename, counters);
+
+		t = nft_table_list_iter_next(iter);
+	}
+
+	nft_table_list_free(list);
+
+err:
+	/* the core expects 1 for success and 0 for error */
+	return ret == 0 ? 1 : 0;
+}
+
+int nft_table_purge_chains(struct nft_handle *h, const char *this_table,
+			   struct nft_chain_list *chain_list)
+{
+	struct nft_chain_list_iter *iter;
+	struct nft_chain *chain_obj;
+
+	iter = nft_chain_list_iter_create(chain_list);
+	if (iter == NULL)
+		return 0;
+
+	chain_obj = nft_chain_list_iter_next(iter);
+	while (chain_obj != NULL) {
+		const char *table =
+			nft_chain_attr_get_str(chain_obj, NFT_CHAIN_ATTR_TABLE);
+
+		if (strcmp(this_table, table) != 0)
+			goto next;
+
+		if (nft_chain_builtin(chain_obj))
+			goto next;
+
+		if ( __nft_chain_del(h, chain_obj) < 0) {
+			if (errno != EBUSY)
+				return -1;
+		}
+next:
+		chain_obj = nft_chain_list_iter_next(iter);
+	}
+	nft_chain_list_iter_destroy(iter);
+
+	return 0;
+}
+
+static int __nft_rule_del(struct nft_handle *h, struct nft_rule_list *list,
+			  struct nft_rule *r)
+{
+	int ret;
+
+	nft_rule_list_del(r);
+
+	ret = rule_update_add(h, NFT_DO_DELETE, r);
+	if (ret < 0) {
+		nft_rule_free(r);
+		return -1;
+	}
+	return 1;
+}
+
+struct nft_rule_list *nft_rule_list_create(struct nft_handle *h)
+{
+	return nft_rule_list_get(h);
+}
+
+void nft_rule_list_destroy(struct nft_rule_list *list)
+{
+	nft_rule_list_free(list);
+}
+
+static struct nft_rule *
+nft_rule_find(struct nft_handle *h, struct nft_rule_list *list,
+	      const char *chain, const char *table, void *data, int rulenum)
+{
+	struct nft_rule *r;
+	struct nft_rule_list_iter *iter;
+	int rule_ctr = 0;
+	bool found = false;
+
+	iter = nft_rule_list_iter_create(list);
+	if (iter == NULL)
+		return 0;
+
+	r = nft_rule_list_iter_next(iter);
+	while (r != NULL) {
+		const char *rule_table =
+			nft_rule_attr_get_str(r, NFT_RULE_ATTR_TABLE);
+		const char *rule_chain =
+			nft_rule_attr_get_str(r, NFT_RULE_ATTR_CHAIN);
+
+		if (strcmp(table, rule_table) != 0 ||
+		    strcmp(chain, rule_chain) != 0) {
+			DEBUGP("different chain / table\n");
+			goto next;
+		}
+
+		if (rulenum >= 0) {
+			/* Delete by rule number case */
+			if (rule_ctr != rulenum)
+				goto next;
+			found = true;
+			break;
+		} else {
+			found = h->ops->rule_find(h->ops, r, data);
+			if (found)
+				break;
+		}
+next:
+		rule_ctr++;
+		r = nft_rule_list_iter_next(iter);
+	}
+
+	nft_rule_list_iter_destroy(iter);
+
+	return found ? r : NULL;
+}
+
+int nft_rule_check(struct nft_handle *h, const char *chain,
+		   const char *table, void *data, bool verbose)
+{
+	struct nft_rule_list *list;
+	int ret;
+
+	nft_fn = nft_rule_check;
+
+	list = nft_rule_list_create(h);
+	if (list == NULL)
+		return 0;
+
+	ret = nft_rule_find(h, list, chain, table, data, -1) ? 1 : 0;
+	if (ret == 0)
+		errno = ENOENT;
+
+	nft_rule_list_destroy(list);
+
+	return ret;
+}
+
+int nft_rule_delete(struct nft_handle *h, const char *chain,
+		    const char *table, void *data, bool verbose)
+{
+	int ret = 0;
+	struct nft_rule *r;
+	struct nft_rule_list *list;
+
+	nft_fn = nft_rule_delete;
+
+	list = nft_rule_list_create(h);
+	if (list == NULL)
+		return 0;
+
+	r = nft_rule_find(h, list, chain, table, data, -1);
+	if (r != NULL) {
+		ret =__nft_rule_del(h, list, r);
+		if (ret < 0)
+			errno = ENOMEM;
+	} else
+		errno = ENOENT;
+
+	nft_rule_list_destroy(list);
+
+	return ret;
+}
+
+static int
+nft_rule_add(struct nft_handle *h, const char *chain,
+	     const char *table, struct iptables_command_state *cs,
+	     uint64_t handle, bool verbose)
+{
+	struct nft_rule *r;
+
+	r = nft_rule_new(h, chain, table, cs);
+	if (r == NULL)
+		return 0;
+
+	if (handle > 0)
+		nft_rule_attr_set_u64(r, NFT_RULE_ATTR_POSITION, handle);
+
+	if (rule_update_add(h, NFT_DO_INSERT, r) < 0) {
+		nft_rule_free(r);
+		return 0;
+	}
+
+	return 1;
+}
+
+int nft_rule_insert(struct nft_handle *h, const char *chain,
+		    const char *table, void *data, int rulenum, bool verbose)
+{
+	struct nft_rule_list *list;
+	struct nft_rule *r;
+	uint64_t handle = 0;
+
+	/* If built-in chains don't exist for this table, create them */
+	if (nft_xtables_config_load(h, XTABLES_CONFIG_DEFAULT, 0) < 0)
+		nft_chain_builtin_init(h, table, chain, NF_ACCEPT);
+
+	nft_fn = nft_rule_insert;
+
+	if (rulenum > 0) {
+		list = nft_rule_list_create(h);
+		if (list == NULL)
+			goto err;
+
+		r = nft_rule_find(h, list, chain, table, data, rulenum);
+		if (r == NULL) {
+			errno = ENOENT;
+			goto err;
+		}
+
+		handle = nft_rule_attr_get_u64(r, NFT_RULE_ATTR_HANDLE);
+		DEBUGP("adding after rule handle %"PRIu64"\n", handle);
+
+		nft_rule_list_destroy(list);
+	}
+
+	return nft_rule_add(h, chain, table, data, handle, verbose);
+err:
+	nft_rule_list_destroy(list);
+	return 0;
+}
+
+int nft_rule_delete_num(struct nft_handle *h, const char *chain,
+			const char *table, int rulenum, bool verbose)
+{
+	int ret = 0;
+	struct nft_rule *r;
+	struct nft_rule_list *list;
+
+	nft_fn = nft_rule_delete_num;
+
+	list = nft_rule_list_create(h);
+	if (list == NULL)
+		return 0;
+
+	r = nft_rule_find(h, list, chain, table, NULL, rulenum);
+	if (r != NULL) {
+		ret = 1;
+
+		DEBUGP("deleting rule by number %d\n", rulenum);
+		ret = __nft_rule_del(h, list, r);
+		if (ret < 0)
+			errno = ENOMEM;
+	} else
+		errno = ENOENT;
+
+	nft_rule_list_destroy(list);
+
+	return ret;
+}
+
+int nft_rule_replace(struct nft_handle *h, const char *chain,
+		     const char *table, void *data, int rulenum, bool verbose)
+{
+	int ret = 0;
+	struct nft_rule *r;
+	struct nft_rule_list *list;
+
+	nft_fn = nft_rule_replace;
+
+	list = nft_rule_list_create(h);
+	if (list == NULL)
+		return 0;
+
+	r = nft_rule_find(h, list, chain, table, data, rulenum);
+	if (r != NULL) {
+		DEBUGP("replacing rule with handle=%llu\n",
+			(unsigned long long)
+			nft_rule_attr_get_u64(r, NFT_RULE_ATTR_HANDLE));
+
+		ret = nft_rule_append(h, chain, table, data,
+				      nft_rule_attr_get_u64(r, NFT_RULE_ATTR_HANDLE),
+				      verbose);
+	} else
+		errno = ENOENT;
+
+	nft_rule_list_destroy(list);
+
+	return ret;
+}
+
+static void
+print_header(unsigned int format, const char *chain, const char *pol,
+	     const struct xt_counters *counters, bool basechain, uint32_t refs)
+{
+	printf("Chain %s", chain);
+	if (basechain) {
+		printf(" (policy %s", pol);
+		if (!(format & FMT_NOCOUNTS)) {
+			fputc(' ', stdout);
+			xtables_print_num(counters->pcnt, (format|FMT_NOTABLE));
+			fputs("packets, ", stdout);
+			xtables_print_num(counters->bcnt, (format|FMT_NOTABLE));
+			fputs("bytes", stdout);
+		}
+		printf(")\n");
+	} else {
+		printf(" (%u references)\n", refs);
+	}
+
+	if (format & FMT_LINENUMBERS)
+		printf(FMT("%-4s ", "%s "), "num");
+	if (!(format & FMT_NOCOUNTS)) {
+		if (format & FMT_KILOMEGAGIGA) {
+			printf(FMT("%5s ","%s "), "pkts");
+			printf(FMT("%5s ","%s "), "bytes");
+		} else {
+			printf(FMT("%8s ","%s "), "pkts");
+			printf(FMT("%10s ","%s "), "bytes");
+		}
+	}
+	if (!(format & FMT_NOTARGET))
+		printf(FMT("%-9s ","%s "), "target");
+	fputs(" prot ", stdout);
+	if (format & FMT_OPTIONS)
+		fputs("opt", stdout);
+	if (format & FMT_VIA) {
+		printf(FMT(" %-6s ","%s "), "in");
+		printf(FMT("%-6s ","%s "), "out");
+	}
+	printf(FMT(" %-19s ","%s "), "source");
+	printf(FMT(" %-19s "," %s "), "destination");
+	printf("\n");
+}
+
+static int
+__nft_rule_list(struct nft_handle *h, const char *chain, const char *table,
+		int rulenum, unsigned int format,
+		void (*cb)(struct nft_rule *r, unsigned int num,
+			   unsigned int format))
+{
+	struct nft_rule_list *list;
+	struct nft_rule_list_iter *iter;
+	struct nft_rule *r;
+	int rule_ctr = 0, ret = 0;
+
+	list = nft_rule_list_get(h);
+	if (list == NULL)
+		return 0;
+
+	iter = nft_rule_list_iter_create(list);
+	if (iter == NULL)
+		goto err;
+
+	r = nft_rule_list_iter_next(iter);
+	while (r != NULL) {
+		const char *rule_table =
+			nft_rule_attr_get_str(r, NFT_RULE_ATTR_TABLE);
+		const char *rule_chain =
+			nft_rule_attr_get_str(r, NFT_RULE_ATTR_CHAIN);
+
+		rule_ctr++;
+
+		if (strcmp(table, rule_table) != 0 ||
+		    strcmp(chain, rule_chain) != 0)
+			goto next;
+
+		if (rulenum > 0 && rule_ctr != rulenum) {
+			/* List by rule number case */
+			goto next;
+		}
+
+		cb(r, rule_ctr, format);
+		if (rulenum > 0 && rule_ctr == rulenum) {
+			ret = 1;
+			break;
+		}
+
+next:
+		r = nft_rule_list_iter_next(iter);
+	}
+
+	nft_rule_list_iter_destroy(iter);
+err:
+	nft_rule_list_free(list);
+
+	if (ret == 0)
+		errno = ENOENT;
+
+	return ret;
+}
+
+int nft_rule_list(struct nft_handle *h, const char *chain, const char *table,
+		  int rulenum, unsigned int format)
+{
+	const struct nft_family_ops *ops;
+	struct nft_chain_list *list;
+	struct nft_chain_list_iter *iter;
+	struct nft_chain *c;
+	bool found = false;
+
+	/* If built-in chains don't exist for this table, create them */
+	if (nft_xtables_config_load(h, XTABLES_CONFIG_DEFAULT, 0) < 0)
+		nft_chain_builtin_init(h, table, NULL, NF_ACCEPT);
+
+	ops = nft_family_ops_lookup(h->family);
+
+	if (chain && rulenum) {
+		__nft_rule_list(h, chain, table,
+				rulenum, format, ops->print_firewall);
+		return 1;
+	}
+
+	list = nft_chain_dump(h);
+
+	iter = nft_chain_list_iter_create(list);
+	if (iter == NULL)
+		goto err;
+
+	c = nft_chain_list_iter_next(iter);
+	while (c != NULL) {
+		const char *chain_table =
+			nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_TABLE);
+		const char *chain_name =
+			nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_NAME);
+		uint32_t policy =
+			nft_chain_attr_get_u32(c, NFT_CHAIN_ATTR_POLICY);
+		uint32_t refs =
+			nft_chain_attr_get_u32(c, NFT_CHAIN_ATTR_USE);
+		struct xt_counters ctrs = {
+			.pcnt = nft_chain_attr_get_u64(c, NFT_CHAIN_ATTR_PACKETS),
+			.bcnt = nft_chain_attr_get_u64(c, NFT_CHAIN_ATTR_BYTES),
+		};
+		bool basechain = false;
+
+		if (nft_chain_attr_get(c, NFT_CHAIN_ATTR_HOOKNUM))
+			basechain = true;
+
+		if (strcmp(table, chain_table) != 0)
+			goto next;
+		if (chain && strcmp(chain, chain_name) != 0)
+			goto next;
+
+		if (found)
+			printf("\n");
+
+		print_header(format, chain_name, policy_name[policy],
+				     &ctrs, basechain, refs);
+
+		__nft_rule_list(h, chain_name, table,
+				rulenum, format, ops->print_firewall);
+
+		/* we printed the chain we wanted, stop processing. */
+		if (chain)
+			break;
+
+		found = true;
+
+next:
+		c = nft_chain_list_iter_next(iter);
+	}
+
+	nft_chain_list_iter_destroy(iter);
+err:
+	nft_chain_list_free(list);
+
+	return 1;
+}
+
+static void
+list_save(struct nft_rule *r, unsigned int num, unsigned int format)
+{
+	struct iptables_command_state cs = {};
+
+	nft_rule_to_iptables_command_state(r, &cs);
+
+	nft_rule_print_save(&cs, r, NFT_RULE_APPEND, !(format & FMT_NOCOUNTS));
+}
+
+static int
+nft_rule_list_chain_save(struct nft_handle *h, const char *chain,
+			 const char *table, struct nft_chain_list *list,
+			 int counters)
+{
+	struct nft_chain_list_iter *iter;
+	struct nft_chain *c;
+
+	iter = nft_chain_list_iter_create(list);
+	if (iter == NULL)
+		return 0;
+
+	c = nft_chain_list_iter_next(iter);
+	while (c != NULL) {
+		const char *chain_table =
+			nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_TABLE);
+		const char *chain_name =
+			nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_NAME);
+		uint32_t policy =
+			nft_chain_attr_get_u32(c, NFT_CHAIN_ATTR_POLICY);
+
+		if (strcmp(table, chain_table) != 0 ||
+		    (chain && strcmp(chain, chain_name) != 0))
+			goto next;
+
+		/* this is a base chain */
+		if (nft_chain_builtin(c)) {
+			printf("-P %s %s", chain_name, policy_name[policy]);
+
+			if (counters) {
+				printf(" -c %"PRIu64" %"PRIu64"\n",
+					nft_chain_attr_get_u64(c, NFT_CHAIN_ATTR_PACKETS),
+					nft_chain_attr_get_u64(c, NFT_CHAIN_ATTR_BYTES));
+			} else
+				printf("\n");
+		} else {
+			printf("-N %s\n", chain_name);
+		}
+next:
+		c = nft_chain_list_iter_next(iter);
+	}
+
+	nft_chain_list_iter_destroy(iter);
+
+	return 1;
+}
+
+int nft_rule_list_save(struct nft_handle *h, const char *chain,
+		       const char *table, int rulenum, int counters)
+{
+	struct nft_chain_list *list;
+	struct nft_chain_list_iter *iter;
+	struct nft_chain *c;
+	int ret = 1;
+
+	list = nft_chain_dump(h);
+
+	/* Dump policies and custom chains first */
+	if (!rulenum)
+		nft_rule_list_chain_save(h, chain, table, list, counters);
+
+	/* Now dump out rules in this table */
+	iter = nft_chain_list_iter_create(list);
+	if (iter == NULL)
+		goto err;
+
+	c = nft_chain_list_iter_next(iter);
+	while (c != NULL) {
+		const char *chain_table =
+			nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_TABLE);
+		const char *chain_name =
+			nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_NAME);
+
+		if (strcmp(table, chain_table) != 0)
+			goto next;
+		if (chain && strcmp(chain, chain_name) != 0)
+			goto next;
+
+		ret = __nft_rule_list(h, chain_name, table, rulenum,
+				      counters ? 0 : FMT_NOCOUNTS, list_save);
+
+		/* we printed the chain we wanted, stop processing. */
+		if (chain)
+			break;
+next:
+		c = nft_chain_list_iter_next(iter);
+	}
+
+	nft_chain_list_iter_destroy(iter);
+err:
+	nft_chain_list_free(list);
+
+	return ret;
+}
+
+int nft_rule_zero_counters(struct nft_handle *h, const char *chain,
+			   const char *table, int rulenum)
+{
+	struct iptables_command_state cs = {};
+	struct nft_rule_list *list;
+	struct nft_rule *r;
+	int ret = 0;
+
+	nft_fn = nft_rule_delete;
+
+	list = nft_rule_list_create(h);
+	if (list == NULL)
+		return 0;
+
+	r = nft_rule_find(h, list, chain, table, NULL, rulenum);
+	if (r == NULL) {
+		errno = ENOENT;
+		ret = 1;
+		goto error;
+	}
+
+	nft_rule_to_iptables_command_state(r, &cs);
+
+	cs.counters.pcnt = cs.counters.bcnt = 0;
+
+	ret =  nft_rule_append(h, chain, table, &cs,
+			       nft_rule_attr_get_u64(r, NFT_RULE_ATTR_HANDLE),
+			       false);
+
+error:
+	nft_rule_list_destroy(list);
+
+	return ret;
+}
+
+static int nft_action(struct nft_handle *h, int action)
+{
+	int flags = NLM_F_CREATE, type;
+	struct rule_update *n, *tmp;
+	struct nlmsghdr *nlh;
+	uint32_t seq = 1;
+	int ret;
+
+	mnl_nft_batch_begin(h->batch, seq++);
+
+	list_for_each_entry_safe(n, tmp, &h->rule_list, head) {
+		switch (n->type) {
+		case NFT_DO_APPEND:
+			type = NFT_MSG_NEWRULE;
+			flags |= NLM_F_APPEND;
+			break;
+		case NFT_DO_INSERT:
+			type = NFT_MSG_NEWRULE;
+			break;
+		case NFT_DO_REPLACE:
+			type = NFT_MSG_NEWRULE;
+			flags |= NLM_F_REPLACE;
+			break;
+		case NFT_DO_DELETE:
+		case NFT_DO_FLUSH:
+			type = NFT_MSG_DELRULE;
+			break;
+		default:
+			return 0;
+		}
+
+		nlh = nft_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(h->batch),
+					       type, h->family, flags, seq++);
+		nft_rule_nlmsg_build_payload(nlh, n->rule);
+		nft_rule_print_debug(n->rule, nlh);
+
+		h->rule_list_num--;
+		list_del(&n->head);
+		nft_rule_free(n->rule);
+		free(n);
+
+		if (!mnl_nlmsg_batch_next(h->batch))
+			h->batch = mnl_nft_batch_page_add(h->batch);
+	}
+
+	switch (action) {
+	case NFT_DO_COMMIT:
+		mnl_nft_batch_end(h->batch, seq++);
+		break;
+	case NFT_DO_ABORT:
+		break;
+	}
+
+	if (!mnl_nlmsg_batch_is_empty(h->batch))
+		h->batch = mnl_nft_batch_page_add(h->batch);
+
+	ret = mnl_nft_batch_talk(h);
+	if (ret < 0)
+		perror("mnl_nft_batch_talk:");
+
+	mnl_nlmsg_batch_reset(h->batch);
+
+	return ret == 0 ? 1 : 0;
+}
+
+int nft_commit(struct nft_handle *h)
+{
+	return nft_action(h, NFT_DO_COMMIT);
+}
+
+int nft_abort(struct nft_handle *h)
+{
+	return nft_action(h, NFT_DO_ABORT);
+}
+
+int nft_compatible_revision(const char *name, uint8_t rev, int opt)
+{
+	struct mnl_socket *nl;
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh;
+	uint32_t portid, seq, type;
+	int ret = 0;
+
+	if (opt == IPT_SO_GET_REVISION_MATCH ||
+	    opt == IP6T_SO_GET_REVISION_MATCH)
+		type = 0;
+	else
+		type = 1;
+
+	nlh = mnl_nlmsg_put_header(buf);
+	nlh->nlmsg_type = (NFNL_SUBSYS_NFT_COMPAT << 8) | NFNL_MSG_COMPAT_GET;
+	nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+	nlh->nlmsg_seq = seq = time(NULL);
+
+	struct nfgenmsg *nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg));
+	nfg->nfgen_family = AF_INET;
+	nfg->version = NFNETLINK_V0;
+	nfg->res_id = 0;
+
+	mnl_attr_put_strz(nlh, NFTA_COMPAT_NAME, name);
+	mnl_attr_put_u32(nlh, NFTA_COMPAT_REV, htonl(rev));
+	mnl_attr_put_u32(nlh, NFTA_COMPAT_TYPE, htonl(type));
+
+	DEBUGP("requesting `%s' rev=%d type=%d via nft_compat\n",
+		name, rev, type);
+
+	nl = mnl_socket_open(NETLINK_NETFILTER);
+	if (nl == NULL) {
+		perror("mnl_socket_open");
+		return 0;
+	}
+
+	if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+		perror("mnl_socket_bind");
+		goto err;
+	}
+	portid = mnl_socket_get_portid(nl);
+
+	if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
+		perror("mnl_socket_send");
+		goto err;
+	}
+
+	ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+	if (ret == -1) {
+		perror("mnl_socket_recvfrom");
+		goto err;
+	}
+
+	ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL);
+	if (ret == -1) {
+		perror("mnl_cb_run");
+		goto err;
+	}
+
+err:
+	mnl_socket_close(nl);
+
+	return ret < 0 ? 0 : 1;
+}
+
+/* Translates errno numbers into more human-readable form than strerror. */
+const char *nft_strerror(int err)
+{
+	unsigned int i;
+	static struct table_struct {
+		void *fn;
+		int err;
+		const char *message;
+	} table[] =
+	  {
+	    { nft_chain_user_del, ENOTEMPTY, "Chain is not empty" },
+	    { nft_chain_user_del, EINVAL, "Can't delete built-in chain" },
+	    { nft_chain_user_del, EMLINK,
+	      "Can't delete chain with references left" },
+	    { nft_chain_user_add, EEXIST, "Chain already exists" },
+	    { nft_rule_add, E2BIG, "Index of insertion too big" },
+	    { nft_rule_replace, E2BIG, "Index of replacement too big" },
+	    { nft_rule_delete_num, E2BIG, "Index of deletion too big" },
+/*	    { TC_READ_COUNTER, E2BIG, "Index of counter too big" },
+	    { TC_ZERO_COUNTER, E2BIG, "Index of counter too big" }, */
+	    { nft_rule_add, ELOOP, "Loop found in table" },
+	    { nft_rule_add, EINVAL, "Target problem" },
+	    /* ENOENT for DELETE probably means no matching rule */
+	    { nft_rule_delete, ENOENT,
+	      "Bad rule (does a matching rule exist in that chain?)" },
+	    { nft_chain_set, ENOENT, "Bad built-in chain name" },
+	    { nft_chain_set, EINVAL, "Bad policy name" },
+	    { NULL, EPERM, "Permission denied (you must be root)" },
+	    { NULL, 0, "Incompatible with this kernel" },
+	    { NULL, ENOPROTOOPT, "iptables who? (do you need to insmod?)" },
+	    { NULL, ENOSYS, "Will be implemented real soon.  I promise ;)" },
+	    { NULL, ENOMEM, "Memory allocation problem" },
+	    { NULL, ENOENT, "No chain/target/match by that name" },
+	  };
+
+	for (i = 0; i < sizeof(table)/sizeof(struct table_struct); i++) {
+		if ((!table[i].fn || table[i].fn == nft_fn)
+		    && table[i].err == err)
+			return table[i].message;
+	}
+
+	return strerror(err);
+}
+
+static void xtables_config_perror(uint32_t flags, const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+
+	if (flags & NFT_LOAD_VERBOSE)
+		vfprintf(stderr, fmt, args);
+
+	va_end(args);
+}
+
+int nft_xtables_config_load(struct nft_handle *h, const char *filename,
+			    uint32_t flags)
+{
+	struct nft_table_list *table_list = nft_table_list_alloc();
+	struct nft_chain_list *chain_list = nft_chain_list_alloc();
+	struct nft_table_list_iter *titer = NULL;
+	struct nft_chain_list_iter *citer = NULL;
+	struct nft_table *table;
+	struct nft_chain *chain;
+	uint32_t table_family, chain_family;
+	bool found = false;
+
+	if (h->restore)
+		return 0;
+
+	if (xtables_config_parse(filename, table_list, chain_list) < 0) {
+		if (errno == ENOENT) {
+			xtables_config_perror(flags,
+				"configuration file `%s' does not exists\n",
+				filename);
+		} else {
+			xtables_config_perror(flags,
+				"Fatal error parsing config file: %s\n",
+				 strerror(errno));
+		}
+		goto err;
+	}
+
+	/* Stage 1) create tables */
+	titer = nft_table_list_iter_create(table_list);
+	while ((table = nft_table_list_iter_next(titer)) != NULL) {
+		table_family = nft_table_attr_get_u32(table,
+						      NFT_TABLE_ATTR_FAMILY);
+		if (h->family != table_family)
+			continue;
+
+		found = true;
+
+		if (nft_table_add(h, table) < 0) {
+			if (errno == EEXIST) {
+				xtables_config_perror(flags,
+					"table `%s' already exists, skipping\n",
+					(char *)nft_table_attr_get(table, NFT_TABLE_ATTR_NAME));
+			} else {
+				xtables_config_perror(flags,
+					"table `%s' cannot be create, reason `%s'. Exitting\n",
+					(char *)nft_table_attr_get(table, NFT_TABLE_ATTR_NAME),
+					strerror(errno));
+				goto err;
+			}
+			continue;
+		}
+		xtables_config_perror(flags, "table `%s' has been created\n",
+			(char *)nft_table_attr_get(table, NFT_TABLE_ATTR_NAME));
+	}
+	nft_table_list_iter_destroy(titer);
+	nft_table_list_free(table_list);
+
+	if (!found)
+		goto err;
+
+	/* Stage 2) create chains */
+	citer = nft_chain_list_iter_create(chain_list);
+	while ((chain = nft_chain_list_iter_next(citer)) != NULL) {
+		chain_family = nft_chain_attr_get_u32(chain,
+						      NFT_CHAIN_ATTR_TABLE);
+		if (h->family != chain_family)
+			continue;
+
+		if (nft_chain_add(h, chain) < 0) {
+			if (errno == EEXIST) {
+				xtables_config_perror(flags,
+					"chain `%s' already exists in table `%s', skipping\n",
+					(char *)nft_chain_attr_get(chain, NFT_CHAIN_ATTR_NAME),
+					(char *)nft_chain_attr_get(chain, NFT_CHAIN_ATTR_TABLE));
+			} else {
+				xtables_config_perror(flags,
+					"chain `%s' cannot be create, reason `%s'. Exitting\n",
+					(char *)nft_chain_attr_get(chain, NFT_CHAIN_ATTR_NAME),
+					strerror(errno));
+				goto err;
+			}
+			continue;
+		}
+
+		xtables_config_perror(flags,
+			"chain `%s' in table `%s' has been created\n",
+			(char *)nft_chain_attr_get(chain, NFT_CHAIN_ATTR_NAME),
+			(char *)nft_chain_attr_get(chain, NFT_CHAIN_ATTR_TABLE));
+	}
+	nft_chain_list_iter_destroy(citer);
+	nft_chain_list_free(chain_list);
+
+	return 0;
+
+err:
+	nft_table_list_free(table_list);
+	nft_chain_list_free(chain_list);
+
+	if (titer != NULL)
+		nft_table_list_iter_destroy(titer);
+	if (citer != NULL)
+		nft_chain_list_iter_destroy(citer);
+
+	return -1;
+}
+
+int nft_chain_zero_counters(struct nft_handle *h, const char *chain, 
+			    const char *table)
+{
+	struct nft_chain_list *list;
+	struct nft_chain_list_iter *iter;
+	struct nft_chain *c;
+	struct nlmsghdr *nlh;
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	int ret = 0;
+
+	list = nft_chain_list_get(h);
+	if (list == NULL)
+		goto err;
+
+	iter = nft_chain_list_iter_create(list);
+	if (iter == NULL)
+		goto err;
+
+	c = nft_chain_list_iter_next(iter);
+	while (c != NULL) {
+		const char *chain_name =
+			nft_chain_attr_get(c, NFT_CHAIN_ATTR_NAME);
+		const char *chain_table =
+			nft_chain_attr_get(c, NFT_CHAIN_ATTR_TABLE);
+
+		if (strcmp(table, chain_table) != 0)
+			goto next;
+
+		if (chain != NULL && strcmp(chain, chain_name) != 0)
+			goto next;
+
+		nft_chain_attr_set_u64(c, NFT_CHAIN_ATTR_PACKETS, 0);
+		nft_chain_attr_set_u64(c, NFT_CHAIN_ATTR_BYTES, 0);
+
+		nft_chain_attr_unset(c, NFT_CHAIN_ATTR_HANDLE);
+
+		nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_NEWCHAIN,
+						h->family, NLM_F_ACK, h->seq);
+
+		nft_chain_nlmsg_build_payload(nlh, c);
+
+		ret = mnl_talk(h, nlh, NULL, NULL);
+		if (ret < 0)
+			perror("mnl_talk:nft_chain_zero_counters");
+
+		if (chain != NULL)
+			break;
+next:
+		c = nft_chain_list_iter_next(iter);
+	}
+
+	nft_chain_list_iter_destroy(iter);
+
+err:
+	nft_chain_list_free(list);
+
+	/* the core expects 1 for success and 0 for error */
+	return ret == 0 ? 1 : 0;
+}
diff --git a/iptables/nft.h b/iptables/nft.h
new file mode 100644
index 0000000..c31371c
--- /dev/null
+++ b/iptables/nft.h
@@ -0,0 +1,175 @@
+#ifndef _NFT_H_
+#define _NFT_H_
+
+#include "xshared.h"
+#include "nft-shared.h"
+#include <libiptc/linux_list.h>
+
+#define FILTER         0
+#define MANGLE         1
+#define RAW            2
+#define SECURITY       3
+#define NAT            4
+#define TABLES_MAX     5
+
+struct builtin_chain {
+	const char *name;
+	const char *type;
+	uint32_t prio;
+	uint32_t hook;
+};
+
+struct builtin_table {
+	const char *name;
+	struct builtin_chain chains[NF_INET_NUMHOOKS];
+	bool initialized;
+};
+
+struct nft_handle {
+	int			family;
+	struct mnl_socket	*nl;
+	uint32_t		portid;
+	uint32_t		seq;
+	struct list_head	rule_list;
+	int			rule_list_num;
+	struct mnl_nlmsg_batch	*batch;
+	struct nft_family_ops	*ops;
+	struct builtin_table	*tables;
+	bool			restore;
+};
+
+extern struct builtin_table xtables_ipv4[TABLES_MAX];
+extern struct builtin_table xtables_arp[TABLES_MAX];
+
+int mnl_talk(struct nft_handle *h, struct nlmsghdr *nlh,
+	     int (*cb)(const struct nlmsghdr *nlh, void *data),
+	     void *data);
+int nft_init(struct nft_handle *h, struct builtin_table *t);
+void nft_fini(struct nft_handle *h);
+
+/*
+ * Operations with tables.
+ */
+struct nft_table;
+struct nft_chain_list;
+
+int nft_table_builtin_add(struct nft_handle *h, struct builtin_table *_t, bool dormant);
+struct builtin_table *nft_table_builtin_find(struct nft_handle *h, const char *table);
+int nft_table_add(struct nft_handle *h, const struct nft_table *t);
+int nft_for_each_table(struct nft_handle *h, int (*func)(struct nft_handle *h, const char *tablename, bool counters), bool counters);
+bool nft_table_find(struct nft_handle *h, const char *tablename);
+int nft_table_set_dormant(struct nft_handle *h, const char *table);
+int nft_table_wake_dormant(struct nft_handle *h, const char *table);
+int nft_table_purge_chains(struct nft_handle *h, const char *table, struct nft_chain_list *list);
+
+/*
+ * Operations with chains.
+ */
+struct nft_chain;
+
+struct nft_chain *nft_chain_builtin_alloc(struct builtin_table *table, struct builtin_chain *chain, int policy);
+void nft_chain_builtin_add(struct nft_handle *h, struct builtin_table *table, struct builtin_chain *chain, int policy);
+struct builtin_chain *nft_chain_builtin_find(struct builtin_table *t, const char *chain);
+int nft_chain_builtin_init(struct nft_handle *h, const char *table, const char *chain, int policy);
+int nft_chain_add(struct nft_handle *h, const struct nft_chain *c);
+int nft_chain_set(struct nft_handle *h, const char *table, const char *chain, const char *policy, const struct xt_counters *counters);
+struct nft_chain_list *nft_chain_dump(struct nft_handle *h);
+struct nft_chain *nft_chain_list_find(struct nft_chain_list *list, const char *table, const char *chain);
+int nft_chain_save(struct nft_handle *h, struct nft_chain_list *list, const char *table);
+int nft_chain_user_add(struct nft_handle *h, const char *chain, const char *table);
+int nft_chain_user_del(struct nft_handle *h, const char *chain, const char *table);
+int nft_chain_user_rename(struct nft_handle *h, const char *chain, const char *table, const char *newname);
+int nft_chain_zero_counters(struct nft_handle *h, const char *chain, const char *table);
+
+/*
+ * Operations with rule-set.
+ */
+struct nft_rule;
+
+int nft_rule_append(struct nft_handle *h, const char *chain, const char *table, void *data, uint64_t handle, bool verbose);
+int nft_rule_insert(struct nft_handle *h, const char *chain, const char *table, void *data, int rulenum, bool verbose);
+int nft_rule_check(struct nft_handle *h, const char *chain, const char *table, void *data, bool verbose);
+int nft_rule_delete(struct nft_handle *h, const char *chain, const char *table, void *data, bool verbose);
+int nft_rule_delete_num(struct nft_handle *h, const char *chain, const char *table, int rulenum, bool verbose);
+int nft_rule_replace(struct nft_handle *h, const char *chain, const char *table, void *data, int rulenum, bool verbose);
+int nft_rule_list(struct nft_handle *h, const char *chain, const char *table, int rulenum, unsigned int format);
+int nft_rule_list_save(struct nft_handle *h, const char *chain, const char *table, int rulenum, int counters);
+int nft_rule_save(struct nft_handle *h, const char *table, bool counters);
+int nft_rule_flush(struct nft_handle *h, const char *chain, const char *table);
+int nft_rule_zero_counters(struct nft_handle *h, const char *chain, const char *table, int rulenum);
+
+struct nft_rule_list *nft_rule_list_create(struct nft_handle *h);
+void nft_rule_list_destroy(struct nft_rule_list *list);
+
+/*
+ * Operations used in userspace tools
+ */
+int add_counters(struct nft_rule *r, uint64_t packets, uint64_t bytes);
+int add_verdict(struct nft_rule *r, int verdict);
+int add_match(struct nft_rule *r, struct xt_entry_match *m);
+int add_target(struct nft_rule *r, struct xt_entry_target *t);
+int add_jumpto(struct nft_rule *r, const char *name, int verdict);
+int add_action(struct nft_rule *r, struct iptables_command_state *cs, bool goto_set);
+
+enum nft_rule_print {
+	NFT_RULE_APPEND,
+	NFT_RULE_DEL,
+};
+
+void nft_rule_print_save(const void *data,
+			 struct nft_rule *r, enum nft_rule_print type,
+			 unsigned int format);
+
+/*
+ * global commit and abort
+ */
+int nft_commit(struct nft_handle *h);
+int nft_abort(struct nft_handle *h);
+
+/*
+ * revision compatibility.
+ */
+int nft_compatible_revision(const char *name, uint8_t rev, int opt);
+
+/*
+ * Error reporting.
+ */
+const char *nft_strerror(int err);
+
+/* For xtables.c */
+int do_commandx(struct nft_handle *h, int argc, char *argv[], char **table, bool restore);
+/* For xtables-arptables.c */
+int do_commandarp(struct nft_handle *h, int argc, char *argv[], char **table);
+
+/*
+ * Parse config for tables and chain helper functions
+ */
+#define XTABLES_CONFIG_DEFAULT  "/etc/xtables.conf"
+
+struct nft_table_list;
+struct nft_chain_list;
+
+extern int xtables_config_parse(const char *filename, struct nft_table_list *table_list, struct nft_chain_list *chain_list);
+
+enum {
+	NFT_LOAD_VERBOSE = (1 << 0),
+};
+
+int nft_xtables_config_load(struct nft_handle *h, const char *filename, uint32_t flags);
+
+/*
+ * ARP
+ */
+
+struct arpt_entry;
+
+int nft_arp_rule_append(struct nft_handle *h, const char *chain,
+			const char *table, struct arpt_entry *fw,
+			bool verbose);
+int nft_arp_rule_insert(struct nft_handle *h, const char *chain,
+			const char *table, struct arpt_entry *fw,
+			int rulenum, bool verbose);
+
+void nft_rule_to_arpt_entry(struct nft_rule *r, struct arpt_entry *fw);
+
+#endif
diff --git a/iptables/xshared.h b/iptables/xshared.h
index 1e2b9b8..27c5b78 100644
--- a/iptables/xshared.h
+++ b/iptables/xshared.h
@@ -58,6 +58,7 @@
 	unsigned int options;
 	struct xtables_rule_match *matches;
 	struct xtables_target *target;
+	struct xt_counters counters;
 	char *protocol;
 	int proto_used;
 	const char *jumpto;
diff --git a/iptables/xtables-arp-standalone.c b/iptables/xtables-arp-standalone.c
new file mode 100644
index 0000000..23b6bcb
--- /dev/null
+++ b/iptables/xtables-arp-standalone.c
@@ -0,0 +1,88 @@
+/*
+ * Author: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au
+ *
+ * Based on the ipchains code by Paul Russell and Michael Neuling
+ *
+ * (C) 2000-2002 by the netfilter coreteam <coreteam@netfilter.org>:
+ * 		    Paul 'Rusty' Russell <rusty@rustcorp.com.au>
+ * 		    Marc Boucher <marc+nf@mbsi.ca>
+ * 		    James Morris <jmorris@intercode.com.au>
+ * 		    Harald Welte <laforge@gnumonks.org>
+ * 		    Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ *
+ *	arptables -- IP firewall administration for kernels with
+ *	firewall table (aimed for the 2.3 kernels)
+ *
+ *	See the accompanying manual page arptables(8) for information
+ *	about proper usage of this program.
+ *
+ *	This program is free software; you can redistribute it and/or modify
+ *	it under the terms of the GNU General Public License as published by
+ *	the Free Software Foundation; either version 2 of the License, or
+ *	(at your option) any later version.
+ *
+ *	This program is distributed in the hope that it will be useful,
+ *	but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *	GNU General Public License for more details.
+ *
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program; if not, write to the Free Software
+ *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <xtables.h>
+#include "nft.h"
+#include <linux/netfilter_arp/arp_tables.h>
+
+#include "xtables-multi.h"
+
+extern struct xtables_globals xtables_globals;
+extern const char *program_version, *program_name;
+
+static const struct xtables_afinfo afinfo_arp = {
+        .kmod          = "arp_tables",
+        .proc_exists   = "/proc/net/arp_tables_names",
+        .libprefix     = "libarp_",
+        .family        = NFPROTO_ARP,
+        .ipproto       = IPPROTO_IP,
+        .so_rev_match  = -1,
+        .so_rev_target = -1,
+};
+
+int xtables_arp_main(int argc, char *argv[])
+{
+	int ret;
+	char *table = "filter";
+	struct nft_handle h = {
+		.family = NFPROTO_ARP,
+	};
+
+	xtables_globals.program_name = "arptables";
+	/* This code below could be replaced by xtables_init_all, which
+	 * doesn't support NFPROTO_ARP yet.
+	 */
+	xtables_init();
+	afinfo = &afinfo_arp;
+	ret = xtables_set_params(&xtables_globals);
+	if (ret < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
+				xtables_globals.program_name,
+				xtables_globals.program_version);
+		exit(1);
+	}
+
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+	init_extensions();
+#endif
+
+	ret = do_commandarp(&h, argc, argv, &table);
+	if (ret)
+		ret = nft_commit(&h);
+
+	exit(!ret);
+}
diff --git a/iptables/xtables-arp.c b/iptables/xtables-arp.c
new file mode 100644
index 0000000..0c79a38
--- /dev/null
+++ b/iptables/xtables-arp.c
@@ -0,0 +1,1502 @@
+/* Code to take an arptables-style command line and do it. */
+
+/*
+ * arptables:
+ * Author: Bart De Schuymer <bdschuym@pandora.be>, but
+ * almost all code is from the iptables userspace program, which has main
+ * authors: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au
+ *
+ *	This program is free software; you can redistribute it and/or modify
+ *	it under the terms of the GNU General Public License as published by
+ *	the Free Software Foundation; either version 2 of the License, or
+ *	(at your option) any later version.
+ *
+ *	This program is distributed in the hope that it will be useful,
+ *	but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *	GNU General Public License for more details.
+ *
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program; if not, write to the Free Software
+ *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+  Currently, only support for specifying hardware addresses for Ethernet
+  is available.
+  This tool is not luser-proof: you can specify an Ethernet source address
+  and set hardware length to something different than 6, f.e.
+*/
+
+#include <getopt.h>
+#include <string.h>
+#include <netdb.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <dlfcn.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+#include <net/if.h>
+#include <netinet/ether.h>
+#include <xtables.h>
+
+#include "xshared.h"
+
+#include "nft.h"
+#include <linux/netfilter_arp/arp_tables.h>
+
+typedef char arpt_chainlabel[32];
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+/* XXX: command defined by nft-shared.h do not overlap with these two */
+#undef CMD_CHECK
+#undef CMD_RENAME_CHAIN
+
+#define CMD_NONE		0x0000U
+#define CMD_INSERT		0x0001U
+#define CMD_DELETE		0x0002U
+#define CMD_DELETE_NUM		0x0004U
+#define CMD_REPLACE		0x0008U
+#define CMD_APPEND		0x0010U
+#define CMD_LIST		0x0020U
+#define CMD_FLUSH		0x0040U
+#define CMD_ZERO		0x0080U
+#define CMD_NEW_CHAIN		0x0100U
+#define CMD_DELETE_CHAIN	0x0200U
+#define CMD_SET_POLICY		0x0400U
+#define CMD_CHECK		0x0800U
+#define CMD_RENAME_CHAIN	0x1000U
+#define NUMBER_OF_CMD	13
+static const char cmdflags[] = { 'I', 'D', 'D', 'R', 'A', 'L', 'F', 'Z',
+				 'N', 'X', 'P', 'E' };
+
+#define OPTION_OFFSET 256
+
+#define OPT_NONE	0x00000U
+#define OPT_NUMERIC	0x00001U
+#define OPT_S_IP	0x00002U
+#define OPT_D_IP	0x00004U
+#define OPT_S_MAC	0x00008U
+#define OPT_D_MAC	0x00010U
+#define OPT_H_LENGTH	0x00020U
+#define OPT_P_LENGTH	0x00040U
+#define OPT_OPCODE	0x00080U
+#define OPT_H_TYPE	0x00100U
+#define OPT_P_TYPE	0x00200U
+#define OPT_JUMP	0x00400U
+#define OPT_VERBOSE	0x00800U
+#define OPT_VIANAMEIN	0x01000U
+#define OPT_VIANAMEOUT	0x02000U
+#define OPT_LINENUMBERS 0x04000U
+#define OPT_COUNTERS	0x08000U
+#define NUMBER_OF_OPT	16
+static const char optflags[NUMBER_OF_OPT]
+= { 'n', 's', 'd', 2, 3, 7, 8, 4, 5, 6, 'j', 'v', 'i', 'o', '0', 'c'};
+
+static struct option original_opts[] = {
+	{ "append", 1, 0, 'A' },
+	{ "delete", 1, 0,  'D' },
+	{ "insert", 1, 0,  'I' },
+	{ "replace", 1, 0,  'R' },
+	{ "list", 2, 0,  'L' },
+	{ "flush", 2, 0,  'F' },
+	{ "zero", 2, 0,  'Z' },
+	{ "new-chain", 1, 0,  'N' },
+	{ "delete-chain", 2, 0,  'X' },
+	{ "rename-chain", 1, 0,  'E' },
+	{ "policy", 1, 0,  'P' },
+	{ "source-ip", 1, 0, 's' },
+	{ "destination-ip", 1, 0,  'd' },
+	{ "src-ip", 1, 0,  's' },
+	{ "dst-ip", 1, 0,  'd' },
+	{ "source-mac", 1, 0, 2},
+	{ "destination-mac", 1, 0, 3},
+	{ "src-mac", 1, 0, 2},
+	{ "dst-mac", 1, 0, 3},
+	{ "h-length", 1, 0,  'l' },
+	{ "p-length", 1, 0,  8 },
+	{ "opcode", 1, 0,  4 },
+	{ "h-type", 1, 0,  5 },
+	{ "proto-type", 1, 0,  6 },
+	{ "in-interface", 1, 0, 'i' },
+	{ "jump", 1, 0, 'j' },
+	{ "table", 1, 0, 't' },
+	{ "match", 1, 0, 'm' },
+	{ "numeric", 0, 0, 'n' },
+	{ "out-interface", 1, 0, 'o' },
+	{ "verbose", 0, 0, 'v' },
+	{ "exact", 0, 0, 'x' },
+	{ "version", 0, 0, 'V' },
+	{ "help", 2, 0, 'h' },
+	{ "line-numbers", 0, 0, '0' },
+	{ "modprobe", 1, 0, 'M' },
+	{ 0 }
+};
+
+int RUNTIME_NF_ARP_NUMHOOKS = 3;
+
+static struct option *opts = original_opts;
+static unsigned int global_option_offset = 0;
+
+extern void xtables_exit_error(enum xtables_exittype status, const char *msg, ...);
+extern struct xtables_globals xtables_globals;
+
+/* Table of legal combinations of commands and options.  If any of the
+ * given commands make an option legal, that option is legal (applies to
+ * CMD_LIST and CMD_ZERO only).
+ * Key:
+ *  +  compulsory
+ *  x  illegal
+ *     optional
+ */
+
+static char commands_v_options[NUMBER_OF_CMD][NUMBER_OF_OPT] =
+/* Well, it's better than "Re: Linux vs FreeBSD" */
+{
+	/*     -n  -s  -d  -p  -j  -v  -x  -i  -o  -f  --line */
+/*INSERT*/    {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '},
+/*DELETE*/    {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '},
+/*DELETE_NUM*/{' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '},
+/*REPLACE*/   {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '},
+/*APPEND*/    {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '},
+/*LIST*/      {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '},
+/*FLUSH*/     {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '},
+/*ZERO*/      {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '},
+/*NEW_CHAIN*/ {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '},
+/*DEL_CHAIN*/ {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '},
+/*SET_POLICY*/{' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '},
+/*CHECK*/     {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '},
+/*RENAME*/    {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '}
+};
+
+static int inverse_for_options[NUMBER_OF_OPT] =
+{
+/* -n */ 0,
+/* -s */ ARPT_INV_SRCIP,
+/* -d */ ARPT_INV_TGTIP,
+/* 2 */ ARPT_INV_SRCDEVADDR,
+/* 3 */ ARPT_INV_TGTDEVADDR,
+/* -l */ ARPT_INV_ARPHLN,
+/* 8 */ 0,
+/* 4 */ ARPT_INV_ARPOP,
+/* 5 */ ARPT_INV_ARPHRD,
+/* 6 */ ARPT_INV_ARPPRO,
+/* -j */ 0,
+/* -v */ 0,
+/* -i */ ARPT_INV_VIA_IN,
+/* -o */ ARPT_INV_VIA_OUT,
+/*--line*/ 0,
+/* -c */ 0,
+};
+
+const char *program_version = XTABLES_VERSION;
+const char *program_name = "arptables";
+
+/* A few hardcoded protocols for 'all' and in case the user has no
+   /etc/protocols */
+struct pprot {
+	char *name;
+	u_int8_t num;
+};
+
+/* Primitive headers... */
+/* defined in netinet/in.h */
+#if 0
+#ifndef IPPROTO_ESP
+#define IPPROTO_ESP 50
+#endif
+#ifndef IPPROTO_AH
+#define IPPROTO_AH 51
+#endif
+#endif
+
+/***********************************************/
+/* ARPTABLES SPECIFIC NEW FUNCTIONS ADDED HERE */
+/***********************************************/
+
+unsigned char mac_type_unicast[ETH_ALEN] =   {0,0,0,0,0,0};
+unsigned char msk_type_unicast[ETH_ALEN] =   {1,0,0,0,0,0};
+unsigned char mac_type_multicast[ETH_ALEN] = {1,0,0,0,0,0};
+unsigned char msk_type_multicast[ETH_ALEN] = {1,0,0,0,0,0};
+unsigned char mac_type_broadcast[ETH_ALEN] = {255,255,255,255,255,255};
+unsigned char msk_type_broadcast[ETH_ALEN] = {255,255,255,255,255,255};
+
+/*
+ * put the mac address into 6 (ETH_ALEN) bytes
+ */
+static int getmac_and_mask(char *from, char *to, char *mask)
+{
+	char *p;
+	int i;
+	struct ether_addr *addr;
+
+	if (strcasecmp(from, "Unicast") == 0) {
+		memcpy(to, mac_type_unicast, ETH_ALEN);
+		memcpy(mask, msk_type_unicast, ETH_ALEN);
+		return 0;
+	}
+	if (strcasecmp(from, "Multicast") == 0) {
+		memcpy(to, mac_type_multicast, ETH_ALEN);
+		memcpy(mask, msk_type_multicast, ETH_ALEN);
+		return 0;
+	}
+	if (strcasecmp(from, "Broadcast") == 0) {
+		memcpy(to, mac_type_broadcast, ETH_ALEN);
+		memcpy(mask, msk_type_broadcast, ETH_ALEN);
+		return 0;
+	}
+	if ( (p = strrchr(from, '/')) != NULL) {
+		*p = '\0';
+		if (!(addr = ether_aton(p + 1)))
+			return -1;
+		memcpy(mask, addr, ETH_ALEN);
+	} else
+		memset(mask, 0xff, ETH_ALEN);
+	if (!(addr = ether_aton(from)))
+		return -1;
+	memcpy(to, addr, ETH_ALEN);
+	for (i = 0; i < ETH_ALEN; i++)
+		to[i] &= mask[i];
+	return 0;
+}
+
+static int getlength_and_mask(char *from, uint8_t *to, uint8_t *mask)
+{
+	char *p, *buffer;
+	int i;
+
+	if ( (p = strrchr(from, '/')) != NULL) {
+		*p = '\0';
+		i = strtol(p+1, &buffer, 10);
+		if (*buffer != '\0' || i < 0 || i > 255)
+			return -1;
+		*mask = (uint8_t)i;
+	} else
+		*mask = 255;
+	i = strtol(from, &buffer, 10);
+	if (*buffer != '\0' || i < 0 || i > 255)
+		return -1;
+	*to = (uint8_t)i;
+	return 0;
+}
+
+static int get16_and_mask(char *from, uint16_t *to, uint16_t *mask, int base)
+{
+	char *p, *buffer;
+	int i;
+
+	if ( (p = strrchr(from, '/')) != NULL) {
+		*p = '\0';
+		i = strtol(p+1, &buffer, base);
+		if (*buffer != '\0' || i < 0 || i > 65535)
+			return -1;
+		*mask = htons((uint16_t)i);
+	} else
+		*mask = 65535;
+	i = strtol(from, &buffer, base);
+	if (*buffer != '\0' || i < 0 || i > 65535)
+		return -1;
+	*to = htons((uint16_t)i);
+	return 0;
+}
+
+static int
+string_to_number(const char *s, unsigned int min, unsigned int max,
+		 unsigned int *ret)
+{
+	long number;
+	char *end;
+
+	/* Handle hex, octal, etc. */
+	errno = 0;
+	number = strtol(s, &end, 0);
+	if (*end == '\0' && end != s) {
+		/* we parsed a number, let's see if we want this */
+		if (errno != ERANGE && min <= number && number <= max) {
+			*ret = number;
+			return 0;
+		}
+	}
+	return -1;
+}
+
+/*********************************************/
+/* ARPTABLES SPECIFIC NEW FUNCTIONS END HERE */
+/*********************************************/
+
+static struct in_addr *
+dotted_to_addr(const char *dotted)
+{
+	static struct in_addr addr;
+	unsigned char *addrp;
+	char *p, *q;
+	unsigned int onebyte;
+	int i;
+	char buf[20];
+
+	/* copy dotted string, because we need to modify it */
+	strncpy(buf, dotted, sizeof(buf) - 1);
+	addrp = (unsigned char *) &(addr.s_addr);
+
+	p = buf;
+	for (i = 0; i < 3; i++) {
+		if ((q = strchr(p, '.')) == NULL)
+			return (struct in_addr *) NULL;
+
+		*q = '\0';
+		if (string_to_number(p, 0, 255, &onebyte) == -1)
+			return (struct in_addr *) NULL;
+
+		addrp[i] = (unsigned char) onebyte;
+		p = q + 1;
+	}
+
+	/* we've checked 3 bytes, now we check the last one */
+	if (string_to_number(p, 0, 255, &onebyte) == -1)
+		return (struct in_addr *) NULL;
+
+	addrp[3] = (unsigned char) onebyte;
+
+	return &addr;
+}
+
+static struct in_addr *
+network_to_addr(const char *name)
+{
+	struct netent *net;
+	static struct in_addr addr;
+
+	if ((net = getnetbyname(name)) != NULL) {
+		if (net->n_addrtype != AF_INET)
+			return (struct in_addr *) NULL;
+		addr.s_addr = htonl((unsigned long) net->n_net);
+		return &addr;
+	}
+
+	return (struct in_addr *) NULL;
+}
+
+static void
+inaddrcpy(struct in_addr *dst, struct in_addr *src)
+{
+	/* memcpy(dst, src, sizeof(struct in_addr)); */
+	dst->s_addr = src->s_addr;
+}
+
+static void
+exit_tryhelp(int status)
+{
+	fprintf(stderr, "Try `%s -h' or '%s --help' for more information.\n",
+			program_name, program_name );
+	exit(status);
+}
+
+static void
+exit_printhelp(void)
+{
+	struct xtables_target *t = NULL;
+	int i;
+
+	printf("%s v%s\n\n"
+"Usage: %s -[AD] chain rule-specification [options]\n"
+"       %s -[RI] chain rulenum rule-specification [options]\n"
+"       %s -D chain rulenum [options]\n"
+"       %s -[LFZ] [chain] [options]\n"
+"       %s -[NX] chain\n"
+"       %s -E old-chain-name new-chain-name\n"
+"       %s -P chain target [options]\n"
+"       %s -h (print this help information)\n\n",
+	       program_name, program_version, program_name, program_name,
+	       program_name, program_name, program_name, program_name,
+	       program_name, program_name);
+
+	printf(
+"Commands:\n"
+"Either long or short options are allowed.\n"
+"  --append  -A chain		Append to chain\n"
+"  --delete  -D chain		Delete matching rule from chain\n"
+"  --delete  -D chain rulenum\n"
+"				Delete rule rulenum (1 = first) from chain\n"
+"  --insert  -I chain [rulenum]\n"
+"				Insert in chain as rulenum (default 1=first)\n"
+"  --replace -R chain rulenum\n"
+"				Replace rule rulenum (1 = first) in chain\n"
+"  --list    -L [chain]		List the rules in a chain or all chains\n"
+"  --flush   -F [chain]		Delete all rules in  chain or all chains\n"
+"  --zero    -Z [chain]		Zero counters in chain or all chains\n"
+"  --new     -N chain		Create a new user-defined chain\n"
+"  --delete-chain\n"
+"            -X [chain]		Delete a user-defined chain\n"
+"  --policy  -P chain target\n"
+"				Change policy on chain to target\n"
+"  --rename-chain\n"
+"            -E old-chain new-chain\n"
+"				Change chain name, (moving any references)\n"
+
+"Options:\n"
+"  --source-ip	-s [!] address[/mask]\n"
+"				source specification\n"
+"  --destination-ip -d [!] address[/mask]\n"
+"				destination specification\n"
+"  --source-mac [!] address[/mask]\n"
+"  --destination-mac [!] address[/mask]\n"
+"  --h-length   -l   length[/mask] hardware length (nr of bytes)\n"
+"  --opcode code[/mask] operation code (2 bytes)\n"
+"  --h-type   type[/mask]  hardware type (2 bytes, hexadecimal)\n"
+"  --proto-type   type[/mask]  protocol type (2 bytes)\n"
+"  --in-interface -i [!] input name[+]\n"
+"				network interface name ([+] for wildcard)\n"
+"  --out-interface -o [!] output name[+]\n"
+"				network interface name ([+] for wildcard)\n"
+"  --jump	-j target\n"
+"				target for rule (may load target extension)\n"
+"  --match	-m match\n"
+"				extended match (may load extension)\n"
+"  --numeric	-n		numeric output of addresses and ports\n"
+"  --table	-t table	table to manipulate (default: `filter')\n"
+"  --verbose	-v		verbose mode\n"
+"  --line-numbers		print line numbers when listing\n"
+"  --exact	-x		expand numbers (display exact values)\n"
+"  --modprobe=<command>		try to insert modules using this command\n"
+"  --set-counters PKTS BYTES	set the counter during insert/append\n"
+"[!] --version	-V		print package version.\n");
+	printf(" opcode strings: \n");
+        for (i = 0; i < NUMOPCODES; i++)
+                printf(" %d = %s\n", i + 1, opcodes[i]);
+        printf(
+" hardware type string: 1 = Ethernet\n"
+" protocol type string: 0x800 = IPv4\n");
+
+	/* Print out any special helps. A user might like to be able
+		to add a --help to the commandline, and see expected
+		results. So we call help for all matches & targets */
+	for (t = xtables_targets; t; t = t->next) {
+		if (strcmp(t->name, "CLASSIFY") && strcmp(t->name, "mangle"))
+			continue;
+		printf("\n");
+		t->help();
+	}
+	exit(0);
+}
+
+static void
+generic_opt_check(int command, int options)
+{
+	int i, j, legal = 0;
+
+	/* Check that commands are valid with options.  Complicated by the
+	 * fact that if an option is legal with *any* command given, it is
+	 * legal overall (ie. -z and -l).
+	 */
+	for (i = 0; i < NUMBER_OF_OPT; i++) {
+		legal = 0; /* -1 => illegal, 1 => legal, 0 => undecided. */
+
+		for (j = 0; j < NUMBER_OF_CMD; j++) {
+			if (!(command & (1<<j)))
+				continue;
+
+			if (!(options & (1<<i))) {
+				if (commands_v_options[j][i] == '+')
+					xtables_error(PARAMETER_PROBLEM,
+						      "You need to supply the `-%c' "
+						      "option for this command\n",
+						      optflags[i]);
+			} else {
+				if (commands_v_options[j][i] != 'x')
+					legal = 1;
+				else if (legal == 0)
+					legal = -1;
+			}
+		}
+		if (legal == -1)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Illegal option `-%c' with this command\n",
+				      optflags[i]);
+	}
+}
+
+static char
+opt2char(int option)
+{
+	const char *ptr;
+	for (ptr = optflags; option > 1; option >>= 1, ptr++);
+
+	return *ptr;
+}
+
+static char
+cmd2char(int option)
+{
+	const char *ptr;
+	for (ptr = cmdflags; option > 1; option >>= 1, ptr++);
+
+	return *ptr;
+}
+
+static void
+add_command(unsigned int *cmd, const int newcmd, const unsigned int othercmds, int invert)
+{
+	if (invert)
+		xtables_error(PARAMETER_PROBLEM, "unexpected ! flag");
+	if (*cmd & (~othercmds))
+		xtables_error(PARAMETER_PROBLEM, "Can't use -%c with -%c\n",
+			      cmd2char(newcmd), cmd2char(*cmd & (~othercmds)));
+	*cmd |= newcmd;
+}
+
+static int
+check_inverse(const char option[], int *invert, int *optidx, int argc)
+{
+	if (option && strcmp(option, "!") == 0) {
+		if (*invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Multiple `!' flags not allowed");
+		*invert = TRUE;
+		if (optidx) {
+			*optidx = *optidx+1;
+			if (argc && *optidx > argc)
+				xtables_error(PARAMETER_PROBLEM,
+					      "no argument following `!'");
+		}
+
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static struct in_addr *
+host_to_addr(const char *name, unsigned int *naddr)
+{
+	struct hostent *host;
+	struct in_addr *addr;
+	unsigned int i;
+
+	*naddr = 0;
+	if ((host = gethostbyname(name)) != NULL) {
+		if (host->h_addrtype != AF_INET ||
+		    host->h_length != sizeof(struct in_addr))
+			return (struct in_addr *) NULL;
+
+		while (host->h_addr_list[*naddr] != (char *) NULL)
+			(*naddr)++;
+		addr = xtables_calloc(*naddr, sizeof(struct in_addr));
+		for (i = 0; i < *naddr; i++)
+			inaddrcpy(&(addr[i]),
+				  (struct in_addr *) host->h_addr_list[i]);
+		return addr;
+	}
+
+	return (struct in_addr *) NULL;
+}
+
+/*
+ *	All functions starting with "parse" should succeed, otherwise
+ *	the program fails.
+ *	Most routines return pointers to static data that may change
+ *	between calls to the same or other routines with a few exceptions:
+ *	"host_to_addr", "parse_hostnetwork", and "parse_hostnetworkmask"
+ *	return global static data.
+*/
+
+static struct in_addr *
+parse_hostnetwork(const char *name, unsigned int *naddrs)
+{
+	struct in_addr *addrp, *addrptmp;
+
+	if ((addrptmp = dotted_to_addr(name)) != NULL ||
+	    (addrptmp = network_to_addr(name)) != NULL) {
+		addrp = xtables_malloc(sizeof(struct in_addr));
+		inaddrcpy(addrp, addrptmp);
+		*naddrs = 1;
+		return addrp;
+	}
+	if ((addrp = host_to_addr(name, naddrs)) != NULL)
+		return addrp;
+
+	xtables_error(PARAMETER_PROBLEM, "host/network `%s' not found", name);
+}
+
+static struct in_addr *
+parse_mask(char *mask)
+{
+	static struct in_addr maskaddr;
+	struct in_addr *addrp;
+	unsigned int bits;
+
+	if (mask == NULL) {
+		/* no mask at all defaults to 32 bits */
+		maskaddr.s_addr = 0xFFFFFFFF;
+		return &maskaddr;
+	}
+	if ((addrp = dotted_to_addr(mask)) != NULL)
+		/* dotted_to_addr already returns a network byte order addr */
+		return addrp;
+	if (string_to_number(mask, 0, 32, &bits) == -1)
+		xtables_error(PARAMETER_PROBLEM,
+			      "invalid mask `%s' specified", mask);
+	if (bits != 0) {
+		maskaddr.s_addr = htonl(0xFFFFFFFF << (32 - bits));
+		return &maskaddr;
+	}
+
+	maskaddr.s_addr = 0L;
+	return &maskaddr;
+}
+
+static void
+parse_hostnetworkmask(const char *name, struct in_addr **addrpp,
+		      struct in_addr *maskp, unsigned int *naddrs)
+{
+	struct in_addr *addrp;
+	char buf[256];
+	char *p;
+	int i, j, k, n;
+
+	strncpy(buf, name, sizeof(buf) - 1);
+	if ((p = strrchr(buf, '/')) != NULL) {
+		*p = '\0';
+		addrp = parse_mask(p + 1);
+	} else
+		addrp = parse_mask(NULL);
+	inaddrcpy(maskp, addrp);
+
+	/* if a null mask is given, the name is ignored, like in "any/0" */
+	if (maskp->s_addr == 0L)
+		strcpy(buf, "0.0.0.0");
+
+	addrp = *addrpp = parse_hostnetwork(buf, naddrs);
+	n = *naddrs;
+	for (i = 0, j = 0; i < n; i++) {
+		addrp[j++].s_addr &= maskp->s_addr;
+		for (k = 0; k < j - 1; k++) {
+			if (addrp[k].s_addr == addrp[j - 1].s_addr) {
+				(*naddrs)--;
+				j--;
+				break;
+			}
+		}
+	}
+}
+
+static void
+parse_interface(const char *arg, char *vianame, unsigned char *mask)
+{
+	int vialen = strlen(arg);
+	unsigned int i;
+
+	memset(mask, 0, IFNAMSIZ);
+	memset(vianame, 0, IFNAMSIZ);
+
+	if (vialen + 1 > IFNAMSIZ)
+		xtables_error(PARAMETER_PROBLEM,
+			      "interface name `%s' must be shorter than IFNAMSIZ"
+			      " (%i)", arg, IFNAMSIZ-1);
+
+	strcpy(vianame, arg);
+	if (vialen == 0)
+		memset(mask, 0, IFNAMSIZ);
+	else if (vianame[vialen - 1] == '+') {
+		memset(mask, 0xFF, vialen - 1);
+		memset(mask + vialen - 1, 0, IFNAMSIZ - vialen + 1);
+		/* Don't remove `+' here! -HW */
+	} else {
+		/* Include nul-terminator in match */
+		memset(mask, 0xFF, vialen + 1);
+		memset(mask + vialen + 1, 0, IFNAMSIZ - vialen - 1);
+		for (i = 0; vianame[i]; i++) {
+			if (!isalnum(vianame[i])
+			    && vianame[i] != '_'
+			    && vianame[i] != '.') {
+				printf("Warning: wierd character in interface"
+				       " `%s' (No aliases, :, ! or *).\n",
+				       vianame);
+				break;
+			}
+		}
+	}
+}
+
+/* Can't be zero. */
+static int
+parse_rulenumber(const char *rule)
+{
+	unsigned int rulenum;
+
+	if (!xtables_strtoui(rule, NULL, &rulenum, 1, INT_MAX))
+		xtables_error(PARAMETER_PROBLEM,
+			      "Invalid rule number `%s'", rule);
+
+	return rulenum;
+}
+
+static const char *
+parse_target(const char *targetname)
+{
+	const char *ptr;
+
+	if (strlen(targetname) < 1)
+		xtables_error(PARAMETER_PROBLEM,
+			      "Invalid target name (too short)");
+
+	if (strlen(targetname)+1 > sizeof(arpt_chainlabel))
+		xtables_error(PARAMETER_PROBLEM,
+			      "Invalid target name `%s' (%zu chars max)",
+			      targetname, sizeof(arpt_chainlabel)-1);
+
+	for (ptr = targetname; *ptr; ptr++)
+		if (isspace(*ptr))
+			xtables_error(PARAMETER_PROBLEM,
+				      "Invalid target name `%s'", targetname);
+	return targetname;
+}
+
+static void
+set_option(unsigned int *options, unsigned int option, u_int16_t *invflg,
+	   int invert)
+{
+	if (*options & option)
+		xtables_error(PARAMETER_PROBLEM, "multiple -%c flags not allowed",
+			      opt2char(option));
+	*options |= option;
+
+	if (invert) {
+		unsigned int i;
+		for (i = 0; 1 << i != option; i++);
+
+		if (!inverse_for_options[i])
+			xtables_error(PARAMETER_PROBLEM,
+				      "cannot have ! before -%c",
+				      opt2char(option));
+		*invflg |= inverse_for_options[i];
+	}
+}
+
+static int
+list_entries(struct nft_handle *h, const char *chain, const char *table,
+	     int rulenum, int verbose, int numeric, int expanded,
+	     int linenumbers)
+{
+	unsigned int format;
+
+	format = FMT_OPTIONS;
+	if (!verbose)
+		format |= FMT_NOCOUNTS;
+	else
+		format |= FMT_VIA;
+
+	if (numeric)
+		format |= FMT_NUMERIC;
+
+	if (!expanded)
+		format |= FMT_KILOMEGAGIGA;
+
+	if (linenumbers)
+		format |= FMT_LINENUMBERS;
+
+	return nft_rule_list(h, chain, table, rulenum, format);
+}
+
+static struct arpt_entry *
+generate_entry(const struct arpt_entry *fw,
+	       struct arpt_entry_target *target)
+{
+	struct arpt_entry_target **t;
+	struct arpt_entry *e;
+	unsigned int size;
+
+
+	size = sizeof(struct arpt_entry);
+
+	e = xtables_malloc(size);
+	*e = *fw;
+	e->target_offset = offsetof(struct arpt_entry, elems);
+	e->next_offset = e->target_offset + target->u.target_size;
+
+	t = (void *) &e->elems;
+	*t = target;
+
+	return e;
+}
+
+static struct xtables_target *command_jump(struct arpt_entry *fw,
+					   const char *jumpto)
+{
+	struct xtables_target *target;
+	size_t size;
+
+	/* XTF_TRY_LOAD (may be chain name) */
+	target = xtables_find_target(jumpto, XTF_TRY_LOAD);
+
+	if (!target)
+		return NULL;
+
+	size = XT_ALIGN(sizeof(struct xt_entry_target))
+		+ target->size;
+
+	target->t = xtables_calloc(1, size);
+	target->t->u.target_size = size;
+	strncpy(target->t->u.user.name, jumpto, sizeof(target->t->u.user.name));
+	target->t->u.user.name[sizeof(target->t->u.user.name)-1] = '\0';
+	target->t->u.user.revision = target->revision;
+
+	xs_init_target(target);
+
+	if (target->x6_options != NULL)
+		opts = xtables_options_xfrm(xtables_globals.orig_opts,
+					    opts, target->x6_options,
+					    &target->option_offset);
+	else
+		opts = xtables_merge_options(xtables_globals.orig_opts,
+					     opts, target->extra_opts,
+					     &target->option_offset);
+
+	return target;
+}
+
+static int
+append_entry(struct nft_handle *h,
+	     const char *chain,
+	     const char *table,
+	     struct arpt_entry *fw,
+	     int rulenum,
+	     unsigned int nsaddrs,
+	     const struct in_addr saddrs[],
+	     unsigned int ndaddrs,
+	     const struct in_addr daddrs[],
+	     bool verbose, bool append)
+{
+	unsigned int i, j;
+	int ret = 1;
+
+	for (i = 0; i < nsaddrs; i++) {
+		fw->arp.src.s_addr = saddrs[i].s_addr;
+		for (j = 0; j < ndaddrs; j++) {
+			fw->arp.tgt.s_addr = daddrs[j].s_addr;
+			if (append) {
+				ret = nft_rule_append(h, chain, table, fw, 0,
+						      verbose);
+			} else {
+				ret = nft_rule_insert(h, chain, table, fw,
+						      rulenum, verbose);
+			}
+		}
+	}
+
+	return ret;
+}
+
+static int
+replace_entry(const char *chain,
+	      const char *table,
+	      struct arpt_entry *fw,
+	      unsigned int rulenum,
+	      const struct in_addr *saddr,
+	      const struct in_addr *daddr,
+	      bool verbose, struct nft_handle *h)
+{
+	fw->arp.src.s_addr = saddr->s_addr;
+	fw->arp.tgt.s_addr = daddr->s_addr;
+
+	return nft_rule_replace(h, chain, table, fw, rulenum, verbose);
+}
+
+static int
+delete_entry(const char *chain,
+	     const char *table,
+	     struct arpt_entry *fw,
+	     unsigned int nsaddrs,
+	     const struct in_addr saddrs[],
+	     unsigned int ndaddrs,
+	     const struct in_addr daddrs[],
+	     bool verbose, struct nft_handle *h)
+{
+	unsigned int i, j;
+	int ret = 1;
+
+	for (i = 0; i < nsaddrs; i++) {
+		fw->arp.src.s_addr = saddrs[i].s_addr;
+		for (j = 0; j < ndaddrs; j++) {
+			fw->arp.tgt.s_addr = daddrs[j].s_addr;
+			ret = nft_rule_delete(h, chain, table, fw, verbose);
+		}
+	}
+
+	return ret;
+}
+
+int do_commandarp(struct nft_handle *h, int argc, char *argv[], char **table)
+{
+	struct arpt_entry fw, *e = NULL;
+	int invert = 0;
+	unsigned int nsaddrs = 0, ndaddrs = 0;
+	struct in_addr *saddrs = NULL, *daddrs = NULL;
+
+	int c, verbose = 0;
+	const char *chain = NULL;
+	const char *shostnetworkmask = NULL, *dhostnetworkmask = NULL;
+	const char *policy = NULL, *newname = NULL;
+	unsigned int rulenum = 0, options = 0, command = 0;
+	const char *pcnt = NULL, *bcnt = NULL;
+	int ret = 1;
+	struct xtables_target *target = NULL;
+	struct xtables_target *t;
+
+	const char *jumpto = "";
+
+	memset(&fw, 0, sizeof(fw));
+	opts = original_opts;
+	global_option_offset = 0;
+
+	xtables_globals.orig_opts = original_opts;
+
+	/* re-set optind to 0 in case do_command gets called
+	 * a second time */
+	optind = 0;
+
+	for (t = xtables_targets; t; t = t->next) {
+		t->tflags = 0;
+		t->used = 0;
+	}
+
+	/* Suppress error messages: we may add new options if we
+	    demand-load a protocol. */
+	opterr = 0;
+
+	while ((c = getopt_long(argc, argv,
+	   "-A:D:R:I:L::M:F::Z::N:X::E:P:Vh::o:p:s:d:j:l:i:vnt:m:c:",
+					   opts, NULL)) != -1) {
+		switch (c) {
+			/*
+			 * Command selection
+			 */
+		case 'A':
+			add_command(&command, CMD_APPEND, CMD_NONE,
+				    invert);
+			chain = optarg;
+			break;
+
+		case 'D':
+			add_command(&command, CMD_DELETE, CMD_NONE,
+				    invert);
+			chain = optarg;
+			if (optind < argc && argv[optind][0] != '-'
+			    && argv[optind][0] != '!') {
+				rulenum = parse_rulenumber(argv[optind++]);
+				command = CMD_DELETE_NUM;
+			}
+			break;
+
+		case 'R':
+			add_command(&command, CMD_REPLACE, CMD_NONE,
+				    invert);
+			chain = optarg;
+			if (optind < argc && argv[optind][0] != '-'
+			    && argv[optind][0] != '!')
+				rulenum = parse_rulenumber(argv[optind++]);
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					      "-%c requires a rule number",
+					      cmd2char(CMD_REPLACE));
+			break;
+
+		case 'I':
+			add_command(&command, CMD_INSERT, CMD_NONE,
+				    invert);
+			chain = optarg;
+			if (optind < argc && argv[optind][0] != '-'
+			    && argv[optind][0] != '!')
+				rulenum = parse_rulenumber(argv[optind++]);
+			else rulenum = 1;
+			break;
+
+		case 'L':
+			add_command(&command, CMD_LIST, CMD_ZERO,
+				    invert);
+			if (optarg) chain = optarg;
+			else if (optind < argc && argv[optind][0] != '-'
+				 && argv[optind][0] != '!')
+				chain = argv[optind++];
+			break;
+
+		case 'F':
+			add_command(&command, CMD_FLUSH, CMD_NONE,
+				    invert);
+			if (optarg) chain = optarg;
+			else if (optind < argc && argv[optind][0] != '-'
+				 && argv[optind][0] != '!')
+				chain = argv[optind++];
+			break;
+
+		case 'Z':
+			add_command(&command, CMD_ZERO, CMD_LIST,
+				    invert);
+			if (optarg) chain = optarg;
+			else if (optind < argc && argv[optind][0] != '-'
+				&& argv[optind][0] != '!')
+				chain = argv[optind++];
+			break;
+
+		case 'N':
+			if (optarg && *optarg == '-')
+				xtables_error(PARAMETER_PROBLEM,
+					      "chain name not allowed to start "
+					      "with `-'\n");
+			if (xtables_find_target(optarg, XTF_TRY_LOAD))
+				xtables_error(PARAMETER_PROBLEM,
+						"chain name may not clash "
+						"with target name\n");
+			add_command(&command, CMD_NEW_CHAIN, CMD_NONE,
+				    invert);
+			chain = optarg;
+			break;
+
+		case 'X':
+			add_command(&command, CMD_DELETE_CHAIN, CMD_NONE,
+				    invert);
+			if (optarg) chain = optarg;
+			else if (optind < argc && argv[optind][0] != '-'
+				 && argv[optind][0] != '!')
+				chain = argv[optind++];
+			break;
+
+		case 'E':
+			add_command(&command, CMD_RENAME_CHAIN, CMD_NONE,
+				    invert);
+			chain = optarg;
+			if (optind < argc && argv[optind][0] != '-'
+			    && argv[optind][0] != '!')
+				newname = argv[optind++];
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					      "-%c requires old-chain-name and "
+					      "new-chain-name",
+					      cmd2char(CMD_RENAME_CHAIN));
+			break;
+
+		case 'P':
+			add_command(&command, CMD_SET_POLICY, CMD_NONE,
+				    invert);
+			chain = optarg;
+			if (optind < argc && argv[optind][0] != '-'
+			    && argv[optind][0] != '!')
+				policy = argv[optind++];
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					      "-%c requires a chain and a policy",
+					      cmd2char(CMD_SET_POLICY));
+			break;
+
+		case 'h':
+			if (!optarg)
+				optarg = argv[optind];
+
+			exit_printhelp();
+			break;
+		case 's':
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_S_IP, &fw.arp.invflags,
+				   invert);
+			shostnetworkmask = argv[optind-1];
+			break;
+
+		case 'd':
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_D_IP, &fw.arp.invflags,
+				   invert);
+			dhostnetworkmask = argv[optind-1];
+			break;
+
+		case 2:/* src-mac */
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_S_MAC, &fw.arp.invflags,
+				   invert);
+			if (getmac_and_mask(argv[optind - 1],
+			    fw.arp.src_devaddr.addr, fw.arp.src_devaddr.mask))
+				xtables_error(PARAMETER_PROBLEM, "Problem with specified "
+						"source mac");
+			break;
+
+		case 3:/* dst-mac */
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_D_MAC, &fw.arp.invflags,
+				   invert);
+
+			if (getmac_and_mask(argv[optind - 1],
+			    fw.arp.tgt_devaddr.addr, fw.arp.tgt_devaddr.mask))
+				xtables_error(PARAMETER_PROBLEM, "Problem with specified "
+						"destination mac");
+			break;
+
+		case 'l':/* hardware length */
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_H_LENGTH, &fw.arp.invflags,
+				   invert);
+			getlength_and_mask(argv[optind - 1], &fw.arp.arhln,
+					   &fw.arp.arhln_mask);
+
+			if (fw.arp.arhln != 6) {
+				xtables_error(PARAMETER_PROBLEM,
+					      "Only harware address length of"
+					      " 6 is supported currently.");
+			}
+
+			break;
+
+		case 8:/* protocol length */
+			xtables_error(PARAMETER_PROBLEM, "not supported");
+/*
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_P_LENGTH, &fw.arp.invflags,
+				   invert);
+
+			getlength_and_mask(argv[optind - 1], &fw.arp.arpln,
+					   &fw.arp.arpln_mask);
+			break;
+*/
+
+		case 4:/* opcode */
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_OPCODE, &fw.arp.invflags,
+				   invert);
+			if (get16_and_mask(argv[optind - 1], &fw.arp.arpop, &fw.arp.arpop_mask, 10)) {
+				int i;
+
+				for (i = 0; i < NUMOPCODES; i++)
+					if (!strcasecmp(opcodes[i], optarg))
+						break;
+				if (i == NUMOPCODES)
+					xtables_error(PARAMETER_PROBLEM, "Problem with specified opcode");
+				fw.arp.arpop = htons(i+1);
+			}
+			break;
+
+		case 5:/* h-type */
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_H_TYPE, &fw.arp.invflags,
+				   invert);
+			if (get16_and_mask(argv[optind - 1], &fw.arp.arhrd, &fw.arp.arhrd_mask, 16)) {
+				if (strcasecmp(argv[optind-1], "Ethernet"))
+					xtables_error(PARAMETER_PROBLEM, "Problem with specified hardware type");
+				fw.arp.arhrd = htons(1);
+			}
+			break;
+
+		case 6:/* proto-type */
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_P_TYPE, &fw.arp.invflags,
+				   invert);
+			if (get16_and_mask(argv[optind - 1], &fw.arp.arpro, &fw.arp.arpro_mask, 0)) {
+				if (strcasecmp(argv[optind-1], "ipv4"))
+					xtables_error(PARAMETER_PROBLEM, "Problem with specified protocol type");
+				fw.arp.arpro = htons(0x800);
+			}
+			break;
+
+		case 'j':
+			set_option(&options, OPT_JUMP, &fw.arp.invflags,
+				   invert);
+			jumpto = parse_target(optarg);
+			target = command_jump(&fw, jumpto);
+			break;
+
+		case 'i':
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_VIANAMEIN, &fw.arp.invflags,
+				   invert);
+			parse_interface(argv[optind-1],
+					fw.arp.iniface,
+					fw.arp.iniface_mask);
+/*			fw.nfcache |= NFC_IP_IF_IN; */
+			break;
+
+		case 'o':
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_VIANAMEOUT, &fw.arp.invflags,
+				   invert);
+			parse_interface(argv[optind-1],
+					fw.arp.outiface,
+					fw.arp.outiface_mask);
+			/* fw.nfcache |= NFC_IP_IF_OUT; */
+			break;
+
+		case 'v':
+			if (!verbose)
+				set_option(&options, OPT_VERBOSE,
+					   &fw.arp.invflags, invert);
+			verbose++;
+			break;
+
+		case 'm': /*{
+			size_t size;
+
+			if (invert)
+				exit_error(PARAMETER_PROBLEM,
+					   "unexpected ! flag before --match");
+
+			m = find_match(optarg, LOAD_MUST_SUCCEED);
+			size = ARPT_ALIGN(sizeof(struct arpt_entry_match))
+					 + m->size;
+			m->m = fw_calloc(1, size);
+			m->m->u.match_size = size;
+			strcpy(m->m->u.user.name, m->name);
+			m->init(m->m, &fw.nfcache);
+			opts = merge_options(opts, m->extra_opts, &m->option_offset);
+		}*/
+		break;
+
+		case 'n':
+			set_option(&options, OPT_NUMERIC, &fw.arp.invflags,
+				   invert);
+			break;
+
+		case 't':
+			if (invert)
+				xtables_error(PARAMETER_PROBLEM,
+					      "unexpected ! flag before --table");
+			*table = argv[optind-1];
+			break;
+
+		case 'V':
+			if (invert)
+				printf("Not %s ;-)\n", program_version);
+			else
+				printf("%s v%s\n",
+				       program_name, program_version);
+			exit(0);
+
+		case '0':
+			set_option(&options, OPT_LINENUMBERS, &fw.arp.invflags,
+				   invert);
+			break;
+
+		case 'M':
+			//modprobe = optarg;
+			break;
+
+		case 'c':
+
+			set_option(&options, OPT_COUNTERS, &fw.arp.invflags,
+				   invert);
+			pcnt = optarg;
+			if (optind < argc && argv[optind][0] != '-'
+			    && argv[optind][0] != '!')
+				bcnt = argv[optind++];
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					      "-%c requires packet and byte counter",
+					      opt2char(OPT_COUNTERS));
+
+			if (sscanf(pcnt, "%llu", &fw.counters.pcnt) != 1)
+			xtables_error(PARAMETER_PROBLEM,
+				"-%c packet counter not numeric",
+				opt2char(OPT_COUNTERS));
+
+			if (sscanf(bcnt, "%llu", &fw.counters.bcnt) != 1)
+				xtables_error(PARAMETER_PROBLEM,
+					      "-%c byte counter not numeric",
+					      opt2char(OPT_COUNTERS));
+
+			break;
+
+
+		case 1: /* non option */
+			if (optarg[0] == '!' && optarg[1] == '\0') {
+				if (invert)
+					xtables_error(PARAMETER_PROBLEM,
+						      "multiple consecutive ! not"
+						      " allowed");
+				invert = TRUE;
+				optarg[0] = '\0';
+				continue;
+			}
+			printf("Bad argument `%s'\n", optarg);
+			exit_tryhelp(2);
+
+		default:
+			if (target) {
+				xtables_option_tpcall(c, argv,
+						      invert, target, &fw);
+			}
+			break;
+		}
+		invert = FALSE;
+	}
+
+	if (target)
+		xtables_option_tfcall(target);
+
+	if (optind < argc)
+		xtables_error(PARAMETER_PROBLEM,
+			      "unknown arguments found on commandline");
+	if (!command)
+		xtables_error(PARAMETER_PROBLEM, "no command specified");
+	if (invert)
+		xtables_error(PARAMETER_PROBLEM,
+			      "nothing appropriate following !");
+
+	if (command & (CMD_REPLACE | CMD_INSERT | CMD_DELETE | CMD_APPEND)) {
+		if (!(options & OPT_D_IP))
+			dhostnetworkmask = "0.0.0.0/0";
+		if (!(options & OPT_S_IP))
+			shostnetworkmask = "0.0.0.0/0";
+	}
+
+	if (shostnetworkmask)
+		parse_hostnetworkmask(shostnetworkmask, &saddrs,
+				      &(fw.arp.smsk), &nsaddrs);
+
+	if (dhostnetworkmask)
+		parse_hostnetworkmask(dhostnetworkmask, &daddrs,
+				      &(fw.arp.tmsk), &ndaddrs);
+
+	if ((nsaddrs > 1 || ndaddrs > 1) &&
+	    (fw.arp.invflags & (ARPT_INV_SRCIP | ARPT_INV_TGTIP)))
+		xtables_error(PARAMETER_PROBLEM, "! not allowed with multiple"
+				" source or destination IP addresses");
+
+	if (command == CMD_REPLACE && (nsaddrs != 1 || ndaddrs != 1))
+		xtables_error(PARAMETER_PROBLEM, "Replacement rule does not "
+						 "specify a unique address");
+
+	generic_opt_check(command, options);
+
+	if (chain && strlen(chain) > ARPT_FUNCTION_MAXNAMELEN)
+		xtables_error(PARAMETER_PROBLEM,
+				"chain name `%s' too long (must be under %i chars)",
+				chain, ARPT_FUNCTION_MAXNAMELEN);
+
+	if (nft_init(h, xtables_arp) < 0)
+		xtables_error(OTHER_PROBLEM,
+			      "Could not initialize nftables layer.");
+
+	h->ops = nft_family_ops_lookup(h->family);
+	if (h->ops == NULL)
+		xtables_error(PARAMETER_PROBLEM, "Unknown family");
+
+	if (command == CMD_APPEND
+	    || command == CMD_DELETE
+	    || command == CMD_INSERT
+	    || command == CMD_REPLACE) {
+		if (strcmp(chain, "PREROUTING") == 0
+		    || strcmp(chain, "INPUT") == 0) {
+			/* -o not valid with incoming packets. */
+			if (options & OPT_VIANAMEOUT)
+				xtables_error(PARAMETER_PROBLEM,
+					      "Can't use -%c with %s\n",
+					      opt2char(OPT_VIANAMEOUT),
+					      chain);
+		}
+
+		if (strcmp(chain, "POSTROUTING") == 0
+		    || strcmp(chain, "OUTPUT") == 0) {
+			/* -i not valid with outgoing packets */
+			if (options & OPT_VIANAMEIN)
+				xtables_error(PARAMETER_PROBLEM,
+						"Can't use -%c with %s\n",
+						opt2char(OPT_VIANAMEIN),
+						chain);
+		}
+
+		if (!target && strlen(jumpto) != 0) {
+			size_t size;
+
+			target = xtables_find_target(XT_STANDARD_TARGET,
+						     XTF_LOAD_MUST_SUCCEED);
+			size = sizeof(struct arpt_entry_target) + target->size;
+			target->t = xtables_calloc(1, size);
+			target->t->u.target_size = size;
+			strcpy(target->t->u.user.name, jumpto);
+		}
+
+		if (!target) {
+			xtables_error(PARAMETER_PROBLEM,
+				      "No target provided or"
+				      " initalization failed");
+		}
+
+		e = generate_entry(&fw, target->t);
+	}
+
+	switch (command) {
+	case CMD_APPEND:
+		ret = append_entry(h, chain, *table, e, 0,
+				   nsaddrs, saddrs, ndaddrs, daddrs,
+				   options&OPT_VERBOSE, true);
+		break;
+	case CMD_DELETE:
+		ret = delete_entry(chain, *table, e,
+				   nsaddrs, saddrs, ndaddrs, daddrs,
+				   options&OPT_VERBOSE, h);
+		break;
+	case CMD_DELETE_NUM:
+		ret = nft_rule_delete_num(h, chain, *table, rulenum - 1, verbose);
+		break;
+	case CMD_REPLACE:
+		ret = replace_entry(chain, *table, e, rulenum - 1,
+				    saddrs, daddrs, options&OPT_VERBOSE, h);
+		break;
+	case CMD_INSERT:
+		ret = append_entry(h, chain, *table, e, rulenum - 1,
+				   nsaddrs, saddrs, ndaddrs, daddrs,
+				   options&OPT_VERBOSE, false);
+		break;
+	case CMD_LIST:
+		ret = list_entries(h, chain, *table,
+				   rulenum,
+				   options&OPT_VERBOSE,
+				   options&OPT_NUMERIC,
+				   /*options&OPT_EXPANDED*/0,
+				   options&OPT_LINENUMBERS);
+		break;
+	case CMD_FLUSH:
+		ret = nft_rule_flush(h, chain, *table);
+		break;
+	case CMD_ZERO:
+		ret = nft_chain_zero_counters(h, chain, *table);
+		break;
+	case CMD_LIST|CMD_ZERO:
+		ret = list_entries(h, chain, *table, rulenum,
+				   options&OPT_VERBOSE,
+				   options&OPT_NUMERIC,
+				   /*options&OPT_EXPANDED*/0,
+				   options&OPT_LINENUMBERS);
+		if (ret)
+			ret = nft_chain_zero_counters(h, chain, *table);
+		break;
+	case CMD_NEW_CHAIN:
+		ret = nft_chain_user_add(h, chain, *table);
+		break;
+	case CMD_DELETE_CHAIN:
+		ret = nft_chain_user_del(h, chain, *table);
+		break;
+	case CMD_RENAME_CHAIN:
+		ret = nft_chain_user_rename(h, chain, *table, newname);
+		break;
+	case CMD_SET_POLICY:
+		ret = nft_chain_set(h, *table, chain, policy, NULL);
+		if (ret < 0)
+			xtables_error(PARAMETER_PROBLEM, "Wrong policy `%s'\n",
+				      policy);
+		break;
+	default:
+		/* We should never reach this... */
+		exit_tryhelp(2);
+	}
+
+/*	if (verbose > 1)
+		dump_entries(*handle);*/
+
+	return ret;
+}
diff --git a/iptables/xtables-compat-multi.c b/iptables/xtables-compat-multi.c
new file mode 100644
index 0000000..4781052
--- /dev/null
+++ b/iptables/xtables-compat-multi.c
@@ -0,0 +1,39 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "xshared.h"
+
+#include "xtables-multi.h"
+
+static const struct subcommand multi_subcommands[] = {
+	{"iptables-xml",		iptables_xml_main},
+	{"xml",				iptables_xml_main},
+	{"iptables",			xtables_ip4_main},
+	{"iptables-compat",		xtables_ip4_main},
+	{"main4",			xtables_ip4_main},
+	{"save4",			xtables_ip4_save_main},
+	{"restore4",			xtables_ip4_restore_main},
+	{"iptables-save",		xtables_ip4_save_main},
+	{"iptables-restore",		xtables_ip4_restore_main},
+	{"iptables-compat-save",	xtables_ip4_save_main},
+	{"iptables-compat-restore",	xtables_ip4_restore_main},
+	{"ip6tables",			xtables_ip6_main},
+	{"ip6tables-compat",		xtables_ip6_main},
+	{"main6",			xtables_ip6_main},
+	{"save6",			xtables_ip6_save_main},
+	{"restore6",			xtables_ip6_restore_main},
+	{"ip6tables-save",		xtables_ip6_save_main},
+	{"ip6tables-restore",		xtables_ip6_restore_main},
+	{"ip6tables-compat-save",	xtables_ip6_save_main},
+	{"ip6tables-compat-restore",	xtables_ip6_restore_main},
+	{"arptables",			xtables_arp_main},
+	{"arptables-compat",		xtables_arp_main},
+	{"xtables-config",		xtables_config_main},
+	{"xtables-events",		xtables_events_main},
+	{NULL},
+};
+
+int main(int argc, char **argv)
+{
+	return subcmd_main(argc, argv, multi_subcommands);
+}
diff --git a/iptables/xtables-config-parser.y b/iptables/xtables-config-parser.y
new file mode 100644
index 0000000..2770a1b
--- /dev/null
+++ b/iptables/xtables-config-parser.y
@@ -0,0 +1,250 @@
+%{
+/*
+ * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software has been sponsored by Sophos Astaro <http://www.sophos.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <libiptc/linux_list.h>
+#include <libnftnl/table.h>
+#include <libnftnl/chain.h>
+
+#include <netinet/in.h>
+#include <linux/netfilter.h>
+
+extern char *yytext;
+extern int yylineno;
+
+static LIST_HEAD(xtables_stack);
+
+struct stack_elem {
+	struct list_head	head;
+	int			token;
+	size_t			size;
+	char			data[];
+};
+
+static void *stack_push(int token, size_t size)
+{
+	struct stack_elem *e;
+
+	e = calloc(1, sizeof(struct stack_elem) + size);
+
+	e->token = token;
+	e->size = size;
+
+	list_add(&e->head, &xtables_stack);
+
+	return e->data;
+}
+
+static struct stack_elem *stack_pop(void)
+{
+	struct stack_elem *e;
+
+	e = list_entry(xtables_stack.next, struct stack_elem, head);
+
+	if (&e->head == &xtables_stack)
+		return NULL;
+
+	list_del(&e->head);
+	return e;
+}
+
+static inline void stack_put_i32(void *data, int value)
+{
+	memcpy(data, &value, sizeof(int));
+}
+
+static inline void stack_put_str(void *data, const char *str)
+{
+	memcpy(data, str, strlen(str));
+}
+
+static void stack_free(struct stack_elem *e)
+{
+	free(e);
+}
+
+%}
+
+%union {
+	int	val;
+	char	*string;
+}
+
+%token T_FAMILY
+%token T_TABLE
+%token T_CHAIN
+%token T_HOOK
+%token T_PRIO
+
+%token <string> T_STRING
+%token <val>	T_INTEGER
+
+%%
+
+configfile	:
+		| lines
+		;
+
+lines		: line
+		| lines line
+		;
+
+line		: family
+		;
+
+family		: T_FAMILY T_STRING '{' tables '}'
+		{
+			void *data = stack_push(T_FAMILY, strlen($2)+1);
+			stack_put_str(data, $2);
+		}
+		;
+
+tables		: table
+		| tables table
+		;
+
+table		: T_TABLE T_STRING '{' chains '}'
+		{
+			/* added in reverse order to pop it in order */
+			void *data = stack_push(T_TABLE, strlen($2)+1);
+			stack_put_str(data, $2);
+		}
+		;
+
+chains		: chain
+		| chains chain
+		;
+
+chain		: T_CHAIN T_STRING T_HOOK T_STRING T_PRIO T_INTEGER
+		{
+			/* added in reverse order to pop it in order */
+			void *data = stack_push(T_PRIO, sizeof(int32_t));
+			stack_put_i32(data, $6);
+			data = stack_push(T_HOOK, strlen($4)+1);
+			stack_put_str(data, $4);
+			data = stack_push(T_CHAIN, strlen($2)+1);
+			stack_put_str(data, $2);
+		}
+		;
+
+%%
+
+int __attribute__((noreturn))
+yyerror(char *msg)
+{
+	fprintf(stderr, "parsing config file in line (%d), symbol '%s': %s\n",
+			 yylineno, yytext, msg);
+	exit(EXIT_FAILURE);
+}
+
+static int hooknametonum(const char *hookname)
+{
+	if (strcmp(hookname, "NF_INET_LOCAL_IN") == 0)
+		return NF_INET_LOCAL_IN;
+	else if (strcmp(hookname, "NF_INET_FORWARD") == 0)
+		return NF_INET_FORWARD;
+	else if (strcmp(hookname, "NF_INET_LOCAL_OUT") == 0)
+		return NF_INET_LOCAL_OUT;
+	else if (strcmp(hookname, "NF_INET_PRE_ROUTING") == 0)
+		return NF_INET_PRE_ROUTING;
+	else if (strcmp(hookname, "NF_INET_POST_ROUTING") == 0)
+		return NF_INET_POST_ROUTING;
+
+	return -1;
+}
+
+static int32_t familytonumber(const char *family)
+{
+	if (strcmp(family, "ipv4") == 0)
+		return AF_INET;
+	else if (strcmp(family, "ipv6") == 0)
+		return AF_INET6;
+
+	return -1;
+}
+
+int xtables_config_parse(char *filename, struct nft_table_list *table_list,
+			 struct nft_chain_list *chain_list)
+{
+	FILE *fp;
+	struct stack_elem *e;
+	struct nft_table *table = NULL;
+	struct nft_chain *chain = NULL;
+	int prio = 0;
+	int32_t family = 0;
+
+	fp = fopen(filename, "r");
+	if (!fp)
+		return -1;
+
+	yyrestart(fp);
+	yyparse();
+	fclose(fp);
+
+	for (e = stack_pop(); e != NULL; e = stack_pop()) {
+		switch(e->token) {
+		case T_FAMILY:
+			family = familytonumber(e->data);
+			if (family == -1)
+				return -1;
+			break;
+		case T_TABLE:
+			table = nft_table_alloc();
+			if (table == NULL) {
+				perror("nft_table_alloc");
+				return -1;
+			}
+			nft_table_attr_set_u32(table, NFT_TABLE_ATTR_FAMILY, family);
+			nft_table_attr_set(table, NFT_TABLE_ATTR_NAME, e->data);
+			/* This is intentionally prepending, instead of
+			 * appending, since the elements in the stack are in
+			 * the reverse order that chains appear in the
+			 * configuration file.
+			 */
+			nft_table_list_add(table, table_list);
+			break;
+		case T_PRIO:
+			memcpy(&prio, e->data, sizeof(int32_t));
+			break;
+		case T_CHAIN:
+			chain = nft_chain_alloc();
+			if (chain == NULL) {
+				perror("nft_chain_alloc");
+				return -1;
+			}
+			nft_chain_attr_set(chain, NFT_CHAIN_ATTR_TABLE,
+				(char *)nft_table_attr_get(table, NFT_TABLE_ATTR_NAME));
+			nft_chain_attr_set_u32(chain, NFT_CHAIN_ATTR_FAMILY,
+				nft_table_attr_get_u32(table, NFT_TABLE_ATTR_FAMILY));
+			nft_chain_attr_set_s32(chain, NFT_CHAIN_ATTR_PRIO, prio);
+			nft_chain_attr_set(chain, NFT_CHAIN_ATTR_NAME, e->data);
+			/* Intentionally prepending, instead of appending */
+			nft_chain_list_add(chain, chain_list);
+			break;
+		case T_HOOK:
+			nft_chain_attr_set_u32(chain, NFT_CHAIN_ATTR_HOOKNUM,
+						hooknametonum(e->data));
+			break;
+		default:
+			printf("unknown token type %d\n", e->token);
+			break;
+		}
+		stack_free(e);
+	}
+
+	return 0;
+}
diff --git a/iptables/xtables-config-syntax.l b/iptables/xtables-config-syntax.l
new file mode 100644
index 0000000..a895c8b
--- /dev/null
+++ b/iptables/xtables-config-syntax.l
@@ -0,0 +1,54 @@
+%{
+/*
+ * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software has been sponsored by Sophos Astaro <http://www.sophos.com>
+ */
+
+#include <string.h>
+#include "xtables-config-parser.h"
+%}
+
+%option yylineno
+%option noinput
+%option nounput
+
+ws		[ \t]+
+comment         #.*$
+nl		[\n\r]
+
+is_on		[o|O][n|N]
+is_off		[o|O][f|F][f|F]
+integer		[\-\+]?[0-9]+
+string		[a-zA-Z][a-zA-Z0-9\.\-\_]*
+
+%%
+"family"		{ return T_FAMILY; }
+"table"			{ return T_TABLE; }
+"chain"			{ return T_CHAIN; }
+"hook"			{ return T_HOOK; }
+"prio"			{ return T_PRIO; }
+
+{integer}		{ yylval.val = atoi(yytext); return T_INTEGER; }
+{string}		{ yylval.string = strdup(yytext); return T_STRING; }
+
+{comment}	;
+{ws}		;
+{nl}		;
+
+<<EOF>>		{ yyterminate(); }
+
+.		{ return yytext[0]; }
+
+%%
+
+int
+yywrap()
+{
+	return 1;
+}
diff --git a/iptables/xtables-config.c b/iptables/xtables-config.c
new file mode 100644
index 0000000..b7cf609
--- /dev/null
+++ b/iptables/xtables-config.c
@@ -0,0 +1,46 @@
+/*
+ * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This code has been sponsored by Sophos Astaro <http://www.sophos.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+
+#include "xtables-multi.h"
+#include "nft.h"
+
+int xtables_config_main(int argc, char *argv[])
+{
+	struct nft_handle h = {
+		.family = AF_INET,
+	};
+	const char *filename = NULL;
+
+	if (argc > 2) {
+		fprintf(stderr, "Usage: %s [<config_file>]\n", argv[0]);
+		return EXIT_SUCCESS;
+	}
+	if (argc == 1)
+		filename = XTABLES_CONFIG_DEFAULT;
+	else
+		filename = argv[1];
+
+	if (nft_init(&h, xtables_ipv4) < 0) {
+                fprintf(stderr, "Failed to initialize nft: %s\n",
+			strerror(errno));
+		return EXIT_FAILURE;
+	}
+
+	return nft_xtables_config_load(&h, filename, NFT_LOAD_VERBOSE) == 0 ?
+						    EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/iptables/xtables-events.c b/iptables/xtables-events.c
new file mode 100644
index 0000000..4be8ab8
--- /dev/null
+++ b/iptables/xtables-events.c
@@ -0,0 +1,217 @@
+/*
+ * (C) 2012-2013 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software has been sponsored by Sophos Astaro <http://www.sophos.com>
+ */
+
+#include <stdlib.h>
+#include <time.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <getopt.h>
+
+#include <linux/netfilter/nfnetlink.h>
+#include <linux/netfilter/nf_tables.h>
+
+#include <libmnl/libmnl.h>
+#include <libnftnl/table.h>
+#include <libnftnl/chain.h>
+#include <libnftnl/rule.h>
+
+#include <include/xtables.h>
+#include "iptables.h" /* for xtables_globals */
+#include "xtables-multi.h"
+#include "nft.h"
+
+static int table_cb(const struct nlmsghdr *nlh, int type)
+{
+	struct nft_table *t;
+	char buf[4096];
+
+	t = nft_table_alloc();
+	if (t == NULL) {
+		perror("OOM");
+		goto err;
+	}
+
+	if (nft_table_nlmsg_parse(nlh, t) < 0) {
+		perror("nft_table_nlmsg_parse");
+		goto err_free;
+	}
+
+	nft_table_snprintf(buf, sizeof(buf), t, NFT_OUTPUT_DEFAULT, 0);
+	/* FIXME: define syntax to represent table events */
+	printf("# [table: %s]\t%s\n", type == NFT_MSG_NEWTABLE ? "NEW" : "DEL", buf);
+
+err_free:
+	nft_table_free(t);
+err:
+	return MNL_CB_OK;
+}
+
+static bool counters;
+
+static int rule_cb(const struct nlmsghdr *nlh, int type)
+{
+	struct iptables_command_state cs = {};
+	struct nft_rule *r;
+
+	r = nft_rule_alloc();
+	if (r == NULL) {
+		perror("OOM");
+		goto err;
+	}
+
+	if (nft_rule_nlmsg_parse(nlh, r) < 0) {
+		perror("nft_rule_nlmsg_parse");
+		goto err_free;
+	}
+
+	nft_rule_to_iptables_command_state(r, &cs);
+
+	switch(nft_rule_attr_get_u8(r, NFT_RULE_ATTR_FAMILY)) {
+	case AF_INET:
+		printf("-4 ");
+		break;
+	case AF_INET6:
+		printf("-6 ");
+		break;
+	default:
+		break;
+	}
+
+
+	nft_rule_print_save(&cs, r,
+			    type == NFT_MSG_NEWRULE ? NFT_RULE_APPEND :
+						      NFT_RULE_DEL,
+			    counters ? 0 : FMT_NOCOUNTS);
+err_free:
+	nft_rule_free(r);
+err:
+	return MNL_CB_OK;
+}
+
+static int chain_cb(const struct nlmsghdr *nlh, int type)
+{
+	struct nft_chain *t;
+	char buf[4096];
+
+	t = nft_chain_alloc();
+	if (t == NULL) {
+		perror("OOM");
+		goto err;
+	}
+
+	if (nft_chain_nlmsg_parse(nlh, t) < 0) {
+		perror("nft_chain_nlmsg_parse");
+		goto err_free;
+	}
+
+	nft_chain_snprintf(buf, sizeof(buf), t, NFT_OUTPUT_DEFAULT, 0);
+	/* FIXME: define syntax to represent chain events */
+	printf("# [chain: %s]\t%s\n", type == NFT_MSG_NEWCHAIN ? "NEW" : "DEL", buf);
+
+err_free:
+	nft_chain_free(t);
+err:
+	return MNL_CB_OK;
+}
+
+static int events_cb(const struct nlmsghdr *nlh, void *data)
+{
+	int ret = MNL_CB_OK;
+	int type = nlh->nlmsg_type & 0xFF;
+
+	switch(type) {
+	case NFT_MSG_NEWTABLE:
+	case NFT_MSG_DELTABLE:
+		ret = table_cb(nlh, type);
+		break;
+	case NFT_MSG_NEWCHAIN:
+	case NFT_MSG_DELCHAIN:
+		ret = chain_cb(nlh, type);
+		break;
+	case NFT_MSG_NEWRULE:
+	case NFT_MSG_DELRULE:
+		ret = rule_cb(nlh, type);
+		break;
+	}
+
+	return ret;
+}
+
+static const struct option options[] = {
+	{.name = "counters", .has_arg = false, .val = 'c'},
+	{NULL},
+};
+
+static void print_usage(const char *name, const char *version)
+{
+	fprintf(stderr, "Usage: %s [-c]\n"
+			"	   [ --counters ]\n", name);
+	exit(EXIT_FAILURE);
+}
+
+int xtables_events_main(int argc, char *argv[])
+{
+	struct mnl_socket *nl;
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	int ret, c;
+
+	xtables_globals.program_name = "xtables-events";
+	/* XXX xtables_init_all does several things we don't want */
+	c = xtables_init_all(&xtables_globals, NFPROTO_IPV4);
+	if (c < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
+				xtables_globals.program_name,
+				xtables_globals.program_version);
+		exit(1);
+	}
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+	init_extensions();
+	init_extensions4();
+#endif
+
+	opterr = 0;
+	while ((c = getopt_long(argc, argv, "c", options, NULL)) != -1) {
+		switch (c) {
+	        case 'c':
+			counters = true;
+			break;
+		default:
+			print_usage(argv[0], XTABLES_VERSION);
+			exit(EXIT_FAILURE);
+		}
+	}
+
+	nl = mnl_socket_open(NETLINK_NETFILTER);
+	if (nl == NULL) {
+		perror("mnl_socket_open");
+		exit(EXIT_FAILURE);
+	}
+
+	if (mnl_socket_bind(nl, (1 << (NFNLGRP_NFTABLES-1)), MNL_SOCKET_AUTOPID) < 0) {
+		perror("mnl_socket_bind");
+		exit(EXIT_FAILURE);
+	}
+
+	ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+	while (ret > 0) {
+		ret = mnl_cb_run(buf, ret, 0, 0, events_cb, NULL);
+		if (ret <= 0)
+			break;
+		ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+	}
+	if (ret == -1) {
+		perror("error");
+		exit(EXIT_FAILURE);
+	}
+	mnl_socket_close(nl);
+
+	return EXIT_SUCCESS;
+}
diff --git a/iptables/xtables-multi.c b/iptables/xtables-multi.c
index 8014d5f..5f48735 100644
--- a/iptables/xtables-multi.c
+++ b/iptables/xtables-multi.c
@@ -13,6 +13,10 @@
 #include "ip6tables-multi.h"
 #endif
 
+#ifdef ENABLE_NFTABLES
+#include "xtables-multi.h"
+#endif
+
 static const struct subcommand multi_subcommands[] = {
 #ifdef ENABLE_IPV4
 	{"iptables",            iptables_main},
@@ -32,6 +36,14 @@
 	{"ip6tables-restore",   ip6tables_restore_main},
 	{"restore6",            ip6tables_restore_main},
 #endif
+#ifdef ENABLE_NFTABLES
+	{"xtables",             xtables_main},
+	{"xtables-save",        xtables_save_main},
+	{"xtables-restore",     xtables_restore_main},
+	{"xtables-config",      xtables_config_main},
+	{"xtables-events",      xtables_events_main},
+	{"xtables-arp",		xtables_arp_main},
+#endif
 	{NULL},
 };
 
diff --git a/iptables/xtables-multi.h b/iptables/xtables-multi.h
index 615724b..e706894 100644
--- a/iptables/xtables-multi.h
+++ b/iptables/xtables-multi.h
@@ -2,5 +2,16 @@
 #define _XTABLES_MULTI_H 1
 
 extern int iptables_xml_main(int, char **);
+#ifdef ENABLE_NFTABLES
+extern int xtables_ip4_main(int, char **);
+extern int xtables_ip4_save_main(int, char **);
+extern int xtables_ip4_restore_main(int, char **);
+extern int xtables_ip6_main(int, char **);
+extern int xtables_ip6_save_main(int, char **);
+extern int xtables_ip6_restore_main(int, char **);
+extern int xtables_arp_main(int, char **);
+extern int xtables_config_main(int, char **);
+extern int xtables_events_main(int, char **);
+#endif
 
 #endif /* _XTABLES_MULTI_H */
diff --git a/iptables/xtables-restore.c b/iptables/xtables-restore.c
new file mode 100644
index 0000000..f7850bb
--- /dev/null
+++ b/iptables/xtables-restore.c
@@ -0,0 +1,488 @@
+/* Code to restore the iptables state, from file by iptables-save.
+ * (C) 2000-2002 by Harald Welte <laforge@gnumonks.org>
+ * based on previous code from Rusty Russell <rusty@linuxcare.com.au>
+ *
+ * This code is distributed under the terms of GNU GPL v2
+ */
+
+#include <getopt.h>
+#include <sys/errno.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "iptables.h"
+#include "xtables.h"
+#include "libiptc/libiptc.h"
+#include "xtables-multi.h"
+#include "nft.h"
+#include <libnftnl/chain.h>
+
+#ifdef DEBUG
+#define DEBUGP(x, args...) fprintf(stderr, x, ## args)
+#else
+#define DEBUGP(x, args...)
+#endif
+
+static int binary = 0, counters = 0, verbose = 0, noflush = 0;
+
+/* Keeping track of external matches and targets.  */
+static const struct option options[] = {
+	{.name = "binary",   .has_arg = false, .val = 'b'},
+	{.name = "counters", .has_arg = false, .val = 'c'},
+	{.name = "verbose",  .has_arg = false, .val = 'v'},
+	{.name = "test",     .has_arg = false, .val = 't'},
+	{.name = "help",     .has_arg = false, .val = 'h'},
+	{.name = "noflush",  .has_arg = false, .val = 'n'},
+	{.name = "modprobe", .has_arg = true,  .val = 'M'},
+	{.name = "table",    .has_arg = true,  .val = 'T'},
+	{.name = "ipv4",     .has_arg = false, .val = '4'},
+	{.name = "ipv6",     .has_arg = false, .val = '6'},
+	{NULL},
+};
+
+static void print_usage(const char *name, const char *version) __attribute__((noreturn));
+
+#define prog_name xtables_globals.program_name
+
+static void print_usage(const char *name, const char *version)
+{
+	fprintf(stderr, "Usage: %s [-b] [-c] [-v] [-t] [-h]\n"
+			"	   [ --binary ]\n"
+			"	   [ --counters ]\n"
+			"	   [ --verbose ]\n"
+			"	   [ --test ]\n"
+			"	   [ --help ]\n"
+			"	   [ --noflush ]\n"
+			"	   [ --table=<TABLE> ]\n"
+			"          [ --modprobe=<command>]\n", name);
+
+	exit(1);
+}
+
+static int parse_counters(char *string, struct xt_counters *ctr)
+{
+	unsigned long long pcnt, bcnt;
+	int ret;
+
+	ret = sscanf(string, "[%llu:%llu]", &pcnt, &bcnt);
+	ctr->pcnt = pcnt;
+	ctr->bcnt = bcnt;
+	return ret == 2;
+}
+
+/* global new argv and argc */
+static char *newargv[255];
+static int newargc;
+
+/* function adding one argument to newargv, updating newargc 
+ * returns true if argument added, false otherwise */
+static int add_argv(char *what) {
+	DEBUGP("add_argv: %s\n", what);
+	if (what && newargc + 1 < ARRAY_SIZE(newargv)) {
+		newargv[newargc] = strdup(what);
+		newargv[++newargc] = NULL;
+		return 1;
+	} else {
+		xtables_error(PARAMETER_PROBLEM,
+			"Parser cannot handle more arguments\n");
+		return 0;
+	}
+}
+
+static void free_argv(void) {
+	int i;
+
+	for (i = 0; i < newargc; i++)
+		free(newargv[i]);
+}
+
+static void add_param_to_argv(char *parsestart)
+{
+	int quote_open = 0, escaped = 0, param_len = 0;
+	char param_buffer[1024], *curchar;
+
+	/* After fighting with strtok enough, here's now
+	 * a 'real' parser. According to Rusty I'm now no
+	 * longer a real hacker, but I can live with that */
+
+	for (curchar = parsestart; *curchar; curchar++) {
+		if (quote_open) {
+			if (escaped) {
+				param_buffer[param_len++] = *curchar;
+				escaped = 0;
+				continue;
+			} else if (*curchar == '\\') {
+				escaped = 1;
+				continue;
+			} else if (*curchar == '"') {
+				quote_open = 0;
+				*curchar = ' ';
+			} else {
+				param_buffer[param_len++] = *curchar;
+				continue;
+			}
+		} else {
+			if (*curchar == '"') {
+				quote_open = 1;
+				continue;
+			}
+		}
+
+		if (*curchar == ' '
+		    || *curchar == '\t'
+		    || * curchar == '\n') {
+			if (!param_len) {
+				/* two spaces? */
+				continue;
+			}
+
+			param_buffer[param_len] = '\0';
+
+			/* check if table name specified */
+			if (!strncmp(param_buffer, "-t", 2)
+			    || !strncmp(param_buffer, "--table", 8)) {
+				xtables_error(PARAMETER_PROBLEM,
+				"The -t option (seen in line %u) cannot be "
+				"used in xtables-restore.\n", line);
+				exit(1);
+			}
+
+			add_argv(param_buffer);
+			param_len = 0;
+		} else {
+			/* regular character, copy to buffer */
+			param_buffer[param_len++] = *curchar;
+
+			if (param_len >= sizeof(param_buffer))
+				xtables_error(PARAMETER_PROBLEM,
+				   "Parameter too long!");
+		}
+	}
+}
+
+static const struct xtc_ops xtc_ops = {
+	.strerror	= nft_strerror,
+};
+
+static int
+xtables_restore_main(int family, const char *progname, int argc, char *argv[])
+{
+	struct nft_handle h = {
+		.family = family,
+		.restore = true,
+	};
+	char buffer[10240];
+	int c;
+	char curtable[XT_TABLE_MAXNAMELEN + 1];
+	FILE *in;
+	int in_table = 0, testing = 0;
+	const char *tablename = NULL;
+	const struct xtc_ops *ops = &xtc_ops;
+	struct nft_chain_list *chain_list;
+	struct nft_chain *chain_obj;
+
+	line = 0;
+
+	xtables_globals.program_name = progname;
+	c = xtables_init_all(&xtables_globals, family);
+	if (c < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
+				xtables_globals.program_name,
+				xtables_globals.program_version);
+		exit(1);
+	}
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+	init_extensions();
+	init_extensions4();
+#endif
+
+	if (nft_init(&h, xtables_ipv4) < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize nft: %s\n",
+				xtables_globals.program_name,
+				xtables_globals.program_version,
+				strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+
+	while ((c = getopt_long(argc, argv, "bcvthnM:T:46", options, NULL)) != -1) {
+		switch (c) {
+			case 'b':
+				binary = 1;
+				break;
+			case 'c':
+				counters = 1;
+				break;
+			case 'v':
+				verbose = 1;
+				break;
+			case 't':
+				testing = 1;
+				break;
+			case 'h':
+				print_usage("xtables-restore",
+					    IPTABLES_VERSION);
+				break;
+			case 'n':
+				noflush = 1;
+				break;
+			case 'M':
+				xtables_modprobe_program = optarg;
+				break;
+			case 'T':
+				tablename = optarg;
+				break;
+			case '4':
+				h.family = AF_INET;
+				break;
+			case '6':
+				h.family = AF_INET6;
+				xtables_set_nfproto(AF_INET6);
+				break;
+		}
+	}
+
+	if (optind == argc - 1) {
+		in = fopen(argv[optind], "re");
+		if (!in) {
+			fprintf(stderr, "Can't open %s: %s\n", argv[optind],
+				strerror(errno));
+			exit(1);
+		}
+	}
+	else if (optind < argc) {
+		fprintf(stderr, "Unknown arguments found on commandline\n");
+		exit(1);
+	}
+	else in = stdin;
+
+	chain_list = nft_chain_dump(&h);
+	if (chain_list == NULL)
+		xtables_error(OTHER_PROBLEM, "cannot retrieve chain list\n");
+
+	/* Grab standard input. */
+	while (fgets(buffer, sizeof(buffer), in)) {
+		int ret = 0;
+
+		line++;
+		if (buffer[0] == '\n')
+			continue;
+		else if (buffer[0] == '#') {
+			if (verbose)
+				fputs(buffer, stdout);
+			continue;
+		} else if ((strcmp(buffer, "COMMIT\n") == 0) && (in_table)) {
+			if (!testing) {
+				/* Commit per table, although we support
+				 * global commit at once, stick by now to
+				 * the existing behaviour.
+				 */
+				DEBUGP("Calling commit\n");
+				ret = nft_commit(&h);
+			} else {
+				DEBUGP("Not calling commit, testing\n");
+				ret = nft_abort(&h);
+			}
+			in_table = 0;
+
+			/* Purge out unused chains in this table */
+			if (!testing)
+				nft_table_purge_chains(&h, curtable, chain_list);
+
+		} else if ((buffer[0] == '*') && (!in_table)) {
+			/* New table */
+			char *table;
+
+			table = strtok(buffer+1, " \t\n");
+			DEBUGP("line %u, table '%s'\n", line, table);
+			if (!table) {
+				xtables_error(PARAMETER_PROBLEM,
+					"%s: line %u table name invalid\n",
+					xt_params->program_name, line);
+				exit(1);
+			}
+			strncpy(curtable, table, XT_TABLE_MAXNAMELEN);
+			curtable[XT_TABLE_MAXNAMELEN] = '\0';
+
+			if (tablename && (strcmp(tablename, table) != 0))
+				continue;
+
+			if (noflush == 0) {
+				DEBUGP("Cleaning all chains of table '%s'\n",
+					table);
+				nft_rule_flush(&h, NULL, table);
+			}
+
+			ret = 1;
+			in_table = 1;
+
+		} else if ((buffer[0] == ':') && (in_table)) {
+			/* New chain. */
+			char *policy, *chain = NULL;
+			struct xt_counters count = {};
+
+			chain = strtok(buffer+1, " \t\n");
+			DEBUGP("line %u, chain '%s'\n", line, chain);
+			if (!chain) {
+				xtables_error(PARAMETER_PROBLEM,
+					   "%s: line %u chain name invalid\n",
+					   xt_params->program_name, line);
+				exit(1);
+			}
+
+			chain_obj = nft_chain_list_find(chain_list,
+							curtable, chain);
+			/* This chain has been found, delete from list. Later
+			 * on, unvisited chains will be purged out.
+			 */
+			if (chain_obj != NULL)
+				nft_chain_list_del(chain_obj);
+
+			if (strlen(chain) >= XT_EXTENSION_MAXNAMELEN)
+				xtables_error(PARAMETER_PROBLEM,
+					   "Invalid chain name `%s' "
+					   "(%u chars max)",
+					   chain, XT_EXTENSION_MAXNAMELEN - 1);
+
+			policy = strtok(NULL, " \t\n");
+			DEBUGP("line %u, policy '%s'\n", line, policy);
+			if (!policy) {
+				xtables_error(PARAMETER_PROBLEM,
+					   "%s: line %u policy invalid\n",
+					   xt_params->program_name, line);
+				exit(1);
+			}
+
+			if (strcmp(policy, "-") != 0) {
+				if (counters) {
+					char *ctrs;
+					ctrs = strtok(NULL, " \t\n");
+
+					if (!ctrs || !parse_counters(ctrs, &count))
+						xtables_error(PARAMETER_PROBLEM,
+							   "invalid policy counters "
+							   "for chain '%s'\n", chain);
+
+				}
+				if (nft_chain_set(&h, curtable, chain, policy, &count) < 0) {
+					xtables_error(OTHER_PROBLEM,
+						      "Can't set policy `%s'"
+						      " on `%s' line %u: %s\n",
+						      policy, chain, line,
+						      ops->strerror(errno));
+				}
+				DEBUGP("Setting policy of chain %s to %s\n",
+				       chain, policy);
+				ret = 1;
+
+			} else {
+				if (nft_chain_user_add(&h, chain, curtable) < 0) {
+					if (errno == EEXIST)
+						continue;
+
+					xtables_error(PARAMETER_PROBLEM,
+						      "cannot create chain "
+						      "'%s' (%s)\n", chain,
+						      strerror(errno));
+				}
+				continue;
+			}
+
+		} else if (in_table) {
+			int a;
+			char *ptr = buffer;
+			char *pcnt = NULL;
+			char *bcnt = NULL;
+			char *parsestart;
+
+			/* reset the newargv */
+			newargc = 0;
+
+			if (buffer[0] == '[') {
+				/* we have counters in our input */
+				ptr = strchr(buffer, ']');
+				if (!ptr)
+					xtables_error(PARAMETER_PROBLEM,
+						   "Bad line %u: need ]\n",
+						   line);
+
+				pcnt = strtok(buffer+1, ":");
+				if (!pcnt)
+					xtables_error(PARAMETER_PROBLEM,
+						   "Bad line %u: need :\n",
+						   line);
+
+				bcnt = strtok(NULL, "]");
+				if (!bcnt)
+					xtables_error(PARAMETER_PROBLEM,
+						   "Bad line %u: need ]\n",
+						   line);
+
+				/* start command parsing after counter */
+				parsestart = ptr + 1;
+			} else {
+				/* start command parsing at start of line */
+				parsestart = buffer;
+			}
+
+			add_argv(argv[0]);
+			add_argv("-t");
+			add_argv(curtable);
+
+			if (counters && pcnt && bcnt) {
+				add_argv("--set-counters");
+				add_argv((char *) pcnt);
+				add_argv((char *) bcnt);
+			}
+
+			add_param_to_argv(parsestart);
+
+			DEBUGP("calling do_command4(%u, argv, &%s, handle):\n",
+				newargc, curtable);
+
+			for (a = 0; a < newargc; a++)
+				DEBUGP("argv[%u]: %s\n", a, newargv[a]);
+
+			ret = do_commandx(&h, newargc, newargv,
+					  &newargv[2], true);
+			if (ret < 0) {
+				ret = nft_abort(&h);
+				if (ret < 0) {
+					fprintf(stderr, "failed to abort "
+							"commit operation\n");
+				}
+				exit(1);
+			}
+
+			free_argv();
+			fflush(stdout);
+		}
+		if (tablename && (strcmp(tablename, curtable) != 0))
+			continue;
+		if (!ret) {
+			fprintf(stderr, "%s: line %u failed\n",
+					xt_params->program_name, line);
+			exit(1);
+		}
+	}
+	if (in_table) {
+		fprintf(stderr, "%s: COMMIT expected at line %u\n",
+				xt_params->program_name, line + 1);
+		exit(1);
+	}
+
+	fclose(in);
+	return 0;
+}
+
+int xtables_ip4_restore_main(int argc, char *argv[])
+{
+	return xtables_restore_main(NFPROTO_IPV4, "iptables-restore",
+				    argc, argv);
+}
+
+int xtables_ip6_restore_main(int argc, char *argv[])
+{
+	return xtables_restore_main(NFPROTO_IPV6, "ip6tables-restore",
+				    argc, argv);
+}
diff --git a/iptables/xtables-save.c b/iptables/xtables-save.c
new file mode 100644
index 0000000..42d2907
--- /dev/null
+++ b/iptables/xtables-save.c
@@ -0,0 +1,154 @@
+/* Code to save the xtables state, in human readable-form. */
+/* (C) 1999 by Paul 'Rusty' Russell <rusty@rustcorp.com.au> and
+ * (C) 2000-2002 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * This code is distributed under the terms of GNU GPL v2
+ *
+ */
+#include <getopt.h>
+#include <sys/errno.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <netdb.h>
+#include "libiptc/libiptc.h"
+#include "iptables.h"
+#include "xtables-multi.h"
+#include "nft.h"
+
+#include <libnftnl/chain.h>
+
+#ifndef NO_SHARED_LIBS
+#include <dlfcn.h>
+#endif
+
+static bool show_counters = false;
+
+static const struct option options[] = {
+	{.name = "counters", .has_arg = false, .val = 'c'},
+	{.name = "dump",     .has_arg = false, .val = 'd'},
+	{.name = "table",    .has_arg = true,  .val = 't'},
+	{.name = "modprobe", .has_arg = true,  .val = 'M'},
+	{.name = "ipv4",     .has_arg = false, .val = '4'},
+	{.name = "ipv6",     .has_arg = false, .val = '6'},
+	{NULL},
+};
+
+static int
+do_output(struct nft_handle *h, const char *tablename, bool counters)
+{
+	struct nft_chain_list *chain_list;
+
+	if (!tablename)
+		return nft_for_each_table(h, do_output, counters);
+
+	if (!nft_table_find(h, tablename)) {
+		printf("Table `%s' does not exist\n", tablename);
+		return 0;
+	}
+
+	chain_list = nft_chain_dump(h);
+
+	time_t now = time(NULL);
+
+	printf("# Generated by xtables-save v%s on %s",
+	       IPTABLES_VERSION, ctime(&now));
+	printf("*%s\n", tablename);
+
+	/* Dump out chain names first,
+	 * thereby preventing dependency conflicts */
+	nft_chain_save(h, chain_list, tablename);
+	nft_rule_save(h, tablename, counters);
+
+	now = time(NULL);
+	printf("COMMIT\n");
+	printf("# Completed on %s", ctime(&now));
+
+	return 1;
+}
+
+/* Format:
+ * :Chain name POLICY packets bytes
+ * rule
+ */
+static int
+xtables_save_main(int family, const char *progname, int argc, char *argv[])
+{
+	const char *tablename = NULL;
+	bool dump = false;
+	struct nft_handle h = {
+		.family	= family,
+	};
+	int c;
+
+	xtables_globals.program_name = progname;
+	c = xtables_init_all(&xtables_globals, family);
+	if (c < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
+				xtables_globals.program_name,
+				xtables_globals.program_version);
+		exit(1);
+	}
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+	init_extensions();
+	init_extensions4();
+#endif
+	if (nft_init(&h, xtables_ipv4) < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize nft: %s\n",
+				xtables_globals.program_name,
+				xtables_globals.program_version,
+				strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+
+	while ((c = getopt_long(argc, argv, "bcdt:46", options, NULL)) != -1) {
+		switch (c) {
+		case 'c':
+			show_counters = true;
+			break;
+
+		case 't':
+			/* Select specific table. */
+			tablename = optarg;
+			break;
+		case 'M':
+			xtables_modprobe_program = optarg;
+			break;
+		case 'd':
+			dump = true;
+			break;
+		case '4':
+			h.family = AF_INET;
+			break;
+		case '6':
+			h.family = AF_INET6;
+			xtables_set_nfproto(AF_INET6);
+			break;
+		}
+	}
+
+	if (optind < argc) {
+		fprintf(stderr, "Unknown arguments found on commandline\n");
+		exit(1);
+	}
+
+	if (dump) {
+		do_output(&h, tablename, show_counters);
+		exit(0);
+	}
+
+	return !do_output(&h, tablename, show_counters);
+}
+
+int xtables_ip4_save_main(int argc, char *argv[])
+{
+	return xtables_save_main(NFPROTO_IPV4, "iptables-save", argc, argv);
+}
+
+int xtables_ip6_save_main(int argc, char *argv[])
+{
+	return xtables_save_main(NFPROTO_IPV6, "ip6tables-save", argc, argv);
+}
diff --git a/iptables/xtables-standalone.c b/iptables/xtables-standalone.c
new file mode 100644
index 0000000..355a446
--- /dev/null
+++ b/iptables/xtables-standalone.c
@@ -0,0 +1,104 @@
+/*
+ * Author: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au
+ *
+ * Based on the ipchains code by Paul Russell and Michael Neuling
+ *
+ * (C) 2000-2002 by the netfilter coreteam <coreteam@netfilter.org>:
+ * 		    Paul 'Rusty' Russell <rusty@rustcorp.com.au>
+ * 		    Marc Boucher <marc+nf@mbsi.ca>
+ * 		    James Morris <jmorris@intercode.com.au>
+ * 		    Harald Welte <laforge@gnumonks.org>
+ * 		    Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ *
+ *	iptables -- IP firewall administration for kernels with
+ *	firewall table (aimed for the 2.3 kernels)
+ *
+ *	See the accompanying manual page iptables(8) for information
+ *	about proper usage of this program.
+ *
+ *	This program is free software; you can redistribute it and/or modify
+ *	it under the terms of the GNU General Public License as published by
+ *	the Free Software Foundation; either version 2 of the License, or
+ *	(at your option) any later version.
+ *
+ *	This program is distributed in the hope that it will be useful,
+ *	but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *	GNU General Public License for more details.
+ *
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program; if not, write to the Free Software
+ *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <iptables.h>
+#include "xtables-multi.h"
+#include "nft.h"
+
+static int
+xtables_main(int family, const char *progname, int argc, char *argv[])
+{
+	int ret;
+	char *table = "filter";
+	struct nft_handle h = {
+		.family = family,
+	};
+
+	xtables_globals.program_name = progname;
+	ret = xtables_init_all(&xtables_globals, family);
+	if (ret < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
+				xtables_globals.program_name,
+				xtables_globals.program_version);
+				exit(1);
+	}
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+	init_extensions();
+	init_extensions4();
+#endif
+
+	if (nft_init(&h, xtables_ipv4) < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize nft: %s\n",
+				xtables_globals.program_name,
+				xtables_globals.program_version,
+				strerror(errno));
+		nft_fini(&h);
+		exit(EXIT_FAILURE);
+	}
+
+	ret = do_commandx(&h, argc, argv, &table, false);
+	if (ret)
+		ret = nft_commit(&h);
+
+	nft_fini(&h);
+
+	if (!ret) {
+		if (errno == EINVAL) {
+			fprintf(stderr, "iptables: %s. "
+					"Run `dmesg' for more information.\n",
+				nft_strerror(errno));
+		} else {
+			fprintf(stderr, "iptables: %s.\n",
+				nft_strerror(errno));
+		}
+		if (errno == EAGAIN) {
+			exit(RESOURCE_PROBLEM);
+		}
+	}
+
+	exit(!ret);
+}
+
+int xtables_ip4_main(int argc, char *argv[])
+{
+	return xtables_main(NFPROTO_IPV4, "iptables", argc, argv);
+}
+
+int xtables_ip6_main(int argc, char *argv[])
+{
+	return xtables_main(NFPROTO_IPV6, "ip6tables", argc, argv);
+}
diff --git a/iptables/xtables.c b/iptables/xtables.c
new file mode 100644
index 0000000..45a5ac6
--- /dev/null
+++ b/iptables/xtables.c
@@ -0,0 +1,1261 @@
+/* Code to take an iptables-style command line and do it. */
+
+/*
+ * Author: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au
+ *
+ * (C) 2000-2002 by the netfilter coreteam <coreteam@netfilter.org>:
+ *		    Paul 'Rusty' Russell <rusty@rustcorp.com.au>
+ *		    Marc Boucher <marc+nf@mbsi.ca>
+ *		    James Morris <jmorris@intercode.com.au>
+ *		    Harald Welte <laforge@gnumonks.org>
+ *		    Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ *
+ *	This program is free software; you can redistribute it and/or modify
+ *	it under the terms of the GNU General Public License as published by
+ *	the Free Software Foundation; either version 2 of the License, or
+ *	(at your option) any later version.
+ *
+ *	This program is distributed in the hope that it will be useful,
+ *	but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *	GNU General Public License for more details.
+ *
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program; if not, write to the Free Software
+ *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <getopt.h>
+#include <string.h>
+#include <netdb.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <unistd.h>
+#include <iptables.h>
+#include <xtables.h>
+#include <fcntl.h>
+#include "xshared.h"
+#include "nft-shared.h"
+#include "nft.h"
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#define NUMBER_OF_CMD	16
+static const char cmdflags[] = { 'I', 'D', 'D', 'R', 'A', 'L', 'F', 'Z',
+				 'N', 'X', 'P', 'E', 'S', 'Z', 'C' };
+
+#define OPT_FRAGMENT	0x00800U
+#define NUMBER_OF_OPT	ARRAY_SIZE(optflags)
+static const char optflags[]
+= { 'n', 's', 'd', 'p', 'j', 'v', 'x', 'i', 'o', '0', 'c', 'f'};
+
+static struct option original_opts[] = {
+	{.name = "append",	  .has_arg = 1, .val = 'A'},
+	{.name = "delete",	  .has_arg = 1, .val = 'D'},
+	{.name = "check",	  .has_arg = 1, .val = 'C'},
+	{.name = "insert",	  .has_arg = 1, .val = 'I'},
+	{.name = "replace",	  .has_arg = 1, .val = 'R'},
+	{.name = "list",	  .has_arg = 2, .val = 'L'},
+	{.name = "list-rules",	  .has_arg = 2, .val = 'S'},
+	{.name = "flush",	  .has_arg = 2, .val = 'F'},
+	{.name = "zero",	  .has_arg = 2, .val = 'Z'},
+	{.name = "new-chain",	  .has_arg = 1, .val = 'N'},
+	{.name = "delete-chain",  .has_arg = 2, .val = 'X'},
+	{.name = "rename-chain",  .has_arg = 1, .val = 'E'},
+	{.name = "policy",	  .has_arg = 1, .val = 'P'},
+	{.name = "source",	  .has_arg = 1, .val = 's'},
+	{.name = "destination",   .has_arg = 1, .val = 'd'},
+	{.name = "src",		  .has_arg = 1, .val = 's'}, /* synonym */
+	{.name = "dst",		  .has_arg = 1, .val = 'd'}, /* synonym */
+	{.name = "protocol",	  .has_arg = 1, .val = 'p'},
+	{.name = "in-interface",  .has_arg = 1, .val = 'i'},
+	{.name = "jump",	  .has_arg = 1, .val = 'j'},
+	{.name = "table",	  .has_arg = 1, .val = 't'},
+	{.name = "match",	  .has_arg = 1, .val = 'm'},
+	{.name = "numeric",	  .has_arg = 0, .val = 'n'},
+	{.name = "out-interface", .has_arg = 1, .val = 'o'},
+	{.name = "verbose",	  .has_arg = 0, .val = 'v'},
+	{.name = "exact",	  .has_arg = 0, .val = 'x'},
+	{.name = "fragments",	  .has_arg = 0, .val = 'f'},
+	{.name = "version",	  .has_arg = 0, .val = 'V'},
+	{.name = "help",	  .has_arg = 2, .val = 'h'},
+	{.name = "line-numbers",  .has_arg = 0, .val = '0'},
+	{.name = "modprobe",	  .has_arg = 1, .val = 'M'},
+	{.name = "set-counters",  .has_arg = 1, .val = 'c'},
+	{.name = "goto",	  .has_arg = 1, .val = 'g'},
+	{.name = "ipv4",	  .has_arg = 0, .val = '4'},
+	{.name = "ipv6",	  .has_arg = 0, .val = '6'},
+	{NULL},
+};
+
+void xtables_exit_error(enum xtables_exittype status, const char *msg, ...) __attribute__((noreturn, format(printf,2,3)));
+
+struct xtables_globals xtables_globals = {
+	.option_offset = 0,
+	.program_version = IPTABLES_VERSION,
+	.orig_opts = original_opts,
+	.exit_err = xtables_exit_error,
+	.compat_rev = nft_compatible_revision,
+};
+
+/* Table of legal combinations of commands and options.  If any of the
+ * given commands make an option legal, that option is legal (applies to
+ * CMD_LIST and CMD_ZERO only).
+ * Key:
+ *  +  compulsory
+ *  x  illegal
+ *     optional
+ */
+
+static const char commands_v_options[NUMBER_OF_CMD][NUMBER_OF_OPT] =
+/* Well, it's better than "Re: Linux vs FreeBSD" */
+{
+	/*     -n  -s  -d  -p  -j  -v  -x  -i  -o --line -c -f */
+/*INSERT*/    {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' ',' '},
+/*DELETE*/    {'x',' ',' ',' ',' ',' ','x',' ',' ','x','x',' '},
+/*DELETE_NUM*/{'x','x','x','x','x',' ','x','x','x','x','x','x'},
+/*REPLACE*/   {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' ',' '},
+/*APPEND*/    {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' ',' '},
+/*LIST*/      {' ','x','x','x','x',' ',' ','x','x',' ','x','x'},
+/*FLUSH*/     {'x','x','x','x','x',' ','x','x','x','x','x','x'},
+/*ZERO*/      {'x','x','x','x','x',' ','x','x','x','x','x','x'},
+/*ZERO_NUM*/  {'x','x','x','x','x',' ','x','x','x','x','x','x'},
+/*NEW_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x','x','x'},
+/*DEL_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x','x','x'},
+/*SET_POLICY*/{'x','x','x','x','x',' ','x','x','x','x',' ','x'},
+/*RENAME*/    {'x','x','x','x','x',' ','x','x','x','x','x','x'},
+/*LIST_RULES*/{'x','x','x','x','x',' ','x','x','x','x','x','x'},
+/*CHECK*/     {'x',' ',' ',' ',' ',' ','x',' ',' ','x','x',' '},
+};
+
+static const int inverse_for_options[NUMBER_OF_OPT] =
+{
+/* -n */ 0,
+/* -s */ IPT_INV_SRCIP,
+/* -d */ IPT_INV_DSTIP,
+/* -p */ XT_INV_PROTO,
+/* -j */ 0,
+/* -v */ 0,
+/* -x */ 0,
+/* -i */ IPT_INV_VIA_IN,
+/* -o */ IPT_INV_VIA_OUT,
+/*--line*/ 0,
+/* -c */ 0,
+/* -f */ IPT_INV_FRAG,
+};
+
+#define opts xtables_globals.opts
+#define prog_name xtables_globals.program_name
+#define prog_vers xtables_globals.program_version
+
+static void __attribute__((noreturn))
+exit_tryhelp(int status)
+{
+	if (line != -1)
+		fprintf(stderr, "Error occurred at line: %d\n", line);
+	fprintf(stderr, "Try `%s -h' or '%s --help' for more information.\n",
+			prog_name, prog_name);
+	xtables_free_opts(1);
+	exit(status);
+}
+
+static void
+exit_printhelp(const struct xtables_rule_match *matches)
+{
+	printf("%s v%s\n\n"
+"Usage: %s -[ACD] chain rule-specification [options]\n"
+"	%s -I chain [rulenum] rule-specification [options]\n"
+"	%s -R chain rulenum rule-specification [options]\n"
+"	%s -D chain rulenum [options]\n"
+"	%s -[LS] [chain [rulenum]] [options]\n"
+"	%s -[FZ] [chain] [options]\n"
+"	%s -[NX] chain\n"
+"	%s -E old-chain-name new-chain-name\n"
+"	%s -P chain target [options]\n"
+"	%s -h (print this help information)\n\n",
+	       prog_name, prog_vers, prog_name, prog_name,
+	       prog_name, prog_name, prog_name, prog_name,
+	       prog_name, prog_name, prog_name, prog_name);
+
+	printf(
+"Commands:\n"
+"Either long or short options are allowed.\n"
+"  --append  -A chain		Append to chain\n"
+"  --check   -C chain		Check for the existence of a rule\n"
+"  --delete  -D chain		Delete matching rule from chain\n"
+"  --delete  -D chain rulenum\n"
+"				Delete rule rulenum (1 = first) from chain\n"
+"  --insert  -I chain [rulenum]\n"
+"				Insert in chain as rulenum (default 1=first)\n"
+"  --replace -R chain rulenum\n"
+"				Replace rule rulenum (1 = first) in chain\n"
+"  --list    -L [chain [rulenum]]\n"
+"				List the rules in a chain or all chains\n"
+"  --list-rules -S [chain [rulenum]]\n"
+"				Print the rules in a chain or all chains\n"
+"  --flush   -F [chain]		Delete all rules in  chain or all chains\n"
+"  --zero    -Z [chain [rulenum]]\n"
+"				Zero counters in chain or all chains\n"
+"  --new     -N chain		Create a new user-defined chain\n"
+"  --delete-chain\n"
+"	     -X [chain]		Delete a user-defined chain\n"
+"  --policy  -P chain target\n"
+"				Change policy on chain to target\n"
+"  --rename-chain\n"
+"	     -E old-chain new-chain\n"
+"				Change chain name, (moving any references)\n"
+
+"Options:\n"
+"    --ipv4	-4		Nothing (line is ignored by ip6tables-restore)\n"
+"    --ipv6	-6		Error (line is ignored by iptables-restore)\n"
+"[!] --proto	-p proto	protocol: by number or name, eg. `tcp'\n"
+"[!] --source	-s address[/mask][...]\n"
+"				source specification\n"
+"[!] --destination -d address[/mask][...]\n"
+"				destination specification\n"
+"[!] --in-interface -i input name[+]\n"
+"				network interface name ([+] for wildcard)\n"
+" --jump	-j target\n"
+"				target for rule (may load target extension)\n"
+#ifdef IPT_F_GOTO
+"  --goto      -g chain\n"
+"			       jump to chain with no return\n"
+#endif
+"  --match	-m match\n"
+"				extended match (may load extension)\n"
+"  --numeric	-n		numeric output of addresses and ports\n"
+"[!] --out-interface -o output name[+]\n"
+"				network interface name ([+] for wildcard)\n"
+"  --table	-t table	table to manipulate (default: `filter')\n"
+"  --verbose	-v		verbose mode\n"
+"  --line-numbers		print line numbers when listing\n"
+"  --exact	-x		expand numbers (display exact values)\n"
+"[!] --fragment	-f		match second or further fragments only\n"
+"  --modprobe=<command>		try to insert modules using this command\n"
+"  --set-counters PKTS BYTES	set the counter during insert/append\n"
+"[!] --version	-V		print package version.\n");
+
+	print_extension_helps(xtables_targets, matches);
+	exit(0);
+}
+
+void
+xtables_exit_error(enum xtables_exittype status, const char *msg, ...)
+{
+	va_list args;
+
+	va_start(args, msg);
+	fprintf(stderr, "%s v%s: ", prog_name, prog_vers);
+	vfprintf(stderr, msg, args);
+	va_end(args);
+	fprintf(stderr, "\n");
+	if (status == PARAMETER_PROBLEM)
+		exit_tryhelp(status);
+	if (status == VERSION_PROBLEM)
+		fprintf(stderr,
+			"Perhaps iptables or your kernel needs to be upgraded.\n");
+	/* On error paths, make sure that we don't leak memory */
+	xtables_free_opts(1);
+	exit(status);
+}
+
+static void
+generic_opt_check(int command, int options)
+{
+	int i, j, legal = 0;
+
+	/* Check that commands are valid with options.	Complicated by the
+	 * fact that if an option is legal with *any* command given, it is
+	 * legal overall (ie. -z and -l).
+	 */
+	for (i = 0; i < NUMBER_OF_OPT; i++) {
+		legal = 0; /* -1 => illegal, 1 => legal, 0 => undecided. */
+
+		for (j = 0; j < NUMBER_OF_CMD; j++) {
+			if (!(command & (1<<j)))
+				continue;
+
+			if (!(options & (1<<i))) {
+				if (commands_v_options[j][i] == '+')
+					xtables_error(PARAMETER_PROBLEM,
+						   "You need to supply the `-%c' "
+						   "option for this command\n",
+						   optflags[i]);
+			} else {
+				if (commands_v_options[j][i] != 'x')
+					legal = 1;
+				else if (legal == 0)
+					legal = -1;
+			}
+		}
+		if (legal == -1)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Illegal option `-%c' with this command\n",
+				   optflags[i]);
+	}
+}
+
+static char
+opt2char(int option)
+{
+	const char *ptr;
+	for (ptr = optflags; option > 1; option >>= 1, ptr++);
+
+	return *ptr;
+}
+
+static char
+cmd2char(int option)
+{
+	const char *ptr;
+	for (ptr = cmdflags; option > 1; option >>= 1, ptr++);
+
+	return *ptr;
+}
+
+static void
+add_command(unsigned int *cmd, const int newcmd, const int othercmds,
+	    int invert)
+{
+	if (invert)
+		xtables_error(PARAMETER_PROBLEM, "unexpected ! flag");
+	if (*cmd & (~othercmds))
+		xtables_error(PARAMETER_PROBLEM, "Cannot use -%c with -%c\n",
+			   cmd2char(newcmd), cmd2char(*cmd & (~othercmds)));
+	*cmd |= newcmd;
+}
+
+/*
+ *	All functions starting with "parse" should succeed, otherwise
+ *	the program fails.
+ *	Most routines return pointers to static data that may change
+ *	between calls to the same or other routines with a few exceptions:
+ *	"host_to_addr", "parse_hostnetwork", and "parse_hostnetworkmask"
+ *	return global static data.
+*/
+
+/* Christophe Burki wants `-p 6' to imply `-m tcp'.  */
+/* Can't be zero. */
+static int
+parse_rulenumber(const char *rule)
+{
+	unsigned int rulenum;
+
+	if (!xtables_strtoui(rule, NULL, &rulenum, 1, INT_MAX))
+		xtables_error(PARAMETER_PROBLEM,
+			   "Invalid rule number `%s'", rule);
+
+	return rulenum;
+}
+
+static const char *
+parse_target(const char *targetname)
+{
+	const char *ptr;
+
+	if (strlen(targetname) < 1)
+		xtables_error(PARAMETER_PROBLEM,
+			   "Invalid target name (too short)");
+
+	if (strlen(targetname) >= XT_EXTENSION_MAXNAMELEN)
+		xtables_error(PARAMETER_PROBLEM,
+			   "Invalid target name `%s' (%u chars max)",
+			   targetname, XT_EXTENSION_MAXNAMELEN - 1);
+
+	for (ptr = targetname; *ptr; ptr++)
+		if (isspace(*ptr))
+			xtables_error(PARAMETER_PROBLEM,
+				   "Invalid target name `%s'", targetname);
+	return targetname;
+}
+
+static void
+set_option(unsigned int *options, unsigned int option, uint8_t *invflg,
+	   int invert)
+{
+	if (*options & option)
+		xtables_error(PARAMETER_PROBLEM, "multiple -%c flags not allowed",
+			   opt2char(option));
+	*options |= option;
+
+	if (invert) {
+		unsigned int i;
+		for (i = 0; 1 << i != option; i++);
+
+		if (!inverse_for_options[i])
+			xtables_error(PARAMETER_PROBLEM,
+				   "cannot have ! before -%c",
+				   opt2char(option));
+		*invflg |= inverse_for_options[i];
+	}
+}
+
+static int
+add_entry(const char *chain,
+	  const char *table,
+	  struct iptables_command_state *cs,
+	  int rulenum, 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 < 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;
+
+				if (append) {
+					ret = nft_rule_append(h, chain, table,
+							      cs, 0,
+							      verbose);
+				} else {
+					ret = nft_rule_insert(h, chain, table,
+							      cs, rulenum,
+							      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));
+				if (append) {
+					ret = nft_rule_append(h, chain, table,
+							      cs, 0,
+							      verbose);
+				} else {
+					ret = nft_rule_insert(h, chain, table,
+							      cs, rulenum,
+							      verbose);
+				}
+			}
+		}
+	}
+
+	return ret;
+}
+
+static int
+replace_entry(const char *chain, const char *table,
+	      struct iptables_command_state *cs,
+	      unsigned int rulenum,
+	      int family,
+	      const struct addr_mask s,
+	      const struct addr_mask d,
+	      bool verbose, struct nft_handle *h)
+{
+	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);
+}
+
+static int
+delete_entry(const char *chain, const char *table,
+	     struct iptables_command_state *cs,
+	     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 < 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);
+			}
+		}
+	}
+
+	return ret;
+}
+
+static int
+check_entry(const char *chain, const char *table,
+	    struct iptables_command_state *cs,
+	    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 < 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);
+			}
+		}
+	}
+
+	return ret;
+}
+
+static int
+list_entries(struct nft_handle *h, const char *chain, const char *table,
+	     int rulenum, int verbose, int numeric, int expanded,
+	     int linenumbers)
+{
+	unsigned int format;
+
+	format = FMT_OPTIONS;
+	if (!verbose)
+		format |= FMT_NOCOUNTS;
+	else
+		format |= FMT_VIA;
+
+	if (numeric)
+		format |= FMT_NUMERIC;
+
+	if (!expanded)
+		format |= FMT_KILOMEGAGIGA;
+
+	if (linenumbers)
+		format |= FMT_LINENUMBERS;
+
+	return nft_rule_list(h, chain, table, rulenum, format);
+}
+
+static int
+list_rules(struct nft_handle *h, const char *chain, const char *table,
+	   int rulenum, int counters)
+{
+	if (counters)
+	    counters = -1;		/* iptables -c format */
+
+	nft_rule_list_save(h, chain, table, rulenum, counters);
+
+	/* iptables does not return error if rule number not found */
+	return 1;
+}
+
+static void command_jump(struct iptables_command_state *cs)
+{
+	size_t size;
+
+	set_option(&cs->options, OPT_JUMP, &cs->fw.ip.invflags, cs->invert);
+	cs->jumpto = parse_target(optarg);
+	/* TRY_LOAD (may be chain name) */
+	cs->target = xtables_find_target(cs->jumpto, XTF_TRY_LOAD);
+
+	if (cs->target == NULL)
+		return;
+
+	size = XT_ALIGN(sizeof(struct xt_entry_target))
+		+ cs->target->size;
+
+	cs->target->t = xtables_calloc(1, size);
+	cs->target->t->u.target_size = size;
+	if (cs->target->real_name == NULL) {
+		strcpy(cs->target->t->u.user.name, cs->jumpto);
+	} else {
+		/* Alias support for userspace side */
+		strcpy(cs->target->t->u.user.name, cs->target->real_name);
+		if (!(cs->target->ext_flags & XTABLES_EXT_ALIAS))
+			fprintf(stderr, "Notice: The %s target is converted into %s target "
+				"in rule listing and saving.\n",
+				cs->jumpto, cs->target->real_name);
+	}
+	cs->target->t->u.user.revision = cs->target->revision;
+	xs_init_target(cs->target);
+
+	if (cs->target->x6_options != NULL)
+		opts = xtables_options_xfrm(xtables_globals.orig_opts, opts,
+					    cs->target->x6_options,
+					    &cs->target->option_offset);
+	else
+		opts = xtables_merge_options(xtables_globals.orig_opts, opts,
+					     cs->target->extra_opts,
+					     &cs->target->option_offset);
+	if (opts == NULL)
+		xtables_error(OTHER_PROBLEM, "can't alloc memory!");
+}
+
+static void command_match(struct iptables_command_state *cs)
+{
+	struct xtables_match *m;
+	size_t size;
+
+	if (cs->invert)
+		xtables_error(PARAMETER_PROBLEM,
+			   "unexpected ! flag before --match");
+
+	m = xtables_find_match(optarg, XTF_LOAD_MUST_SUCCEED, &cs->matches);
+	size = XT_ALIGN(sizeof(struct xt_entry_match)) + m->size;
+	m->m = xtables_calloc(1, size);
+	m->m->u.match_size = size;
+	if (m->real_name == NULL) {
+		strcpy(m->m->u.user.name, m->name);
+	} else {
+		strcpy(m->m->u.user.name, m->real_name);
+		if (!(m->ext_flags & XTABLES_EXT_ALIAS))
+			fprintf(stderr, "Notice: the %s match is converted into %s match "
+				"in rule listing and saving.\n", m->name, m->real_name);
+	}
+	m->m->u.user.revision = m->revision;
+	xs_init_match(m);
+	if (m == m->next)
+		return;
+	/* Merge options for non-cloned matches */
+	if (m->x6_options != NULL)
+		opts = xtables_options_xfrm(xtables_globals.orig_opts, opts,
+					    m->x6_options, &m->option_offset);
+	else if (m->extra_opts != NULL)
+		opts = xtables_merge_options(xtables_globals.orig_opts, opts,
+					     m->extra_opts, &m->option_offset);
+	if (opts == NULL)
+		xtables_error(OTHER_PROBLEM, "can't alloc memory!");
+}
+
+int do_commandx(struct nft_handle *h, int argc, char *argv[], char **table,
+		bool restore)
+{
+	struct iptables_command_state cs;
+	int verbose = 0;
+	const char *chain = NULL;
+	const char *policy = NULL, *newname = NULL;
+	unsigned int rulenum = 0, command = 0;
+	int ret = 1;
+	struct xtables_match *m;
+	struct xtables_rule_match *matchp;
+	struct xtables_target *t;
+	struct xtables_args args = {
+		.family	= h->family,
+	};
+
+	memset(&cs, 0, sizeof(cs));
+	cs.jumpto = "";
+	cs.argv = argv;
+
+	/* re-set optind to 0 in case do_command4 gets called
+	 * a second time */
+	optind = 0;
+
+	/* clear mflags in case do_command4 gets called a second time
+	 * (we clear the global list of all matches for security)*/
+	for (m = xtables_matches; m; m = m->next)
+		m->mflags = 0;
+
+	for (t = xtables_targets; t; t = t->next) {
+		t->tflags = 0;
+		t->used = 0;
+	}
+
+	/* Suppress error messages: we may add new options if we
+	   demand-load a protocol. */
+	opterr = 0;
+
+	h->ops = nft_family_ops_lookup(h->family);
+	if (h->ops == NULL)
+		xtables_error(PARAMETER_PROBLEM, "Unknown family");
+
+	opts = xt_params->orig_opts;
+	while ((cs.c = getopt_long(argc, argv,
+	   "-:A:C:D:R:I:L::S::M:F::Z::N:X::E:P:Vh::o:p:s:d:j:i:fbvnt:m:xc:g:46",
+					   opts, NULL)) != -1) {
+		switch (cs.c) {
+			/*
+			 * Command selection
+			 */
+		case 'A':
+			add_command(&command, CMD_APPEND, CMD_NONE,
+				    cs.invert);
+			chain = optarg;
+			break;
+
+		case 'C':
+			add_command(&command, CMD_CHECK, CMD_NONE,
+				    cs.invert);
+			chain = optarg;
+			break;
+
+		case 'D':
+			add_command(&command, CMD_DELETE, CMD_NONE,
+				    cs.invert);
+			chain = optarg;
+			if (optind < argc && argv[optind][0] != '-'
+			    && argv[optind][0] != '!') {
+				rulenum = parse_rulenumber(argv[optind++]);
+				command = CMD_DELETE_NUM;
+			}
+			break;
+
+		case 'R':
+			add_command(&command, CMD_REPLACE, CMD_NONE,
+				    cs.invert);
+			chain = optarg;
+			if (optind < argc && argv[optind][0] != '-'
+			    && argv[optind][0] != '!')
+				rulenum = parse_rulenumber(argv[optind++]);
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					   "-%c requires a rule number",
+					   cmd2char(CMD_REPLACE));
+			break;
+
+		case 'I':
+			add_command(&command, CMD_INSERT, CMD_NONE,
+				    cs.invert);
+			chain = optarg;
+			if (optind < argc && argv[optind][0] != '-'
+			    && argv[optind][0] != '!')
+				rulenum = parse_rulenumber(argv[optind++]);
+			else rulenum = 1;
+			break;
+
+		case 'L':
+			add_command(&command, CMD_LIST,
+				    CMD_ZERO | CMD_ZERO_NUM, cs.invert);
+			if (optarg) chain = optarg;
+			else if (optind < argc && argv[optind][0] != '-'
+				 && argv[optind][0] != '!')
+				chain = argv[optind++];
+			if (optind < argc && argv[optind][0] != '-'
+			    && argv[optind][0] != '!')
+				rulenum = parse_rulenumber(argv[optind++]);
+			break;
+
+		case 'S':
+			add_command(&command, CMD_LIST_RULES,
+				    CMD_ZERO|CMD_ZERO_NUM, cs.invert);
+			if (optarg) chain = optarg;
+			else if (optind < argc && argv[optind][0] != '-'
+				 && argv[optind][0] != '!')
+				chain = argv[optind++];
+			if (optind < argc && argv[optind][0] != '-'
+			    && argv[optind][0] != '!')
+				rulenum = parse_rulenumber(argv[optind++]);
+			break;
+
+		case 'F':
+			add_command(&command, CMD_FLUSH, CMD_NONE,
+				    cs.invert);
+			if (optarg) chain = optarg;
+			else if (optind < argc && argv[optind][0] != '-'
+				 && argv[optind][0] != '!')
+				chain = argv[optind++];
+			break;
+
+		case 'Z':
+			add_command(&command, CMD_ZERO, CMD_LIST|CMD_LIST_RULES,
+				    cs.invert);
+			if (optarg) chain = optarg;
+			else if (optind < argc && argv[optind][0] != '-'
+				&& argv[optind][0] != '!')
+				chain = argv[optind++];
+			if (optind < argc && argv[optind][0] != '-'
+				&& argv[optind][0] != '!') {
+				rulenum = parse_rulenumber(argv[optind++]);
+				command = CMD_ZERO_NUM;
+			}
+			break;
+
+		case 'N':
+			if (optarg && (*optarg == '-' || *optarg == '!'))
+				xtables_error(PARAMETER_PROBLEM,
+					   "chain name not allowed to start "
+					   "with `%c'\n", *optarg);
+			if (xtables_find_target(optarg, XTF_TRY_LOAD))
+				xtables_error(PARAMETER_PROBLEM,
+					   "chain name may not clash "
+					   "with target name\n");
+			add_command(&command, CMD_NEW_CHAIN, CMD_NONE,
+				    cs.invert);
+			chain = optarg;
+			break;
+
+		case 'X':
+			add_command(&command, CMD_DELETE_CHAIN, CMD_NONE,
+				    cs.invert);
+			if (optarg) chain = optarg;
+			else if (optind < argc && argv[optind][0] != '-'
+				 && argv[optind][0] != '!')
+				chain = argv[optind++];
+			break;
+
+		case 'E':
+			add_command(&command, CMD_RENAME_CHAIN, CMD_NONE,
+				    cs.invert);
+			chain = optarg;
+			if (optind < argc && argv[optind][0] != '-'
+			    && argv[optind][0] != '!')
+				newname = argv[optind++];
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					   "-%c requires old-chain-name and "
+					   "new-chain-name",
+					    cmd2char(CMD_RENAME_CHAIN));
+			break;
+
+		case 'P':
+			add_command(&command, CMD_SET_POLICY, CMD_NONE,
+				    cs.invert);
+			chain = optarg;
+			if (optind < argc && argv[optind][0] != '-'
+			    && argv[optind][0] != '!')
+				policy = argv[optind++];
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					   "-%c requires a chain and a policy",
+					   cmd2char(CMD_SET_POLICY));
+			break;
+
+		case 'h':
+			if (!optarg)
+				optarg = argv[optind];
+
+			/* iptables -p icmp -h */
+			if (!cs.matches && cs.protocol)
+				xtables_find_match(cs.protocol,
+					XTF_TRY_LOAD, &cs.matches);
+
+			exit_printhelp(cs.matches);
+
+			/*
+			 * Option selection
+			 */
+		case 'p':
+			set_option(&cs.options, OPT_PROTOCOL, &args.invflags,
+				   cs.invert);
+
+			/* Canonicalize into lower case */
+			for (cs.protocol = optarg; *cs.protocol; cs.protocol++)
+				*cs.protocol = tolower(*cs.protocol);
+
+			cs.protocol = optarg;
+			args.proto = xtables_parse_protocol(cs.protocol);
+
+			if (args.proto == 0 && (args.invflags & XT_INV_PROTO))
+				xtables_error(PARAMETER_PROBLEM,
+					   "rule would never match protocol");
+
+			/* This needs to happen here to parse extensions */
+			h->ops->proto_parse(&cs, &args);
+			break;
+
+		case 's':
+			set_option(&cs.options, OPT_SOURCE, &args.invflags,
+				   cs.invert);
+			args.shostnetworkmask = optarg;
+			break;
+
+		case 'd':
+			set_option(&cs.options, OPT_DESTINATION,
+				   &args.invflags, cs.invert);
+			args.dhostnetworkmask = optarg;
+			break;
+
+#ifdef IPT_F_GOTO
+		case 'g':
+			set_option(&cs.options, OPT_JUMP, &args.invflags,
+				   cs.invert);
+			args.goto_set = true;
+			cs.jumpto = parse_target(optarg);
+			break;
+#endif
+
+		case 'j':
+			command_jump(&cs);
+			break;
+
+
+		case 'i':
+			if (*optarg == '\0')
+				xtables_error(PARAMETER_PROBLEM,
+					"Empty interface is likely to be "
+					"undesired");
+			set_option(&cs.options, OPT_VIANAMEIN, &args.invflags,
+				   cs.invert);
+			xtables_parse_interface(optarg,
+						args.iniface,
+						args.iniface_mask);
+			break;
+
+		case 'o':
+			if (*optarg == '\0')
+				xtables_error(PARAMETER_PROBLEM,
+					"Empty interface is likely to be "
+					"undesired");
+			set_option(&cs.options, OPT_VIANAMEOUT, &args.invflags,
+				   cs.invert);
+			xtables_parse_interface(optarg,
+						args.outiface,
+						args.outiface_mask);
+			break;
+
+		case 'f':
+			if (args.family == AF_INET6) {
+				xtables_error(PARAMETER_PROBLEM,
+					"`-f' is not supported in IPv6, "
+					"use -m frag instead");
+			}
+			set_option(&cs.options, OPT_FRAGMENT, &args.invflags,
+				   cs.invert);
+			args.flags |= IPT_F_FRAG;
+			break;
+
+		case 'v':
+			if (!verbose)
+				set_option(&cs.options, OPT_VERBOSE,
+					   &args.invflags, cs.invert);
+			verbose++;
+			break;
+
+		case 'm':
+			command_match(&cs);
+			break;
+
+		case 'n':
+			set_option(&cs.options, OPT_NUMERIC, &args.invflags,
+				   cs.invert);
+			break;
+
+		case 't':
+			if (cs.invert)
+				xtables_error(PARAMETER_PROBLEM,
+					   "unexpected ! flag before --table");
+			*table = optarg;
+			break;
+
+		case 'x':
+			set_option(&cs.options, OPT_EXPANDED, &args.invflags,
+				   cs.invert);
+			break;
+
+		case 'V':
+			if (cs.invert)
+				printf("Not %s ;-)\n", prog_vers);
+			else
+				printf("%s v%s\n",
+				       prog_name, prog_vers);
+			exit(0);
+
+		case 'w':
+			if (restore) {
+				xtables_error(PARAMETER_PROBLEM,
+					      "You cannot use `-w' from "
+					      "iptables-restore");
+			}
+			break;
+
+		case '0':
+			set_option(&cs.options, OPT_LINENUMBERS,
+				   &args.invflags, cs.invert);
+			break;
+
+		case 'M':
+			xtables_modprobe_program = optarg;
+			break;
+
+		case 'c':
+
+			set_option(&cs.options, OPT_COUNTERS, &args.invflags,
+				   cs.invert);
+			args.pcnt = optarg;
+			args.bcnt = strchr(args.pcnt + 1, ',');
+			if (args.bcnt)
+			    args.bcnt++;
+			if (!args.bcnt && optind < argc &&
+			    argv[optind][0] != '-' &&
+			    argv[optind][0] != '!')
+				args.bcnt = argv[optind++];
+			if (!args.bcnt)
+				xtables_error(PARAMETER_PROBLEM,
+					"-%c requires packet and byte counter",
+					opt2char(OPT_COUNTERS));
+
+			if (sscanf(args.pcnt, "%llu", &args.pcnt_cnt) != 1)
+				xtables_error(PARAMETER_PROBLEM,
+					"-%c packet counter not numeric",
+					opt2char(OPT_COUNTERS));
+
+			if (sscanf(args.bcnt, "%llu", &args.bcnt_cnt) != 1)
+				xtables_error(PARAMETER_PROBLEM,
+					"-%c byte counter not numeric",
+					opt2char(OPT_COUNTERS));
+			break;
+
+		case '4':
+			if (args.family != AF_INET)
+				exit_tryhelp(2);
+
+			h->ops = nft_family_ops_lookup(args.family);
+			break;
+
+		case '6':
+			args.family = AF_INET6;
+			xtables_set_nfproto(AF_INET6);
+
+			h->ops = nft_family_ops_lookup(args.family);
+			if (h->ops == NULL)
+				xtables_error(PARAMETER_PROBLEM,
+					      "Unknown family");
+			break;
+
+		case 1: /* non option */
+			if (optarg[0] == '!' && optarg[1] == '\0') {
+				if (cs.invert)
+					xtables_error(PARAMETER_PROBLEM,
+						   "multiple consecutive ! not"
+						   " allowed");
+				cs.invert = TRUE;
+				optarg[0] = '\0';
+				continue;
+			}
+			fprintf(stderr, "Bad argument `%s'\n", optarg);
+			exit_tryhelp(2);
+
+		default:
+			if (command_default(&cs, &xtables_globals) == 1)
+				/* cf. ip6tables.c */
+				continue;
+			break;
+		}
+		cs.invert = FALSE;
+	}
+
+	if (strcmp(*table, "nat") == 0 &&
+	    ((policy != NULL && strcmp(policy, "DROP") == 0) ||
+	    (cs.jumpto != NULL && strcmp(cs.jumpto, "DROP") == 0)))
+		xtables_error(PARAMETER_PROBLEM,
+			"\nThe \"nat\" table is not intended for filtering, "
+			"the use of DROP is therefore inhibited.\n\n");
+
+	for (matchp = cs.matches; matchp; matchp = matchp->next)
+		xtables_option_mfcall(matchp->match);
+	if (cs.target != NULL)
+		xtables_option_tfcall(cs.target);
+
+	/* Fix me: must put inverse options checking here --MN */
+
+	if (optind < argc)
+		xtables_error(PARAMETER_PROBLEM,
+			   "unknown arguments found on commandline");
+	if (!command)
+		xtables_error(PARAMETER_PROBLEM, "no command specified");
+	if (cs.invert)
+		xtables_error(PARAMETER_PROBLEM,
+			   "nothing appropriate following !");
+
+	/* Set only if required, needed by xtables-restore */
+	if (h->family == AF_UNSPEC)
+		h->family = args.family;
+
+	h->ops->post_parse(command, &cs, &args);
+
+	if (command == CMD_REPLACE &&
+	    (args.s.naddrs != 1 || args.d.naddrs != 1))
+		xtables_error(PARAMETER_PROBLEM, "Replacement rule does not "
+			   "specify a unique address");
+
+	generic_opt_check(command, cs.options);
+
+	if (chain != NULL && strlen(chain) >= XT_EXTENSION_MAXNAMELEN)
+		xtables_error(PARAMETER_PROBLEM,
+			   "chain name `%s' too long (must be under %u chars)",
+			   chain, XT_EXTENSION_MAXNAMELEN);
+
+	if (command == CMD_APPEND
+	    || command == CMD_DELETE
+	    || command == CMD_CHECK
+	    || command == CMD_INSERT
+	    || command == CMD_REPLACE) {
+		if (strcmp(chain, "PREROUTING") == 0
+		    || strcmp(chain, "INPUT") == 0) {
+			/* -o not valid with incoming packets. */
+			if (cs.options & OPT_VIANAMEOUT)
+				xtables_error(PARAMETER_PROBLEM,
+					   "Can't use -%c with %s\n",
+					   opt2char(OPT_VIANAMEOUT),
+					   chain);
+		}
+
+		if (strcmp(chain, "POSTROUTING") == 0
+		    || strcmp(chain, "OUTPUT") == 0) {
+			/* -i not valid with outgoing packets */
+			if (cs.options & OPT_VIANAMEIN)
+				xtables_error(PARAMETER_PROBLEM,
+					   "Can't use -%c with %s\n",
+					   opt2char(OPT_VIANAMEIN),
+					   chain);
+		}
+
+		/*
+		 * Contrary to what iptables does, we assume that any jumpto
+		 * is a custom chain jumps (if no target is found). Later on,
+		 * nf_table will spot the error if the chain does not exists.
+		 */
+	}
+
+	switch (command) {
+	case CMD_APPEND:
+		ret = add_entry(chain, *table, &cs, 0, h->family,
+				args.s, args.d, cs.options&OPT_VERBOSE,
+				h, true);
+		break;
+	case CMD_DELETE:
+		ret = delete_entry(chain, *table, &cs, h->family,
+				   args.s, args.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, h->family,
+				   args.s, args.d, cs.options&OPT_VERBOSE, h);
+		break;
+	case CMD_REPLACE:
+		ret = replace_entry(chain, *table, &cs, rulenum - 1,
+				    h->family, args.s, args.d,
+				    cs.options&OPT_VERBOSE, h);
+		break;
+	case CMD_INSERT:
+		ret = add_entry(chain, *table, &cs, rulenum - 1, h->family,
+				args.s, args.d, cs.options&OPT_VERBOSE, h,
+				false);
+		break;
+	case CMD_FLUSH:
+		ret = nft_rule_flush(h, chain, *table);
+		break;
+	case CMD_ZERO:
+		ret = nft_chain_zero_counters(h, chain, *table);
+		break;
+	case CMD_ZERO_NUM:
+		ret = nft_rule_zero_counters(h, chain, *table, rulenum - 1);
+		break;
+	case CMD_LIST:
+	case CMD_LIST|CMD_ZERO:
+	case CMD_LIST|CMD_ZERO_NUM:
+		ret = list_entries(h, chain, *table,
+				   rulenum,
+				   cs.options&OPT_VERBOSE,
+				   cs.options&OPT_NUMERIC,
+				   cs.options&OPT_EXPANDED,
+				   cs.options&OPT_LINENUMBERS);
+		if (ret && (command & CMD_ZERO))
+			ret = nft_chain_zero_counters(h, chain, *table);
+		if (ret && (command & CMD_ZERO_NUM))
+			ret = nft_rule_zero_counters(h, chain, *table,
+						     rulenum - 1);
+		break;
+	case CMD_LIST_RULES:
+	case CMD_LIST_RULES|CMD_ZERO:
+	case CMD_LIST_RULES|CMD_ZERO_NUM:
+		ret = list_rules(h, chain, *table, rulenum, cs.options&OPT_VERBOSE);
+		if (ret && (command & CMD_ZERO))
+			ret = nft_chain_zero_counters(h, chain, *table);
+		if (ret && (command & CMD_ZERO_NUM))
+			ret = nft_rule_zero_counters(h, chain, *table,
+						     rulenum - 1);
+		break;
+	case CMD_NEW_CHAIN:
+		ret = nft_chain_user_add(h, chain, *table);
+		break;
+	case CMD_DELETE_CHAIN:
+		ret = nft_chain_user_del(h, chain, *table);
+		break;
+	case CMD_RENAME_CHAIN:
+		ret = nft_chain_user_rename(h, chain, *table, newname);
+		break;
+	case CMD_SET_POLICY:
+		ret = nft_chain_set(h, *table, chain, policy, NULL);
+		if (ret < 0)
+			xtables_error(PARAMETER_PROBLEM, "Wrong policy `%s'\n",
+				      policy);
+		break;
+	default:
+		/* We should never reach this... */
+		exit_tryhelp(2);
+	}
+
+/*	if (verbose > 1)
+		dump_entries(*handle); */
+
+	xtables_rule_matches_free(&cs.matches);
+
+	if (h->family == AF_INET) {
+		free(args.s.addr.v4);
+		free(args.s.mask.v4);
+		free(args.d.addr.v4);
+		free(args.d.mask.v4);
+	} else if (h->family == AF_INET6) {
+		free(args.s.addr.v6);
+		free(args.s.mask.v6);
+		free(args.d.addr.v6);
+		free(args.d.mask.v6);
+	}
+	xtables_free_opts(1);
+
+	return ret;
+}
diff --git a/libxtables/xtables.c b/libxtables/xtables.c
index fb60c01..a511c08 100644
--- a/libxtables/xtables.c
+++ b/libxtables/xtables.c
@@ -743,7 +743,7 @@
 	return ptr;
 }
 
-static int compatible_revision(const char *name, uint8_t revision, int opt)
+int xtables_compatible_revision(const char *name, uint8_t revision, int opt)
 {
 	struct xt_get_revision rev;
 	socklen_t s = sizeof(rev);
@@ -799,12 +799,12 @@
 
 static int compatible_match_revision(const char *name, uint8_t revision)
 {
-	return compatible_revision(name, revision, afinfo->so_rev_match);
+	return xt_params->compat_rev(name, revision, afinfo->so_rev_match);
 }
 
 static int compatible_target_revision(const char *name, uint8_t revision)
 {
-	return compatible_revision(name, revision, afinfo->so_rev_target);
+	return xt_params->compat_rev(name, revision, afinfo->so_rev_target);
 }
 
 static void xtables_check_options(const char *name, const struct option *opt)