use nf_tables and nf_tables compatibility interface

This patch adds the following utilities:

* xtables
* xtables-restore
* xtables-save
* xtables-config

They all use Patrick's nf_tables infrastructure plus my compatibility
layer.

xtables, xtables-restore and xtables-save are syntax compatible with
ip[6]tables, ip[6]tables-restore and ip[6]tables-save.

Semantics aims to be similar, still the main exception is that there
is no commit operation. Thus, we incrementally add/delete rules without
entire table locking.

The following options are also not yet implemented:

-Z (this requires adding expr->ops->reset(...) so nft_counters can reset
    internal state of expressions while dumping it)

-R and -E (this requires adding this feature to nf_tables)

-f (can be implemented with expressions: payload 6 (2-bytes) + bitwise a&b^!b + cmp neq 0)

-IPv6 support.

But those are a matter of time to get them done.

A new utility, xtables-config, is available to register tables and
chains. By default there is a configuration file that adds backward
compatible tables and chains under iptables/etc/xtables.conf. You have
to call this utility first to register tables and chains.

However, it would be possible to automagically register tables and
chains while using xtables and xtables-restore to get similar operation
than with iptables.

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
diff --git a/configure.ac b/configure.ac
index e83304c..eb2c367 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-xtables], [Do not build xtables]),
+	[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,45 @@
 	[nfnetlink=1], [nfnetlink=0])
 AM_CONDITIONAL([HAVE_LIBNFNETLINK], [test "$nfnetlink" = 1])
 
+PKG_CHECK_MODULES([libmnl], [libmnl >= 1.0],
+	[mnl=1], [mnl=0])
+AM_CONDITIONAL([HAVE_LIBMNL], [test "$mnl" = 1])
+
+PKG_CHECK_MODULES([libnfables], [libnftables >= 1.0],
+	[nftables=1], [nftables=0])
+AM_CONDITIONAL([HAVE_LIBNFTABLES], [test "$nftables" = 1])
+
+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 conntrack-tools. Please, consider to upgrade
+	flex.])
+fi
+
 regular_CFLAGS="-Wall -Waggregate-return -Wmissing-declarations \
 	-Wmissing-prototypes -Wredundant-decls -Wshadow -Wstrict-prototypes \
 	-Winline -pipe";
@@ -182,6 +225,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..00b5df4
--- /dev/null
+++ b/etc/xtables.conf
@@ -0,0 +1,24 @@
+table raw prio -300 {
+	chain PREROUTING hook NF_INET_PRE_ROUTING
+	chain OUTPUT hook NF_INET_LOCAL_OUT
+}
+
+table mangle prio -150 {
+	chain PREROUTING hook NF_INET_PRE_ROUTING
+	chain INPUT hook NF_INET_LOCAL_IN
+	chain FORWARD hook NF_INET_FORWARD
+	chain OUTPUT hook NF_INET_LOCAL_OUT
+	chain POSTROUTING hook NF_INET_POST_ROUTING
+}
+
+table filter prio 0 {
+	chain INPUT hook NF_INET_LOCAL_IN
+	chain FORWARD hook NF_INET_FORWARD
+	chain OUTPUT hook NF_INET_LOCAL_OUT
+}
+
+table security prio 150 {
+	chain INPUT hook NF_INET_LOCAL_IN
+	chain FORWARD hook NF_INET_FORWARD
+	chain OUTPUT hook NF_INET_LOCAL_OUT
+}
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..a6d5137
--- /dev/null
+++ b/include/linux/netfilter/nf_tables.h
@@ -0,0 +1,418 @@
+#ifndef _LINUX_NF_TABLES_H
+#define _LINUX_NF_TABLES_H
+
+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 {
+	NFT_CONTINUE	= -1,
+	NFT_BREAK	= -2,
+	NFT_JUMP	= -3,
+	NFT_GOTO	= -4,
+	NFT_RETURN	= -5,
+};
+
+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 {
+	NFTA_LIST_UNPEC,
+	NFTA_LIST_ELEM,
+	__NFTA_LIST_MAX
+};
+#define NFTA_LIST_MAX		(__NFTA_LIST_MAX - 1)
+
+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_attributes {
+	NFTA_TABLE_UNSPEC,
+	NFTA_TABLE_NAME,
+	__NFTA_TABLE_MAX
+};
+#define NFTA_TABLE_MAX		(__NFTA_TABLE_MAX - 1)
+
+enum nft_chain_attributes {
+	NFTA_CHAIN_UNSPEC,
+	NFTA_CHAIN_TABLE,
+	NFTA_CHAIN_NAME,
+	NFTA_CHAIN_HOOK,
+	__NFTA_CHAIN_MAX
+};
+#define NFTA_CHAIN_MAX		(__NFTA_CHAIN_MAX - 1)
+
+enum nft_rule_attributes {
+	NFTA_RULE_UNSPEC,
+	NFTA_RULE_TABLE,
+	NFTA_RULE_CHAIN,
+	NFTA_RULE_HANDLE,
+	NFTA_RULE_EXPRESSIONS,
+	__NFTA_RULE_MAX
+};
+#define NFTA_RULE_MAX		(__NFTA_RULE_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 {
+	NFTA_DATA_UNSPEC,
+	NFTA_DATA_VALUE,
+	NFTA_DATA_VERDICT,
+	__NFTA_DATA_MAX
+};
+#define NFTA_DATA_MAX		(__NFTA_DATA_MAX - 1)
+
+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 {
+	NFTA_EXPR_UNSPEC,
+	NFTA_EXPR_NAME,
+	NFTA_EXPR_DATA,
+	__NFTA_EXPR_MAX
+};
+#define NFTA_EXPR_MAX		(__NFTA_EXPR_MAX - 1)
+
+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 {
+	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 {
+	NFT_BYTEORDER_NTOH,
+	NFT_BYTEORDER_HTON,
+};
+
+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 {
+	NFT_CMP_EQ,
+	NFT_CMP_NEQ,
+	NFT_CMP_LT,
+	NFT_CMP_LTE,
+	NFT_CMP_GT,
+	NFT_CMP_GTE,
+};
+
+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 {
+	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 {
+	NFT_PAYLOAD_LL_HEADER,
+	NFT_PAYLOAD_NETWORK_HEADER,
+	NFT_PAYLOAD_TRANSPORT_HEADER,
+};
+
+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 {
+	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 {
+	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 {
+	NFTA_META_UNSPEC,
+	NFTA_META_DREG,
+	NFTA_META_KEY,
+	__NFTA_META_MAX
+};
+#define NFTA_META_MAX		(__NFTA_META_MAX - 1)
+
+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_PROTOCOL,
+	NFT_CT_SRC,
+	NFT_CT_DST,
+	NFT_CT_PROTO_SRC,
+	NFT_CT_PROTO_DST,
+};
+
+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 {
+	NFTA_LIMIT_UNSPEC,
+	NFTA_LIMIT_RATE,
+	NFTA_LIMIT_DEPTH,
+	__NFTA_LIMIT_MAX
+};
+#define NFTA_LIMIT_MAX		(__NFTA_LIMIT_MAX - 1)
+
+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 {
+	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 {
+	NFT_REJECT_ICMP_UNREACH,
+	NFT_REJECT_TCP_RST,
+};
+
+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 {
+	NFT_NAT_SNAT,
+	NFT_NAT_DNAT,
+};
+
+enum nft_nat_attributes {
+	NFTA_NAT_UNSPEC,
+	NFTA_NAT_TYPE,
+	NFTA_NAT_ADDR_MIN,
+	NFTA_NAT_ADDR_MAX,
+	NFTA_NAT_PROTO_MIN,
+	NFTA_NAT_PROTO_MAX,
+	__NFTA_NAT_MAX
+};
+#define NFTA_NAT_MAX		(__NFTA_NAT_MAX - 1)
+
+enum nft_target_attributes {
+	NFTA_TARGET_UNSPEC,
+	NFTA_TARGET_NAME,
+	NFTA_TARGET_REV,
+	NFTA_TARGET_INFO,
+	__NFTA_TARGET_MAX
+};
+#define NFTA_TARGET_MAX		(__NFTA_TARGET_MAX - 1)
+
+enum nft_match_attributes {
+	NFTA_MATCH_UNSPEC,
+	NFTA_MATCH_NAME,
+	NFTA_MATCH_REV,
+	NFTA_MATCH_INFO,
+	__NFTA_MATCH_MAX
+};
+#define NFTA_MATCH_MAX		(__NFTA_MATCH_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..91eebab
--- /dev/null
+++ b/include/linux/netfilter/nfnetlink.h
@@ -0,0 +1,100 @@
+#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
+
+#ifdef __KERNEL__
+
+#include <linux/netlink.h>
+#include <linux/capability.h>
+#include <net/netlink.h>
+
+struct nfnl_callback {
+	int (*call)(struct sock *nl, struct sk_buff *skb, 
+		    const struct nlmsghdr *nlh,
+		    const struct nlattr * const cda[]);
+	int (*call_rcu)(struct sock *nl, struct sk_buff *skb, 
+		    const struct nlmsghdr *nlh,
+		    const struct nlattr * const cda[]);
+	const struct nla_policy *policy;	/* netlink attribute policy */
+	const u_int16_t attr_count;		/* number of nlattr's */
+};
+
+struct nfnetlink_subsystem {
+	const char *name;
+	__u8 subsys_id;			/* nfnetlink subsystem ID */
+	__u8 cb_count;			/* number of callbacks */
+	const struct nfnl_callback *cb;	/* callback for individual types */
+};
+
+extern int nfnetlink_subsys_register(const struct nfnetlink_subsystem *n);
+extern int nfnetlink_subsys_unregister(const struct nfnetlink_subsystem *n);
+
+extern int nfnetlink_has_listeners(struct net *net, unsigned int group);
+extern int nfnetlink_send(struct sk_buff *skb, struct net *net, u32 pid, unsigned int group,
+			  int echo, gfp_t flags);
+extern int nfnetlink_set_err(struct net *net, u32 pid, u32 group, int error);
+extern int nfnetlink_unicast(struct sk_buff *skb, struct net *net, u_int32_t pid, int flags);
+
+extern void nfnl_lock(void);
+extern void nfnl_unlock(void);
+
+#define MODULE_ALIAS_NFNL_SUBSYS(subsys) \
+	MODULE_ALIAS("nfnetlink-subsys-" __stringify(subsys))
+
+#endif	/* __KERNEL__ */
+#endif	/* _NFNETLINK_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/Makefile.am b/iptables/Makefile.am
index a4246eb..2b1f3fa 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_CPPFLAGS} ${libnftables_CPPFLAGS}
+AM_YFLAGS = -d
 
 xtables_multi_SOURCES  = xtables-multi.c iptables-xml.c
 xtables_multi_CFLAGS   = ${AM_CFLAGS}
@@ -24,11 +25,27 @@
 xtables_multi_SOURCES += xshared.c
 xtables_multi_LDADD   += ../libxtables/libxtables.la -lm
 
+if ENABLE_NFTABLES
+if HAVE_LIBMNL
+if HAVE_LIBNFTABLES
+xtables_multi_SOURCES += xtables-save.c xtables-restore.c \
+			 xtables-standalone.c xtables.c nft.c \
+			 xtables-config-parser.y xtables-config-syntax.l \
+			 xtables-config.c
+xtables_multi_LDADD   += -lmnl -lnftables
+xtables_multi_CFLAGS  += -DENABLE_NFTABLES
+# yacc and lex generate dirty code
+xtables_multi-xtables-config-parser.o xtables_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
+endif
+endif
+endif
+
 sbin_PROGRAMS    = xtables-multi
 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 +54,9 @@
 if ENABLE_IPV6
 v6_sbin_links  = ip6tables ip6tables-restore ip6tables-save
 endif
+if ENABLE_NFTABLES
+x_sbin_links  = xtables xtables-restore xtables-save xtables-config
+endif
 
 iptables-extensions.8: iptables-extensions.8.tmpl ../extensions/matches.man ../extensions/targets.man
 	${AM_VERBOSE_GEN} sed \
@@ -52,3 +72,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-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.c b/iptables/nft.c
new file mode 100644
index 0000000..91383bf
--- /dev/null
+++ b/iptables/nft.c
@@ -0,0 +1,2764 @@
+/*
+ * (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>
+ */
+
+#if 0
+#define DEBUGP(x, args...) fprintf(stdout, x, ## args)
+#define NLDEBUG
+#define DEBUG_DEL
+#else
+#define DEBUGP(x, args...)
+#endif
+
+#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 <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>	/* FIXME: only IPV4 by now */
+
+#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 <libnftables/table.h>
+#include <libnftables/chain.h>
+#include <libnftables/rule.h>
+#include <libnftables/expr.h>
+
+#include <netinet/in.h>	/* inet_ntoa */
+#include <arpa/inet.h>
+
+#include "nft.h"
+#include "xshared.h" /* proto_to_name */
+
+static void *nft_fn;
+
+static 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 void nft_table_init_one(struct nft_handle *h, const char *name, int portid)
+{
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh;
+	struct nft_table *t;
+
+	t = nft_table_alloc();
+	if (t == NULL)
+		return;
+
+	nft_table_attr_set(t, NFT_TABLE_ATTR_NAME, (char *)name);
+
+	nlh = nft_table_nlmsg_build_hdr(buf, NFT_MSG_NEWTABLE, AF_INET,
+					NLM_F_ACK|NLM_F_EXCL, h->seq);
+	nft_table_nlmsg_build_payload(nlh, t);
+	nft_table_free(t);
+
+	if (mnl_talk(h, nlh, NULL, NULL) < 0) {
+		if (errno != EEXIST)
+			perror("mnl-talk:nft_table_init_one");
+	}
+}
+
+#define FILTER		0
+#define MANGLE		1
+#define RAW		2
+#define SECURITY	3
+#define TABLES_MAX	4
+
+static struct default_table {
+	const char *name;
+	uint32_t prio;
+} tables[TABLES_MAX] = {
+	[RAW] = {
+		.name	= "raw",
+		.prio	= -300,	/* NF_IP_PRI_RAW */
+	},
+	[MANGLE] = {
+		.name	= "mangle",
+		.prio	= -150,	/* NF_IP_PRI_MANGLE */
+	},
+	[FILTER] = {
+		.name	= "filter",
+		.prio	= 0,	/* NF_IP_PRI_FILTER */
+	},
+	[SECURITY] = {
+		.name	= "security",
+		.prio	= 150,	/* NF_IP_PRI_SECURITY */
+	},
+	/* nat already registered by nf_tables */
+};
+
+static void nft_table_init(struct nft_handle *h)
+{
+	int i;
+
+	for (i=0; i<TABLES_MAX; i++)
+		nft_table_init_one(h, tables[i].name, h->portid);
+}
+
+static struct default_chain {
+	const char *name;
+	uint32_t hook;
+} chains[TABLES_MAX][NF_IP_NUMHOOKS] = {
+	[FILTER] = {
+		{
+			.name	= "INPUT",
+			.hook	= NF_INET_LOCAL_IN,
+		},
+		{
+			.name	= "FORWARD",
+			.hook	= NF_INET_FORWARD,
+		},
+		{
+			.name	= "OUTPUT",
+			.hook	= NF_INET_LOCAL_OUT,
+		},
+	},
+	[MANGLE] = {
+		{
+			.name	= "PREROUTING",
+			.hook	= NF_INET_PRE_ROUTING,
+		},
+		{
+			.name	= "INPUT",
+			.hook	= NF_INET_LOCAL_IN,
+		},
+		{
+			.name	= "FORWARD",
+			.hook	= NF_INET_FORWARD,
+		},
+		{
+			.name	= "OUTPUT",
+			.hook	= NF_INET_LOCAL_OUT,
+		},
+		{
+			.name	= "POSTROUTING",
+			.hook	= NF_INET_POST_ROUTING,
+		},
+	},
+	[RAW] = {
+		{
+			.name	= "PREROUTING",
+			.hook	= NF_INET_PRE_ROUTING,
+		},
+		{
+			.name	= "OUTPUT",
+			.hook	= NF_INET_LOCAL_OUT,
+		},
+	},
+	[SECURITY] = {
+		{
+			.name	= "INPUT",
+			.hook	= NF_INET_LOCAL_IN,
+		},
+		{
+			.name	= "FORWARD",
+			.hook	= NF_INET_FORWARD,
+		},
+		{
+			.name	= "OUTPUT",
+			.hook	= NF_INET_LOCAL_OUT,
+		},
+	},
+};
+
+static void
+nft_chain_default_add(struct nft_handle *h, struct default_table *table,
+		      struct default_chain *chain, int policy)
+{
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh;
+	struct nft_chain *c;
+
+	c = nft_chain_alloc();
+	if (c == NULL)
+		return;
+
+	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, table->prio);
+	nft_chain_attr_set_u32(c, NFT_CHAIN_ATTR_POLICY, policy);
+
+	nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_NEWCHAIN, AF_INET,
+					NLM_F_ACK|NLM_F_EXCL, h->seq);
+	nft_chain_nlmsg_build_payload(nlh, c);
+	nft_chain_free(c);
+
+	if (mnl_talk(h, nlh, NULL, NULL) < 0) {
+		if (errno != EEXIST)
+			perror("mnl_talk:nft_chain_default_add");
+	}
+}
+
+static void nft_chain_init(struct nft_handle *h)
+{
+	int i, j;
+
+	for (i=0; i<TABLES_MAX; i++) {
+		for (j=0; j<NF_IP_NUMHOOKS; j++) {
+			if (chains[i][j].name == NULL)
+				break;
+
+			nft_chain_default_add(h, &tables[i], &chains[i][j],
+					      NF_ACCEPT);
+		}
+	}
+}
+
+int nft_init(struct nft_handle *h)
+{
+	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);
+
+/*
+ * In case we want to inconditionally register all tables / chain.
+ * This is not flexible and performance consuming.
+ *
+ *	nft_table_init(h);
+ *	nft_chain_init(h);
+ */
+
+	return 0;
+}
+
+void nft_fini(struct nft_handle *h)
+{
+	mnl_socket_close(h->nl);
+}
+
+int nft_table_add(struct nft_handle *h, const struct nft_table *t)
+{
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh;
+
+	nlh = nft_table_nlmsg_build_hdr(buf, NFT_MSG_NEWTABLE, AF_INET,
+					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, AF_INET,
+					NLM_F_ACK|NLM_F_EXCL, h->seq);
+	nft_chain_nlmsg_build_payload(nlh, c);
+
+	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", 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;
+	int ret;
+
+	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 *)chain);
+	nft_chain_attr_set_u32(c, NFT_CHAIN_ATTR_POLICY, policy);
+	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, AF_INET,
+					NLM_F_ACK, h->seq);
+	nft_chain_nlmsg_build_payload(nlh, c);
+
+	nft_chain_print_debug(c, nlh);
+
+	nft_chain_free(c);
+
+	ret = mnl_talk(h, nlh, NULL, NULL);
+	if (ret < 0)
+		perror("mnl_talk:__nft_chain_policy");
+
+	return ret;
+}
+
+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 void __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;
+
+	memcpy(info, m->data, m->u.match_size);
+	nft_rule_expr_set(e, NFT_EXPR_MT_INFO, info, m->u.match_size - sizeof(*m));
+}
+
+static void add_match(struct nft_rule *r, struct xt_entry_match *m)
+{
+	struct nft_rule_expr *expr;
+
+	expr = nft_rule_expr_alloc("match");
+	if (expr == NULL)
+		return;
+
+	__add_match(expr, m);
+	nft_rule_add_expr(r, expr);
+}
+
+static void __add_target(struct nft_rule_expr *e, struct xt_entry_target *t)
+{
+	void *info = NULL;
+
+	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);
+
+	if (info == NULL) {
+		info = calloc(1, t->u.target_size);
+		if (info == NULL)
+			return;
+
+		memcpy(info, t->data, t->u.target_size);
+	}
+
+	nft_rule_expr_set(e, NFT_EXPR_TG_INFO, info, t->u.target_size - sizeof(*t));
+}
+
+static void add_target(struct nft_rule *r, struct xt_entry_target *t)
+{
+	struct nft_rule_expr *expr;
+
+	expr = nft_rule_expr_alloc("target");
+	if (expr == NULL)
+		return;
+
+	__add_target(expr, t);
+	nft_rule_add_expr(r, expr);
+}
+
+static void 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;
+
+	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);
+}
+
+static void add_verdict(struct nft_rule *r, int verdict)
+{
+	struct nft_rule_expr *expr;
+
+	expr = nft_rule_expr_alloc("immediate");
+	if (expr == NULL)
+		return;
+
+	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);
+}
+
+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", tmp);
+	mnl_nlmsg_fprintf(stdout, nlh, nlh->nlmsg_len, sizeof(struct nfgenmsg));
+#endif
+}
+
+static 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);
+}
+
+static 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);
+}
+
+static 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_u8(expr, NFT_EXPR_CMP_SREG, NFT_REG_1);
+	nft_rule_expr_set_u8(expr, NFT_EXPR_CMP_OP, op);
+	nft_rule_expr_set(expr, NFT_EXPR_CMP_DATA, data, len);
+
+	nft_rule_add_expr(r, expr);
+}
+
+static void add_cmp_u32(struct nft_rule *r, uint32_t val, uint32_t op)
+{
+	add_cmp_ptr(r, op, &val, sizeof(val));
+}
+
+static void 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;
+
+	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);
+}
+
+int
+nft_rule_add(struct nft_handle *h, const char *chain, const char *table,
+	     struct iptables_command_state *cs, bool append, bool verbose)
+{
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh;
+	struct xtables_rule_match *matchp;
+	struct nft_rule *r;
+	int ret = 1;
+	uint32_t op;
+	int flags = append ? NLM_F_APPEND : 0;
+
+	nft_fn = nft_rule_add;
+
+	r = nft_rule_alloc();
+	if (r == NULL) {
+		ret = 0;
+		goto err;
+	}
+
+	nft_rule_attr_set(r, NFT_RULE_ATTR_TABLE, (char *)table);
+	nft_rule_attr_set(r, NFT_RULE_ATTR_CHAIN, (char *)chain);
+
+	if (cs->fw.ip.iniface[0] != '\0') {
+		int iface_len = strlen(cs->fw.ip.iniface);
+
+		if (cs->fw.ip.invflags & IPT_INV_VIA_IN)
+			op = NFT_CMP_NEQ;
+		else
+			op = NFT_CMP_EQ;
+
+		if (cs->fw.ip.iniface[iface_len - 1] == '+') {
+			add_meta(r, NFT_META_IIFNAME);
+			add_cmp_ptr(r, op, cs->fw.ip.iniface, iface_len - 1);
+		} else {
+			add_meta(r, NFT_META_IIF);
+			add_cmp_u32(r, if_nametoindex(cs->fw.ip.iniface), op);
+		}
+	}
+	if (cs->fw.ip.outiface[0] != '\0') {
+		int iface_len = strlen(cs->fw.ip.outiface);
+
+		if (cs->fw.ip.invflags & IPT_INV_VIA_OUT)
+			op = NFT_CMP_NEQ;
+		else
+			op = NFT_CMP_EQ;
+
+		if (cs->fw.ip.outiface[iface_len - 1] == '+') {
+			add_meta(r, NFT_META_OIFNAME);
+			add_cmp_ptr(r, op, cs->fw.ip.outiface, iface_len - 1);
+		} else {
+			add_meta(r, NFT_META_OIF);
+			add_cmp_u32(r, if_nametoindex(cs->fw.ip.outiface), op);
+		}
+	}
+	if (cs->fw.ip.src.s_addr != 0) {
+		add_payload(r, offsetof(struct iphdr, saddr), 4);
+		if (cs->fw.ip.invflags & IPT_INV_SRCIP)
+			op = NFT_CMP_NEQ;
+		else
+			op = NFT_CMP_EQ;
+
+		add_cmp_u32(r, cs->fw.ip.src.s_addr, op);
+	}
+	if (cs->fw.ip.dst.s_addr != 0) {
+		add_payload(r, offsetof(struct iphdr, daddr), 4);
+		if (cs->fw.ip.invflags & IPT_INV_DSTIP)
+			op = NFT_CMP_NEQ;
+		else
+			op = NFT_CMP_EQ;
+
+		add_cmp_u32(r, cs->fw.ip.dst.s_addr, op);
+	}
+	if (cs->fw.ip.proto != 0) {
+		add_payload(r, offsetof(struct iphdr, protocol), 1);
+		if (cs->fw.ip.invflags & XT_INV_PROTO)
+			op = NFT_CMP_NEQ;
+		else
+			op = NFT_CMP_EQ;
+
+		add_cmp_u32(r, cs->fw.ip.proto, op);
+	}
+
+	for (matchp = cs->matches; matchp; matchp = matchp->next)
+		add_match(r, matchp->match->m);
+
+	/* Counters need to me added before the target, otherwise they are
+	 * increased for each rule because of the way nf_tables works.
+	 */
+	add_counters(r, cs->counters.pcnt, cs->counters.bcnt);
+
+	/* If no target at all, add nothing (default to continue) */
+	if (cs->target != NULL) {
+		/* Standard target? */
+		if (strcmp(cs->jumpto, XTC_LABEL_ACCEPT) == 0)
+			add_verdict(r, NF_ACCEPT);
+		else if (strcmp(cs->jumpto, XTC_LABEL_DROP) == 0)
+			add_verdict(r, NF_DROP);
+		else if (strcmp(cs->jumpto, XTC_LABEL_RETURN) == 0)
+			add_verdict(r, NFT_RETURN);
+		else
+			add_target(r, cs->target->t);
+	} else if (strlen(cs->jumpto) > 0) {
+		/* Not standard, then it's a go / jump to chain */
+		if (cs->fw.ip.flags & IPT_F_GOTO)
+			add_jumpto(r, cs->jumpto, NFT_GOTO);
+		else
+			add_jumpto(r, cs->jumpto, NFT_JUMP);
+	}
+
+	/* NLM_F_CREATE autoloads the built-in table if it does not exists */
+	nlh = nft_rule_nlmsg_build_hdr(buf, NFT_MSG_NEWRULE, AF_INET,
+					NLM_F_ACK|NLM_F_CREATE|flags, h->seq);
+	nft_rule_nlmsg_build_payload(nlh, r);
+
+	nft_rule_print_debug(r, nlh);
+
+	nft_rule_free(r);
+
+	ret = mnl_talk(h, nlh, NULL, NULL);
+	if (ret < 0)
+		perror("mnl_talk:nft_rule_add");
+
+err:
+	/* the core expects 1 for success and 0 for error */
+	return ret == 0 ? 1 : 0;
+}
+
+static void nft_match_save(struct nft_rule_expr *expr)
+{
+	const char *name;
+	const struct xtables_match *match;
+	struct xt_entry_match *emu;
+	const void *mtinfo;
+	size_t len;
+
+	name = nft_rule_expr_get_str(expr, NFT_EXPR_MT_NAME);
+
+	match = xtables_find_match(name, XTF_TRY_LOAD, NULL);
+	if (match == NULL)
+		return;
+
+	mtinfo = nft_rule_expr_get(expr, NFT_EXPR_MT_INFO, &len);
+	if (mtinfo == NULL)
+		return;
+
+	emu = calloc(1, sizeof(struct xt_entry_match) + len);
+	if (emu == NULL)
+		return;
+
+	memcpy(&emu->data, mtinfo, len);
+
+	if (match->alias)
+		printf("-m %s", match->alias(emu));
+	else
+		printf("-m %s", match->name);
+
+	/* FIXME missing parameter */
+	match->save(NULL, emu);
+
+	printf(" ");
+
+	free(emu);
+}
+
+static void nft_target_save(struct nft_rule_expr *expr)
+{
+	const char *name;
+	const struct xtables_target *target;
+	struct xt_entry_target *emu;
+	const void *tginfo;
+	size_t len;
+
+	name = nft_rule_expr_get_str(expr, NFT_EXPR_TG_NAME);
+
+	/* Standard target not supported, we use native immediate expression */
+	if (strcmp(name, "") == 0) {
+		printf("ERROR: standard target seen, should not happen\n");
+		return;
+	}
+
+	target = xtables_find_target(name, XTF_TRY_LOAD);
+	if (target == NULL)
+		return;
+
+	tginfo = nft_rule_expr_get(expr, NFT_EXPR_TG_INFO, &len);
+	if (tginfo == NULL)
+		return;
+
+	emu = calloc(1, sizeof(struct xt_entry_match) + len);
+	if (emu == NULL)
+		return;
+
+	memcpy(emu->data, tginfo, len);
+
+	if (target->alias)
+		printf("-j %s", target->alias(emu));
+	else
+		printf("-j %s", target->name);
+
+	/* FIXME missing parameter */
+	target->save(NULL, emu);
+
+	free(emu);
+}
+
+static void nft_immediate_save(struct nft_rule_expr *expr)
+{
+	uint32_t verdict;
+
+	verdict = nft_rule_expr_get_u32(expr, NFT_EXPR_IMM_VERDICT);
+
+	switch(verdict) {
+	case NF_ACCEPT:
+		printf("-j ACCEPT");
+		break;
+	case NF_DROP:
+		printf("-j DROP");
+		break;
+	case NFT_RETURN:
+		printf("-j RETURN");
+		break;
+	case NFT_GOTO:
+		printf("-g %s",
+			nft_rule_expr_get_str(expr, NFT_EXPR_IMM_CHAIN));
+		break;
+	case NFT_JUMP:
+		printf("-j %s",
+			nft_rule_expr_get_str(expr, NFT_EXPR_IMM_CHAIN));
+		break;
+	}
+}
+
+static void
+nft_print_meta(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter)
+{
+	uint8_t key = nft_rule_expr_get_u8(e, NFT_EXPR_META_KEY);
+	uint32_t value;
+	const char *name;
+	char ifname[IFNAMSIZ];
+	const char *ifname_ptr;
+	size_t len;
+
+	e = nft_rule_expr_iter_next(iter);
+	if (e == NULL)
+		return;
+
+	name = nft_rule_expr_get_str(e, NFT_RULE_EXPR_ATTR_NAME);
+	/* meta should be followed by cmp */
+	if (strcmp(name, "cmp") != 0) {
+		DEBUGP("skipping no cmp after meta\n");
+		return;
+	}
+
+	switch(key) {
+	case NFT_META_IIF:
+		value = nft_rule_expr_get_u32(e, NFT_EXPR_CMP_DATA);
+		if_indextoname(value, ifname);
+
+		switch(nft_rule_expr_get_u8(e, NFT_EXPR_CMP_OP)) {
+		case NFT_CMP_EQ:
+			printf("-i %s ", ifname);
+			break;
+		case NFT_CMP_NEQ:
+			printf("! -i %s ", ifname);
+			break;
+		}
+		break;
+	case NFT_META_OIF:
+		value = nft_rule_expr_get_u32(e, NFT_EXPR_CMP_DATA);
+		if_indextoname(value, ifname);
+
+		switch(nft_rule_expr_get_u8(e, NFT_EXPR_CMP_OP)) {
+		case NFT_CMP_EQ:
+			printf("-o %s ", ifname);
+			break;
+		case NFT_CMP_NEQ:
+			printf("! -o %s ", ifname);
+			break;
+		}
+		break;
+	case NFT_META_IIFNAME:
+		ifname_ptr = nft_rule_expr_get(e, NFT_EXPR_CMP_DATA, &len);
+		memcpy(ifname, ifname_ptr, len);
+		ifname[len] = '\0';
+
+		/* if this is zero, then assume this is a interface mask */
+		if (if_nametoindex(ifname) == 0) {
+			ifname[len] = '+';
+			ifname[len+1] = '\0';
+		}
+
+		switch(nft_rule_expr_get_u8(e, NFT_EXPR_CMP_OP)) {
+		case NFT_CMP_EQ:
+			printf("-i %s ", ifname);
+			break;
+		case NFT_CMP_NEQ:
+			printf("! -i %s ", ifname);
+			break;
+		}
+		break;
+	case NFT_META_OIFNAME:
+		ifname_ptr = nft_rule_expr_get(e, NFT_EXPR_CMP_DATA, &len);
+		memcpy(ifname, ifname_ptr, len);
+		ifname[len] = '\0';
+
+		/* if this is zero, then assume this is a interface mask */
+		if (if_nametoindex(ifname) == 0) {
+			ifname[len] = '+';
+			ifname[len+1] = '\0';
+		}
+
+		switch(nft_rule_expr_get_u8(e, NFT_EXPR_CMP_OP)) {
+		case NFT_CMP_EQ:
+			printf("-o %s ", ifname);
+			break;
+		case NFT_CMP_NEQ:
+			printf("! -o %s ", ifname);
+			break;
+		}
+		break;
+	default:
+		DEBUGP("unknown meta key %d\n", key);
+		break;
+	}
+}
+
+static 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;
+	size_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_u8(e, NFT_EXPR_CMP_OP);
+	if (op == NFT_CMP_NEQ)
+		*inv = true;
+	else
+		*inv = false;
+}
+
+static 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);
+}
+
+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_print_payload(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter)
+{
+	uint32_t offset;
+	bool inv;
+
+	offset = nft_rule_expr_get_u32(e, NFT_EXPR_PAYLOAD_OFFSET);
+
+	switch(offset) {
+	struct in_addr addr;
+	uint8_t proto;
+
+	case offsetof(struct iphdr, saddr):
+		get_cmp_data(iter, &addr, sizeof(addr), &inv);
+		if (inv)
+			printf("! -s %s/%s ", inet_ntoa(addr),
+						mask_to_str(0xffffffff));
+		else
+			printf("-s %s/%s ", inet_ntoa(addr),
+						mask_to_str(0xffffffff));
+		break;
+	case offsetof(struct iphdr, daddr):
+		get_cmp_data(iter, &addr, sizeof(addr), &inv);
+		if (inv)
+			printf("! -d %s/%s ", inet_ntoa(addr),
+						mask_to_str(0xffffffff));
+		else
+			printf("-d %s/%s ", inet_ntoa(addr),
+						mask_to_str(0xffffffff));
+		break;
+	case offsetof(struct iphdr, protocol):
+		get_cmp_data(iter, &proto, sizeof(proto), &inv);
+		print_proto(proto, inv);
+		break;
+	default:
+		DEBUGP("unknown payload offset %d\n", offset);
+		break;
+	}
+}
+
+static void
+nft_print_counters(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter,
+		   bool counters)
+{
+	if (counters) {
+		printf("-c %lu %lu ",
+			nft_rule_expr_get_u64(e, NFT_EXPR_CTR_PACKETS),
+			nft_rule_expr_get_u64(e, NFT_EXPR_CTR_BYTES));
+	}
+}
+
+static void nft_rule_print_save(struct nft_rule *r, bool counters)
+{
+	struct nft_rule_expr_iter *iter;
+	struct nft_rule_expr *expr;
+
+	/* print chain name */
+	printf("-A %s ", nft_rule_attr_get_str(r, NFT_RULE_ATTR_CHAIN));
+
+	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_print_counters(expr, iter, counters);
+		} else if (strcmp(name, "payload") == 0) {
+			nft_print_payload(expr, iter);
+		} else if (strcmp(name, "meta") == 0) {
+			nft_print_meta(expr, iter);
+		} else if (strcmp(name, "match") == 0) {
+			nft_match_save(expr);
+		} else if (strcmp(name, "target") == 0) {
+			nft_target_save(expr);
+		} else if (strcmp(name, "immediate") == 0) {
+			nft_immediate_save(expr);
+		}
+
+		expr = nft_rule_expr_iter_next(iter);
+	}
+
+	printf("\n");
+}
+
+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(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;
+	int ret;
+	struct nft_chain_list *list;
+
+	list = nft_chain_list_alloc();
+	if (list == NULL) {
+		DEBUGP("cannot allocate rule list\n");
+		return 0;
+	}
+
+	nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_GETCHAIN, AF_INET,
+					NLM_F_DUMP, h->seq);
+
+	ret = mnl_talk(h, nlh, nft_chain_list_cb, list);
+	if (ret < 0)
+		perror("mnl_talk:nft_chain_list_get");
+
+	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 [%lu:%lu]\n", chain, policy_name[pol],
+					     pkts, bytes);
+	} else
+		printf(":%s - [%lu:%lu]\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) {
+		DEBUGP("cannot allocate rule list iterator\n");
+		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;
+
+		if (nft_chain_attr_get(c, NFT_CHAIN_ATTR_HOOKNUM))
+			basechain = true;
+
+		nft_chain_print_save(c, basechain);
+next:
+		c = nft_chain_list_iter_next(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(r, list);
+
+	return MNL_CB_OK;
+out:
+	nft_rule_free(r);
+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) {
+		DEBUGP("cannot allocate rule list\n");
+		return 0;
+	}
+
+	nlh = nft_rule_nlmsg_build_hdr(buf, NFT_MSG_GETRULE, AF_INET,
+					NLM_F_DUMP, h->seq);
+
+	ret = mnl_talk(h, nlh, nft_rule_list_cb, list);
+	if (ret < 0) {
+		perror("mnl_talk:nft_rule_save");
+		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) {
+		DEBUGP("cannot retrieve rule list from kernel\n");
+		return 0;
+	}
+
+	iter = nft_rule_list_iter_create(list);
+	if (iter == NULL) {
+		DEBUGP("cannot allocate rule list iterator\n");
+		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);
+
+		if (strcmp(table, rule_table) != 0)
+			goto next;
+
+		nft_rule_print_save(r, counters);
+
+next:
+		r = nft_rule_list_iter_next(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)
+{
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh;
+	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);
+
+	/* Delete all rules in this table + chain */
+	nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_DELRULE, AF_INET,
+					NLM_F_ACK, h->seq);
+	nft_rule_nlmsg_build_payload(nlh, r);
+	nft_rule_free(r);
+
+	if (mnl_talk(h, nlh, NULL, NULL) < 0) {
+		if (errno != EEXIST)
+			perror("mnl_talk:__nft_rule_flush");
+	}
+}
+
+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) {
+		DEBUGP("cannot allocate rule list iterator\n");
+		return 0;
+	}
+
+	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);
+
+next:
+		c = nft_chain_list_iter_next(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;
+
+	c = nft_chain_alloc();
+	if (c == NULL) {
+		DEBUGP("cannot allocate chain\n");
+		return -1;
+	}
+
+	nft_chain_attr_set(c, NFT_CHAIN_ATTR_TABLE, (char *)table);
+	nft_chain_attr_set(c, NFT_CHAIN_ATTR_NAME, (char *)chain);
+
+	nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_NEWCHAIN, AF_INET,
+					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);
+	if (ret < 0) {
+		if (errno != EEXIST)
+			perror("mnl_talk:nft_chain_add");
+	}
+
+	/* 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;
+	int ret;
+
+	nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_DELCHAIN, AF_INET,
+					NLM_F_ACK, h->seq);
+	nft_chain_nlmsg_build_payload(nlh, c);
+
+	ret = mnl_talk(h, nlh, NULL, NULL);
+	if (ret < 0) {
+		if (errno != EEXIST && errno != ENOENT)
+			perror("mnl_talk:__nft_chain_del");
+	}
+
+	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_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) {
+		DEBUGP("cannot allocate rule list iterator\n");
+		return 0;
+	}
+
+	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++;
+next:
+		c = nft_chain_list_iter_next(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;
+}
+
+int nft_chain_user_rename(struct nft_handle *h,const char *chain,
+			  const char *table, const char *newname)
+{
+	int ret;
+
+	/* XXX need new operation in nf_tables to support this */
+	ret = nft_chain_user_del(h, chain, table);
+	if (ret < 0)
+		return ret;
+
+	return nft_chain_user_add(h, newname, table);
+}
+
+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(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;
+	int ret;
+	struct nft_table_list *list;
+
+	list = nft_table_list_alloc();
+	if (list == NULL) {
+		DEBUGP("cannot allocate table list\n");
+		return 0;
+	}
+
+	nlh = nft_rule_nlmsg_build_hdr(buf, NFT_MSG_GETTABLE, AF_INET,
+					NLM_F_DUMP, h->seq);
+
+	ret = mnl_talk(h, nlh, nft_table_list_cb, list);
+	if (ret < 0)
+		perror("mnl_talk:nft_table_list_get");
+
+	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) {
+		DEBUGP("cannot allocate rule list iterator\n");
+		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) {
+		DEBUGP("cannot allocate rule list iterator\n");
+		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;
+}
+
+static inline int
+match_different(const struct xt_entry_match *a,
+		const unsigned char *a_elems,
+		const unsigned char *b_elems,
+		unsigned char **maskptr)
+{
+	const struct xt_entry_match *b;
+	unsigned int i;
+
+	/* Offset of b is the same as a. */
+	b = (void *)b_elems + ((unsigned char *)a - a_elems);
+
+	if (a->u.match_size != b->u.match_size)
+		return 1;
+
+	if (strcmp(a->u.user.name, b->u.user.name) != 0)
+		return 1;
+
+	*maskptr += XT_ALIGN(sizeof(*a));
+
+	for (i = 0; i < a->u.match_size - XT_ALIGN(sizeof(*a)); i++)
+		if (((a->data[i] ^ b->data[i]) & (*maskptr)[i]) != 0)
+			return 1;
+	*maskptr += i;
+	return 0;
+}
+
+static bool
+is_same(const struct iptables_command_state *a, const struct iptables_command_state *b)
+{
+	unsigned int i;
+
+	/* Always compare head structures: ignore mask here. */
+	if (a->fw.ip.src.s_addr != b->fw.ip.src.s_addr
+	    || a->fw.ip.dst.s_addr != b->fw.ip.dst.s_addr
+	    || a->fw.ip.smsk.s_addr != b->fw.ip.smsk.s_addr
+	    || a->fw.ip.dmsk.s_addr != b->fw.ip.dmsk.s_addr
+	    || a->fw.ip.proto != b->fw.ip.proto
+	    || a->fw.ip.flags != b->fw.ip.flags
+	    || a->fw.ip.invflags != b->fw.ip.invflags) {
+		DEBUGP("different src/dst/proto/flags/invflags\n");
+		return false;
+	}
+
+	for (i = 0; i < IFNAMSIZ; i++) {
+		if (a->fw.ip.iniface_mask[i] != b->fw.ip.iniface_mask[i]) {
+			DEBUGP("different iniface mask %x, %x (%d)\n",
+			a->fw.ip.iniface_mask[i] & 0xff, b->fw.ip.iniface_mask[i] & 0xff, i);
+			return false;
+		}
+		if ((a->fw.ip.iniface[i] & a->fw.ip.iniface_mask[i])
+		    != (b->fw.ip.iniface[i] & b->fw.ip.iniface_mask[i])) {
+			DEBUGP("different iniface\n");
+			return false;
+		}
+		if (a->fw.ip.outiface_mask[i] != b->fw.ip.outiface_mask[i]) {
+			DEBUGP("different outiface mask\n");
+			return false;
+		}
+		if ((a->fw.ip.outiface[i] & a->fw.ip.outiface_mask[i])
+		    != (b->fw.ip.outiface[i] & b->fw.ip.outiface_mask[i])) {
+			DEBUGP("different outiface\n");
+			return false;
+		}
+	}
+
+	return true;
+}
+
+static void
+nft_parse_meta(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter,
+	       struct iptables_command_state *cs)
+{
+	uint8_t key = nft_rule_expr_get_u8(e, NFT_EXPR_META_KEY);
+	uint32_t value;
+	const char *name;
+	const void *ifname;
+	size_t len;
+
+	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;
+	}
+
+	switch(key) {
+	case NFT_META_IIF:
+		value = nft_rule_expr_get_u32(e, NFT_EXPR_CMP_DATA);
+		if (nft_rule_expr_get_u8(e, NFT_EXPR_CMP_OP) == NFT_CMP_NEQ)
+			cs->fw.ip.invflags |= IPT_INV_VIA_IN;
+
+		if_indextoname(value, cs->fw.ip.iniface);
+
+		memset(cs->fw.ip.iniface_mask, 0xff,
+		       strlen(cs->fw.ip.iniface)+1);
+		break;
+	case NFT_META_OIF:
+		value = nft_rule_expr_get_u32(e, NFT_EXPR_CMP_DATA);
+		if (nft_rule_expr_get_u8(e, NFT_EXPR_CMP_OP) == NFT_CMP_NEQ)
+			cs->fw.ip.invflags |= IPT_INV_VIA_OUT;
+
+		if_indextoname(value, cs->fw.ip.outiface);
+
+		memset(cs->fw.ip.outiface_mask, 0xff,
+		       strlen(cs->fw.ip.outiface)+1);
+		break;
+	case NFT_META_IIFNAME:
+		ifname = nft_rule_expr_get(e, NFT_EXPR_CMP_DATA, &len);
+		if (nft_rule_expr_get_u8(e, NFT_EXPR_CMP_OP) == NFT_CMP_NEQ)
+			cs->fw.ip.invflags |= IPT_INV_VIA_IN;
+
+		memcpy(cs->fw.ip.iniface, ifname, len);
+		cs->fw.ip.iniface[len] = '\0';
+
+		/* If zero, then this is an interface mask */
+		if (if_nametoindex(cs->fw.ip.iniface) == 0) {
+			cs->fw.ip.iniface[len] = '+';
+			cs->fw.ip.iniface[len+1] = '\0';
+		}
+
+		memset(cs->fw.ip.iniface_mask, 0xff, len);
+		break;
+	case NFT_META_OIFNAME:
+		ifname = nft_rule_expr_get(e, NFT_EXPR_CMP_DATA, &len);
+		if (nft_rule_expr_get_u8(e, NFT_EXPR_CMP_OP) == NFT_CMP_NEQ)
+			cs->fw.ip.invflags |= IPT_INV_VIA_OUT;
+
+		memcpy(cs->fw.ip.outiface, ifname, len);
+		cs->fw.ip.outiface[len] = '\0';
+
+		/* If zero, then this is an interface mask */
+		if (if_nametoindex(cs->fw.ip.outiface) == 0) {
+			cs->fw.ip.outiface[len] = '+';
+			cs->fw.ip.outiface[len+1] = '\0';
+		}
+
+		memset(cs->fw.ip.outiface_mask, 0xff, len);
+		break;
+	default:
+		DEBUGP("unknown meta key %d\n", key);
+		break;
+	}
+}
+
+static void
+nft_parse_payload(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter,
+		  struct iptables_command_state *cs)
+{
+	uint32_t offset;
+	bool inv;
+
+	offset = nft_rule_expr_get_u32(e, NFT_EXPR_PAYLOAD_OFFSET);
+
+	switch(offset) {
+	struct in_addr addr;
+	uint8_t proto;
+
+	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;
+	default:
+		DEBUGP("unknown payload offset %d\n", offset);
+		break;
+	}
+}
+
+static 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);
+}
+
+static void
+nft_parse_immediate(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter,
+		    struct iptables_command_state *cs)
+{
+	int verdict = nft_rule_expr_get_u32(e, NFT_EXPR_IMM_VERDICT);
+	const char *chain = nft_rule_expr_get_str(e, NFT_EXPR_IMM_CHAIN);
+
+	/* Standard target? */
+	switch(verdict) {
+	case NF_ACCEPT:
+		cs->jumpto = "ACCEPT";
+		return;
+	case NF_DROP:
+		cs->jumpto = "DROP";
+		return;
+	case NFT_RETURN:
+		cs->jumpto = "RETURN";
+		return;
+	case NFT_GOTO:
+		cs->fw.ip.flags |= IPT_F_GOTO;
+	case NFT_JUMP:
+		cs->jumpto = chain;
+		return;
+	}
+}
+
+static 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;
+
+	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, cs);
+		} else if (strcmp(name, "meta") == 0) {
+			nft_parse_meta(expr, iter, cs);
+		} else if (strcmp(name, "immediate") == 0) {
+			nft_parse_immediate(expr, iter, cs);
+		}
+
+		expr = nft_rule_expr_iter_next(iter);
+	}
+
+	nft_rule_expr_iter_destroy(iter);
+}
+
+static int matches_howmany(struct xtables_rule_match *matches)
+{
+	struct xtables_rule_match *matchp;
+	int matches_ctr = 0;
+
+	for (matchp = matches; matchp; matchp = matchp->next)
+		matches_ctr++;
+
+	return matches_ctr;
+}
+
+static bool
+__find_match(struct nft_rule_expr *expr, struct xtables_rule_match *matches)
+{
+	const char *matchname = nft_rule_expr_get_str(expr, NFT_EXPR_MT_NAME);
+	/* Netlink aligns this match info, don't trust this length variable */
+	const char *data = nft_rule_expr_get_str(expr, NFT_EXPR_MT_INFO);
+	struct xtables_rule_match *matchp;
+	bool found = false;
+
+	for (matchp = matches; matchp; matchp = matchp->next) {
+		struct xt_entry_match *m = matchp->match->m;
+
+		if (strcmp(m->u.user.name, matchname) != 0) {
+			DEBUGP("mismatching match name\n");
+			continue;
+		}
+
+		if (memcmp(data, m->data, m->u.user.match_size - sizeof(*m)) != 0) {
+			DEBUGP("mismatch match data\n");
+			continue;
+		}
+		found = true;
+		break;
+	}
+
+	return found;
+}
+
+static bool find_matches(struct xtables_rule_match *matches, struct nft_rule *r)
+{
+	struct nft_rule_expr_iter *iter;
+	struct nft_rule_expr *expr;
+	int kernel_matches = 0;
+
+	iter = nft_rule_expr_iter_create(r);
+	if (iter == NULL) {
+		DEBUGP("cannot allocate rule list iterator\n");
+		return false;
+	}
+
+	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, "match") == 0) {
+			if (!__find_match(expr, matches))
+				return false;
+
+			kernel_matches++;
+		}
+		expr = nft_rule_expr_iter_next(iter);
+	}
+	nft_rule_expr_iter_destroy(iter);
+
+	/* same number of matches? */
+	if (matches_howmany(matches) != kernel_matches)
+		return false;
+
+	return true;
+}
+
+static bool __find_target(struct nft_rule_expr *expr, struct xt_entry_target *t)
+{
+	size_t len;
+	const char *tgname = nft_rule_expr_get_str(expr, NFT_EXPR_TG_NAME);
+	/* Netlink aligns this target info, don't trust this length variable */
+	const char *data = nft_rule_expr_get(expr, NFT_EXPR_TG_INFO, &len);
+
+	if (strcmp(t->u.user.name, tgname) != 0) {
+		DEBUGP("mismatching target name\n");
+		return false;
+	}
+
+	if (memcmp(data, t->data,  t->u.user.target_size - sizeof(*t)) != 0)
+		return false;
+
+	return true;
+}
+
+static int targets_howmany(struct xtables_target *target)
+{
+	return target != NULL ? 1 : 0;
+}
+
+static bool
+find_target(struct xtables_target *target, struct nft_rule *r)
+{
+	struct nft_rule_expr_iter *iter;
+	struct nft_rule_expr *expr;
+	int kernel_targets = 0;
+
+	/* Special case: we use native immediate expressions to emulated
+	 * standard targets. Also, we don't want to crash with no targets.
+	 */
+	if (target == NULL || strcmp(target->name, "standard") == 0)
+		return true;
+
+	iter = nft_rule_expr_iter_create(r);
+	if (iter == NULL) {
+		DEBUGP("cannot allocate rule list iterator\n");
+		return false;
+	}
+
+	expr = nft_rule_expr_iter_next(iter);
+	while (expr != NULL) {
+		const char *name =
+			nft_rule_expr_get_str(expr, NFT_RULE_EXPR_ATTR_NAME);
+
+		if (strcmp(name, "target") == 0) {
+			/* we may support several targets in the future */
+			if (!__find_target(expr, target->t))
+				return false;
+
+			kernel_targets++;
+		}
+		expr = nft_rule_expr_iter_next(iter);
+	}
+	nft_rule_expr_iter_destroy(iter);
+
+	/* same number of targets? */
+	if (targets_howmany(target) != kernel_targets) {
+		DEBUGP("kernel targets is %d but we passed %d\n",
+		kernel_targets, targets_howmany(target));
+		return false;
+	}
+
+	return true;
+}
+
+static bool
+find_immediate(struct nft_rule *r, const char *jumpto)
+{
+	struct nft_rule_expr_iter *iter;
+	struct nft_rule_expr *expr;
+
+	iter = nft_rule_expr_iter_create(r);
+	if (iter == NULL) {
+		DEBUGP("cannot allocate rule list iterator\n");
+		return false;
+	}
+
+	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, "immediate") == 0) {
+			int verdict = nft_rule_expr_get_u32(expr, NFT_EXPR_IMM_VERDICT);
+			const char *verdict_name = NULL;
+
+			/* No target specified but immediate shows up, this
+			 * is not the rule we are looking for.
+			 */
+			if (strlen(jumpto) == 0)
+				return false;
+
+			switch(verdict) {
+			case NF_ACCEPT:
+				verdict_name = "ACCEPT";
+				break;
+			case NF_DROP:
+				verdict_name = "DROP";
+				break;
+			case NFT_RETURN:
+				verdict_name = "RETURN";
+				break;
+			}
+
+			/* Standard target? */
+			if (verdict_name && strcmp(jumpto, verdict_name) != 0)
+				return false;
+		}
+		expr = nft_rule_expr_iter_next(iter);
+	}
+	nft_rule_expr_iter_destroy(iter);
+
+	return true;
+}
+
+static void
+__nft_rule_del(struct nft_handle *h, struct nft_rule *r)
+{
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh;
+	int ret;
+
+	nlh = nft_rule_nlmsg_build_hdr(buf, NFT_MSG_DELRULE, AF_INET,
+					NLM_F_ACK, h->seq);
+	nft_rule_nlmsg_build_payload(nlh, r);
+
+	nft_rule_print_debug(r, nlh);
+
+	ret = mnl_talk(h, nlh, NULL, NULL);
+	if (ret < 0)
+		perror("mnl_talk:nft_rule_del");
+}
+
+static int
+__nft_rule_check(struct nft_handle *h, const char *chain, const char *table,
+		 struct iptables_command_state *cs,
+		 bool delete, int rulenum, bool verbose)
+{
+	struct nft_rule_list *list;
+	struct nft_rule_list_iter *iter;
+	struct nft_rule *r;
+	int ret = 0;
+	int rule_ctr = 0;
+	bool found = false;
+
+	list = nft_rule_list_get(h);
+	if (list == NULL) {
+		DEBUGP("cannot retrieve rule list from kernel\n");
+		return 0;
+	}
+
+	iter = nft_rule_list_iter_create(list);
+	if (iter == NULL) {
+		DEBUGP("cannot allocate rule list iterator\n");
+		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);
+		struct iptables_command_state this = {};
+
+		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) {
+				rule_ctr++;
+				goto next;
+			}
+		} else {
+			/* Delete by matching rule case */
+			DEBUGP("comparing with... ");
+#ifdef DEBUG_DEL
+			nft_rule_print_save(r, 0);
+#endif
+
+			nft_rule_to_iptables_command_state(r, &this);
+
+			if (!is_same(cs, &this))
+				goto next;
+
+			if (!find_matches(cs->matches, r)) {
+				DEBUGP("matches not found\n");
+				goto next;
+			}
+
+			if (!find_target(cs->target, r)) {
+				DEBUGP("target not found\n");
+				goto next;
+			}
+
+			if (!find_immediate(r, cs->jumpto)) {
+				DEBUGP("immediate not found\n");
+				goto next;
+			}
+
+			found = true;
+			break;
+		}
+next:
+		r = nft_rule_list_iter_next(iter);
+	}
+
+	if (found) {
+		ret = 1;
+
+		if (delete) {
+			DEBUGP("deleting rule\n");
+			__nft_rule_del(h, r);
+		}
+	}
+
+	nft_rule_list_iter_destroy(iter);
+	nft_rule_list_free(list);
+
+	if (ret == 0)
+		errno = ENOENT;
+
+	return ret;
+}
+
+int nft_rule_check(struct nft_handle *h, const char *chain,
+		   const char *table, struct iptables_command_state *e,
+		   bool verbose)
+{
+	nft_fn = nft_rule_check;
+
+	return __nft_rule_check(h, chain, table, e, false, -1, verbose);
+}
+
+int nft_rule_delete(struct nft_handle *h, const char *chain,
+		    const char *table, struct iptables_command_state *e,
+		    bool verbose)
+{
+	nft_fn = nft_rule_delete;
+
+	return __nft_rule_check(h, chain, table, e, true, -1, verbose);
+}
+
+int nft_rule_delete_num(struct nft_handle *h, const char *chain,
+			const char *table, int rulenum,
+			bool verbose)
+{
+	nft_fn = nft_rule_delete_num;
+
+	return __nft_rule_check(h, chain, table, NULL, true, rulenum, verbose);
+}
+
+int nft_rule_replace(struct nft_handle *h, const char *chain,
+		     const char *table, struct iptables_command_state *cs,
+		     int rulenum, bool verbose)
+{
+	int ret;
+
+	nft_fn = nft_rule_replace;
+
+	ret = __nft_rule_check(h, chain, table, NULL, true, rulenum, verbose);
+	if (ret < 0)
+		return ret;
+
+	/* XXX needs to be inserted in position, this is appending */
+	return nft_rule_add(h, chain, table, cs, true, verbose);
+}
+
+/*
+ * 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))
+
+static void
+print_num(uint64_t number, unsigned int format)
+{
+	if (format & FMT_KILOMEGAGIGA) {
+		if (number > 99999) {
+			number = (number + 500) / 1000;
+			if (number > 9999) {
+				number = (number + 500) / 1000;
+				if (number > 9999) {
+					number = (number + 500) / 1000;
+					if (number > 9999) {
+						number = (number + 500) / 1000;
+						printf(FMT("%4lluT ","%lluT "), (unsigned long long)number);
+					}
+					else printf(FMT("%4lluG ","%lluG "), (unsigned long long)number);
+				}
+				else printf(FMT("%4lluM ","%lluM "), (unsigned long long)number);
+			} else
+				printf(FMT("%4lluK ","%lluK "), (unsigned long long)number);
+		} else
+			printf(FMT("%5llu ","%llu "), (unsigned long long)number);
+	} else
+		printf(FMT("%8llu ","%llu "), (unsigned long long)number);
+}
+
+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);
+			print_num(counters->pcnt, (format|FMT_NOTABLE));
+			fputs("packets, ", stdout);
+			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 void
+print_match(struct nft_rule_expr *expr, int numeric)
+{
+	size_t len;
+	const char *match_name = nft_rule_expr_get_str(expr, NFT_EXPR_MT_NAME);
+	const void *match_info = nft_rule_expr_get(expr, NFT_EXPR_MT_INFO, &len);
+	const struct xtables_match *match =
+		xtables_find_match(match_name, XTF_TRY_LOAD, NULL);
+	struct xt_entry_match *m =
+		calloc(1, sizeof(struct xt_entry_match) + len);
+
+	/* emulate struct xt_entry_match since ->print needs it */
+	memcpy((void *)&m->data, match_info, len);
+
+	if (match) {
+		if (match->print)
+			/* FIXME missing first parameter */
+			match->print(NULL, m, numeric);
+		else
+			printf("%s ", match_name);
+	} else {
+		if (match_name[0])
+			printf("UNKNOWN match `%s' ", match_name);
+	}
+
+	free(m);
+}
+
+static void
+print_firewall(const struct iptables_command_state *cs, struct nft_rule *r,
+	       unsigned int num, unsigned int format)
+{
+	const struct xtables_target *target = NULL;
+	const char *targname = NULL;
+	const void *targinfo = NULL;
+	uint8_t flags;
+	char buf[BUFSIZ];
+	struct nft_rule_expr_iter *iter;
+	struct nft_rule_expr *expr;
+	struct xt_entry_target *t;
+	size_t target_len = 0;
+
+	iter = nft_rule_expr_iter_create(r);
+	if (iter == NULL) {
+		DEBUGP("cannot allocate rule list iterator\n");
+		return;
+	}
+
+	expr = nft_rule_expr_iter_next(iter);
+	while (expr != NULL) {
+		const char *name =
+			nft_rule_expr_get_str(expr, NFT_RULE_EXPR_ATTR_NAME);
+
+		if (strcmp(name, "target") == 0) {
+			targname = nft_rule_expr_get_str(expr, NFT_EXPR_TG_NAME);
+			targinfo = nft_rule_expr_get(expr, NFT_EXPR_TG_INFO, &target_len);
+			break;
+		} else if (strcmp(name, "immediate") == 0) {
+			uint32_t verdict =
+			nft_rule_expr_get_u32(expr, NFT_EXPR_IMM_VERDICT);
+
+			switch(verdict) {
+			case NF_ACCEPT:
+				targname = "ACCEPT";
+				break;
+			case NF_DROP:
+				targname = "DROP";
+				break;
+			case NFT_RETURN:
+				targname = "RETURN";
+				break;
+			case NFT_GOTO:
+				targname = nft_rule_expr_get_str(expr, NFT_EXPR_IMM_CHAIN);
+				break;
+			case NFT_JUMP:
+				targname = nft_rule_expr_get_str(expr, NFT_EXPR_IMM_CHAIN);
+			break;
+			}
+		}
+		expr = nft_rule_expr_iter_next(iter);
+	}
+	nft_rule_expr_iter_destroy(iter);
+
+	flags = cs->fw.ip.flags;
+
+	if (format & FMT_LINENUMBERS)
+		printf(FMT("%-4u ", "%u "), num);
+
+	if (!(format & FMT_NOCOUNTS)) {
+		print_num(cs->counters.pcnt, format);
+		print_num(cs->counters.bcnt, format);
+	}
+
+	if (!(format & FMT_NOTARGET))
+		printf(FMT("%-9s ", "%s "), targname ? targname : "");
+
+	fputc(cs->fw.ip.invflags & XT_INV_PROTO ? '!' : ' ', stdout);
+	{
+		const char *pname =
+			proto_to_name(cs->fw.ip.proto, format&FMT_NUMERIC);
+		if (pname)
+			printf(FMT("%-5s", "%s "), pname);
+		else
+			printf(FMT("%-5hu", "%hu "), cs->fw.ip.proto);
+	}
+
+	if (format & FMT_OPTIONS) {
+		if (format & FMT_NOTABLE)
+			fputs("opt ", stdout);
+		fputc(cs->fw.ip.invflags & IPT_INV_FRAG ? '!' : '-', stdout);
+		fputc(flags & IPT_F_FRAG ? 'f' : '-', stdout);
+		fputc(' ', stdout);
+	}
+
+	if (format & FMT_VIA) {
+		char iface[IFNAMSIZ+2];
+		if (cs->fw.ip.invflags & IPT_INV_VIA_IN) {
+			iface[0] = '!';
+			iface[1] = '\0';
+		}
+		else iface[0] = '\0';
+
+		if (cs->fw.ip.iniface[0] != '\0') {
+			strcat(iface, cs->fw.ip.iniface);
+		}
+		else if (format & FMT_NUMERIC) strcat(iface, "*");
+		else strcat(iface, "any");
+		printf(FMT(" %-6s ","in %s "), iface);
+
+		if (cs->fw.ip.invflags & IPT_INV_VIA_OUT) {
+			iface[0] = '!';
+			iface[1] = '\0';
+		}
+		else iface[0] = '\0';
+
+		if (cs->fw.ip.outiface[0] != '\0') {
+			strcat(iface, cs->fw.ip.outiface);
+		}
+		else if (format & FMT_NUMERIC) strcat(iface, "*");
+		else strcat(iface, "any");
+		printf(FMT("%-6s ","out %s "), iface);
+	}
+
+	fputc(cs->fw.ip.invflags & IPT_INV_SRCIP ? '!' : ' ', stdout);
+	if (cs->fw.ip.smsk.s_addr == 0L && !(format & FMT_NUMERIC))
+		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);
+	}
+
+	if (format & FMT_NOTABLE)
+		fputs("  ", stdout);
+
+#ifdef IPT_F_GOTO
+	if(cs->fw.ip.flags & IPT_F_GOTO)
+		printf("[goto] ");
+#endif
+
+	iter = nft_rule_expr_iter_create(r);
+	if (iter == NULL) {
+		DEBUGP("cannot allocate rule list iterator\n");
+		return;
+	}
+
+	expr = nft_rule_expr_iter_next(iter);
+	while (expr != NULL) {
+		const char *name =
+			nft_rule_expr_get_str(expr, NFT_RULE_EXPR_ATTR_NAME);
+
+		if (strcmp(name, "match") == 0)
+			print_match(expr, format & FMT_NUMERIC);
+
+		expr = nft_rule_expr_iter_next(iter);
+	}
+	nft_rule_expr_iter_destroy(iter);
+
+	t = calloc(1, sizeof(struct xt_entry_target) + target_len);
+	if (t == NULL)
+		return;
+
+	/* emulate struct xt_entry_match since ->print needs it */
+	memcpy((void *)&t->data, targinfo, target_len);
+
+	if (targname) {
+		target = xtables_find_target(targname, XTF_TRY_LOAD);
+		if (target) {
+			if (target->print)
+				/* FIXME missing first parameter */
+				target->print(NULL, t, format & FMT_NUMERIC);
+		} else
+			printf("[%ld bytes of unknown target data] ",
+				target_len);
+	}
+	free(t);
+
+	if (!(format & FMT_NONEWLINE))
+		fputc('\n', stdout);
+}
+
+static int
+__nft_rule_list(struct nft_handle *h, struct nft_chain *c, const char *table,
+		int rulenum, unsigned int format,
+		void (*cb)(const struct iptables_command_state *cs,
+			   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;
+	const char *chain = nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_NAME);
+
+	list = nft_rule_list_get(h);
+	if (list == NULL) {
+		DEBUGP("cannot retrieve rule list from kernel\n");
+		return 0;
+	}
+
+	iter = nft_rule_list_iter_create(list);
+	if (iter == NULL) {
+		DEBUGP("cannot allocate rule list iterator\n");
+		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);
+
+		rule_ctr++;
+
+		if (strcmp(table, rule_table) != 0 ||
+		    strcmp(chain, rule_chain) != 0)
+			goto next;
+
+		if (rulenum > 0) {
+			/* List by rule number case */
+			if (rule_ctr != rulenum) {
+				rule_ctr++;
+				goto next;
+			}
+		} else {
+			struct iptables_command_state cs = {};
+			/* Show all rules case */
+			nft_rule_to_iptables_command_state(r, &cs);
+
+			cb(&cs, r, rule_ctr, format);
+		}
+next:
+		r = nft_rule_list_iter_next(iter);
+	}
+
+	nft_rule_list_iter_destroy(iter);
+	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)
+{
+	struct nft_chain_list *list;
+	struct nft_chain_list_iter *iter;
+	struct nft_chain *c;
+
+	list = nft_chain_dump(h);
+
+	iter = nft_chain_list_iter_create(list);
+	if (iter == NULL) {
+		DEBUGP("cannot allocate rule list iterator\n");
+		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);
+		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;
+
+		print_header(format, chain_name, policy_name[policy], &ctrs,
+			     basechain, refs);
+
+		/* this is a base chain */
+		if (nft_chain_attr_get(c, NFT_CHAIN_ATTR_HOOKNUM)) {
+			__nft_rule_list(h, c, table, rulenum, format,
+					print_firewall);
+		}
+next:
+		c = nft_chain_list_iter_next(iter);
+	}
+
+	nft_chain_list_free(list);
+
+	return 1;
+}
+
+static void
+list_save(const struct iptables_command_state *cs, struct nft_rule *r,
+	  unsigned int num, unsigned int format)
+{
+	nft_rule_print_save(r, !(format & FMT_NOCOUNTS));
+}
+
+static int
+nft_rule_list_chain_save(struct nft_handle *h, 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) {
+		DEBUGP("cannot allocate rule list iterator\n");
+		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)
+			goto next;
+
+		/* this is a base chain */
+		if (nft_chain_attr_get(c, NFT_CHAIN_ATTR_HOOKNUM)) {
+			printf("-P %s %s", chain_name, policy_name[policy]);
+
+			if (counters) {
+				printf(" -c %lu %lu\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);
+	}
+
+	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;
+
+	list = nft_chain_dump(h);
+
+	/* Dump policies and custom chains first */
+	nft_rule_list_chain_save(h, table, list, counters);
+
+	/* Now dump out rules in this table */
+	iter = nft_chain_list_iter_create(list);
+	if (iter == NULL) {
+		DEBUGP("cannot allocate rule list iterator\n");
+		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);
+
+		if (strcmp(table, chain_table) != 0)
+			goto next;
+		if (chain && strcmp(chain, chain_name) != 0)
+			goto next;
+
+		__nft_rule_list(h, c, table, rulenum,
+				counters ? 0 : FMT_NOCOUNTS, list_save);
+next:
+		c = nft_chain_list_iter_next(iter);
+	}
+
+	nft_chain_list_free(list);
+
+	return 1;
+}
+
+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)
+		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);
+}
diff --git a/iptables/nft.h b/iptables/nft.h
new file mode 100644
index 0000000..f5a9efb
--- /dev/null
+++ b/iptables/nft.h
@@ -0,0 +1,62 @@
+#ifndef _NFT_H_
+#define _NFT_H_
+
+#include "xshared.h"
+
+struct nft_handle {
+	struct mnl_socket	*nl;
+	uint32_t		portid;
+	uint32_t		seq;
+};
+
+int nft_init(struct nft_handle *h);
+void nft_fini(struct nft_handle *h);
+
+/*
+ * Operations with tables.
+ */
+struct nft_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);
+
+/*
+ * Operations with chains.
+ */
+struct nft_chain;
+
+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);
+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);
+
+/*
+ * Operations with rule-set.
+ */
+struct nft_rule;
+
+int nft_rule_add(struct nft_handle *h, const char *chain, const char *table, struct iptables_command_state *cmd, bool append, bool verbose);
+int nft_rule_check(struct nft_handle *h, const char *chain, const char *table, struct iptables_command_state *cmd, bool verbose);
+int nft_rule_delete(struct nft_handle *h, const char *chain, const char *table, struct iptables_command_state *cmd, 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, struct iptables_command_state *cmd, 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);
+
+/*
+ * revision compatibility.
+ */
+int nft_compatible_revision(const char *name, uint8_t rev, int opt);
+
+/*
+ * Error reporting.
+ */
+const char *nft_strerror(int err);
+
+#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-config-parser.y b/iptables/xtables-config-parser.y
new file mode 100644
index 0000000..fe5bcbf
--- /dev/null
+++ b/iptables/xtables-config-parser.y
@@ -0,0 +1,213 @@
+%{
+/*
+ * (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 <libnftables/table.h>
+#include <libnftables/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)+1);
+}
+
+static void stack_free(struct stack_elem *e)
+{
+	free(e);
+}
+
+%}
+
+%union {
+	int	val;
+	char	*string;
+}
+
+%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		: table
+		;
+
+table		: T_TABLE T_STRING T_PRIO T_INTEGER '{' chains '}'
+		{
+			/* added in reverse order to pop it in order */
+			void *data = stack_push(T_PRIO, sizeof(int32_t));
+			stack_put_i32(data, $4);
+			data = stack_push(T_TABLE, strlen($2));
+			stack_put_str(data, $2);
+		}
+		;
+
+chains		: chain
+		| chains chain
+		;
+
+chain		: T_CHAIN T_STRING T_HOOK T_STRING
+		{
+			/* added in reverse order to pop it in order */
+			void *data = stack_push(T_HOOK, strlen($4));
+			stack_put_str(data, $4);
+			data = stack_push(T_CHAIN, strlen($2));
+			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;
+}
+
+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;
+
+	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_TABLE:
+			table = nft_table_alloc();
+			if (table == NULL) {
+				perror("nft_table_alloc");
+				return -1;
+			}
+			nft_table_attr_set(table, NFT_TABLE_ATTR_NAME, e->data);
+			nft_table_list_add(table, table_list);
+			break;
+		case T_PRIO:
+			prio = *((int32_t *)e->data);
+			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(chain, NFT_CHAIN_ATTR_NAME, e->data);
+			nft_chain_list_add(chain, chain_list);
+			break;
+		case T_HOOK:
+			nft_chain_attr_set_u32(chain, NFT_CHAIN_ATTR_HOOKNUM,
+						hooknametonum(e->data));
+			nft_chain_attr_set_s32(chain, NFT_CHAIN_ATTR_PRIO, prio);
+			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..7a66ef3
--- /dev/null
+++ b/iptables/xtables-config-syntax.l
@@ -0,0 +1,53 @@
+%{
+/*
+ * (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\.\-\_]*
+
+%%
+"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..16918bf
--- /dev/null
+++ b/iptables/xtables-config.c
@@ -0,0 +1,107 @@
+/*
+ * (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 <libnftables/table.h>
+#include <libnftables/chain.h>
+
+#include "xtables-multi.h"
+#include "xtables-config-parser.h"
+
+#include "nft.h"
+
+extern int xtables_config_parse(const char *filename,
+				struct nft_table_list *table_list,
+				struct nft_chain_list *chain_list);
+
+#define XTABLES_CONFIG_DEFAULT	"/etc/xtables.conf"
+
+int xtables_config_main(int argc, char *argv[])
+{
+	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;
+	struct nft_chain_list_iter *citer;
+	struct nft_table *table;
+	struct nft_chain *chain;
+	const char *filename = NULL;
+	struct nft_handle h;
+
+	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 (xtables_config_parse(filename, table_list, chain_list) < 0) {
+		if (errno == ENOENT) {
+			fprintf(stderr, "configuration file `%s' does not "
+					"exists\n", filename);
+		} else {
+			fprintf(stderr, "Fatal error: %s\n", strerror(errno));
+		}
+		return EXIT_FAILURE;
+	}
+
+	nft_init(&h);
+
+	/* Stage 1) create tables */
+	titer = nft_table_list_iter_create(table_list);
+	while ((table = nft_table_list_iter_next(titer)) != NULL) {
+		if (nft_table_add(&h, table) < 0) {
+			if (errno == EEXIST) {
+				printf("table `%s' already exists, skipping\n",
+					(char *)nft_table_attr_get(table, NFT_TABLE_ATTR_NAME));
+			} else {
+				printf("table `%s' cannot be create, reason `%s'. Exitting\n",
+					(char *)nft_table_attr_get(table, NFT_TABLE_ATTR_NAME),
+					strerror(errno));
+				return EXIT_FAILURE;
+			}
+			continue;
+		}
+		printf("table `%s' has been created\n",
+			(char *)nft_table_attr_get(table, NFT_TABLE_ATTR_NAME));
+	}
+
+	/* Stage 2) create chains */
+	citer = nft_chain_list_iter_create(chain_list);
+	while ((chain = nft_chain_list_iter_next(citer)) != NULL) {
+		if (nft_chain_add(&h, chain) < 0) {
+			if (errno == EEXIST) {
+				printf("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 {
+				printf("chain `%s' cannot be create, reason `%s'. Exitting\n",
+					(char *)nft_chain_attr_get(chain, NFT_CHAIN_ATTR_NAME),
+					strerror(errno));
+				return EXIT_FAILURE;
+			}
+			continue;
+		}
+
+		printf("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));
+	}
+
+	return EXIT_SUCCESS;
+}
diff --git a/iptables/xtables-multi.c b/iptables/xtables-multi.c
index 8014d5f..c174643 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,12 @@
 	{"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},
+#endif
 	{NULL},
 };
 
diff --git a/iptables/xtables-multi.h b/iptables/xtables-multi.h
index 615724b..be2b3ad 100644
--- a/iptables/xtables-multi.h
+++ b/iptables/xtables-multi.h
@@ -2,5 +2,9 @@
 #define _XTABLES_MULTI_H 1
 
 extern int iptables_xml_main(int, char **);
+extern int xtables_main(int, char **);
+extern int xtables_save_main(int, char **);
+extern int xtables_restore_main(int, char **);
+extern int xtables_config_main(int, char **);
 
 #endif /* _XTABLES_MULTI_H */
diff --git a/iptables/xtables-restore.c b/iptables/xtables-restore.c
new file mode 100644
index 0000000..09922a0
--- /dev/null
+++ b/iptables/xtables-restore.c
@@ -0,0 +1,417 @@
+/* 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"
+
+#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'},
+	{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!");
+		}
+	}
+}
+
+int
+xtables_restore_main(int argc, char *argv[])
+{
+	struct nft_handle h;
+	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 = &iptc_ops;
+
+	line = 0;
+
+	xtables_globals.program_name = "xtables-restore";
+	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
+
+	nft_init(&h);
+
+	while ((c = getopt_long(argc, argv, "bcvthnM:T:", 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;
+		}
+	}
+
+	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;
+
+	/* 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)) {
+			/* FIXME commit/testing operation not supported */
+			if (!testing) {
+				DEBUGP("Calling commit\n");
+				ret = 1;
+			} else {
+				DEBUGP("Not calling commit, testing\n");
+				ret = 1;
+			}
+			in_table = 0;
+		} 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);
+
+				DEBUGP("Deleting all user-defined chains "
+				       "of table '%s'\n", table);
+				nft_chain_user_del(&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);
+			}
+
+			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);
+
+				}
+
+				DEBUGP("Setting policy of chain %s to %s\n",
+					chain, policy);
+			}
+
+			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));
+			}
+
+			ret = 1;
+
+		} 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]);
+
+			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;
+}
diff --git a/iptables/xtables-save.c b/iptables/xtables-save.c
new file mode 100644
index 0000000..046c948
--- /dev/null
+++ b/iptables/xtables-save.c
@@ -0,0 +1,122 @@
+/* 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 <libnftables/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'},
+	{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
+ */
+int
+xtables_save_main(int argc, char *argv[])
+{
+	const char *tablename = NULL;
+	struct nft_handle h;
+	int c;
+
+	xtables_globals.program_name = "xtables-save";
+	/* 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
+	nft_init(&h);
+
+	while ((c = getopt_long(argc, argv, "bcdt:", 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':
+			do_output(&h, tablename, show_counters);
+			exit(0);
+		}
+	}
+
+	if (optind < argc) {
+		fprintf(stderr, "Unknown arguments found on commandline\n");
+		exit(1);
+	}
+
+	return !do_output(&h, tablename, show_counters);
+}
diff --git a/iptables/xtables-standalone.c b/iptables/xtables-standalone.c
new file mode 100644
index 0000000..f746c90
--- /dev/null
+++ b/iptables/xtables-standalone.c
@@ -0,0 +1,80 @@
+/*
+ * 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"
+
+int
+xtables_main(int argc, char *argv[])
+{
+	int ret;
+	char *table = "filter";
+	struct nft_handle h;
+
+	iptables_globals.program_name = "xtables";
+	ret = xtables_init_all(&xtables_globals, NFPROTO_IPV4);
+	if (ret < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
+				iptables_globals.program_name,
+				iptables_globals.program_version);
+				exit(1);
+	}
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+	init_extensions();
+	init_extensions4();
+#endif
+
+	nft_init(&h);
+
+	ret = do_commandx(&h, argc, argv, &table);
+	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);
+}
diff --git a/iptables/xtables.c b/iptables/xtables.c
new file mode 100644
index 0000000..a687575
--- /dev/null
+++ b/iptables/xtables.c
@@ -0,0 +1,1251 @@
+/* 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.h"
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#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))
+
+
+#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
+#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 iptables_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 = iptables_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
+
+/* 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
+
+enum {
+	IPT_DOTTED_ADDR = 0,
+	IPT_DOTTED_MASK
+};
+
+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);
+}
+
+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,
+	  unsigned int nsaddrs,
+	  const struct in_addr saddrs[],
+	  const struct in_addr smasks[],
+	  unsigned int ndaddrs,
+	  const struct in_addr daddrs[],
+	  const struct in_addr dmasks[],
+	  bool verbose, struct nft_handle *h, bool append)
+{
+	unsigned int i, j;
+	int ret = 1;
+
+	for (i = 0; i < nsaddrs; i++) {
+		cs->fw.ip.src.s_addr = saddrs[i].s_addr;
+		cs->fw.ip.smsk.s_addr = smasks[i].s_addr;
+		for (j = 0; j < ndaddrs; j++) {
+			cs->fw.ip.dst.s_addr = daddrs[j].s_addr;
+			cs->fw.ip.dmsk.s_addr = dmasks[j].s_addr;
+
+			ret = nft_rule_add(h, chain, table, cs, append, verbose);
+		}
+	}
+
+	return ret;
+}
+
+static int
+replace_entry(const char *chain, const char *table,
+	      struct iptables_command_state *cs,
+	      unsigned int rulenum,
+	      const struct in_addr *saddr, const struct in_addr *smask,
+	      const struct in_addr *daddr, const struct in_addr *dmask,
+	      bool verbose, struct nft_handle *h)
+{
+	cs->fw.ip.src.s_addr = saddr->s_addr;
+	cs->fw.ip.dst.s_addr = daddr->s_addr;
+	cs->fw.ip.smsk.s_addr = smask->s_addr;
+	cs->fw.ip.dmsk.s_addr = dmask->s_addr;
+
+	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,
+	     unsigned int nsaddrs,
+	     const struct in_addr saddrs[],
+	     const struct in_addr smasks[],
+	     unsigned int ndaddrs,
+	     const struct in_addr daddrs[],
+	     const struct in_addr dmasks[],
+	     bool verbose,
+	     struct nft_handle *h)
+{
+	unsigned int i, j;
+	int ret = 1;
+
+	for (i = 0; i < nsaddrs; i++) {
+		cs->fw.ip.src.s_addr = saddrs[i].s_addr;
+		cs->fw.ip.smsk.s_addr = smasks[i].s_addr;
+		for (j = 0; j < ndaddrs; j++) {
+			cs->fw.ip.dst.s_addr = daddrs[j].s_addr;
+			cs->fw.ip.dmsk.s_addr = dmasks[j].s_addr;
+			ret = nft_rule_delete(h, chain, table, cs, verbose);
+		}
+	}
+
+	return ret;
+}
+
+static int
+check_entry(const char *chain, const char *table,
+	    struct iptables_command_state *cs,
+	    unsigned int nsaddrs, const struct in_addr *saddrs,
+	    const struct in_addr *smasks, unsigned int ndaddrs,
+	    const struct in_addr *daddrs, const struct in_addr *dmasks,
+	    bool verbose, struct nft_handle *h)
+{
+	unsigned int i, j;
+	int ret = 1;
+
+	for (i = 0; i < nsaddrs; i++) {
+		cs->fw.ip.src.s_addr = saddrs[i].s_addr;
+		cs->fw.ip.smsk.s_addr = smasks[i].s_addr;
+		for (j = 0; j < ndaddrs; j++) {
+			cs->fw.ip.dst.s_addr = daddrs[j].s_addr;
+			cs->fw.ip.dmsk.s_addr = dmasks[j].s_addr;
+			ret = nft_rule_check(h, chain, table, cs, verbose);
+		}
+	}
+
+	return ret;
+}
+
+static int
+zero_entries(const xt_chainlabel chain, int verbose,
+	     struct xtc_handle *handle)
+{
+	/* XXX iterate over chains and reset counters */
+	return 1;
+}
+
+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;
+
+	/* FIXME should return found or not, and errno = ENOENT in such case */
+	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);
+
+	/* FIXME found */
+	return 1;
+}
+
+static void clear_rule_matches(struct xtables_rule_match **matches)
+{
+	struct xtables_rule_match *matchp, *tmp;
+
+	for (matchp = *matches; matchp;) {
+		tmp = matchp->next;
+		if (matchp->match->m) {
+			free(matchp->match->m);
+			matchp->match->m = NULL;
+		}
+		if (matchp->match == matchp->match->next) {
+			free(matchp->match);
+			matchp->match = NULL;
+		}
+		free(matchp);
+		matchp = tmp;
+	}
+
+	*matches = NULL;
+}
+
+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(iptables_globals.orig_opts, opts,
+					    cs->target->x6_options,
+					    &cs->target->option_offset);
+	else
+		opts = xtables_merge_options(iptables_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(iptables_globals.orig_opts, opts,
+					    m->x6_options, &m->option_offset);
+	else if (m->extra_opts != NULL)
+		opts = xtables_merge_options(iptables_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)
+{
+	struct iptables_command_state cs;
+	unsigned int nsaddrs = 0, ndaddrs = 0;
+	struct in_addr *saddrs = NULL, *smasks = NULL;
+	struct in_addr *daddrs = NULL, *dmasks = NULL;
+
+	int verbose = 0;
+	const char *chain = NULL;
+	const char *shostnetworkmask = NULL, *dhostnetworkmask = NULL;
+	const char *policy = NULL, *newname = NULL;
+	unsigned int rulenum = 0, command = 0;
+	const char *pcnt = NULL, *bcnt = NULL;
+	int ret = 1;
+	struct xtables_match *m;
+	struct xtables_rule_match *matchp;
+	struct xtables_target *t;
+	unsigned long long cnt;
+
+	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;
+
+	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, &cs.fw.ip.invflags,
+				   cs.invert);
+
+			/* Canonicalize into lower case */
+			for (cs.protocol = optarg; *cs.protocol; cs.protocol++)
+				*cs.protocol = tolower(*cs.protocol);
+
+			cs.protocol = optarg;
+			cs.fw.ip.proto = xtables_parse_protocol(cs.protocol);
+
+			if (cs.fw.ip.proto == 0
+			    && (cs.fw.ip.invflags & XT_INV_PROTO))
+				xtables_error(PARAMETER_PROBLEM,
+					   "rule would never match protocol");
+			break;
+
+		case 's':
+			set_option(&cs.options, OPT_SOURCE, &cs.fw.ip.invflags,
+				   cs.invert);
+			shostnetworkmask = optarg;
+			break;
+
+		case 'd':
+			set_option(&cs.options, OPT_DESTINATION, &cs.fw.ip.invflags,
+				   cs.invert);
+			dhostnetworkmask = optarg;
+			break;
+
+#ifdef IPT_F_GOTO
+		case 'g':
+			set_option(&cs.options, OPT_JUMP, &cs.fw.ip.invflags,
+				   cs.invert);
+			cs.fw.ip.flags |= IPT_F_GOTO;
+			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, &cs.fw.ip.invflags,
+				   cs.invert);
+			xtables_parse_interface(optarg,
+					cs.fw.ip.iniface,
+					cs.fw.ip.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, &cs.fw.ip.invflags,
+				   cs.invert);
+			xtables_parse_interface(optarg,
+					cs.fw.ip.outiface,
+					cs.fw.ip.outiface_mask);
+			break;
+
+		case 'f':
+			set_option(&cs.options, OPT_FRAGMENT, &cs.fw.ip.invflags,
+				   cs.invert);
+			cs.fw.ip.flags |= IPT_F_FRAG;
+			break;
+
+		case 'v':
+			if (!verbose)
+				set_option(&cs.options, OPT_VERBOSE,
+					   &cs.fw.ip.invflags, cs.invert);
+			verbose++;
+			break;
+
+		case 'm':
+			command_match(&cs);
+			break;
+
+		case 'n':
+			set_option(&cs.options, OPT_NUMERIC, &cs.fw.ip.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, &cs.fw.ip.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 '0':
+			set_option(&cs.options, OPT_LINENUMBERS, &cs.fw.ip.invflags,
+				   cs.invert);
+			break;
+
+		case 'M':
+			xtables_modprobe_program = optarg;
+			break;
+
+		case 'c':
+
+			set_option(&cs.options, OPT_COUNTERS, &cs.fw.ip.invflags,
+				   cs.invert);
+			pcnt = optarg;
+			bcnt = strchr(pcnt + 1, ',');
+			if (bcnt)
+			    bcnt++;
+			if (!bcnt && optind < argc && argv[optind][0] != '-'
+			    && argv[optind][0] != '!')
+				bcnt = argv[optind++];
+			if (!bcnt)
+				xtables_error(PARAMETER_PROBLEM,
+					"-%c requires packet and byte counter",
+					opt2char(OPT_COUNTERS));
+
+			if (sscanf(pcnt, "%llu", &cnt) != 1)
+				xtables_error(PARAMETER_PROBLEM,
+					"-%c packet counter not numeric",
+					opt2char(OPT_COUNTERS));
+			cs.counters.pcnt = cnt;
+
+			if (sscanf(bcnt, "%llu", &cnt) != 1)
+				xtables_error(PARAMETER_PROBLEM,
+					"-%c byte counter not numeric",
+					opt2char(OPT_COUNTERS));
+			cs.counters.bcnt = cnt;
+			break;
+
+		case '4':
+			/* This is indeed the IPv4 iptables */
+			break;
+
+		case '6':
+			/* This is not the IPv6 ip6tables */
+			if (line != -1)
+				return 1; /* success: line ignored */
+			fprintf(stderr, "This is the IPv4 version of iptables.\n");
+			exit_tryhelp(2);
+
+		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, &iptables_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 !");
+
+	if (command & (CMD_REPLACE | CMD_INSERT | CMD_DELETE | CMD_APPEND | CMD_CHECK)) {
+		if (!(cs.options & OPT_DESTINATION))
+			dhostnetworkmask = "0.0.0.0/0";
+		if (!(cs.options & OPT_SOURCE))
+			shostnetworkmask = "0.0.0.0/0";
+	}
+
+	if (shostnetworkmask)
+		xtables_ipparse_multiple(shostnetworkmask, &saddrs,
+					 &smasks, &nsaddrs);
+
+	if (dhostnetworkmask)
+		xtables_ipparse_multiple(dhostnetworkmask, &daddrs,
+					 &dmasks, &ndaddrs);
+
+	if ((nsaddrs > 1 || ndaddrs > 1) &&
+	    (cs.fw.ip.invflags & (IPT_INV_SRCIP | IPT_INV_DSTIP)))
+		xtables_error(PARAMETER_PROBLEM, "! not allowed with multiple"
+			   " source or destination IP addresses");
+
+	if (command == CMD_REPLACE && (nsaddrs != 1 || ndaddrs != 1))
+		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,
+				nsaddrs, saddrs, smasks,
+				ndaddrs, daddrs, dmasks,
+				cs.options&OPT_VERBOSE,
+				h, true);
+		break;
+	case CMD_DELETE:
+		ret = delete_entry(chain, *table, &cs,
+				   nsaddrs, saddrs, smasks,
+				   ndaddrs, daddrs, dmasks,
+				   cs.options&OPT_VERBOSE, h);
+		break;
+	case CMD_DELETE_NUM:
+		ret = nft_rule_delete_num(h, chain, *table, rulenum - 1, verbose);
+		break;
+	case CMD_CHECK:
+		ret = check_entry(chain, *table, &cs,
+				   nsaddrs, saddrs, smasks,
+				   ndaddrs, daddrs, dmasks,
+				   cs.options&OPT_VERBOSE, h);
+		break;
+	case CMD_REPLACE:
+		/* FIXME replace at rulenum */
+		ret = replace_entry(chain, *table, &cs, rulenum - 1,
+				    saddrs, smasks, daddrs, dmasks,
+				    cs.options&OPT_VERBOSE, h);
+		break;
+	case CMD_INSERT:
+		/* FIXME insert at rulenum */
+		ret = add_entry(chain, *table, &cs,
+				nsaddrs, saddrs, smasks,
+				ndaddrs, daddrs, dmasks,
+				cs.options&OPT_VERBOSE, h, false);
+		break;
+	case CMD_FLUSH:
+		ret = nft_rule_flush(h, chain, *table);
+		break;
+	case CMD_ZERO:
+		/* FIXME */
+//		ret = zero_entries(chain, cs.options&OPT_VERBOSE, *handle);
+		break;
+	case CMD_ZERO_NUM:
+		/* FIXME */
+//		ret = iptc_zero_counter(chain, rulenum, *handle);
+		break;
+	case CMD_LIST:
+	case CMD_LIST|CMD_ZERO:
+	case CMD_LIST|CMD_ZERO_NUM:
+		/* FIXME */
+		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 = zero_entries(chain,
+					   cs.options&OPT_VERBOSE, *handle);
+		if (ret && (command & CMD_ZERO_NUM))
+			ret = iptc_zero_counter(chain, rulenum, *handle); */
+		break;
+	case CMD_LIST_RULES:
+	case CMD_LIST_RULES|CMD_ZERO:
+	case CMD_LIST_RULES|CMD_ZERO_NUM:
+		/* FIXME */
+		ret = list_rules(h, chain, *table, rulenum, cs.options&OPT_VERBOSE);
+/*		if (ret && (command & CMD_ZERO))
+			ret = zero_entries(chain,
+					   cs.options&OPT_VERBOSE, *handle);
+		if (ret && (command & CMD_ZERO_NUM))
+			ret = iptc_zero_counter(chain, rulenum, *handle); */
+		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:
+		/* XXX iptables allows renaming an used chain, we don't */
+		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); */
+
+	clear_rule_matches(&cs.matches);
+
+	free(saddrs);
+	free(smasks);
+	free(daddrs);
+	free(dmasks);
+	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)