Project import generated by Copybara.

GitOrigin-RevId: 3e0db6fd0aae4b9a7d999da784bf7cc6d086069d
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..cec2851
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,70 @@
+package {
+    default_applicable_licenses: ["external_iptables_license"],
+}
+
+// Added automatically by a large-scale-change that took the approach of
+// 'apply every license found to every target'. While this makes sure we respect
+// every license restriction, it may not be entirely correct.
+//
+// e.g. GPL in an MIT project might only apply to the contrib/ directory.
+//
+// Please consider splitting the single license below into multiple licenses,
+// taking care not to lose any license_kind information, and overriding the
+// default license using the 'licenses: [...]' property on targets as needed.
+//
+// For unused files, consider creating a 'fileGroup' with "//visibility:private"
+// to attach the license to, and including a comment whether the files may be
+// used in the current project.
+// See: http://go/android-license-faq
+license {
+    name: "external_iptables_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "SPDX-license-identifier-Artistic",
+        "SPDX-license-identifier-Artistic-2.0",
+        "SPDX-license-identifier-GPL",
+        "SPDX-license-identifier-GPL-2.0",
+        "SPDX-license-identifier-LGPL",
+        "SPDX-license-identifier-MIT",
+    ],
+    license_text: [
+        "COPYING",
+    ],
+}
+
+cc_library_headers {
+    name: "iptables_headers",
+    export_include_dirs: ["include"],
+}
+
+cc_library_headers {
+    name: "iptables_config_header",
+    export_include_dirs: ["."],
+}
+
+cc_library_headers {
+    name: "iptables_iptables_headers",
+    export_include_dirs: ["iptables"],
+}
+
+cc_defaults {
+    name: "iptables_defaults",
+
+    cflags: [
+        "-D_LARGEFILE_SOURCE=1",
+        "-D_LARGE_FILES",
+        "-D_FILE_OFFSET_BITS=64",
+        "-D_REENTRANT",
+
+        "-DENABLE_IPV4",
+        "-DENABLE_IPV6",
+
+        "-Wall",
+        "-Werror",
+        "-Wno-pointer-arith",
+        "-Wno-sign-compare",
+        "-Wno-unused-parameter",
+    ],
+
+    header_libs: ["iptables_headers"],
+}
diff --git a/Android.mk b/Android.mk
index 31eae91..4a2bd29 100644
--- a/Android.mk
+++ b/Android.mk
@@ -1,5 +1,3 @@
-BUILD_IPTABLES_V14 := 1
-
 LOCAL_PATH:= $(call my-dir)
 
 include $(call all-subdir-makefiles)
diff --git a/INSTALL b/INSTALL
index acb56cd..d62b428 100644
--- a/INSTALL
+++ b/INSTALL
@@ -31,7 +31,7 @@
 --with-xtlibdir=
 
 	The path to where Xtables extensions should be installed to. It
-	defaults to ${prefix}/libexec/xtables.
+	defaults to ${libdir}/xtables.
 
 --enable-devel (or --disable-devel)
 
@@ -70,6 +70,8 @@
 (-O0 is used to turn off instruction reordering, which makes debugging
 much easier.)
 
+To show debug traces you can add -DDEBUG to CFLAGS option
+
 
 Other notes
 ===========
diff --git a/Makefile.am b/Makefile.am
index 34b3501..799bf8b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3,15 +3,22 @@
 ACLOCAL_AMFLAGS  = -I m4
 AUTOMAKE_OPTIONS = foreign subdir-objects
 
-SUBDIRS          = extensions libiptc iptables
+SUBDIRS          = libiptc libxtables
 if ENABLE_DEVEL
 SUBDIRS         += include
 endif
 if ENABLE_LIBIPQ
 SUBDIRS         += libipq
 endif
-if HAVE_LIBNFNETLINK
 SUBDIRS         += utils
+# Depends on libxtables:
+SUBDIRS         += extensions
+# Depends on extensions/libext.a:
+SUBDIRS         += iptables
+
+if ENABLE_NFTABLES
+confdir		= $(sysconfdir)
+dist_conf_DATA	= etc/ethertypes
 endif
 
 .PHONY: tarball
@@ -23,4 +30,4 @@
 	rm -Rf /tmp/${PACKAGE_TARNAME}-${PACKAGE_VERSION};
 
 config.status: extensions/GNUmakefile.in \
-	include/xtables.h.in include/iptables/internal.h.in
+	include/xtables-version.h.in
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..07fc03a
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,2 @@
+allenlintwo@google.com
+jlevasseur@google.com
diff --git a/TEST_MAPPING b/TEST_MAPPING
new file mode 100644
index 0000000..2db0cd5
--- /dev/null
+++ b/TEST_MAPPING
@@ -0,0 +1,10 @@
+{
+    "presubmit": [
+        { "name": "libnetdbpf_test" },
+        { "name": "netd_integration_test" },
+        { "name": "netd_unit_test" },
+        { "name": "netdutils_test" },
+        { "name": "resolv_integration_test" },
+        { "name": "resolv_unit_test" }
+    ]
+}
diff --git a/autogen.sh b/autogen.sh
index 62a89e1..a0c4395 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh -e
 
 autoreconf -fi;
 rm -Rf autom4te*.cache;
diff --git a/config.h b/config.h
new file mode 100644
index 0000000..f4a827d
--- /dev/null
+++ b/config.h
@@ -0,0 +1,86 @@
+/* config.h.  Generated from config.h.in by configure.  */
+/* config.h.in.  Generated from configure.ac by autoheader.  */
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#define HAVE_DLFCN_H 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `pcap' library (-lpcap). */
+/* #undef HAVE_LIBPCAP */
+
+/* Define to 1 if you have the <linux/bpf.h> header file. */
+#define HAVE_LINUX_BPF_H 1
+
+/* Define to 1 if you have the <linux/dccp.h> header file. */
+#define HAVE_LINUX_DCCP_H 1
+
+/* Define to 1 if you have the <linux/ip_vs.h> header file. */
+#define HAVE_LINUX_IP_VS_H 1
+
+/* Define to 1 if you have the <linux/magic.h> header file. */
+#define HAVE_LINUX_MAGIC_H 1
+
+/* Define to 1 if you have the <linux/proc_fs.h> header file. */
+/* #undef HAVE_LINUX_PROC_FS_H */
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to the sub-directory where libtool stores uninstalled libraries. */
+#define LT_OBJDIR ".libs/"
+
+/* Name of package */
+#define PACKAGE "iptables"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "iptables"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "iptables 1.8.7"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "iptables"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "1.8.7"
+
+/* The size of `struct ip6_hdr', as computed by sizeof. */
+#define SIZEOF_STRUCT_IP6_HDR 40
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Version number of package */
+#define VERSION "1.8.7"
+
+/* Location of the iptables lock file */
+#define XT_LOCK_NAME "/system/etc/xtables.lock"
diff --git a/configure.ac b/configure.ac
index e902ab9..6864378 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,10 +1,11 @@
 
-AC_INIT([iptables], [1.4.11.1])
+AC_INIT([iptables], [1.8.7])
 
 # See libtool.info "Libtool's versioning system"
-libxtables_vcurrent=6
-libxtables_vage=0
+libxtables_vcurrent=16
+libxtables_vage=4
 
+AC_CONFIG_AUX_DIR([build-aux])
 AC_CONFIG_HEADERS([config.h])
 AC_CONFIG_MACRO_DIR([m4])
 AC_PROG_INSTALL
@@ -12,6 +13,7 @@
 AC_PROG_CC
 AM_PROG_CC_C_O
 AC_DISABLE_STATIC
+m4_ifdef([AM_PROG_AR], [AM_PROG_AR])
 AM_PROG_LIBTOOL
 
 AC_ARG_WITH([kernel],
@@ -30,7 +32,7 @@
 	AS_HELP_STRING([--with-xtlibdir=PATH],
 	[Path where to install Xtables extensions [[LIBEXECDIR/xtables]]]),
 	[xtlibdir="$withval"],
-	[xtlibdir="${libexecdir}/xtables"])
+	[xtlibdir="${libdir}/xtables"])
 AC_ARG_ENABLE([ipv4],
 	AS_HELP_STRING([--disable-ipv4], [Do not build iptables]),
 	[enable_ipv4="$enableval"], [enable_ipv4="yes"])
@@ -40,36 +42,62 @@
 AC_ARG_ENABLE([largefile],
 	AS_HELP_STRING([--disable-largefile], [Do not build largefile support]),
 	[enable_largefile="$enableval"],
-	[enable_largefile="yes";
-	largefile_cppflags='-D_LARGEFILE_SOURCE=1 -D_LARGE_FILES -D_FILE_OFFSET_BITS=64'])
+	[enable_largefile="yes"])
+AS_IF([test "$enable_largefile" = "yes"], [largefile_cppflags='-D_LARGEFILE_SOURCE=1 -D_LARGE_FILES -D_FILE_OFFSET_BITS=64'])
+
 AC_ARG_ENABLE([devel],
 	AS_HELP_STRING([--enable-devel],
 	[Install Xtables development headers]),
 	[enable_devel="$enableval"], [enable_devel="yes"])
 AC_ARG_ENABLE([libipq],
-	AS_HELP_STRING([--enable-libipq], [Build and install libipq]))
+	AS_HELP_STRING([--enable-libipq], [Build and install libipq]),
+	[enable_libipq="$enableval"], [enable_libipq="no"])
+AC_ARG_ENABLE([bpf-compiler],
+	AS_HELP_STRING([--enable-bpf-compiler], [Build bpf compiler]),
+	[enable_bpfc="$enableval"], [enable_bpfc="no"])
+AC_ARG_ENABLE([nfsynproxy],
+	AS_HELP_STRING([--enable-nfsynproxy], [Build SYNPROXY configuration tool]),
+	[enable_nfsynproxy="$enableval"], [enable_nfsynproxy="no"])
 AC_ARG_WITH([pkgconfigdir], AS_HELP_STRING([--with-pkgconfigdir=PATH],
 	[Path to the pkgconfig directory [[LIBDIR/pkgconfig]]]),
 	[pkgconfigdir="$withval"], [pkgconfigdir='${libdir}/pkgconfig'])
+AC_ARG_ENABLE([nftables],
+	AS_HELP_STRING([--disable-nftables], [Do not build nftables compat]),
+	[enable_nftables="$enableval"], [enable_nftables="yes"])
+AC_ARG_ENABLE([connlabel],
+	AS_HELP_STRING([--disable-connlabel],
+	[Do not build libnetfilter_conntrack]),
+	[enable_connlabel="$enableval"], [enable_connlabel="yes"])
+AC_ARG_WITH([xt-lock-name], AS_HELP_STRING([--with-xt-lock-name=PATH],
+	[Path to the xtables lock [[/run/xtables.lock]]]),
+	[xt_lock_name="$withval"],
+	[xt_lock_name="/run/xtables.lock"])
 
-libiptc_LDFLAGS2="";
-AX_CHECK_LINKER_FLAGS([-Wl,--no-as-needed],
-	[libiptc_LDFLAGS2="-Wl,--no-as-needed"])
-AC_SUBST([libiptc_LDFLAGS2])
+AC_MSG_CHECKING([whether $LD knows -Wl,--no-undefined])
+saved_LDFLAGS="$LDFLAGS";
+LDFLAGS="-Wl,--no-undefined";
+AC_LINK_IFELSE([AC_LANG_SOURCE([int main(void) {}])],
+	[noundef_LDFLAGS="$LDFLAGS"; AC_MSG_RESULT([yes])],
+	[AC_MSG_RESULT([no])]
+)
+LDFLAGS="$saved_LDFLAGS";
 
-blacklist_modules="";
+blacklist_modules=""
+blacklist_x_modules=""
+blacklist_b_modules=""
+blacklist_a_modules=""
+blacklist_4_modules=""
+blacklist_6_modules=""
 
-AC_CHECK_HEADER([linux/dccp.h])
+AC_CHECK_HEADERS([linux/dccp.h linux/ip_vs.h linux/magic.h linux/proc_fs.h linux/bpf.h])
 if test "$ac_cv_header_linux_dccp_h" != "yes"; then
 	blacklist_modules="$blacklist_modules dccp";
 fi;
-
-AC_CHECK_HEADER([linux/ip_vs.h])
 if test "$ac_cv_header_linux_ip_vs_h" != "yes"; then
 	blacklist_modules="$blacklist_modules ipvs";
 fi;
 
-AC_SUBST([blacklist_modules])
+AC_CHECK_SIZEOF([struct ip6_hdr], [], [#include <netinet/ip6.h>])
 
 AM_CONDITIONAL([ENABLE_STATIC], [test "$enable_static" = "yes"])
 AM_CONDITIONAL([ENABLE_SHARED], [test "$enable_shared" = "yes"])
@@ -78,27 +106,106 @@
 AM_CONDITIONAL([ENABLE_LARGEFILE], [test "$enable_largefile" = "yes"])
 AM_CONDITIONAL([ENABLE_DEVEL], [test "$enable_devel" = "yes"])
 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"])
+AM_CONDITIONAL([ENABLE_CONNLABEL], [test "$enable_connlabel" = "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))
+fi
 
 PKG_CHECK_MODULES([libnfnetlink], [libnfnetlink >= 1.0],
 	[nfnetlink=1], [nfnetlink=0])
 AM_CONDITIONAL([HAVE_LIBNFNETLINK], [test "$nfnetlink" = 1])
 
+if test "x$enable_nftables" = "xyes"; then
+	PKG_CHECK_MODULES([libmnl], [libmnl >= 1.0], [mnl=1], [mnl=0])
+
+	if test "$mnl" = 0;
+	then
+		echo "*** Error: No suitable libmnl found. ***"
+		echo "    Please install the 'libmnl' package"
+		echo "    Or consider --disable-nftables to skip"
+		echo "    iptables-compat over nftables support."
+		exit 1
+	fi
+
+	PKG_CHECK_MODULES([libnftnl], [libnftnl >= 1.1.6], [nftables=1], [nftables=0])
+
+	if test "$nftables" = 0;
+	then
+		echo "*** Error: no suitable libnftnl found. ***"
+		echo "    Please install the 'libnftnl' package"
+		echo "    Or consider --disable-nftables to skip"
+		echo "    iptables-compat over nftables support."
+		exit 1
+	fi
+fi
+
+AM_CONDITIONAL([HAVE_LIBMNL], [test "$mnl" = 1])
+AM_CONDITIONAL([HAVE_LIBNFTNL], [test "$nftables" = 1])
+
+if test "$nftables" != 1; then
+	blacklist_b_modules="$blacklist_b_modules limit mark nflog mangle"
+	blacklist_a_modules="$blacklist_a_modules mangle"
+fi
+
+if test "x$enable_connlabel" = "xyes"; then
+	PKG_CHECK_MODULES([libnetfilter_conntrack],
+		[libnetfilter_conntrack >= 1.0.6],
+		[nfconntrack=1], [nfconntrack=0])
+
+	if test "$nfconntrack" -ne 1; then
+		blacklist_modules="$blacklist_modules connlabel";
+		echo "WARNING: libnetfilter_conntrack not found, connlabel match will not be built";
+		enable_connlabel="no";
+	fi;
+else
+	blacklist_modules="$blacklist_modules connlabel";
+fi;
+
+AM_CONDITIONAL([HAVE_LIBNETFILTER_CONNTRACK], [test "$nfconntrack" = 1])
+
+AC_SUBST([blacklist_modules])
+AC_SUBST([blacklist_x_modules])
+AC_SUBST([blacklist_b_modules])
+AC_SUBST([blacklist_a_modules])
+AC_SUBST([blacklist_4_modules])
+AC_SUBST([blacklist_6_modules])
+
 regular_CFLAGS="-Wall -Waggregate-return -Wmissing-declarations \
 	-Wmissing-prototypes -Wredundant-decls -Wshadow -Wstrict-prototypes \
+	-Wlogical-op \
 	-Winline -pipe";
 regular_CPPFLAGS="${largefile_cppflags} -D_REENTRANT \
 	-DXTABLES_LIBDIR=\\\"\${xtlibdir}\\\" -DXTABLES_INTERNAL";
 kinclude_CPPFLAGS="";
 if [[ -n "$kbuilddir" ]]; then
-	kinclude_CPPFLAGS="$kinclude_CPPFLAGS -I$kbuilddir/include";
+	kinclude_CPPFLAGS="$kinclude_CPPFLAGS -I$kbuilddir/include/uapi -I$kbuilddir/include";
 fi;
 if [[ -n "$ksourcedir" ]]; then
-	kinclude_CPPFLAGS="$kinclude_CPPFLAGS -I$ksourcedir/include";
+	kinclude_CPPFLAGS="$kinclude_CPPFLAGS -I$ksourcedir/include/uapi -I$ksourcedir/include";
 fi;
 pkgdatadir='${datadir}/xtables';
 
+define([EXPAND_VARIABLE],
+[$2=[$]$1
+if test $prefix = 'NONE'; then
+	prefix="/usr/local"
+fi
+while true; do
+  case "[$]$2" in
+    *\[$]* ) eval "$2=[$]$2" ;;
+    *) break ;;
+  esac
+done
+eval "$2=[$]$2"
+])dnl EXPAND_VARIABLE
+
 AC_SUBST([regular_CFLAGS])
 AC_SUBST([regular_CPPFLAGS])
+AC_SUBST([noundef_LDFLAGS])
 AC_SUBST([kinclude_CPPFLAGS])
 AC_SUBST([kbuilddir])
 AC_SUBST([ksourcedir])
@@ -110,8 +217,58 @@
 libxtables_vmajor=$(($libxtables_vcurrent - $libxtables_vage));
 AC_SUBST([libxtables_vmajor])
 
+AC_DEFINE_UNQUOTED([XT_LOCK_NAME], "${xt_lock_name}",
+	[Location of the iptables lock file])
+AC_SUBST([XT_LOCK_NAME], "${xt_lock_name}")
+
 AC_CONFIG_FILES([Makefile extensions/GNUmakefile include/Makefile
 	iptables/Makefile iptables/xtables.pc
-	libipq/Makefile libiptc/Makefile libiptc/libiptc.pc utils/Makefile
-	include/xtables.h include/iptables/internal.h])
+	iptables/iptables.8 iptables/iptables-extensions.8.tmpl
+	iptables/iptables-save.8 iptables/iptables-restore.8
+	iptables/iptables-apply.8 iptables/iptables-xml.1
+	libipq/Makefile libipq/libipq.pc
+	libiptc/Makefile libiptc/libiptc.pc
+	libiptc/libip4tc.pc libiptc/libip6tc.pc
+	libxtables/Makefile utils/Makefile
+	include/xtables-version.h
+	iptables/xtables-monitor.8
+	utils/nfnl_osf.8
+	utils/nfbpf_compile.8])
 AC_OUTPUT
+
+
+EXPAND_VARIABLE(xtlibdir, e_xtlibdir)
+EXPAND_VARIABLE(pkgconfigdir, e_pkgconfigdir)
+
+echo "
+Iptables Configuration:
+  IPv4 support:				${enable_ipv4}
+  IPv6 support:				${enable_ipv6}
+  Devel support:			${enable_devel}
+  IPQ support:				${enable_libipq}
+  Large file support:			${enable_largefile}
+  BPF utils support:			${enable_bpfc}
+  nfsynproxy util support:		${enable_nfsynproxy}
+  nftables support:			${enable_nftables}
+  connlabel support:			${enable_connlabel}
+
+Build parameters:
+  Put plugins into executable (static):	${enable_static}
+  Support plugins via dlopen (shared):	${enable_shared}
+  Installation prefix (--prefix):	${prefix}
+  Xtables extension directory:		${e_xtlibdir}
+  Pkg-config directory:			${e_pkgconfigdir}
+  Xtables lock file:			${xt_lock_name}"
+
+if [[ -n "$ksourcedir" ]]; then
+	echo "  Kernel source directory:		${ksourcedir}"
+fi;
+if [[ -n "$kbuilddir" ]]; then
+	echo "  Kernel build directory:		${kbuilddir}"
+fi;
+
+echo "  Host:					${host}
+  GCC binary:				${CC}"
+
+test x"$blacklist_modules" = "x" || echo "
+Iptables modules that will not be built: $blacklist_modules"
diff --git a/etc/ethertypes b/etc/ethertypes
new file mode 100644
index 0000000..813177b
--- /dev/null
+++ b/etc/ethertypes
@@ -0,0 +1,39 @@
+#
+# Ethernet frame types
+#		This file describes some of the various Ethernet
+#		protocol types that are used on Ethernet networks.
+#
+# This list could be found on:
+#         http://www.iana.org/assignments/ethernet-numbers
+#         http://www.iana.org/assignments/ieee-802-numbers
+#
+# <name>    <hexnumber> <alias1>...<alias35> #Comment
+#
+IPv4	 	0800  	ip ip4 		# Internet IP (IPv4)
+X25		0805
+ARP		0806	ether-arp	#
+FR_ARP		0808    		# Frame Relay ARP        [RFC1701]
+BPQ		08FF			# G8BPQ AX.25 Ethernet Packet
+DEC		6000			# DEC Assigned proto
+DNA_DL		6001			# DEC DNA Dump/Load
+DNA_RC		6002			# DEC DNA Remote Console
+DNA_RT		6003			# DEC DNA Routing
+LAT		6004			# DEC LAT
+DIAG		6005			# DEC Diagnostics
+CUST		6006			# DEC Customer use
+SCA		6007			# DEC Systems Comms Arch
+TEB		6558			# Trans Ether Bridging   [RFC1701]
+RAW_FR  	6559			# Raw Frame Relay        [RFC1701]
+RARP		8035			# Reverse ARP            [RFC903]
+AARP		80F3			# Appletalk AARP
+ATALK		809B			# Appletalk
+802_1Q		8100	8021q 1q 802.1q	dot1q # 802.1Q Virtual LAN tagged frame
+IPX		8137			# Novell IPX
+NetBEUI		8191			# NetBEUI
+IPv6		86DD	ip6 		# IP version 6
+PPP		880B			# PPP
+ATMMPOA		884C			# MultiProtocol over ATM
+PPP_DISC	8863			# PPPoE discovery messages
+PPP_SES		8864			# PPPoE session messages
+ATMFATE		8884			# Frame-based ATM Transport over Ethernet
+LOOP		9000	loopback 	# loop proto
diff --git a/etc/xtables.conf b/etc/xtables.conf
new file mode 100644
index 0000000..3c54ced
--- /dev/null
+++ b/etc/xtables.conf
@@ -0,0 +1,74 @@
+family ipv4 {
+	table raw {
+		chain PREROUTING hook NF_INET_PRE_ROUTING prio -300
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio -300
+	}
+
+	table mangle {
+		chain PREROUTING hook NF_INET_PRE_ROUTING prio -150
+		chain INPUT hook NF_INET_LOCAL_IN prio -150
+		chain FORWARD hook NF_INET_FORWARD prio -150
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio -150
+		chain POSTROUTING hook NF_INET_POST_ROUTING prio -150
+	}
+
+	table filter {
+		chain INPUT hook NF_INET_LOCAL_IN prio 0
+		chain FORWARD hook NF_INET_FORWARD prio 0
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio 0
+	}
+
+	table nat {
+		chain PREROUTING hook NF_INET_PRE_ROUTING prio -100
+		chain INPUT hook NF_INET_LOCAL_IN prio 100
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio -100
+		chain POSTROUTING hook NF_INET_POST_ROUTING prio 100
+	}
+
+	table security {
+		chain INPUT hook NF_INET_LOCAL_IN prio 50
+		chain FORWARD hook NF_INET_FORWARD prio 50
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio 50
+	}
+}
+
+family ipv6 {
+	table raw {
+		chain PREROUTING hook NF_INET_PRE_ROUTING prio -300
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio -300
+	}
+
+	table mangle {
+		chain PREROUTING hook NF_INET_PRE_ROUTING prio -150
+		chain INPUT hook NF_INET_LOCAL_IN prio -150
+		chain FORWARD hook NF_INET_FORWARD prio -150
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio -150
+		chain POSTROUTING hook NF_INET_POST_ROUTING prio -150
+	}
+
+	table filter {
+		chain INPUT hook NF_INET_LOCAL_IN prio 0
+		chain FORWARD hook NF_INET_FORWARD prio 0
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio 0
+	}
+
+	table nat {
+		chain PREROUTING hook NF_INET_PRE_ROUTING prio -100
+		chain INPUT hook NF_INET_LOCAL_IN prio 100
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio -100
+		chain POSTROUTING hook NF_INET_POST_ROUTING prio 100
+	}
+
+	table security {
+		chain INPUT hook NF_INET_LOCAL_IN prio 50
+		chain FORWARD hook NF_INET_FORWARD prio 50
+		chain OUTPUT hook NF_INET_LOCAL_OUT prio 50
+	}
+}
+
+family arp {
+	table filter {
+		chain INPUT hook NF_ARP_IN prio 0
+		chain OUTPUT hook NF_ARP_OUT prio 0
+	}
+}
diff --git a/extensions/.gitignore b/extensions/.gitignore
new file mode 100644
index 0000000..b1260f0
--- /dev/null
+++ b/extensions/.gitignore
@@ -0,0 +1,9 @@
+.*.d
+.*.dd
+*.oo
+
+/GNUmakefile
+/initext.c
+/initext?.c
+/matches.man
+/targets.man
diff --git a/extensions/Android.bp b/extensions/Android.bp
new file mode 100644
index 0000000..8a6b6e9
--- /dev/null
+++ b/extensions/Android.bp
@@ -0,0 +1,139 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "external_iptables_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-GPL
+    //   SPDX-license-identifier-GPL-2.0
+    default_applicable_licenses: ["external_iptables_license"],
+}
+
+cc_defaults {
+    name: "libext_defaults",
+    defaults: ["iptables_defaults"],
+
+    header_libs: ["iptables_config_header"],
+
+    cflags: [
+        "-DNO_SHARED_LIBS=1",
+        "-DXTABLES_INTERNAL",
+
+        "-Wno-format",
+        "-Wno-missing-field-initializers",
+        // libxt_recent.c:202:11: error: address of array 'info->name' will always evaluate to 'true' [-Werror,-Wpointer-bool-conversion]
+        "-Wno-pointer-bool-conversion",
+        "-Wno-tautological-pointer-compare",
+    ],
+}
+
+// All of the extension source files have the same function name (_init). Since we don't support
+// per-file cflags that upstream uses, instead:
+//
+//  1. Rewrite the source files with filter_init to have per-file function names. (libext*_srcs)
+//  2. Create a new source file that defines a function (init_extensions*) with gen_init that calls
+//     all of the renamed _init functions (libext*_init)
+//
+// This all happens three times -- once each for libext, libext4, libext6
+
+genrule {
+    name: "libext_init",
+    cmd: "$(location gen_init) '' $(locations libxt_*.c) > $(out)",
+    srcs: [
+        "gen_init",
+        "libxt_*.c",
+    ],
+    out: ["initext.c"],
+    exclude_srcs: [
+        // Exclude some modules that are problematic to compile (types/headers)
+        "libxt_TCPOPTSTRIP.c",
+        "libxt_connlabel.c",
+        "libxt_cgroup.c",
+
+        "libxt_dccp.c",
+        "libxt_ipvs.c",
+    ],
+}
+
+gensrcs {
+    name: "libext_srcs",
+    tool_files: ["filter_init"],
+    cmd: "$(location filter_init) $(in) > $(out)",
+    output_extension: "c",
+    srcs: ["libxt_*.c"],
+    exclude_srcs: [
+        // Exclude some modules that are problematic to compile (types/headers)
+        "libxt_TCPOPTSTRIP.c",
+        "libxt_connlabel.c",
+        "libxt_cgroup.c",
+
+        "libxt_dccp.c",
+        "libxt_ipvs.c",
+    ],
+}
+
+cc_library_static {
+    name: "libext",
+    defaults: ["libext_defaults"],
+    srcs: [
+        ":libext_init",
+        ":libext_srcs",
+    ],
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+genrule {
+    name: "libext4_init",
+    cmd: "$(location gen_init) '4' $(locations libipt_*.c) > $(out)",
+    srcs: [
+        "gen_init",
+        "libipt_*.c",
+    ],
+    out: ["initext.c"],
+}
+
+gensrcs {
+    name: "libext4_srcs",
+    tool_files: ["filter_init"],
+    cmd: "$(location filter_init) $(in) > $(out)",
+    output_extension: "c",
+    srcs: ["libipt_*.c"],
+}
+
+cc_library_static {
+    name: "libext4",
+    defaults: ["libext_defaults"],
+    srcs: [
+        ":libext4_init",
+        ":libext4_srcs",
+    ],
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+genrule {
+    name: "libext6_init",
+    cmd: "$(location gen_init) '6' $(locations libip6t_*.c) > $(out)",
+    srcs: [
+        "gen_init",
+        "libip6t_*.c",
+    ],
+    out: ["initext.c"],
+}
+
+gensrcs {
+    name: "libext6_srcs",
+    tool_files: ["filter_init"],
+    cmd: "$(location filter_init) $(in) > $(out)",
+    output_extension: "c",
+    srcs: ["libip6t_*.c"],
+}
+
+cc_library_static {
+    name: "libext6",
+    defaults: ["libext_defaults"],
+    srcs: [
+        ":libext6_init",
+        ":libext6_srcs",
+    ],
+}
diff --git a/extensions/Android.mk b/extensions/Android.mk
index 0b7429c..74f1cab 100644
--- a/extensions/Android.mk
+++ b/extensions/Android.mk
@@ -4,219 +4,42 @@
 
 MY_srcdir:=$(LOCAL_PATH)
 # Exclude some modules that are problematic to compile (types/header).
-MY_excluded_modules:=DNAT SNAT TCPOPTSTRIP
+MY_excluded_modules:=TCPOPTSTRIP connlabel cgroup
 
-MY_pfx_build_mod := $(patsubst ${MY_srcdir}/libxt_%.c,%,$(wildcard ${MY_srcdir}/libxt_*.c))
-MY_pf4_build_mod := $(patsubst ${MY_srcdir}/libipt_%.c,%,$(wildcard ${MY_srcdir}/libipt_*.c))
-MY_pf6_build_mod := $(patsubst ${MY_srcdir}/libip6t_%.c,%,$(wildcard ${MY_srcdir}/libip6t_*.c))
+MY_pfx_build_mod := $(patsubst ${MY_srcdir}/libxt_%.c,%,$(sort $(wildcard ${MY_srcdir}/libxt_*.c)))
+MY_pf4_build_mod := $(patsubst ${MY_srcdir}/libipt_%.c,%,$(sort $(wildcard ${MY_srcdir}/libipt_*.c)))
+MY_pf6_build_mod := $(patsubst ${MY_srcdir}/libip6t_%.c,%,$(sort $(wildcard ${MY_srcdir}/libip6t_*.c)))
 MY_pfx_build_mod := $(filter-out ${MY_excluded_modules} dccp ipvs,${MY_pfx_build_mod})
 MY_pf4_build_mod := $(filter-out ${MY_excluded_modules} dccp ipvs,${MY_pf4_build_mod})
 MY_pf6_build_mod := $(filter-out ${MY_excluded_modules} dccp ipvs,${MY_pf6_build_mod})
 MY_pfx_objs      := $(patsubst %,libxt_%.o,${MY_pfx_build_mod})
 MY_pf4_objs      := $(patsubst %,libipt_%.o,${MY_pf4_build_mod})
 MY_pf6_objs      := $(patsubst %,libip6t_%.o,${MY_pf6_build_mod})
+# libxt_recent.c:202:11: error: address of array 'info->name' will always evaluate to 'true' [-Werror,-Wpointer-bool-conversion]
+MY_warnings      := \
+    -Wall -Werror \
+    -Wno-format \
+    -Wno-missing-field-initializers \
+    -Wno-pointer-arith \
+    -Wno-pointer-bool-conversion \
+    -Wno-sign-compare \
+    -Wno-tautological-pointer-compare \
+    -Wno-unused-parameter \
 
-#----------------------------------------------------------------
-# libext
-# TODO(jpa): Trun this into a function/macro as libext{,4,6} are all the same.
+libext_suffix :=
+libext_prefix := xt
+libext_build_mod := $(MY_pfx_build_mod)
+include $(LOCAL_PATH)/libext.mk
 
-include $(CLEAR_VARS)
+libext_suffix := 4
+libext_prefix := ipt
+libext_build_mod := $(MY_pf4_build_mod)
+include $(LOCAL_PATH)/libext.mk
 
-LOCAL_MODULE_TAGS:=
-LOCAL_MODULE:=libext
+libext_suffix := 6
+libext_prefix := ip6t
+libext_build_mod := $(MY_pf6_build_mod)
+include $(LOCAL_PATH)/libext.mk
 
-# LOCAL_MODULE_CLASS must be defined before calling $(local-intermediates-dir)
-#
-LOCAL_MODULE_CLASS := SHARED_LIBRARIES
-MY_intermediates := $(call local-intermediates-dir)
-
-# LOCAL_PATH needed because of dirty #include "blabla.c"
-LOCAL_C_INCLUDES:= \
-	$(LOCAL_PATH)/../include/ \
-	$(KERNEL_HEADERS) \
-	$(MY_intermediates) \
-	$(LOCAL_PATH)
-
-LOCAL_CFLAGS:=-DNO_SHARED_LIBS=1
-# The $* does not work as expected. It ends up empty. Even with SECONDEXPANSION.
-# LOCAL_CFLAGS+=-D_INIT=lib$*_init
-LOCAL_CFLAGS+=-DXTABLES_INTERNAL
-# Accommodate arm-eabi-4.4.3 tools that don't set __ANDROID__
-LOCAL_CFLAGS+=-D__ANDROID__
-
-ifeq ($(USE_CLANG),1)
-LOCAL_CFLAGS+=-Wno-error=pointer-bool-conversion
-endif
-
-MY_initext_func := $(addprefix xt_,${MY_pfx_build_mod})
-MY_GEN_INITEXT:= $(MY_intermediates)/initext.c
-$(MY_GEN_INITEXT):
-	@mkdir -p $(dir $@)
-	@( \
-	echo "" >$@; \
-	for i in ${MY_initext_func}; do \
-		echo "extern void lib$${i}_init(void);" >>$@; \
-	done; \
-	echo "void init_extensions(void);" >>$@; \
-	echo "void init_extensions(void)" >>$@; \
-	echo "{" >>$@; \
-	for i in ${MY_initext_func}; do \
-		echo " ""lib$${i}_init();" >>$@; \
-	done; \
-	echo "}" >>$@; \
-	);
-
-MY_lib_sources:= \
-	$(patsubst %,$(LOCAL_PATH)/libxt_%.c,${MY_pfx_build_mod})
-
-MY_gen_lib_sources:= $(patsubst $(LOCAL_PATH)/%,${MY_intermediates}/%,${MY_lib_sources})
-
-${MY_gen_lib_sources}: PRIVATE_PATH := $(LOCAL_PATH)
-${MY_gen_lib_sources}: PRIVATE_CUSTOM_TOOL = $(PRIVATE_PATH)/filter_init $(PRIVATE_PATH)/$(notdir $@) > $@
-${MY_gen_lib_sources}: PRIVATE_MODULE := $(LOCAL_MODULE)
-${MY_gen_lib_sources}: PRIVATE_C_INCLUDES := $(LOCAL_C_INCLUDES)
-${MY_gen_lib_sources}: $(MY_lib_sources)
-	$(transform-generated-source)
-
-$(MY_intermediates)/initext.o : $(MY_GEN_INITEXT) $(MY_gen_lib_sources)
-
-LOCAL_GENERATED_SOURCES:= $(MY_GEN_INITEXT) $(MY_gen_lib_sources)
-
-LOCAL_SHARED_LIBRARIES := \
-	libxtables
-
-include $(BUILD_SHARED_LIBRARY)
-
-#----------------------------------------------------------------
-# libext4
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS:=
-LOCAL_MODULE:=libext4
-
-# LOCAL_MODULE_CLASS must be defined before calling $(local-intermediates-dir)
-#
-LOCAL_MODULE_CLASS := SHARED_LIBRARIES
-MY_intermediates := $(call local-intermediates-dir)
-
-# LOCAL_PATH needed because of dirty #include "blabla.c"
-LOCAL_C_INCLUDES:= \
-	$(LOCAL_PATH)/../include/ \
-	$(KERNEL_HEADERS) \
-	$(MY_intermediates)/ \
-	$(LOCAL_PATH)/
-
-LOCAL_CFLAGS:=-DNO_SHARED_LIBS=1
-# The $* does not work as expected. It ends up empty. Even with SECONDEXPANSION.
-# LOCAL_CFLAGS+=-D_INIT=lib$*_init
-LOCAL_CFLAGS+=-DXTABLES_INTERNAL
-# Accommodate arm-eabi-4.4.3 tools that don't set __ANDROID__
-LOCAL_CFLAGS+=-D__ANDROID__
-
-MY_initext4_func  := $(addprefix ipt_,${MY_pf4_build_mod})
-MY_GEN_INITEXT4:= $(MY_intermediates)/initext4.c
-$(MY_GEN_INITEXT4):
-	@mkdir -p $(dir $@)
-	@( \
-	echo "" >$@; \
-	for i in ${MY_initext4_func}; do \
-		echo "extern void lib$${i}_init(void);" >>$@; \
-	done; \
-	echo "void init_extensions4(void);" >>$@; \
-	echo "void init_extensions4(void)" >>$@; \
-	echo "{" >>$@; \
-	for i in ${MY_initext4_func}; do \
-		echo  " ""lib$${i}_init();" >>$@; \
-	done; \
-	echo "}" >>$@; \
-	);
-
-MY_lib_sources:= \
-	$(patsubst %,$(LOCAL_PATH)/libipt_%.c,${MY_pf4_build_mod})
-
-MY_gen_lib_sources:= $(patsubst $(LOCAL_PATH)/%,${MY_intermediates}/%,${MY_lib_sources})
-
-${MY_gen_lib_sources}: PRIVATE_PATH := $(LOCAL_PATH)
-${MY_gen_lib_sources}: PRIVATE_CUSTOM_TOOL = $(PRIVATE_PATH)/filter_init $(PRIVATE_PATH)/$(notdir $@) > $@
-${MY_gen_lib_sources}: PRIVATE_MODULE := $(LOCAL_MODULE)
-${MY_gen_lib_sources}: PRIVATE_C_INCLUDES := $(LOCAL_C_INCLUDES)
-${MY_gen_lib_sources}: $(MY_lib_sources)
-	$(transform-generated-source)
-
-$(MY_intermediates)/initext4.o : $(MY_GEN_INITEXT4) $(MY_gen_lib_sources)
-
-LOCAL_GENERATED_SOURCES:= $(MY_GEN_INITEXT4) ${MY_gen_lib_sources}
-
-LOCAL_SHARED_LIBRARIES := \
-	libxtables
-
-include $(BUILD_SHARED_LIBRARY)
-
-#----------------------------------------------------------------
-# libext6
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS:=
-LOCAL_MODULE:=libext6
-
-# LOCAL_MODULE_CLASS must be defined before calling $(local-intermediates-dir)
-#
-LOCAL_MODULE_CLASS := SHARED_LIBRARIES
-MY_intermediates := $(call local-intermediates-dir)
-
-# LOCAL_PATH needed because of dirty #include "blabla.c"
-LOCAL_C_INCLUDES:= \
-	$(LOCAL_PATH)/../include/ \
-	$(KERNEL_HEADERS) \
-	$(MY_intermediates) \
-	$(LOCAL_PATH)
-
-LOCAL_CFLAGS:=-DNO_SHARED_LIBS=1
-# The $* does not work as expected. It ends up empty. Even with SECONDEXPANSION.
-# LOCAL_CFLAGS+=-D_INIT=lib$*_init
-LOCAL_CFLAGS+=-DXTABLES_INTERNAL
-# Accommodate arm-eabi-4.4.3 tools that don't set __ANDROID__
-LOCAL_CFLAGS+=-D__ANDROID__
-
-MY_initext6_func := $(addprefix ip6t_,${MY_pf6_build_mod})
-MY_GEN_INITEXT6:= $(MY_intermediates)/initext6.c
-$(MY_GEN_INITEXT6):
-	@mkdir -p $(dir $@)
-	@( \
-	echo "" >$@; \
-	for i in ${MY_initext6_func}; do \
-		echo "extern void lib$${i}_init(void);" >>$@; \
-	done; \
-	echo "void init_extensions6(void);" >>$@; \
-	echo "void init_extensions6(void)" >>$@; \
-	echo "{" >>$@; \
-	for i in ${MY_initext6_func}; do \
-		echo " ""lib$${i}_init();" >>$@; \
-	done; \
-	echo "}" >>$@; \
-	);
-
-MY_lib_sources:= \
-	$(patsubst %,$(LOCAL_PATH)/libip6t_%.c,${MY_pf6_build_mod})
-
-MY_gen_lib_sources:= $(patsubst $(LOCAL_PATH)/%,${MY_intermediates}/%,${MY_lib_sources})
-
-${MY_gen_lib_sources}: PRIVATE_PATH := $(LOCAL_PATH)
-${MY_gen_lib_sources}: PRIVATE_CUSTOM_TOOL = $(PRIVATE_PATH)/filter_init $(PRIVATE_PATH)/$(notdir $@) > $@
-${MY_gen_lib_sources}: PRIVATE_MODULE := $(LOCAL_MODULE)
-${MY_gen_lib_sources}: PRIVATE_C_INCLUDES := $(LOCAL_C_INCLUDES)
-${MY_gen_lib_sources}: $(MY_lib_sources)
-	$(transform-generated-source)
-
-$(MY_intermediates)/initext6.o : $(MY_GEN_INITEXT6) $(MY_gen_lib_sources)
-
-LOCAL_GENERATED_SOURCES:= $(MY_GEN_INITEXT6) $(MY_gen_lib_sources)
-
-LOCAL_SHARED_LIBRARIES := \
-	libxtables
-
-include $(BUILD_SHARED_LIBRARY)
 
 #----------------------------------------------------------------
diff --git a/extensions/GNUmakefile.in b/extensions/GNUmakefile.in
index fbaf2ec..956ccb3 100644
--- a/extensions/GNUmakefile.in
+++ b/extensions/GNUmakefile.in
@@ -1,28 +1,30 @@
 # -*- Makefile -*-
 
-top_builddir := @top_builddir@
-builddir     := @builddir@
-top_srcdir  := @top_srcdir@
-srcdir      := @srcdir@
-ksourcedir  := @ksourcedir@
-prefix      := @prefix@
-exec_prefix := @exec_prefix@
-libdir      := @libdir@
-libexecdir  := @libexecdir@
-xtlibdir    := @xtlibdir@
+top_builddir = @top_builddir@
+builddir     = @builddir@
+top_srcdir   = @top_srcdir@
+srcdir       = @srcdir@
+ksourcedir   = @ksourcedir@
+prefix       = @prefix@
+exec_prefix  = @exec_prefix@
+libdir       = @libdir@
+libexecdir   = @libexecdir@
+xtlibdir     = @xtlibdir@
 
-CC             := @CC@
-CCLD           := ${CC}
-CFLAGS         := @CFLAGS@
-CPPFLAGS       := @CPPFLAGS@
-LDFLAGS        := @LDFLAGS@
-regular_CFLAGS := @regular_CFLAGS@
-regular_CPPFLAGS := @regular_CPPFLAGS@
-kinclude_CPPFLAGS := @kinclude_CPPFLAGS@
+AR                 = @AR@
+CC                 = @CC@
+CCLD               = ${CC}
+CFLAGS             = @CFLAGS@
+CPPFLAGS           = @CPPFLAGS@
+LDFLAGS            = @LDFLAGS@
+regular_CFLAGS     = @regular_CFLAGS@
+regular_CPPFLAGS   = @regular_CPPFLAGS@
+kinclude_CPPFLAGS  = @kinclude_CPPFLAGS@
 
-AM_CFLAGS      := ${regular_CFLAGS}
-AM_CPPFLAGS     = ${regular_CPPFLAGS} -I${top_builddir}/include -I${top_srcdir}/include ${kinclude_CPPFLAGS}
+AM_CFLAGS       = ${regular_CFLAGS}
+AM_CPPFLAGS     = ${regular_CPPFLAGS} -I${top_builddir}/include -I${top_builddir} -I${top_srcdir}/include -I${top_srcdir} ${kinclude_CPPFLAGS} ${CPPFLAGS} @libnetfilter_conntrack_CFLAGS@ @libnftnl_CFLAGS@
 AM_DEPFLAGS     = -Wp,-MMD,$(@D)/.$(@F).d,-MT,$@
+AM_LDFLAGS      = @noundef_LDFLAGS@
 
 ifeq (${V},)
 AM_LIBTOOL_SILENT = --silent
@@ -37,48 +39,77 @@
 #
 #	Wildcard module list
 #
-pfx_build_mod := $(patsubst ${srcdir}/libxt_%.c,%,$(wildcard ${srcdir}/libxt_*.c))
-@ENABLE_IPV4_TRUE@ pf4_build_mod := $(patsubst ${srcdir}/libipt_%.c,%,$(wildcard ${srcdir}/libipt_*.c))
-@ENABLE_IPV6_TRUE@ pf6_build_mod := $(patsubst ${srcdir}/libip6t_%.c,%,$(wildcard ${srcdir}/libip6t_*.c))
-pfx_build_mod := $(filter-out @blacklist_modules@,${pfx_build_mod})
-pf4_build_mod := $(filter-out @blacklist_modules@,${pf4_build_mod})
-pf6_build_mod := $(filter-out @blacklist_modules@,${pf6_build_mod})
+pfx_build_mod := $(patsubst ${srcdir}/libxt_%.c,%,$(sort $(wildcard ${srcdir}/libxt_*.c)))
+@ENABLE_NFTABLES_TRUE@ pfb_build_mod := $(patsubst ${srcdir}/libebt_%.c,%,$(sort $(wildcard ${srcdir}/libebt_*.c)))
+@ENABLE_NFTABLES_TRUE@ pfa_build_mod := $(patsubst ${srcdir}/libarpt_%.c,%,$(sort $(wildcard ${srcdir}/libarpt_*.c)))
+pfx_symlinks  := NOTRACK state
+@ENABLE_IPV4_TRUE@ pf4_build_mod := $(patsubst ${srcdir}/libipt_%.c,%,$(sort $(wildcard ${srcdir}/libipt_*.c)))
+@ENABLE_IPV6_TRUE@ pf6_build_mod := $(patsubst ${srcdir}/libip6t_%.c,%,$(sort $(wildcard ${srcdir}/libip6t_*.c)))
+pfx_build_mod := $(filter-out @blacklist_modules@ @blacklist_x_modules@,${pfx_build_mod})
+pfb_build_mod := $(filter-out @blacklist_modules@ @blacklist_b_modules@,${pfb_build_mod})
+pfa_build_mod := $(filter-out @blacklist_modules@ @blacklist_a_modules@,${pfa_build_mod})
+pf4_build_mod := $(filter-out @blacklist_modules@ @blacklist_4_modules@,${pf4_build_mod})
+pf6_build_mod := $(filter-out @blacklist_modules@ @blacklist_6_modules@,${pf6_build_mod})
 pfx_objs      := $(patsubst %,libxt_%.o,${pfx_build_mod})
+pfb_objs      := $(patsubst %,libebt_%.o,${pfb_build_mod})
+pfa_objs      := $(patsubst %,libarpt_%.o,${pfa_build_mod})
 pf4_objs      := $(patsubst %,libipt_%.o,${pf4_build_mod})
 pf6_objs      := $(patsubst %,libip6t_%.o,${pf6_build_mod})
 pfx_solibs    := $(patsubst %,libxt_%.so,${pfx_build_mod})
+pfb_solibs    := $(patsubst %,libebt_%.so,${pfb_build_mod})
+pfa_solibs    := $(patsubst %,libarpt_%.so,${pfa_build_mod})
 pf4_solibs    := $(patsubst %,libipt_%.so,${pf4_build_mod})
 pf6_solibs    := $(patsubst %,libip6t_%.so,${pf6_build_mod})
+pfx_symlink_files := $(patsubst %,libxt_%.so,${pfx_symlinks})
 
 
 #
 # Building blocks
 #
-targets := libext.a libext4.a libext6.a \
-           matches4.man matches6.man \
-           targets4.man targets6.man
+targets := libext.a libext4.a libext6.a libext_ebt.a libext_arpt.a matches.man targets.man
 targets_install :=
 @ENABLE_STATIC_TRUE@ libext_objs := ${pfx_objs}
+@ENABLE_STATIC_TRUE@ libext_ebt_objs := ${pfb_objs}
+@ENABLE_STATIC_TRUE@ libext_arpt_objs := ${pfa_objs}
 @ENABLE_STATIC_TRUE@ libext4_objs := ${pf4_objs}
 @ENABLE_STATIC_TRUE@ libext6_objs := ${pf6_objs}
-@ENABLE_STATIC_FALSE@ targets += ${pfx_solibs} ${pf4_solibs} ${pf6_solibs}
-@ENABLE_STATIC_FALSE@ targets_install += ${pfx_solibs} ${pf4_solibs} ${pf6_solibs}
+@ENABLE_STATIC_FALSE@ targets += ${pfx_solibs} ${pfb_solibs} ${pf4_solibs} ${pf6_solibs} ${pfa_solibs} ${pfx_symlink_files}
+@ENABLE_STATIC_FALSE@ targets_install += ${pfx_solibs} ${pfb_solibs} ${pf4_solibs} ${pf6_solibs} ${pfa_solibs}
+@ENABLE_STATIC_FALSE@ symlinks_install := ${pfx_symlink_files}
 
 .SECONDARY:
 
-.PHONY: all install clean distclean FORCE
+.PHONY: all install uninstall clean distclean FORCE
 
 all: ${targets}
 
-install: ${targets_install}
+install: ${targets_install} ${symlinks_install}
 	@mkdir -p "${DESTDIR}${xtlibdir}";
-	if test -n "${targets_install}"; then install -pm0755 $^ "${DESTDIR}${xtlibdir}/"; fi;
+	if test -n "${targets_install}"; then \
+		install -pm0755 ${targets_install} "${DESTDIR}${xtlibdir}/"; \
+	fi;
+	if test -n "${symlinks_install}"; then \
+		cp -P ${symlinks_install} "${DESTDIR}${xtlibdir}/"; \
+	fi;
+
+uninstall:
+	dir=${DESTDIR}${xtlibdir}; { \
+		test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; \
+	} || { \
+		test -z "${targets_install}" || ( \
+			cd "$$dir" && rm -f ${targets_install} \
+		); \
+		test -z "${symlinks_install}" || ( \
+			cd "$$dir" && rm -f ${symlinks_install} \
+		); \
+		rmdir -p --ignore-fail-on-non-empty "$$dir"; \
+	}
 
 clean:
-	rm -f *.o *.oo *.so *.a {matches,targets}[46].man initext.c initext4.c initext6.c;
+	rm -f *.o *.oo *.so *.a {matches,targets}.man initext.c initext4.c initext6.c initextb.c initexta.c;
+	rm -f .*.d .*.dd;
 
 distclean: clean
-	rm -f .*.d .*.dd;
 
 init%.o: init%.c
 	${AM_VERBOSE_CC} ${CC} ${AM_CPPFLAGS} ${AM_DEPFLAGS} ${AM_CFLAGS} -D_INIT=$*_init ${CFLAGS} -o $@ -c $<;
@@ -90,11 +121,20 @@
 #	Shared libraries
 #
 lib%.so: lib%.oo
-	${AM_VERBOSE_CCLD} ${CCLD} ${AM_LDFLAGS} -shared ${LDFLAGS} -o $@ $<;
+	${AM_VERBOSE_CCLD} ${CCLD} ${AM_LDFLAGS} ${LDFLAGS} -shared -o $@ $< -L../libxtables/.libs -lxtables ${$*_LIBADD};
 
 lib%.oo: ${srcdir}/lib%.c
 	${AM_VERBOSE_CC} ${CC} ${AM_CPPFLAGS} ${AM_DEPFLAGS} ${AM_CFLAGS} -D_INIT=lib$*_init -DPIC -fPIC ${CFLAGS} -o $@ -c $<;
 
+libxt_NOTRACK.so: libxt_CT.so
+	ln -fs $< $@
+libxt_state.so: libxt_conntrack.so
+	ln -fs $< $@
+
+# Need the LIBADDs in iptables/Makefile.am too for libxtables_la_LIBADD
+xt_RATEEST_LIBADD   = -lm
+xt_statistic_LIBADD = -lm
+xt_connlabel_LIBADD = @libnetfilter_conntrack_LIBS@
 
 #
 #	Static bits
@@ -109,6 +149,12 @@
 libext.a: initext.o ${libext_objs}
 	${AM_VERBOSE_AR} ${AR} crs $@ $^;
 
+libext_ebt.a: initextb.o ${libext_ebt_objs}
+	${AM_VERBOSE_AR} ${AR} crs $@ $^;
+
+libext_arpt.a: initexta.o ${libext_arpt_objs}
+	${AM_VERBOSE_AR} ${AR} crs $@ $^;
+
 libext4.a: initext4.o ${libext4_objs}
 	${AM_VERBOSE_AR} ${AR} crs $@ $^;
 
@@ -116,6 +162,8 @@
 	${AM_VERBOSE_AR} ${AR} crs $@ $^;
 
 initext_func  := $(addprefix xt_,${pfx_build_mod})
+initextb_func := $(addprefix ebt_,${pfb_build_mod})
+initexta_func := $(addprefix arpt_,${pfa_build_mod})
 initext4_func := $(addprefix ipt_,${pf4_build_mod})
 initext6_func := $(addprefix ip6t_,${pf6_build_mod})
 
@@ -124,6 +172,16 @@
 	cmp -s $@ $@.tmp || mv $@.tmp $@; \
 	rm -f $@.tmp;
 
+.initextb.dd: FORCE
+	@echo "${initextb_func}" >$@.tmp; \
+	cmp -s $@ $@.tmp || mv $@.tmp $@; \
+	rm -f $@.tmp;
+
+.initexta.dd: FORCE
+	@echo "${initexta_func}" >$@.tmp; \
+	cmp -s $@ $@.tmp || mv $@.tmp $@; \
+	rm -f $@.tmp;
+
 .initext4.dd: FORCE
 	@echo "${initext4_func}" >$@.tmp; \
 	cmp -s $@ $@.tmp || mv $@.tmp $@; \
@@ -150,6 +208,38 @@
 	echo "}" >>$@; \
 	);
 
+initextb.c: .initextb.dd
+	${AM_VERBOSE_GEN}
+	@( \
+	echo "" >$@; \
+	for i in ${initextb_func}; do \
+		echo "extern void lib$${i}_init(void);" >>$@; \
+	done; \
+	echo "void init_extensionsb(void);" >>$@; \
+	echo "void init_extensionsb(void)" >>$@; \
+	echo "{" >>$@; \
+	for i in ${initextb_func}; do \
+		echo  " ""lib$${i}_init();" >>$@; \
+	done; \
+	echo "}" >>$@; \
+	);
+
+initexta.c: .initexta.dd
+	${AM_VERBOSE_GEN}
+	@( \
+	echo "" >$@; \
+	for i in ${initexta_func}; do \
+		echo "extern void lib$${i}_init(void);" >>$@; \
+	done; \
+	echo "void init_extensionsa(void);" >>$@; \
+	echo "void init_extensionsa(void)" >>$@; \
+	echo "{" >>$@; \
+	for i in ${initexta_func}; do \
+		echo  " ""lib$${i}_init();" >>$@; \
+	done; \
+	echo "}" >>$@; \
+	);
+
 initext4.c: .initext4.dd
 	${AM_VERBOSE_GEN}
 	@( \
@@ -185,36 +275,33 @@
 #
 #	Manual pages
 #
-ex_matches = $(sort $(shell echo $(1) | LC_ALL=POSIX grep -Eo '\b[[:lower:][:digit:]_]+\b'))
-ex_targets = $(sort $(shell echo $(1) | LC_ALL=POSIX grep -Eo '\b[[:upper:][:digit:]_]+\b'))
+ex_matches = $(shell echo ${1} | LC_ALL=POSIX grep -Eo '\b[[:lower:][:digit:]_]+\b')
+ex_targets = $(shell echo ${1} | LC_ALL=POSIX grep -Eo '\b[[:upper:][:digit:]_]+\b')
 man_run    = \
 	${AM_VERBOSE_GEN} \
-	for ext in $(1); do \
+	for ext in $(sort ${1}); do \
 		f="${srcdir}/libxt_$$ext.man"; \
-		cf="${srcdir}/libxt_$$ext.c"; \
-		if [ -f "$$f" ] && grep -Eq "$(3)|NFPROTO_UNSPEC" "$$cf"; then \
-			echo -e "\t+ $$f" >&2; \
-			echo ".SS $$ext"; \
-			cat "$$f" || exit $$?; \
-			continue; \
-		fi; \
-		f="${srcdir}/lib$(2)t_$$ext.man"; \
 		if [ -f "$$f" ]; then \
 			echo -e "\t+ $$f" >&2; \
 			echo ".SS $$ext"; \
 			cat "$$f" || exit $$?; \
-			continue; \
+		fi; \
+		f="${srcdir}/libip6t_$$ext.man"; \
+		if [ -f "$$f" ]; then \
+			echo -e "\t+ $$f" >&2; \
+			echo ".SS $$ext (IPv6-specific)"; \
+			cat "$$f" || exit $$?; \
+		fi; \
+		f="${srcdir}/libipt_$$ext.man"; \
+		if [ -f "$$f" ]; then \
+			echo -e "\t+ $$f" >&2; \
+			echo ".SS $$ext (IPv4-specific)"; \
+			cat "$$f" || exit $$?; \
 		fi; \
 	done >$@;
 
-matches4.man: .initext.dd .initext4.dd $(wildcard ${srcdir}/lib*.man)
-	$(call man_run,$(call ex_matches,${pfx_build_mod} ${pf4_build_mod}),ip,NFPROTO_IPV4)
+matches.man: .initext.dd .initextb.dd .initexta.dd .initext4.dd .initext6.dd $(wildcard ${srcdir}/lib*.man)
+	$(call man_run,$(call ex_matches,${pfx_build_mod} ${pfb_build_mod} ${pfa_build_mod} ${pf4_build_mod} ${pf6_build_mod} ${pfx_symlinks}))
 
-matches6.man: .initext.dd .initext6.dd $(wildcard ${srcdir}/lib*.man)
-	$(call man_run,$(call ex_matches,${pfx_build_mod} ${pf6_build_mod}),ip6,NFPROTO_IPV6)
-
-targets4.man: .initext.dd .initext4.dd $(wildcard ${srcdir}/lib*.man)
-	$(call man_run,$(call ex_targets,${pfx_build_mod} ${pf4_build_mod}),ip,NFPROTO_IPV4)
-
-targets6.man: .initext.dd .initext6.dd $(wildcard ${srcdir}/lib*.man)
-	$(call man_run,$(call ex_targets,${pfx_build_mod} ${pf6_build_mod}),ip6,NFPROTO_IPV6)
+targets.man: .initext.dd .initextb.dd .initexta.dd .initext4.dd .initext6.dd $(wildcard ${srcdir}/lib*.man)
+	$(call man_run,$(call ex_targets,${pfx_build_mod} ${pfb_build_mod} ${pfa_build_mod} ${pf4_build_mod} ${pf6_build_mod} ${pfx_symlinks}))
diff --git a/extensions/gen_init b/extensions/gen_init
new file mode 100755
index 0000000..c1844f3
--- /dev/null
+++ b/extensions/gen_init
@@ -0,0 +1,36 @@
+#!/bin/bash -e
+#
+# Generate init_extensions* functions to call all the _init functions from
+# filter_init
+#
+# Usage: gen_init <suffix> filename...
+#
+# Example output:
+#
+#   void libxt_tcp_init(void);
+#   void libxt_udp_init(void);
+#   void init_extensions(void);
+#   void init_extensions(void) {
+#     libxt_tcp_init();
+#     libxt_udp_init();
+#   }
+
+EXT=$1
+shift
+
+for i in "$@"; do
+  f=${i##*/}
+  f=${f%%.*}
+  echo "void ${f}_init(void);"
+done
+
+echo "void init_extensions${EXT}(void);"
+echo "void init_extensions${EXT}(void) {"
+
+for i in "$@"; do
+  f=${i##*/}
+  f=${f%%.*}
+  echo "  ${f}_init();"
+done
+
+echo "}"
diff --git a/extensions/generic.txlate b/extensions/generic.txlate
new file mode 100644
index 0000000..0e256c3
--- /dev/null
+++ b/extensions/generic.txlate
@@ -0,0 +1,36 @@
+iptables-translate -I OUTPUT -p udp -d 8.8.8.8 -j ACCEPT
+nft insert rule ip filter OUTPUT ip protocol udp ip daddr 8.8.8.8 counter accept
+
+iptables-translate -F -t nat
+nft flush table ip nat
+
+iptables-translate -I INPUT -i iifname -s 10.0.0.0/8
+nft insert rule ip filter INPUT iifname "iifname" ip saddr 10.0.0.0/8 counter
+
+iptables-translate -A INPUT -i iif+ ! -d 10.0.0.0/8
+nft add rule ip filter INPUT iifname "iif*" ip daddr != 10.0.0.0/8 counter
+
+ebtables-translate -I INPUT -i iname --logical-in ilogname -s 0:0:0:0:0:0
+nft insert rule bridge filter INPUT iifname "iname" meta ibrname "ilogname" ether saddr 00:00:00:00:00:00 counter
+
+ebtables-translate -A FORWARD ! -i iname --logical-in ilogname -o out+ --logical-out lout+ -d 1:2:3:4:de:af
+nft add rule bridge filter FORWARD iifname != "iname" meta ibrname "ilogname" oifname "out*" meta obrname "lout*" ether daddr 01:02:03:04:de:af counter
+
+ebtables-translate -I INPUT -p ip -d 1:2:3:4:5:6/ff:ff:ff:ff:00:00
+nft insert rule bridge filter INPUT ether type 0x800 ether daddr 01:02:03:04:00:00 and ff:ff:ff:ff:00:00 == 01:02:03:04:00:00 counter
+
+# asterisk is not special in iptables and it is even a valid interface name
+iptables-translate -A FORWARD -i '*' -o 'eth*foo'
+nft add rule ip filter FORWARD iifname "\*" oifname "eth\*foo" counter
+
+# escape all asterisks but translate only the first plus character
+iptables-translate -A FORWARD -i 'eth*foo*+' -o 'eth++'
+nft add rule ip filter FORWARD iifname "eth\*foo\**" oifname "eth+*" counter
+
+# skip for always matching interface names
+iptables-translate -A FORWARD -i '+'
+nft add rule ip filter FORWARD counter
+
+# match against invalid interface name to simulate never matching rule
+iptables-translate -A FORWARD ! -i '+'
+nft add rule ip filter FORWARD iifname "INVAL/D" counter
diff --git a/extensions/iptables.t b/extensions/iptables.t
new file mode 100644
index 0000000..b4b6d67
--- /dev/null
+++ b/extensions/iptables.t
@@ -0,0 +1,6 @@
+:FORWARD
+-i alongifacename0;=;OK
+-i thisinterfaceistoolong0;;FAIL
+-i eth+ -o alongifacename+;=;OK
+! -i eth0;=;OK
+! -o eth+;=;OK
diff --git a/extensions/libarpt_CLASSIFY.t b/extensions/libarpt_CLASSIFY.t
new file mode 100644
index 0000000..0cf0f2c
--- /dev/null
+++ b/extensions/libarpt_CLASSIFY.t
@@ -0,0 +1,4 @@
+:OUTPUT
+-o lo --destination-mac 11:22:33:44:55:66;-o lo --dst-mac 11:22:33:44:55:66;OK
+--dst-mac Broadcast ;--dst-mac ff:ff:ff:ff:ff:ff;OK
+! -o eth+ -d 1.2.3.4/24 -j CLASSIFY --set-class 0:0;-j CLASSIFY ! -o eth+ -d 1.2.3.0/24 --set-class 0:0;OK
diff --git a/extensions/libarpt_MARK.t b/extensions/libarpt_MARK.t
new file mode 100644
index 0000000..3b13d44
--- /dev/null
+++ b/extensions/libarpt_MARK.t
@@ -0,0 +1,4 @@
+:INPUT,OUTPUT
+-j MARK -d 0.0.0.0/8 --set-mark 1;=;OK
+-s ! 0.0.0.0 -j MARK --and-mark 0x17;-j MARK ! -s 0.0.0.0 --and-mark 17;OK
+-j MARK -s 0.0.0.0 --or-mark 17;=;OK
diff --git a/extensions/libarpt_mangle.c b/extensions/libarpt_mangle.c
new file mode 100644
index 0000000..a2378a8
--- /dev/null
+++ b/extensions/libarpt_mangle.c
@@ -0,0 +1,193 @@
+/*
+ * Arturo Borrero Gonzalez <arturo@debian.org> adapted
+ * this code to libxtables for arptables-compat in 2015
+ */
+
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <getopt.h>
+#include <netinet/ether.h>
+#include <xtables.h>
+#include <linux/netfilter_arp/arpt_mangle.h>
+#include "iptables/nft.h"
+#include "iptables/nft-arp.h"
+
+static void arpmangle_print_help(void)
+{
+	printf(
+	"mangle target options:\n"
+	"--mangle-ip-s IP address\n"
+	"--mangle-ip-d IP address\n"
+	"--mangle-mac-s MAC address\n"
+	"--mangle-mac-d MAC address\n"
+	"--mangle-target target (DROP, CONTINUE or ACCEPT -- default is ACCEPT)\n");
+}
+
+#define MANGLE_IPS    '1'
+#define MANGLE_IPT    '2'
+#define MANGLE_DEVS   '3'
+#define MANGLE_DEVT   '4'
+#define MANGLE_TARGET '5'
+
+static const struct option arpmangle_opts[] = {
+	{ .name = "mangle-ip-s",	.has_arg = true, .val = MANGLE_IPS },
+	{ .name = "mangle-ip-d",	.has_arg = true, .val = MANGLE_IPT },
+	{ .name = "mangle-mac-s",	.has_arg = true, .val = MANGLE_DEVS },
+	{ .name = "mangle-mac-d",	.has_arg = true, .val = MANGLE_DEVT },
+	{ .name = "mangle-target",	.has_arg = true, .val = MANGLE_TARGET },
+	XT_GETOPT_TABLEEND,
+};
+
+static void arpmangle_init(struct xt_entry_target *target)
+{
+	struct arpt_mangle *mangle = (struct arpt_mangle *)target->data;
+
+	mangle->target = NF_ACCEPT;
+}
+
+static int
+arpmangle_parse(int c, char **argv, int invert, unsigned int *flags,
+		const void *entry, struct xt_entry_target **target)
+{
+	struct arpt_mangle *mangle = (struct arpt_mangle *)(*target)->data;
+	struct in_addr *ipaddr, mask;
+	struct ether_addr *macaddr;
+	const struct arpt_entry *e = (const struct arpt_entry *)entry;
+	unsigned int nr;
+	int ret = 1;
+
+	memset(&mask, 0, sizeof(mask));
+
+	switch (c) {
+	case MANGLE_IPS:
+		xtables_ipparse_any(optarg, &ipaddr, &mask, &nr);
+		mangle->u_s.src_ip.s_addr = ipaddr->s_addr;
+		free(ipaddr);
+		mangle->flags |= ARPT_MANGLE_SIP;
+		break;
+	case MANGLE_IPT:
+		xtables_ipparse_any(optarg, &ipaddr, &mask, &nr);
+		mangle->u_t.tgt_ip.s_addr = ipaddr->s_addr;
+		free(ipaddr);
+		mangle->flags |= ARPT_MANGLE_TIP;
+		break;
+	case MANGLE_DEVS:
+		if (e->arp.arhln_mask == 0)
+			xtables_error(PARAMETER_PROBLEM,
+				      "no --h-length defined");
+		if (e->arp.invflags & ARPT_INV_ARPHLN)
+			xtables_error(PARAMETER_PROBLEM,
+				      "! --h-length not allowed for "
+				      "--mangle-mac-s");
+		if (e->arp.arhln != 6)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only --h-length 6 supported");
+		macaddr = ether_aton(optarg);
+		if (macaddr == NULL)
+			xtables_error(PARAMETER_PROBLEM,
+				      "invalid source MAC");
+		memcpy(mangle->src_devaddr, macaddr, e->arp.arhln);
+		mangle->flags |= ARPT_MANGLE_SDEV;
+		break;
+	case MANGLE_DEVT:
+		if (e->arp.arhln_mask == 0)
+			xtables_error(PARAMETER_PROBLEM,
+				      "no --h-length defined");
+		if (e->arp.invflags & ARPT_INV_ARPHLN)
+			xtables_error(PARAMETER_PROBLEM,
+				      "! hln not allowed for --mangle-mac-d");
+		if (e->arp.arhln != 6)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only --h-length 6 supported");
+		macaddr = ether_aton(optarg);
+		if (macaddr == NULL)
+			xtables_error(PARAMETER_PROBLEM, "invalid target MAC");
+		memcpy(mangle->tgt_devaddr, macaddr, e->arp.arhln);
+		mangle->flags |= ARPT_MANGLE_TDEV;
+		break;
+	case MANGLE_TARGET:
+		if (!strcmp(optarg, "DROP"))
+			mangle->target = NF_DROP;
+		else if (!strcmp(optarg, "ACCEPT"))
+			mangle->target = NF_ACCEPT;
+		else if (!strcmp(optarg, "CONTINUE"))
+			mangle->target = XT_CONTINUE;
+		else
+			xtables_error(PARAMETER_PROBLEM,
+				      "bad target for --mangle-target");
+		break;
+	default:
+		ret = 0;
+	}
+
+	return ret;
+}
+
+static void arpmangle_final_check(unsigned int flags)
+{
+}
+
+static const char *ipaddr_to(const struct in_addr *addrp, int numeric)
+{
+	if (numeric)
+		return xtables_ipaddr_to_numeric(addrp);
+	else
+		return xtables_ipaddr_to_anyname(addrp);
+}
+
+static void
+arpmangle_print(const void *ip, const struct xt_entry_target *target,
+		int numeric)
+{
+	struct arpt_mangle *m = (struct arpt_mangle *)(target->data);
+
+	if (m->flags & ARPT_MANGLE_SIP) {
+		printf(" --mangle-ip-s %s",
+		       ipaddr_to(&(m->u_s.src_ip), numeric));
+	}
+	if (m->flags & ARPT_MANGLE_SDEV) {
+		printf(" --mangle-mac-s ");
+		xtables_print_mac((unsigned char *)m->src_devaddr);
+	}
+	if (m->flags & ARPT_MANGLE_TIP) {
+		printf(" --mangle-ip-d %s",
+		       ipaddr_to(&(m->u_t.tgt_ip), numeric));
+	}
+	if (m->flags & ARPT_MANGLE_TDEV) {
+		printf(" --mangle-mac-d ");
+		xtables_print_mac((unsigned char *)m->tgt_devaddr);
+	}
+	if (m->target != NF_ACCEPT) {
+		printf(" --mangle-target %s",
+		       m->target == NF_DROP ? "DROP" : "CONTINUE");
+	}
+}
+
+static void arpmangle_save(const void *ip, const struct xt_entry_target *target)
+{
+	arpmangle_print(ip, target, 0);
+}
+
+static struct xtables_target arpmangle_target = {
+	.name		= "mangle",
+	.revision	= 0,
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_ARP,
+	.size		= XT_ALIGN(sizeof(struct arpt_mangle)),
+	.userspacesize	= XT_ALIGN(sizeof(struct arpt_mangle)),
+	.help		= arpmangle_print_help,
+	.init		= arpmangle_init,
+	.parse		= arpmangle_parse,
+	.final_check	= arpmangle_final_check,
+	.print		= arpmangle_print,
+	.save		= arpmangle_save,
+	.extra_opts	= arpmangle_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&arpmangle_target);
+}
diff --git a/extensions/libarpt_mangle.t b/extensions/libarpt_mangle.t
new file mode 100644
index 0000000..da96694
--- /dev/null
+++ b/extensions/libarpt_mangle.t
@@ -0,0 +1,5 @@
+:OUTPUT
+-j mangle -s 1.2.3.4 --mangle-ip-s 1.2.3.5;=;OK
+-j mangle -d 1.2.3.4 --mangle-ip-d 1.2.3.5;=;OK
+-j mangle -d 1.2.3.4 --mangle-mac-d 00:01:02:03:04:05;=;OK
+-d 1.2.3.4 --h-length 5 -j mangle --mangle-mac-s 00:01:02:03:04:05;=;FAIL
diff --git a/extensions/libarpt_standard.t b/extensions/libarpt_standard.t
new file mode 100644
index 0000000..e84a00b
--- /dev/null
+++ b/extensions/libarpt_standard.t
@@ -0,0 +1,14 @@
+:INPUT
+-s 192.168.0.1;=;OK
+-s 0.0.0.0/8;=;OK
+-s ! 0.0.0.0;! -s 0.0.0.0;OK
+-d 192.168.0.1;=;OK
+! -d 0.0.0.0;=;OK
+-d 0.0.0.0/24;=;OK
+-j DROP -i lo;=;OK
+-j ACCEPT ! -i lo;=;OK
+-i ppp+;=;OK
+! -i ppp+;=;OK
+-i lo --destination-mac 11:22:33:44:55:66;-i lo --dst-mac 11:22:33:44:55:66;OK
+--source-mac Unicast;--src-mac 00:00:00:00:00:00/01:00:00:00:00:00;OK
+! --src-mac Multicast;! --src-mac 01:00:00:00:00:00/01:00:00:00:00:00;OK
diff --git a/extensions/libebt_802_3.c b/extensions/libebt_802_3.c
new file mode 100644
index 0000000..f05d02e
--- /dev/null
+++ b/extensions/libebt_802_3.c
@@ -0,0 +1,133 @@
+/* 802_3
+ *
+ * Author:
+ * Chris Vitale <csv@bluetail.com>
+ *
+ * May 2003
+ *
+ * Adapted by Arturo Borrero Gonzalez <arturo@debian.org>
+ * to use libxtables for ebtables-compat
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <xtables.h>
+#include <linux/netfilter_bridge/ebt_802_3.h>
+
+#define _802_3_SAP	'1'
+#define _802_3_TYPE	'2'
+
+static const struct option br802_3_opts[] = {
+	{ .name = "802_3-sap",	.has_arg = true, .val = _802_3_SAP },
+	{ .name = "802_3-type",	.has_arg = true, .val = _802_3_TYPE },
+	XT_GETOPT_TABLEEND,
+};
+
+static void br802_3_print_help(void)
+{
+	printf(
+"802_3 options:\n"
+"--802_3-sap [!] protocol       : 802.3 DSAP/SSAP- 1 byte value (hex)\n"
+"  DSAP and SSAP are always the same.  One SAP applies to both fields\n"
+"--802_3-type [!] protocol      : 802.3 SNAP Type- 2 byte value (hex)\n"
+"  Type implies SAP value 0xaa\n");
+}
+
+static void br802_3_init(struct xt_entry_match *match)
+{
+	struct ebt_802_3_info *info = (struct ebt_802_3_info *)match->data;
+
+	info->invflags = 0;
+	info->bitmask = 0;
+}
+
+static int
+br802_3_parse(int c, char **argv, int invert, unsigned int *flags,
+	      const void *entry, struct xt_entry_match **match)
+{
+	struct ebt_802_3_info *info = (struct ebt_802_3_info *) (*match)->data;
+	unsigned int i;
+	char *end;
+
+	switch (c) {
+	case _802_3_SAP:
+		if (invert)
+			info->invflags |= EBT_802_3_SAP;
+		i = strtoul(optarg, &end, 16);
+		if (i > 255 || *end != '\0')
+			xtables_error(PARAMETER_PROBLEM,
+				      "Problem with specified "
+					"sap hex value, %x",i);
+		info->sap = i; /* one byte, so no byte order worries */
+		info->bitmask |= EBT_802_3_SAP;
+		break;
+	case _802_3_TYPE:
+		if (invert)
+			info->invflags |= EBT_802_3_TYPE;
+		i = strtoul(optarg, &end, 16);
+		if (i > 65535 || *end != '\0') {
+			xtables_error(PARAMETER_PROBLEM,
+				      "Problem with the specified "
+					"type hex value, %x",i);
+		}
+		info->type = htons(i);
+		info->bitmask |= EBT_802_3_TYPE;
+		break;
+	default:
+		return 0;
+	}
+
+	*flags |= info->bitmask;
+	return 1;
+}
+
+static void
+br802_3_final_check(unsigned int flags)
+{
+	if (!flags)
+		xtables_error(PARAMETER_PROBLEM,
+			      "You must specify proper arguments");
+}
+
+static void br802_3_print(const void *ip, const struct xt_entry_match *match,
+			  int numeric)
+{
+	struct ebt_802_3_info *info = (struct ebt_802_3_info *)match->data;
+
+	if (info->bitmask & EBT_802_3_SAP) {
+		printf("--802_3-sap ");
+		if (info->invflags & EBT_802_3_SAP)
+			printf("! ");
+		printf("0x%.2x ", info->sap);
+	}
+	if (info->bitmask & EBT_802_3_TYPE) {
+		printf("--802_3-type ");
+		if (info->invflags & EBT_802_3_TYPE)
+			printf("! ");
+		printf("0x%.4x ", ntohs(info->type));
+	}
+}
+
+static struct xtables_match br802_3_match =
+{
+	.name		= "802_3",
+	.revision	= 0,
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size		= XT_ALIGN(sizeof(struct ebt_802_3_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_802_3_info)),
+	.init		= br802_3_init,
+	.help		= br802_3_print_help,
+	.parse		= br802_3_parse,
+	.final_check	= br802_3_final_check,
+	.print		= br802_3_print,
+	.extra_opts	= br802_3_opts,
+};
+
+void _init(void)
+{
+	xtables_register_match(&br802_3_match);
+}
diff --git a/extensions/libebt_802_3.t b/extensions/libebt_802_3.t
new file mode 100644
index 0000000..ddfb2f0
--- /dev/null
+++ b/extensions/libebt_802_3.t
@@ -0,0 +1,3 @@
+:INPUT,FORWARD,OUTPUT
+--802_3-sap ! 0x0a -j CONTINUE;=;OK
+--802_3-type 0x000a -j RETURN;=;OK
diff --git a/extensions/libebt_among.c b/extensions/libebt_among.c
new file mode 100644
index 0000000..2b9a1b6
--- /dev/null
+++ b/extensions/libebt_among.c
@@ -0,0 +1,243 @@
+/* ebt_among
+ *
+ * Authors:
+ * Grzegorz Borowiak <grzes@gnu.univ.gda.pl>
+ *
+ * August, 2003
+ */
+
+#include <errno.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <xtables.h>
+#include <arpa/inet.h>
+#include <netinet/ether.h>
+#include <netinet/in.h>
+#include <linux/if_ether.h>
+#include <linux/netfilter_bridge/ebt_among.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include "iptables/nft.h"
+#include "iptables/nft-bridge.h"
+
+#define AMONG_DST '1'
+#define AMONG_SRC '2'
+#define AMONG_DST_F '3'
+#define AMONG_SRC_F '4'
+
+static const struct option bramong_opts[] = {
+	{"among-dst", required_argument, 0, AMONG_DST},
+	{"among-src", required_argument, 0, AMONG_SRC},
+	{"among-dst-file", required_argument, 0, AMONG_DST_F},
+	{"among-src-file", required_argument, 0, AMONG_SRC_F},
+	{0}
+};
+
+static void bramong_print_help(void)
+{
+	printf(
+"`among' options:\n"
+"--among-dst      [!] list      : matches if ether dst is in list\n"
+"--among-src      [!] list      : matches if ether src is in list\n"
+"--among-dst-file [!] file      : obtain dst list from file\n"
+"--among-src-file [!] file      : obtain src list from file\n"
+"list has form:\n"
+" xx:xx:xx:xx:xx:xx[=ip.ip.ip.ip],yy:yy:yy:yy:yy:yy[=ip.ip.ip.ip]"
+",...,zz:zz:zz:zz:zz:zz[=ip.ip.ip.ip][,]\n"
+"Things in brackets are optional.\n"
+"If you want to allow two (or more) IP addresses to one MAC address, you\n"
+"can specify two (or more) pairs with the same MAC, e.g.\n"
+" 00:00:00:fa:eb:fe=153.19.120.250,00:00:00:fa:eb:fe=192.168.0.1\n"
+	);
+}
+
+static void
+parse_nft_among_pair(char *buf, struct nft_among_pair *pair, bool have_ip)
+{
+	char *sep = index(buf, '=');
+	struct ether_addr *ether;
+
+	if (sep) {
+		*sep = '\0';
+
+		if (!inet_aton(sep + 1, &pair->in))
+			xtables_error(PARAMETER_PROBLEM,
+				      "Invalid IP address '%s'\n", sep + 1);
+	}
+	ether = ether_aton(buf);
+	if (!ether)
+		xtables_error(PARAMETER_PROBLEM,
+			      "Invalid MAC address '%s'\n", buf);
+	memcpy(&pair->ether, ether, sizeof(*ether));
+}
+
+static void
+parse_nft_among_pairs(struct nft_among_pair *pairs, char *buf,
+		      size_t cnt, bool have_ip)
+{
+	size_t tmpcnt = 0;
+
+	buf = strtok(buf, ",");
+	while (buf) {
+		struct nft_among_pair pair = {};
+
+		parse_nft_among_pair(buf, &pair, have_ip);
+		nft_among_insert_pair(pairs, &tmpcnt, &pair);
+		buf = strtok(NULL, ",");
+	}
+}
+
+static size_t count_nft_among_pairs(char *buf)
+{
+	size_t cnt = 0;
+	char *p = buf;
+
+	if (!*buf)
+		return 0;
+
+	do {
+		cnt++;
+		p = index(++p, ',');
+	} while (p);
+
+	return cnt;
+}
+
+static bool nft_among_pairs_have_ip(char *buf)
+{
+	return !!index(buf, '=');
+}
+
+static int bramong_parse(int c, char **argv, int invert,
+		 unsigned int *flags, const void *entry,
+		 struct xt_entry_match **match)
+{
+	struct nft_among_data *data = (struct nft_among_data *)(*match)->data;
+	struct xt_entry_match *new_match;
+	bool have_ip, dst = false;
+	size_t new_size, cnt;
+	struct stat stats;
+	int fd = -1, poff;
+	long flen = 0;
+
+	switch (c) {
+	case AMONG_DST_F:
+		dst = true;
+		/* fall through */
+	case AMONG_SRC_F:
+		if ((fd = open(optarg, O_RDONLY)) == -1)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Couldn't open file '%s'", optarg);
+		if (fstat(fd, &stats) < 0)
+			xtables_error(PARAMETER_PROBLEM,
+				      "fstat(%s) failed: '%s'",
+				      optarg, strerror(errno));
+		flen = stats.st_size;
+		/* use mmap because the file will probably be big */
+		optarg = mmap(0, flen, PROT_READ | PROT_WRITE,
+			      MAP_PRIVATE, fd, 0);
+		if (optarg == MAP_FAILED)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Couldn't map file to memory");
+		if (optarg[flen-1] != '\n')
+			xtables_error(PARAMETER_PROBLEM,
+				      "File should end with a newline");
+		if (strchr(optarg, '\n') != optarg+flen-1)
+			xtables_error(PARAMETER_PROBLEM,
+				      "File should only contain one line");
+		optarg[flen-1] = '\0';
+		/* fall through */
+	case AMONG_DST:
+		if (c == AMONG_DST)
+			dst = true;
+		/* fall through */
+	case AMONG_SRC:
+		break;
+	default:
+		return 0;
+	}
+
+	cnt = count_nft_among_pairs(optarg);
+	if (cnt == 0)
+		return 0;
+
+	new_size = data->src.cnt + data->dst.cnt + cnt;
+	new_size *= sizeof(struct nft_among_pair);
+	new_size += XT_ALIGN(sizeof(struct xt_entry_match)) +
+			sizeof(struct nft_among_data);
+	new_match = xtables_calloc(1, new_size);
+	memcpy(new_match, *match, (*match)->u.match_size);
+	new_match->u.match_size = new_size;
+
+	data = (struct nft_among_data *)new_match->data;
+	have_ip = nft_among_pairs_have_ip(optarg);
+	poff = nft_among_prepare_data(data, dst, cnt, invert, have_ip);
+	parse_nft_among_pairs(data->pairs + poff, optarg, cnt, have_ip);
+
+	free(*match);
+	*match = new_match;
+
+	if (c == AMONG_DST_F || c == AMONG_SRC_F) {
+		munmap(argv, flen);
+		close(fd);
+	}
+	return 1;
+}
+
+static void __bramong_print(struct nft_among_pair *pairs,
+			    int cnt, bool inv, bool have_ip)
+{
+	const char *isep = inv ? "! " : "";
+	int i;
+
+	for (i = 0; i < cnt; i++) {
+		printf("%s", isep);
+		isep = ",";
+
+		printf("%s", ether_ntoa(&pairs[i].ether));
+		if (pairs[i].in.s_addr != INADDR_ANY)
+			printf("=%s", inet_ntoa(pairs[i].in));
+	}
+	printf(" ");
+}
+
+static void bramong_print(const void *ip, const struct xt_entry_match *match,
+			  int numeric)
+{
+	struct nft_among_data *data = (struct nft_among_data *)match->data;
+
+	if (data->src.cnt) {
+		printf("--among-src ");
+		__bramong_print(data->pairs,
+				data->src.cnt, data->src.inv, data->src.ip);
+	}
+	if (data->dst.cnt) {
+		printf("--among-dst ");
+		__bramong_print(data->pairs + data->src.cnt,
+				data->dst.cnt, data->dst.inv, data->dst.ip);
+	}
+}
+
+static struct xtables_match bramong_match = {
+	.name		= "among",
+	.revision	= 0,
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size		= XT_ALIGN(sizeof(struct nft_among_data)),
+	.userspacesize	= XT_ALIGN(sizeof(struct nft_among_data)),
+	.help		= bramong_print_help,
+	.parse		= bramong_parse,
+	.print		= bramong_print,
+	.extra_opts	= bramong_opts,
+};
+
+void _init(void)
+{
+	xtables_register_match(&bramong_match);
+}
diff --git a/extensions/libebt_among.t b/extensions/libebt_among.t
new file mode 100644
index 0000000..a02206f
--- /dev/null
+++ b/extensions/libebt_among.t
@@ -0,0 +1,16 @@
+:INPUT,FORWARD,OUTPUT
+--among-dst de:ad:0:be:ee:ff,c0:ff:ee:0:ba:be;--among-dst c0:ff:ee:0:ba:be,de:ad:0:be:ee:ff;OK
+--among-dst ! c0:ff:ee:0:ba:be,de:ad:0:be:ee:ff;=;OK
+--among-src be:ef:0:c0:ff:ee,c0:ff:ee:0:ba:be,de:ad:0:be:ee:ff;=;OK
+--among-src de:ad:0:be:ee:ff=10.0.0.1,c0:ff:ee:0:ba:be=192.168.1.1;--among-src c0:ff:ee:0:ba:be=192.168.1.1,de:ad:0:be:ee:ff=10.0.0.1;OK
+--among-src ! c0:ff:ee:0:ba:be=192.168.1.1,de:ad:0:be:ee:ff=10.0.0.1;=;OK
+--among-src de:ad:0:be:ee:ff --among-dst c0:ff:ee:0:ba:be;=;OK
+--among-src de:ad:0:be:ee:ff=10.0.0.1 --among-dst c0:ff:ee:0:ba:be=192.168.1.1;=;OK
+--among-src ! de:ad:0:be:ee:ff --among-dst c0:ff:ee:0:ba:be;=;OK
+--among-src de:ad:0:be:ee:ff=10.0.0.1 --among-dst ! c0:ff:ee:0:ba:be=192.168.1.1;=;OK
+--among-src ! de:ad:0:be:ee:ff --among-dst c0:ff:ee:0:ba:be=192.168.1.1;=;OK
+--among-src de:ad:0:be:ee:ff=10.0.0.1 --among-dst ! c0:ff:ee:0:ba:be=192.168.1.1;=;OK
+--among-src;=;FAIL
+--among-src 00:11=10.0.0.1;=;FAIL
+--among-src de:ad:0:be:ee:ff=10.256.0.1;=;FAIL
+--among-src c0:ff:ee:0:ba:be=192.168.1.1,de:ad:0:be:ee:ff;=;OK
diff --git a/extensions/libebt_arp.c b/extensions/libebt_arp.c
new file mode 100644
index 0000000..d5035b9
--- /dev/null
+++ b/extensions/libebt_arp.c
@@ -0,0 +1,363 @@
+/* ebt_arp
+ *
+ * Authors:
+ * Bart De Schuymer <bdschuym@pandora.be>
+ * Tim Gardner <timg@tpi.com>
+ *
+ * April, 2002
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <xtables.h>
+#include <netinet/ether.h>
+
+#include <xtables.h>
+#include <net/if_arp.h>
+#include <linux/netfilter_bridge/ebt_arp.h>
+#include "iptables/nft.h"
+#include "iptables/nft-bridge.h"
+
+#define ARP_OPCODE '1'
+#define ARP_HTYPE  '2'
+#define ARP_PTYPE  '3'
+#define ARP_IP_S   '4'
+#define ARP_IP_D   '5'
+#define ARP_MAC_S  '6'
+#define ARP_MAC_D  '7'
+#define ARP_GRAT   '8'
+
+static const struct option brarp_opts[] = {
+	{ "arp-opcode"    , required_argument, 0, ARP_OPCODE },
+	{ "arp-op"        , required_argument, 0, ARP_OPCODE },
+	{ "arp-htype"     , required_argument, 0, ARP_HTYPE  },
+	{ "arp-ptype"     , required_argument, 0, ARP_PTYPE  },
+	{ "arp-ip-src"    , required_argument, 0, ARP_IP_S   },
+	{ "arp-ip-dst"    , required_argument, 0, ARP_IP_D   },
+	{ "arp-mac-src"   , required_argument, 0, ARP_MAC_S  },
+	{ "arp-mac-dst"   , required_argument, 0, ARP_MAC_D  },
+	{ "arp-gratuitous",       no_argument, 0, ARP_GRAT   },
+	XT_GETOPT_TABLEEND,
+};
+
+/* a few names */
+static char *opcodes[] =
+{
+	"Request",
+	"Reply",
+	"Request_Reverse",
+	"Reply_Reverse",
+	"DRARP_Request",
+	"DRARP_Reply",
+	"DRARP_Error",
+	"InARP_Request",
+	"ARP_NAK",
+};
+
+static void brarp_print_help(void)
+{
+	int i;
+
+	printf(
+"arp options:\n"
+"--arp-opcode  [!] opcode        : ARP opcode (integer or string)\n"
+"--arp-htype   [!] type          : ARP hardware type (integer or string)\n"
+"--arp-ptype   [!] type          : ARP protocol type (hexadecimal or string)\n"
+"--arp-ip-src  [!] address[/mask]: ARP IP source specification\n"
+"--arp-ip-dst  [!] address[/mask]: ARP IP target specification\n"
+"--arp-mac-src [!] address[/mask]: ARP MAC source specification\n"
+"--arp-mac-dst [!] address[/mask]: ARP MAC target specification\n"
+"[!] --arp-gratuitous            : ARP gratuitous packet\n"
+" opcode strings: \n");
+	for (i = 0; i < ARRAY_SIZE(opcodes); i++)
+		printf(" %d = %s\n", i + 1, opcodes[i]);
+	printf(
+" hardware type string: 1 = Ethernet\n"
+" protocol type string: see "XT_PATH_ETHERTYPES"\n");
+}
+
+#define OPT_OPCODE 0x01
+#define OPT_HTYPE  0x02
+#define OPT_PTYPE  0x04
+#define OPT_IP_S   0x08
+#define OPT_IP_D   0x10
+#define OPT_MAC_S  0x20
+#define OPT_MAC_D  0x40
+#define OPT_GRAT   0x80
+
+static int undot_ip(char *ip, unsigned char *ip2)
+{
+	char *p, *q, *end;
+	long int onebyte;
+	int i;
+	char buf[20];
+
+	strncpy(buf, ip, sizeof(buf) - 1);
+
+	p = buf;
+	for (i = 0; i < 3; i++) {
+		if ((q = strchr(p, '.')) == NULL)
+			return -1;
+		*q = '\0';
+		onebyte = strtol(p, &end, 10);
+		if (*end != '\0' || onebyte > 255 || onebyte < 0)
+			return -1;
+		ip2[i] = (unsigned char)onebyte;
+		p = q + 1;
+	}
+
+	onebyte = strtol(p, &end, 10);
+	if (*end != '\0' || onebyte > 255 || onebyte < 0)
+		return -1;
+	ip2[3] = (unsigned char)onebyte;
+
+	return 0;
+}
+
+static int ip_mask(char *mask, unsigned char *mask2)
+{
+	char *end;
+	long int bits;
+	uint32_t mask22;
+
+	if (undot_ip(mask, mask2)) {
+		/* not the /a.b.c.e format, maybe the /x format */
+		bits = strtol(mask, &end, 10);
+		if (*end != '\0' || bits > 32 || bits < 0)
+			return -1;
+		if (bits != 0) {
+			mask22 = htonl(0xFFFFFFFF << (32 - bits));
+			memcpy(mask2, &mask22, 4);
+		} else {
+			mask22 = 0xFFFFFFFF;
+			memcpy(mask2, &mask22, 4);
+		}
+	}
+	return 0;
+}
+
+static void ebt_parse_ip_address(char *address, uint32_t *addr, uint32_t *msk)
+{
+	char *p;
+
+	/* first the mask */
+	if ((p = strrchr(address, '/')) != NULL) {
+		*p = '\0';
+		if (ip_mask(p + 1, (unsigned char *)msk)) {
+			xtables_error(PARAMETER_PROBLEM,
+				      "Problem with the IP mask '%s'", p + 1);
+			return;
+		}
+	} else
+		*msk = 0xFFFFFFFF;
+
+	if (undot_ip(address, (unsigned char *)addr)) {
+		xtables_error(PARAMETER_PROBLEM,
+			      "Problem with the IP address '%s'", address);
+		return;
+	}
+	*addr = *addr & *msk;
+}
+
+static int
+brarp_parse(int c, char **argv, int invert, unsigned int *flags,
+	    const void *entry, struct xt_entry_match **match)
+{
+	struct ebt_arp_info *arpinfo = (struct ebt_arp_info *)(*match)->data;
+	long int i;
+	char *end;
+	uint32_t *addr;
+	uint32_t *mask;
+	unsigned char *maddr;
+	unsigned char *mmask;
+
+	switch (c) {
+	case ARP_OPCODE:
+		EBT_CHECK_OPTION(flags, OPT_OPCODE);
+		if (invert)
+			arpinfo->invflags |= EBT_ARP_OPCODE;
+		i = strtol(optarg, &end, 10);
+		if (i < 0 || i >= (0x1 << 16) || *end !='\0') {
+			for (i = 0; i < ARRAY_SIZE(opcodes); i++)
+				if (!strcasecmp(opcodes[i], optarg))
+					break;
+			if (i == ARRAY_SIZE(opcodes))
+				xtables_error(PARAMETER_PROBLEM, "Problem with specified ARP opcode");
+			i++;
+		}
+		arpinfo->opcode = htons(i);
+		arpinfo->bitmask |= EBT_ARP_OPCODE;
+		break;
+
+	case ARP_HTYPE:
+		EBT_CHECK_OPTION(flags, OPT_HTYPE);
+		if (invert)
+			arpinfo->invflags |= EBT_ARP_HTYPE;
+		i = strtol(optarg, &end, 10);
+		if (i < 0 || i >= (0x1 << 16) || *end !='\0') {
+			if (!strcasecmp("Ethernet", argv[optind - 1]))
+				i = 1;
+			else
+				xtables_error(PARAMETER_PROBLEM, "Problem with specified ARP hardware type");
+		}
+		arpinfo->htype = htons(i);
+		arpinfo->bitmask |= EBT_ARP_HTYPE;
+		break;
+	case ARP_PTYPE: {
+		uint16_t proto;
+
+		EBT_CHECK_OPTION(flags, OPT_PTYPE);
+		if (invert)
+			arpinfo->invflags |= EBT_ARP_PTYPE;
+
+		i = strtol(optarg, &end, 16);
+		if (i < 0 || i >= (0x1 << 16) || *end !='\0') {
+			struct xt_ethertypeent *ent;
+
+			ent = xtables_getethertypebyname(argv[optind - 1]);
+			if (!ent)
+				xtables_error(PARAMETER_PROBLEM, "Problem with specified ARP "
+								 "protocol type");
+			proto = ent->e_ethertype;
+
+		} else
+			proto = i;
+		arpinfo->ptype = htons(proto);
+		arpinfo->bitmask |= EBT_ARP_PTYPE;
+		break;
+	}
+
+	case ARP_IP_S:
+	case ARP_IP_D:
+		if (c == ARP_IP_S) {
+			EBT_CHECK_OPTION(flags, OPT_IP_S);
+			addr = &arpinfo->saddr;
+			mask = &arpinfo->smsk;
+			arpinfo->bitmask |= EBT_ARP_SRC_IP;
+		} else {
+			EBT_CHECK_OPTION(flags, OPT_IP_D);
+			addr = &arpinfo->daddr;
+			mask = &arpinfo->dmsk;
+			arpinfo->bitmask |= EBT_ARP_DST_IP;
+		}
+		if (invert) {
+			if (c == ARP_IP_S)
+				arpinfo->invflags |= EBT_ARP_SRC_IP;
+			else
+				arpinfo->invflags |= EBT_ARP_DST_IP;
+		}
+		ebt_parse_ip_address(optarg, addr, mask);
+		break;
+	case ARP_MAC_S:
+	case ARP_MAC_D:
+		if (c == ARP_MAC_S) {
+			EBT_CHECK_OPTION(flags, OPT_MAC_S);
+			maddr = arpinfo->smaddr;
+			mmask = arpinfo->smmsk;
+			arpinfo->bitmask |= EBT_ARP_SRC_MAC;
+		} else {
+			EBT_CHECK_OPTION(flags, OPT_MAC_D);
+			maddr = arpinfo->dmaddr;
+			mmask = arpinfo->dmmsk;
+			arpinfo->bitmask |= EBT_ARP_DST_MAC;
+		}
+		if (invert) {
+			if (c == ARP_MAC_S)
+				arpinfo->invflags |= EBT_ARP_SRC_MAC;
+			else
+				arpinfo->invflags |= EBT_ARP_DST_MAC;
+		}
+		if (xtables_parse_mac_and_mask(optarg, maddr, mmask))
+			xtables_error(PARAMETER_PROBLEM, "Problem with ARP MAC address argument");
+		break;
+	case ARP_GRAT:
+		EBT_CHECK_OPTION(flags, OPT_GRAT);
+		arpinfo->bitmask |= EBT_ARP_GRAT;
+		if (invert)
+			arpinfo->invflags |= EBT_ARP_GRAT;
+		break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static void brarp_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct ebt_arp_info *arpinfo = (struct ebt_arp_info *)match->data;
+
+	if (arpinfo->bitmask & EBT_ARP_OPCODE) {
+		int opcode = ntohs(arpinfo->opcode);
+		printf("--arp-op ");
+		if (arpinfo->invflags & EBT_ARP_OPCODE)
+			printf("! ");
+		if (opcode > 0 && opcode <= ARRAY_SIZE(opcodes))
+			printf("%s ", opcodes[opcode - 1]);
+		else
+			printf("%d ", opcode);
+	}
+	if (arpinfo->bitmask & EBT_ARP_HTYPE) {
+		printf("--arp-htype ");
+		if (arpinfo->invflags & EBT_ARP_HTYPE)
+			printf("! ");
+		printf("%d ", ntohs(arpinfo->htype));
+	}
+	if (arpinfo->bitmask & EBT_ARP_PTYPE) {
+		printf("--arp-ptype ");
+		if (arpinfo->invflags & EBT_ARP_PTYPE)
+			printf("! ");
+		printf("0x%x ", ntohs(arpinfo->ptype));
+	}
+	if (arpinfo->bitmask & EBT_ARP_SRC_IP) {
+		printf("--arp-ip-src ");
+		if (arpinfo->invflags & EBT_ARP_SRC_IP)
+			printf("! ");
+		printf("%s%s ", xtables_ipaddr_to_numeric((const struct in_addr*) &arpinfo->saddr),
+		       xtables_ipmask_to_numeric((const struct in_addr*)&arpinfo->smsk));
+	}
+	if (arpinfo->bitmask & EBT_ARP_DST_IP) {
+		printf("--arp-ip-dst ");
+		if (arpinfo->invflags & EBT_ARP_DST_IP)
+			printf("! ");
+		printf("%s%s ", xtables_ipaddr_to_numeric((const struct in_addr*) &arpinfo->daddr),
+		       xtables_ipmask_to_numeric((const struct in_addr*)&arpinfo->dmsk));
+	}
+	if (arpinfo->bitmask & EBT_ARP_SRC_MAC) {
+		printf("--arp-mac-src ");
+		if (arpinfo->invflags & EBT_ARP_SRC_MAC)
+			printf("! ");
+		xtables_print_mac_and_mask(arpinfo->smaddr, arpinfo->smmsk);
+		printf(" ");
+	}
+	if (arpinfo->bitmask & EBT_ARP_DST_MAC) {
+		printf("--arp-mac-dst ");
+		if (arpinfo->invflags & EBT_ARP_DST_MAC)
+			printf("! ");
+		xtables_print_mac_and_mask(arpinfo->dmaddr, arpinfo->dmmsk);
+		printf(" ");
+	}
+	if (arpinfo->bitmask & EBT_ARP_GRAT) {
+		if (arpinfo->invflags & EBT_ARP_GRAT)
+			printf("! ");
+		printf("--arp-gratuitous ");
+	}
+}
+
+static struct xtables_match brarp_match = {
+	.name		= "arp",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size		= XT_ALIGN(sizeof(struct ebt_arp_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_arp_info)),
+	.help		= brarp_print_help,
+	.parse		= brarp_parse,
+	.print		= brarp_print,
+	.extra_opts	= brarp_opts,
+};
+
+void _init(void)
+{
+	xtables_register_match(&brarp_match);
+}
diff --git a/extensions/libebt_arp.t b/extensions/libebt_arp.t
new file mode 100644
index 0000000..14ff0f0
--- /dev/null
+++ b/extensions/libebt_arp.t
@@ -0,0 +1,12 @@
+:INPUT,FORWARD,OUTPUT
+-p ARP --arp-op Request;=;OK
+-p ARP --arp-htype ! 1;=;OK
+-p ARP --arp-ptype 0x2;=;OK
+-p ARP --arp-ip-src 1.2.3.4;=;OK
+-p ARP ! --arp-ip-dst 1.2.3.4;-p ARP --arp-ip-dst ! 1.2.3.4 -j CONTINUE;OK
+-p ARP --arp-ip-src ! 0.0.0.0;=;OK
+-p ARP --arp-ip-dst ! 0.0.0.0/8;=;OK
+-p ARP --arp-mac-src 00:de:ad:be:ef:00;=;OK
+-p ARP --arp-mac-dst de:ad:be:ef:00:00/ff:ff:ff:ff:00:00;=;OK
+-p ARP --arp-gratuitous;=;OK
+--arp-htype 1;=;FAIL
diff --git a/extensions/libebt_arpreply.c b/extensions/libebt_arpreply.c
new file mode 100644
index 0000000..80ba215
--- /dev/null
+++ b/extensions/libebt_arpreply.c
@@ -0,0 +1,101 @@
+/* ebt_arpreply
+ *
+ * Authors:
+ * Grzegorz Borowiak <grzes@gnu.univ.gda.pl>
+ * Bart De Schuymer <bdschuym@pandora.be>
+ *
+ *  August, 2003
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <xtables.h>
+#include <netinet/ether.h>
+#include <linux/netfilter_bridge/ebt_arpreply.h>
+#include "iptables/nft.h"
+#include "iptables/nft-bridge.h"
+
+#define OPT_REPLY_MAC     0x01
+#define OPT_REPLY_TARGET  0x02
+
+#define REPLY_MAC '1'
+#define REPLY_TARGET '2'
+static const struct option brarpreply_opts[] = {
+	{ "arpreply-mac" ,    required_argument, 0, REPLY_MAC    },
+	{ "arpreply-target" , required_argument, 0, REPLY_TARGET },
+	XT_GETOPT_TABLEEND,
+};
+
+static void brarpreply_print_help(void)
+{
+	printf(
+	"arpreply target options:\n"
+	" --arpreply-mac address           : source MAC of generated reply\n"
+	" --arpreply-target target         : ACCEPT, DROP, RETURN or CONTINUE\n"
+	"                                    (standard target is DROP)\n");
+}
+
+static void brarpreply_init(struct xt_entry_target *target)
+{
+	struct ebt_arpreply_info *replyinfo = (void *)target->data;
+
+	replyinfo->target = EBT_DROP;
+}
+
+static int
+brarpreply_parse(int c, char **argv, int invert, unsigned int *flags,
+	    const void *entry, struct xt_entry_target **tg)
+
+{
+	struct ebt_arpreply_info *replyinfo = (void *)(*tg)->data;
+	struct ether_addr *addr;
+
+	switch (c) {
+	case REPLY_MAC:
+		EBT_CHECK_OPTION(flags, OPT_REPLY_MAC);
+		if (!(addr = ether_aton(optarg)))
+			xtables_error(PARAMETER_PROBLEM, "Problem with specified --arpreply-mac mac");
+		memcpy(replyinfo->mac, addr, ETH_ALEN);
+		break;
+	case REPLY_TARGET:
+		EBT_CHECK_OPTION(flags, OPT_REPLY_TARGET);
+		if (ebt_fill_target(optarg, (unsigned int *)&replyinfo->target))
+			xtables_error(PARAMETER_PROBLEM, "Illegal --arpreply-target target");
+		break;
+
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static void brarpreply_print(const void *ip, const struct xt_entry_target *t, int numeric)
+{
+	struct ebt_arpreply_info *replyinfo = (void *)t->data;
+
+	printf("--arpreply-mac ");
+	xtables_print_mac(replyinfo->mac);
+	if (replyinfo->target == EBT_DROP)
+		return;
+	printf(" --arpreply-target %s", ebt_target_name(replyinfo->target));
+}
+
+static struct xtables_target arpreply_target = {
+	.name		= "arpreply",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.init		= brarpreply_init,
+	.size		= XT_ALIGN(sizeof(struct ebt_arpreply_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_arpreply_info)),
+	.help		= brarpreply_print_help,
+	.parse		= brarpreply_parse,
+	.print		= brarpreply_print,
+	.extra_opts	= brarpreply_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&arpreply_target);
+}
diff --git a/extensions/libebt_arpreply.t b/extensions/libebt_arpreply.t
new file mode 100644
index 0000000..6734501
--- /dev/null
+++ b/extensions/libebt_arpreply.t
@@ -0,0 +1,4 @@
+:PREROUTING
+*nat
+-p ARP -i foo -j arpreply --arpreply-mac de:ad:00:be:ee:ff --arpreply-target ACCEPT;=;OK
+-p ARP -i foo -j arpreply --arpreply-mac de:ad:00:be:ee:ff;=;OK
diff --git a/extensions/libebt_dnat.c b/extensions/libebt_dnat.c
new file mode 100644
index 0000000..9f5f721
--- /dev/null
+++ b/extensions/libebt_dnat.c
@@ -0,0 +1,129 @@
+/* ebt_nat
+ *
+ * Authors:
+ * Bart De Schuymer <bdschuym@pandora.be>
+ *
+ * June, 2002
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <netinet/ether.h>
+#include <xtables.h>
+#include <linux/netfilter_bridge/ebt_nat.h>
+#include "iptables/nft.h"
+#include "iptables/nft-bridge.h"
+
+#define NAT_D '1'
+#define NAT_D_TARGET '2'
+static const struct option brdnat_opts[] =
+{
+	{ "to-destination", required_argument, 0, NAT_D },
+	{ "to-dst"        , required_argument, 0, NAT_D },
+	{ "dnat-target"   , required_argument, 0, NAT_D_TARGET },
+	{ 0 }
+};
+
+static void brdnat_print_help(void)
+{
+	printf(
+	"dnat options:\n"
+	" --to-dst address       : MAC address to map destination to\n"
+	" --dnat-target target   : ACCEPT, DROP, RETURN or CONTINUE\n");
+}
+
+static void brdnat_init(struct xt_entry_target *target)
+{
+	struct ebt_nat_info *natinfo = (struct ebt_nat_info *)target->data;
+
+	natinfo->target = EBT_ACCEPT;
+}
+
+#define OPT_DNAT        0x01
+#define OPT_DNAT_TARGET 0x02
+static int brdnat_parse(int c, char **argv, int invert, unsigned int *flags,
+			 const void *entry, struct xt_entry_target **target)
+{
+	struct ebt_nat_info *natinfo = (struct ebt_nat_info *)(*target)->data;
+	struct ether_addr *addr;
+
+	switch (c) {
+	case NAT_D:
+		EBT_CHECK_OPTION(flags, OPT_DNAT);
+		if (!(addr = ether_aton(optarg)))
+			xtables_error(PARAMETER_PROBLEM, "Problem with specified --to-destination mac");
+		memcpy(natinfo->mac, addr, ETH_ALEN);
+		break;
+	case NAT_D_TARGET:
+		EBT_CHECK_OPTION(flags, OPT_DNAT_TARGET);
+		if (ebt_fill_target(optarg, (unsigned int *)&natinfo->target))
+			xtables_error(PARAMETER_PROBLEM, "Illegal --dnat-target target");
+		break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static void brdnat_final_check(unsigned int flags)
+{
+	if (!flags)
+		xtables_error(PARAMETER_PROBLEM,
+			      "You must specify proper arguments");
+}
+
+static void brdnat_print(const void *ip, const struct xt_entry_target *target, int numeric)
+{
+	struct ebt_nat_info *natinfo = (struct ebt_nat_info *)target->data;
+
+	printf("--to-dst ");
+	xtables_print_mac(natinfo->mac);
+	printf(" --dnat-target %s", ebt_target_name(natinfo->target));
+}
+
+static const char* brdnat_verdict(int verdict)
+{
+	switch (verdict) {
+	case EBT_ACCEPT: return "accept";
+	case EBT_DROP: return "drop";
+	case EBT_CONTINUE: return "continue";
+	case EBT_RETURN: return "return";
+	}
+
+	return "";
+}
+
+static int brdnat_xlate(struct xt_xlate *xl,
+			 const struct xt_xlate_tg_params *params)
+{
+	const struct ebt_nat_info *natinfo = (const void*)params->target->data;
+
+	xt_xlate_add(xl, "ether daddr set %s %s ",
+		     ether_ntoa((struct ether_addr *)natinfo->mac),
+		     brdnat_verdict(natinfo->target));
+
+	return 1;
+}
+
+static struct xtables_target brdnat_target =
+{
+	.name		= "dnat",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size           = XT_ALIGN(sizeof(struct ebt_nat_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_nat_info)),
+	.help		= brdnat_print_help,
+	.init		= brdnat_init,
+	.parse		= brdnat_parse,
+	.final_check	= brdnat_final_check,
+	.print		= brdnat_print,
+	.xlate		= brdnat_xlate,
+	.extra_opts	= brdnat_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&brdnat_target);
+}
diff --git a/extensions/libebt_dnat.t b/extensions/libebt_dnat.t
new file mode 100644
index 0000000..9428d23
--- /dev/null
+++ b/extensions/libebt_dnat.t
@@ -0,0 +1,5 @@
+:PREROUTING
+*nat
+-i someport -j dnat --to-dst de:ad:0:be:ee:ff;-i someport -j dnat --to-dst de:ad:00:be:ee:ff --dnat-target ACCEPT;OK
+-j dnat --to-dst de:ad:00:be:ee:ff --dnat-target ACCEPT;=;OK
+-j dnat --to-dst de:ad:00:be:ee:ff --dnat-target CONTINUE;=;OK
diff --git a/extensions/libebt_dnat.txlate b/extensions/libebt_dnat.txlate
new file mode 100644
index 0000000..2652dd5
--- /dev/null
+++ b/extensions/libebt_dnat.txlate
@@ -0,0 +1,8 @@
+ebtables-translate -t nat -A PREROUTING -i someport --to-dst de:ad:00:be:ee:ff
+nft add rule bridge nat PREROUTING iifname "someport" ether daddr set de:ad:0:be:ee:ff accept counter
+
+ebtables-translate -t nat -A PREROUTING -i someport --to-dst de:ad:00:be:ee:ff --dnat-target ACCEPT
+nft add rule bridge nat PREROUTING iifname "someport" ether daddr set de:ad:0:be:ee:ff accept counter
+
+ebtables-translate -t nat -A PREROUTING -i someport --to-dst de:ad:00:be:ee:ff --dnat-target CONTINUE
+nft add rule bridge nat PREROUTING iifname "someport" ether daddr set de:ad:0:be:ee:ff continue counter
diff --git a/extensions/libebt_ip.c b/extensions/libebt_ip.c
new file mode 100644
index 0000000..acb9bfc
--- /dev/null
+++ b/extensions/libebt_ip.c
@@ -0,0 +1,732 @@
+/* ebt_ip
+ *
+ * Authors:
+ * Bart De Schuymer <bdschuym@pandora.be>
+ *
+ * Changes:
+ *    added ip-sport and ip-dport; parsing of port arguments is
+ *    based on code from iptables-1.2.7a
+ *    Innominate Security Technologies AG <mhopf@innominate.com>
+ *    September, 2002
+ *
+ * Adapted by Arturo Borrero Gonzalez <arturo@debian.org>
+ * to use libxtables for ebtables-compat in 2015.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <netdb.h>
+#include <inttypes.h>
+#include <xtables.h>
+
+#include "libxt_icmp.h"
+
+#define EBT_IP_SOURCE 0x01
+#define EBT_IP_DEST 0x02
+#define EBT_IP_TOS 0x04
+#define EBT_IP_PROTO 0x08
+#define EBT_IP_SPORT 0x10
+#define EBT_IP_DPORT 0x20
+#define EBT_IP_ICMP 0x40
+#define EBT_IP_IGMP 0x80
+#define EBT_IP_MASK (EBT_IP_SOURCE | EBT_IP_DEST | EBT_IP_TOS | EBT_IP_PROTO |\
+		     EBT_IP_SPORT | EBT_IP_DPORT | EBT_IP_ICMP | EBT_IP_IGMP)
+
+struct ebt_ip_info {
+	__be32 saddr;
+	__be32 daddr;
+	__be32 smsk;
+	__be32 dmsk;
+	__u8  tos;
+	__u8  protocol;
+	__u8  bitmask;
+	__u8  invflags;
+	union {
+		__u16 sport[2];
+		__u8 icmp_type[2];
+		__u8 igmp_type[2];
+	};
+	union {
+		__u16 dport[2];
+		__u8 icmp_code[2];
+	};
+};
+
+#define IP_SOURCE	'1'
+#define IP_DEST		'2'
+#define IP_EBT_TOS	'3' /* include/bits/in.h seems to already define IP_TOS */
+#define IP_PROTO	'4'
+#define IP_SPORT	'5'
+#define IP_DPORT	'6'
+#define IP_EBT_ICMP	'7'
+#define IP_EBT_IGMP	'8'
+
+static const struct option brip_opts[] = {
+	{ .name = "ip-source",		.has_arg = true, .val = IP_SOURCE },
+	{ .name = "ip-src",		.has_arg = true, .val = IP_SOURCE },
+	{ .name = "ip-destination",	.has_arg = true, .val = IP_DEST },
+	{ .name = "ip-dst",		.has_arg = true, .val = IP_DEST },
+	{ .name = "ip-tos",		.has_arg = true, .val = IP_EBT_TOS },
+	{ .name = "ip-protocol",	.has_arg = true, .val = IP_PROTO },
+	{ .name = "ip-proto",		.has_arg = true, .val = IP_PROTO },
+	{ .name = "ip-source-port",	.has_arg = true, .val = IP_SPORT },
+	{ .name = "ip-sport",		.has_arg = true, .val = IP_SPORT },
+	{ .name = "ip-destination-port",.has_arg = true, .val = IP_DPORT },
+	{ .name = "ip-dport",		.has_arg = true, .val = IP_DPORT },
+	{ .name = "ip-icmp-type",       .has_arg = true, .val = IP_EBT_ICMP },
+	{ .name = "ip-igmp-type",       .has_arg = true, .val = IP_EBT_IGMP },
+	XT_GETOPT_TABLEEND,
+};
+
+static const struct xt_icmp_names icmp_codes[] = {
+	{ "echo-reply", 0, 0, 0xFF },
+	/* Alias */ { "pong", 0, 0, 0xFF },
+
+	{ "destination-unreachable", 3, 0, 0xFF },
+	{   "network-unreachable", 3, 0, 0 },
+	{   "host-unreachable", 3, 1, 1 },
+	{   "protocol-unreachable", 3, 2, 2 },
+	{   "port-unreachable", 3, 3, 3 },
+	{   "fragmentation-needed", 3, 4, 4 },
+	{   "source-route-failed", 3, 5, 5 },
+	{   "network-unknown", 3, 6, 6 },
+	{   "host-unknown", 3, 7, 7 },
+	{   "network-prohibited", 3, 9, 9 },
+	{   "host-prohibited", 3, 10, 10 },
+	{   "TOS-network-unreachable", 3, 11, 11 },
+	{   "TOS-host-unreachable", 3, 12, 12 },
+	{   "communication-prohibited", 3, 13, 13 },
+	{   "host-precedence-violation", 3, 14, 14 },
+	{   "precedence-cutoff", 3, 15, 15 },
+
+	{ "source-quench", 4, 0, 0xFF },
+
+	{ "redirect", 5, 0, 0xFF },
+	{   "network-redirect", 5, 0, 0 },
+	{   "host-redirect", 5, 1, 1 },
+	{   "TOS-network-redirect", 5, 2, 2 },
+	{   "TOS-host-redirect", 5, 3, 3 },
+
+	{ "echo-request", 8, 0, 0xFF },
+	/* Alias */ { "ping", 8, 0, 0xFF },
+
+	{ "router-advertisement", 9, 0, 0xFF },
+
+	{ "router-solicitation", 10, 0, 0xFF },
+
+	{ "time-exceeded", 11, 0, 0xFF },
+	/* Alias */ { "ttl-exceeded", 11, 0, 0xFF },
+	{   "ttl-zero-during-transit", 11, 0, 0 },
+	{   "ttl-zero-during-reassembly", 11, 1, 1 },
+
+	{ "parameter-problem", 12, 0, 0xFF },
+	{   "ip-header-bad", 12, 0, 0 },
+	{   "required-option-missing", 12, 1, 1 },
+
+	{ "timestamp-request", 13, 0, 0xFF },
+
+	{ "timestamp-reply", 14, 0, 0xFF },
+
+	{ "address-mask-request", 17, 0, 0xFF },
+
+	{ "address-mask-reply", 18, 0, 0xFF }
+};
+
+static const struct xt_icmp_names igmp_types[] = {
+	{ "membership-query", 0x11 },
+	{ "membership-report-v1", 0x12 },
+	{ "membership-report-v2", 0x16 },
+	{ "leave-group", 0x17 },
+	{ "membership-report-v3", 0x22 },
+};
+
+static void brip_print_help(void)
+{
+	printf(
+"ip options:\n"
+"--ip-src    [!] address[/mask]: ip source specification\n"
+"--ip-dst    [!] address[/mask]: ip destination specification\n"
+"--ip-tos    [!] tos           : ip tos specification\n"
+"--ip-proto  [!] protocol      : ip protocol specification\n"
+"--ip-sport  [!] port[:port]   : tcp/udp source port or port range\n"
+"--ip-dport  [!] port[:port]   : tcp/udp destination port or port range\n"
+"--ip-icmp-type [!] type[[:type]/code[:code]] : icmp type/code or type/code range\n"
+"--ip-igmp-type [!] type[:type]               : igmp type or type range\n");
+
+	printf("\nValid ICMP Types:\n");
+	xt_print_icmp_types(icmp_codes, ARRAY_SIZE(icmp_codes));
+	printf("\nValid IGMP Types:\n");
+	xt_print_icmp_types(igmp_types, ARRAY_SIZE(igmp_types));
+}
+
+static void brip_init(struct xt_entry_match *match)
+{
+	struct ebt_ip_info *info = (struct ebt_ip_info *)match->data;
+
+	info->invflags = 0;
+	info->bitmask = 0;
+}
+
+static void
+parse_port_range(const char *protocol, const char *portstring, uint16_t *ports)
+{
+	char *buffer;
+	char *cp;
+
+	buffer = strdup(portstring);
+	if ((cp = strchr(buffer, ':')) == NULL)
+		ports[0] = ports[1] = xtables_parse_port(buffer, NULL);
+	else {
+		*cp = '\0';
+		cp++;
+
+		ports[0] = buffer[0] ? xtables_parse_port(buffer, NULL) : 0;
+		ports[1] = cp[0] ? xtables_parse_port(cp, NULL) : 0xFFFF;
+
+		if (ports[0] > ports[1])
+			xtables_error(PARAMETER_PROBLEM,
+				      "invalid portrange (min > max)");
+	}
+	free(buffer);
+}
+
+/* original code from ebtables: useful_functions.c */
+static int undot_ip(char *ip, unsigned char *ip2)
+{
+	char *p, *q, *end;
+	long int onebyte;
+	int i;
+	char buf[20];
+
+	strncpy(buf, ip, sizeof(buf) - 1);
+
+	p = buf;
+	for (i = 0; i < 3; i++) {
+		if ((q = strchr(p, '.')) == NULL)
+			return -1;
+		*q = '\0';
+		onebyte = strtol(p, &end, 10);
+		if (*end != '\0' || onebyte > 255 || onebyte < 0)
+			return -1;
+		ip2[i] = (unsigned char)onebyte;
+		p = q + 1;
+	}
+
+	onebyte = strtol(p, &end, 10);
+	if (*end != '\0' || onebyte > 255 || onebyte < 0)
+		return -1;
+	ip2[3] = (unsigned char)onebyte;
+
+	return 0;
+}
+
+static int ip_mask(char *mask, unsigned char *mask2)
+{
+	char *end;
+	long int bits;
+	uint32_t mask22;
+
+	if (undot_ip(mask, mask2)) {
+		/* not the /a.b.c.e format, maybe the /x format */
+		bits = strtol(mask, &end, 10);
+		if (*end != '\0' || bits > 32 || bits < 0)
+			return -1;
+		if (bits != 0) {
+			mask22 = htonl(0xFFFFFFFF << (32 - bits));
+			memcpy(mask2, &mask22, 4);
+		} else {
+			mask22 = 0xFFFFFFFF;
+			memcpy(mask2, &mask22, 4);
+		}
+	}
+	return 0;
+}
+
+static void ebt_parse_ip_address(char *address, uint32_t *addr, uint32_t *msk)
+{
+	char *p;
+
+	/* first the mask */
+	if ((p = strrchr(address, '/')) != NULL) {
+		*p = '\0';
+		if (ip_mask(p + 1, (unsigned char *)msk)) {
+			xtables_error(PARAMETER_PROBLEM,
+				      "Problem with the IP mask '%s'", p + 1);
+			return;
+		}
+	} else
+		*msk = 0xFFFFFFFF;
+
+	if (undot_ip(address, (unsigned char *)addr)) {
+		xtables_error(PARAMETER_PROBLEM,
+			      "Problem with the IP address '%s'", address);
+		return;
+	}
+	*addr = *addr & *msk;
+}
+
+static char *parse_range(const char *str, unsigned int res[])
+{
+	char *next;
+
+	if (!xtables_strtoui(str, &next, &res[0], 0, 255))
+		return NULL;
+
+	res[1] = res[0];
+	if (*next == ':') {
+		str = next + 1;
+		if (!xtables_strtoui(str, &next, &res[1], 0, 255))
+			return NULL;
+	}
+
+	return next;
+}
+
+static int ebt_parse_icmp(const struct xt_icmp_names *codes, size_t n_codes,
+			  const char *icmptype, uint8_t type[], uint8_t code[])
+{
+	unsigned int match = n_codes;
+	unsigned int i, number[2];
+
+	for (i = 0; i < n_codes; i++) {
+		if (strncasecmp(codes[i].name, icmptype, strlen(icmptype)))
+			continue;
+		if (match != n_codes)
+			xtables_error(PARAMETER_PROBLEM, "Ambiguous ICMP type `%s':"
+					" `%s' or `%s'?",
+					icmptype, codes[match].name,
+					codes[i].name);
+		match = i;
+	}
+
+	if (match < n_codes) {
+		type[0] = type[1] = codes[match].type;
+		if (code) {
+			code[0] = codes[match].code_min;
+			code[1] = codes[match].code_max;
+		}
+	} else {
+		char *next = parse_range(icmptype, number);
+		if (!next) {
+			xtables_error(PARAMETER_PROBLEM, "Unknown ICMP type `%s'",
+							icmptype);
+			return -1;
+		}
+
+		type[0] = (uint8_t) number[0];
+		type[1] = (uint8_t) number[1];
+		switch (*next) {
+		case 0:
+			if (code) {
+				code[0] = 0;
+				code[1] = 255;
+			}
+			return 0;
+		case '/':
+			if (code) {
+				next = parse_range(next+1, number);
+				code[0] = (uint8_t) number[0];
+				code[1] = (uint8_t) number[1];
+				if (next == NULL)
+					return -1;
+				if (next && *next == 0)
+					return 0;
+			}
+		/* fallthrough */
+		default:
+			xtables_error(PARAMETER_PROBLEM, "unknown character %c", *next);
+			return -1;
+		}
+	}
+	return 0;
+}
+
+static void print_icmp_code(uint8_t *code)
+{
+	if (!code)
+		return;
+
+	if (code[0] == code[1])
+		printf("/%"PRIu8 " ", code[0]);
+	else
+		printf("/%"PRIu8":%"PRIu8 " ", code[0], code[1]);
+}
+
+static void ebt_print_icmp_type(const struct xt_icmp_names *codes,
+				size_t n_codes, uint8_t *type, uint8_t *code)
+{
+	unsigned int i;
+
+	if (type[0] != type[1]) {
+		printf("%"PRIu8 ":%" PRIu8, type[0], type[1]);
+		print_icmp_code(code);
+		return;
+	}
+
+	for (i = 0; i < n_codes; i++) {
+		if (codes[i].type != type[0])
+			continue;
+
+		if (!code || (codes[i].code_min == code[0] &&
+			      codes[i].code_max == code[1])) {
+			printf("%s ", codes[i].name);
+			return;
+		}
+	}
+	printf("%"PRIu8, type[0]);
+	print_icmp_code(code);
+}
+
+static int
+brip_parse(int c, char **argv, int invert, unsigned int *flags,
+	   const void *entry, struct xt_entry_match **match)
+{
+	struct ebt_ip_info *info = (struct ebt_ip_info *)(*match)->data;
+
+	switch (c) {
+	case IP_SOURCE:
+		if (invert)
+			info->invflags |= EBT_IP_SOURCE;
+		ebt_parse_ip_address(optarg, &info->saddr, &info->smsk);
+		info->bitmask |= EBT_IP_SOURCE;
+		break;
+	case IP_DEST:
+		if (invert)
+			info->invflags |= EBT_IP_DEST;
+		ebt_parse_ip_address(optarg, &info->daddr, &info->dmsk);
+		info->bitmask |= EBT_IP_DEST;
+		break;
+	case IP_SPORT:
+		if (invert)
+			info->invflags |= EBT_IP_SPORT;
+		parse_port_range(NULL, optarg, info->sport);
+		info->bitmask |= EBT_IP_SPORT;
+		break;
+	case IP_DPORT:
+		if (invert)
+			info->invflags |= EBT_IP_DPORT;
+		parse_port_range(NULL, optarg, info->dport);
+		info->bitmask |= EBT_IP_DPORT;
+		break;
+	case IP_EBT_ICMP:
+		if (invert)
+			info->invflags |= EBT_IP_ICMP;
+		ebt_parse_icmp(icmp_codes, ARRAY_SIZE(icmp_codes), optarg,
+			      info->icmp_type, info->icmp_code);
+		info->bitmask |= EBT_IP_ICMP;
+		break;
+	case IP_EBT_IGMP:
+		if (invert)
+			info->invflags |= EBT_IP_IGMP;
+		ebt_parse_icmp(igmp_types, ARRAY_SIZE(igmp_types), optarg,
+			       info->igmp_type, NULL);
+		info->bitmask |= EBT_IP_IGMP;
+		break;
+	case IP_EBT_TOS: {
+		uintmax_t tosvalue;
+
+		if (invert)
+			info->invflags |= EBT_IP_TOS;
+		if (!xtables_strtoul(optarg, NULL, &tosvalue, 0, 255))
+			xtables_error(PARAMETER_PROBLEM,
+				      "Problem with specified IP tos");
+		info->tos = tosvalue;
+		info->bitmask |= EBT_IP_TOS;
+	}
+		break;
+	case IP_PROTO:
+		if (invert)
+			info->invflags |= EBT_IP_PROTO;
+		info->protocol = xtables_parse_protocol(optarg);
+		info->bitmask |= EBT_IP_PROTO;
+		break;
+	default:
+		return 0;
+	}
+
+	*flags |= info->bitmask;
+	return 1;
+}
+
+static void brip_final_check(unsigned int flags)
+{
+	if (!flags)
+		xtables_error(PARAMETER_PROBLEM,
+			      "You must specify proper arguments");
+}
+
+static void print_port_range(uint16_t *ports)
+{
+	if (ports[0] == ports[1])
+		printf("%d ", ports[0]);
+	else
+		printf("%d:%d ", ports[0], ports[1]);
+}
+
+static void brip_print(const void *ip, const struct xt_entry_match *match,
+		       int numeric)
+{
+	struct ebt_ip_info *info = (struct ebt_ip_info *)match->data;
+	struct in_addr *addrp, *maskp;
+
+	if (info->bitmask & EBT_IP_SOURCE) {
+		printf("--ip-src ");
+		if (info->invflags & EBT_IP_SOURCE)
+			printf("! ");
+		addrp = (struct in_addr *)&info->saddr;
+		maskp = (struct in_addr *)&info->smsk;
+		printf("%s%s ", xtables_ipaddr_to_numeric(addrp),
+		       xtables_ipmask_to_numeric(maskp));
+	}
+	if (info->bitmask & EBT_IP_DEST) {
+		printf("--ip-dst ");
+		if (info->invflags & EBT_IP_DEST)
+			printf("! ");
+		addrp = (struct in_addr *)&info->daddr;
+		maskp = (struct in_addr *)&info->dmsk;
+		printf("%s%s ", xtables_ipaddr_to_numeric(addrp),
+		       xtables_ipmask_to_numeric(maskp));
+	}
+	if (info->bitmask & EBT_IP_TOS) {
+		printf("--ip-tos ");
+		if (info->invflags & EBT_IP_TOS)
+			printf("! ");
+		printf("0x%02X ", info->tos);
+	}
+	if (info->bitmask & EBT_IP_PROTO) {
+		struct protoent *pe;
+
+		printf("--ip-proto ");
+		if (info->invflags & EBT_IP_PROTO)
+			printf("! ");
+		pe = getprotobynumber(info->protocol);
+		if (pe == NULL) {
+			printf("%d ", info->protocol);
+		} else {
+			printf("%s ", pe->p_name);
+		}
+	}
+	if (info->bitmask & EBT_IP_SPORT) {
+		printf("--ip-sport ");
+		if (info->invflags & EBT_IP_SPORT)
+			printf("! ");
+		print_port_range(info->sport);
+	}
+	if (info->bitmask & EBT_IP_DPORT) {
+		printf("--ip-dport ");
+		if (info->invflags & EBT_IP_DPORT)
+			printf("! ");
+		print_port_range(info->dport);
+	}
+	if (info->bitmask & EBT_IP_ICMP) {
+		printf("--ip-icmp-type ");
+		if (info->invflags & EBT_IP_ICMP)
+			printf("! ");
+		ebt_print_icmp_type(icmp_codes, ARRAY_SIZE(icmp_codes),
+				    info->icmp_type, info->icmp_code);
+	}
+	if (info->bitmask & EBT_IP_IGMP) {
+		printf("--ip-igmp-type ");
+		if (info->invflags & EBT_IP_IGMP)
+			printf("! ");
+		ebt_print_icmp_type(igmp_types, ARRAY_SIZE(igmp_types),
+				    info->igmp_type, NULL);
+	}
+}
+
+static const char *brip_xlate_proto_to_name(uint8_t proto)
+{
+	switch (proto) {
+	case IPPROTO_TCP:
+		return "tcp";
+	case IPPROTO_UDP:
+		return "udp";
+	case IPPROTO_UDPLITE:
+		return "udplite";
+	case IPPROTO_SCTP:
+		return "sctp";
+	case IPPROTO_DCCP:
+		return "dccp";
+	default:
+		return NULL;
+	}
+}
+
+static void brip_xlate_icmp(struct xt_xlate *xl,
+			    const struct ebt_ip_info *info, int bit)
+{
+	if ((info->bitmask & bit) == 0)
+		return;
+
+	xt_xlate_add(xl, "icmp type ");
+	if (info->invflags & bit)
+		xt_xlate_add(xl, "!= ");
+	if (info->icmp_type[0] == info->icmp_type[1])
+		xt_xlate_add(xl, "%d ", info->icmp_type[0]);
+	else
+		xt_xlate_add(xl, "%d-%d ", info->icmp_type[0],
+					   info->icmp_type[1]);
+	if (info->icmp_code[0] == 0 &&
+	    info->icmp_code[1] == 0xff)
+		return;
+
+	xt_xlate_add(xl, "icmp code ");
+	if (info->invflags & bit)
+		xt_xlate_add(xl, "!= ");
+	if (info->icmp_code[0] == info->icmp_code[1])
+		xt_xlate_add(xl, "%d ", info->icmp_code[0]);
+	else
+		xt_xlate_add(xl, "%d-%d ", info->icmp_code[0],
+					   info->icmp_code[1]);
+}
+
+static void brip_xlate_igmp(struct xt_xlate *xl,
+			    const struct ebt_ip_info *info, int bit)
+{
+	if ((info->bitmask & bit) == 0)
+		return;
+
+	xt_xlate_add(xl, "@th,0,8 ");
+	if (info->invflags & bit)
+		xt_xlate_add(xl, "!= ");
+	if (info->icmp_type[0] == info->icmp_type[1])
+		xt_xlate_add(xl, "%d ", info->icmp_type[0]);
+	else
+		xt_xlate_add(xl, "%d-%d ", info->icmp_type[0],
+					   info->icmp_type[1]);
+}
+
+static void brip_xlate_th(struct xt_xlate *xl,
+			  const struct ebt_ip_info *info, int bit,
+			  const char *pname)
+{
+	const uint16_t *ports;
+
+	if ((info->bitmask & bit) == 0)
+		return;
+
+	switch (bit) {
+	case EBT_IP_SPORT:
+		if (pname)
+			xt_xlate_add(xl, "%s sport ", pname);
+		else
+			xt_xlate_add(xl, "@th,0,16 ");
+
+		ports = info->sport;
+		break;
+	case EBT_IP_DPORT:
+		if (pname)
+			xt_xlate_add(xl, "%s dport ", pname);
+		else
+			xt_xlate_add(xl, "@th,16,16 ");
+
+		ports = info->dport;
+		break;
+	default:
+		return;
+	}
+
+	if (info->invflags & bit)
+		xt_xlate_add(xl, "!= ");
+
+	if (ports[0] == ports[1])
+		xt_xlate_add(xl, "%d ", ports[0]);
+	else
+		xt_xlate_add(xl, "%d-%d ", ports[0], ports[1]);
+}
+
+static void brip_xlate_nh(struct xt_xlate *xl,
+			  const struct ebt_ip_info *info, int bit)
+{
+	struct in_addr *addrp, *maskp;
+
+	if ((info->bitmask & bit) == 0)
+		return;
+
+	switch (bit) {
+	case EBT_IP_SOURCE:
+		xt_xlate_add(xl, "ip saddr ");
+		addrp = (struct in_addr *)&info->saddr;
+		maskp = (struct in_addr *)&info->smsk;
+		break;
+	case EBT_IP_DEST:
+		xt_xlate_add(xl, "ip daddr ");
+		addrp = (struct in_addr *)&info->daddr;
+		maskp = (struct in_addr *)&info->dmsk;
+		break;
+	default:
+		return;
+	}
+
+	if (info->invflags & bit)
+		xt_xlate_add(xl, "!= ");
+
+	xt_xlate_add(xl, "%s%s ", xtables_ipaddr_to_numeric(addrp),
+				  xtables_ipmask_to_numeric(maskp));
+}
+
+static int brip_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_mt_params *params)
+{
+	const struct ebt_ip_info *info = (const void *)params->match->data;
+	const char *pname = NULL;
+
+	brip_xlate_nh(xl, info, EBT_IP_SOURCE);
+	brip_xlate_nh(xl, info, EBT_IP_DEST);
+
+	if (info->bitmask & EBT_IP_TOS) {
+		xt_xlate_add(xl, "ip dscp ");
+		if (info->invflags & EBT_IP_TOS)
+			xt_xlate_add(xl, "!= ");
+		xt_xlate_add(xl, "0x%02x ", info->tos & 0x3f); /* remove ECN bits */
+	}
+	if (info->bitmask & EBT_IP_PROTO) {
+		struct protoent *pe;
+
+		if (info->bitmask & (EBT_IP_SPORT|EBT_IP_DPORT|EBT_IP_ICMP) &&
+		    (info->invflags & EBT_IP_PROTO) == 0) {
+			/* port number or icmp given and not inverted, no need to print this */
+			pname = brip_xlate_proto_to_name(info->protocol);
+		} else {
+			xt_xlate_add(xl, "ip protocol ");
+			if (info->invflags & EBT_IP_PROTO)
+				xt_xlate_add(xl, "!= ");
+			pe = getprotobynumber(info->protocol);
+			if (pe == NULL)
+				xt_xlate_add(xl, "%d ", info->protocol);
+			else
+				xt_xlate_add(xl, "%s ", pe->p_name);
+		}
+	}
+
+	brip_xlate_th(xl, info, EBT_IP_SPORT, pname);
+	brip_xlate_th(xl, info, EBT_IP_DPORT, pname);
+
+	brip_xlate_icmp(xl, info, EBT_IP_ICMP);
+	brip_xlate_igmp(xl, info, EBT_IP_IGMP);
+
+	return 1;
+}
+
+static struct xtables_match brip_match = {
+	.name		= "ip",
+	.revision	= 0,
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size		= XT_ALIGN(sizeof(struct ebt_ip_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_ip_info)),
+	.init		= brip_init,
+	.help		= brip_print_help,
+	.parse		= brip_parse,
+	.final_check	= brip_final_check,
+	.print		= brip_print,
+	.xlate		= brip_xlate,
+	.extra_opts	= brip_opts,
+};
+
+void _init(void)
+{
+	xtables_register_match(&brip_match);
+}
diff --git a/extensions/libebt_ip.t b/extensions/libebt_ip.t
new file mode 100644
index 0000000..8be5dfb
--- /dev/null
+++ b/extensions/libebt_ip.t
@@ -0,0 +1,13 @@
+:INPUT,FORWARD,OUTPUT
+-p ip --ip-src ! 192.168.0.0/24 -j ACCEPT;-p IPv4 --ip-src ! 192.168.0.0/24 -j ACCEPT;OK
+-p IPv4 --ip-dst 10.0.0.1;=;OK
+-p IPv4 --ip-tos 0xFF;=;OK
+-p IPv4 --ip-tos ! 0xFF;=;OK
+-p IPv4 --ip-proto tcp --ip-dport 22;=;OK
+-p IPv4 --ip-proto udp --ip-sport 1024:65535;=;OK
+-p IPv4 --ip-proto 253;=;OK
+-p IPv4 --ip-proto icmp --ip-icmp-type echo-request;=;OK
+-p IPv4 --ip-proto icmp --ip-icmp-type 1/1;=;OK
+-p ip --ip-protocol icmp --ip-icmp-type ! 1:10;-p IPv4 --ip-proto icmp --ip-icmp-type ! 1:10/0:255 -j CONTINUE;OK
+--ip-proto icmp --ip-icmp-type 1/1;=;FAIL
+! -p ip --ip-proto icmp --ip-icmp-type 1/1;=;FAIL
diff --git a/extensions/libebt_ip.txlate b/extensions/libebt_ip.txlate
new file mode 100644
index 0000000..b5882c3
--- /dev/null
+++ b/extensions/libebt_ip.txlate
@@ -0,0 +1,26 @@
+ebtables-translate -A FORWARD -p ip --ip-src ! 192.168.0.0/24 -j ACCEPT
+nft add rule bridge filter FORWARD ip saddr != 192.168.0.0/24 counter accept
+
+ebtables-translate -I FORWARD -p ip --ip-dst 10.0.0.1
+nft insert rule bridge filter FORWARD ip daddr 10.0.0.1 counter
+
+ebtables-translate -I OUTPUT 3 -p ip -o eth0 --ip-tos 0xff
+nft insert rule bridge filter OUTPUT oifname "eth0" ip dscp 0x3f counter
+
+ebtables-translate -A FORWARD -p ip --ip-proto tcp --ip-dport 22
+nft add rule bridge filter FORWARD tcp dport 22 counter
+
+ebtables-translate -A FORWARD -p ip --ip-proto udp --ip-sport 1024:65535
+nft add rule bridge filter FORWARD udp sport 1024-65535 counter
+
+ebtables-translate -A FORWARD -p ip --ip-proto 253
+nft add rule bridge filter FORWARD ip protocol 253 counter
+
+ebtables-translate -A FORWARD -p ip --ip-protocol icmp --ip-icmp-type "echo-request"
+nft add rule bridge filter FORWARD icmp type 8 counter
+
+ebtables-translate -A FORWARD -p ip --ip-proto icmp --ip-icmp-type 1/1
+nft add rule bridge filter FORWARD icmp type 1 icmp code 1 counter
+
+ebtables-translate -A FORWARD -p ip --ip-protocol icmp --ip-icmp-type ! 1:10
+nft add rule bridge filter FORWARD icmp type != 1-10 counter
diff --git a/extensions/libebt_ip6.c b/extensions/libebt_ip6.c
new file mode 100644
index 0000000..b8a5a5d
--- /dev/null
+++ b/extensions/libebt_ip6.c
@@ -0,0 +1,632 @@
+/* ebt_ip6
+ *
+ * Authors:
+ * Kuo-Lang Tseng <kuo-lang.tseng@intel.com>
+ * Manohar Castelino <manohar.castelino@intel.com>
+ *
+ * Summary:
+ * This is just a modification of the IPv4 code written by
+ * Bart De Schuymer <bdschuym@pandora.be>
+ * with the changes required to support IPv6
+ *
+ */
+
+#include <errno.h>
+#include <arpa/inet.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <netdb.h>
+#include <xtables.h>
+#include <linux/netfilter_bridge/ebt_ip6.h>
+
+#include "libxt_icmp.h"
+
+#define IP_SOURCE '1'
+#define IP_DEST   '2'
+#define IP_TCLASS '3'
+#define IP_PROTO  '4'
+#define IP_SPORT  '5'
+#define IP_DPORT  '6'
+#define IP_ICMP6  '7'
+
+static const struct option brip6_opts[] = {
+	{ .name = "ip6-source",		.has_arg = true, .val = IP_SOURCE },
+	{ .name = "ip6-src",		.has_arg = true, .val = IP_SOURCE },
+	{ .name = "ip6-destination",	.has_arg = true, .val = IP_DEST },
+	{ .name = "ip6-dst",		.has_arg = true, .val = IP_DEST },
+	{ .name = "ip6-tclass",		.has_arg = true, .val = IP_TCLASS },
+	{ .name = "ip6-protocol",	.has_arg = true, .val = IP_PROTO },
+	{ .name = "ip6-proto",		.has_arg = true, .val = IP_PROTO },
+	{ .name = "ip6-source-port",	.has_arg = true, .val = IP_SPORT },
+	{ .name = "ip6-sport",		.has_arg = true, .val = IP_SPORT },
+	{ .name = "ip6-destination-port",.has_arg = true,.val = IP_DPORT },
+	{ .name = "ip6-dport",		.has_arg = true, .val = IP_DPORT },
+	{ .name = "ip6-icmp-type",	.has_arg = true, .val = IP_ICMP6 },
+	XT_GETOPT_TABLEEND,
+};
+
+static const struct xt_icmp_names icmpv6_codes[] = {
+	{ "destination-unreachable", 1, 0, 0xFF },
+	{ "no-route", 1, 0, 0 },
+	{ "communication-prohibited", 1, 1, 1 },
+	{ "address-unreachable", 1, 3, 3 },
+	{ "port-unreachable", 1, 4, 4 },
+
+	{ "packet-too-big", 2, 0, 0xFF },
+
+	{ "time-exceeded", 3, 0, 0xFF },
+	/* Alias */ { "ttl-exceeded", 3, 0, 0xFF },
+	{ "ttl-zero-during-transit", 3, 0, 0 },
+	{ "ttl-zero-during-reassembly", 3, 1, 1 },
+
+	{ "parameter-problem", 4, 0, 0xFF },
+	{ "bad-header", 4, 0, 0 },
+	{ "unknown-header-type", 4, 1, 1 },
+	{ "unknown-option", 4, 2, 2 },
+
+	{ "echo-request", 128, 0, 0xFF },
+	/* Alias */ { "ping", 128, 0, 0xFF },
+
+	{ "echo-reply", 129, 0, 0xFF },
+	/* Alias */ { "pong", 129, 0, 0xFF },
+
+	{ "router-solicitation", 133, 0, 0xFF },
+
+	{ "router-advertisement", 134, 0, 0xFF },
+
+	{ "neighbour-solicitation", 135, 0, 0xFF },
+	/* Alias */ { "neighbor-solicitation", 135, 0, 0xFF },
+
+	{ "neighbour-advertisement", 136, 0, 0xFF },
+	/* Alias */ { "neighbor-advertisement", 136, 0, 0xFF },
+
+	{ "redirect", 137, 0, 0xFF },
+};
+
+static void
+parse_port_range(const char *protocol, const char *portstring, uint16_t *ports)
+{
+	char *buffer;
+	char *cp;
+
+	buffer = strdup(portstring);
+	if ((cp = strchr(buffer, ':')) == NULL)
+		ports[0] = ports[1] = xtables_parse_port(buffer, NULL);
+	else {
+		*cp = '\0';
+		cp++;
+
+		ports[0] = buffer[0] ? xtables_parse_port(buffer, NULL) : 0;
+		ports[1] = cp[0] ? xtables_parse_port(cp, NULL) : 0xFFFF;
+
+		if (ports[0] > ports[1])
+			xtables_error(PARAMETER_PROBLEM,
+				      "invalid portrange (min > max)");
+	}
+	free(buffer);
+}
+
+static char *parse_range(const char *str, unsigned int res[])
+{
+	char *next;
+
+	if (!xtables_strtoui(str, &next, &res[0], 0, 255))
+		return NULL;
+
+	res[1] = res[0];
+	if (*next == ':') {
+		str = next + 1;
+		if (!xtables_strtoui(str, &next, &res[1], 0, 255))
+			return NULL;
+	}
+
+	return next;
+}
+
+static int
+parse_icmpv6(const char *icmpv6type, uint8_t type[], uint8_t code[])
+{
+	static const unsigned int limit = ARRAY_SIZE(icmpv6_codes);
+	unsigned int match = limit;
+	unsigned int i, number[2];
+
+	for (i = 0; i < limit; i++) {
+		if (strncasecmp(icmpv6_codes[i].name, icmpv6type, strlen(icmpv6type)))
+			continue;
+		if (match != limit)
+			xtables_error(PARAMETER_PROBLEM, "Ambiguous ICMPv6 type `%s':"
+					" `%s' or `%s'?",
+					icmpv6type, icmpv6_codes[match].name,
+					icmpv6_codes[i].name);
+		match = i;
+	}
+
+	if (match < limit) {
+		type[0] = type[1] = icmpv6_codes[match].type;
+		code[0] = icmpv6_codes[match].code_min;
+		code[1] = icmpv6_codes[match].code_max;
+	} else {
+		char *next = parse_range(icmpv6type, number);
+		if (!next) {
+			xtables_error(PARAMETER_PROBLEM, "Unknown ICMPv6 type `%s'",
+							icmpv6type);
+			return -1;
+		}
+		type[0] = (uint8_t) number[0];
+		type[1] = (uint8_t) number[1];
+		switch (*next) {
+		case 0:
+			code[0] = 0;
+			code[1] = 255;
+			return 0;
+		case '/':
+			next = parse_range(next+1, number);
+			code[0] = (uint8_t) number[0];
+			code[1] = (uint8_t) number[1];
+			if (next == NULL)
+				return -1;
+			if (next && *next == 0)
+				return 0;
+		/* fallthrough */
+		default:
+			xtables_error(PARAMETER_PROBLEM, "unknown character %c", *next);
+			return -1;
+		}
+	}
+	return 0;
+}
+
+static void print_port_range(uint16_t *ports)
+{
+	if (ports[0] == ports[1])
+		printf("%d ", ports[0]);
+	else
+		printf("%d:%d ", ports[0], ports[1]);
+}
+
+static void print_icmp_code(uint8_t *code)
+{
+	if (code[0] == code[1])
+		printf("/%"PRIu8 " ", code[0]);
+	else
+		printf("/%"PRIu8":%"PRIu8 " ", code[0], code[1]);
+}
+
+static void print_icmp_type(uint8_t *type, uint8_t *code)
+{
+	unsigned int i;
+
+	if (type[0] != type[1]) {
+		printf("%"PRIu8 ":%" PRIu8, type[0], type[1]);
+		print_icmp_code(code);
+		return;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(icmpv6_codes); i++) {
+		if (icmpv6_codes[i].type != type[0])
+			continue;
+
+		if (icmpv6_codes[i].code_min == code[0] &&
+		    icmpv6_codes[i].code_max == code[1]) {
+			printf("%s ", icmpv6_codes[i].name);
+			return;
+		}
+	}
+	printf("%"PRIu8, type[0]);
+	print_icmp_code(code);
+}
+
+static void brip6_print_help(void)
+{
+	printf(
+"ip6 options:\n"
+"--ip6-src    [!] address[/mask]: ipv6 source specification\n"
+"--ip6-dst    [!] address[/mask]: ipv6 destination specification\n"
+"--ip6-tclass [!] tclass        : ipv6 traffic class specification\n"
+"--ip6-proto  [!] protocol      : ipv6 protocol specification\n"
+"--ip6-sport  [!] port[:port]   : tcp/udp source port or port range\n"
+"--ip6-dport  [!] port[:port]   : tcp/udp destination port or port range\n"
+"--ip6-icmp-type [!] type[[:type]/code[:code]] : ipv6-icmp type/code or type/code range\n");
+	printf("Valid ICMPv6 Types:");
+	xt_print_icmp_types(icmpv6_codes, ARRAY_SIZE(icmpv6_codes));
+}
+
+static void brip6_init(struct xt_entry_match *match)
+{
+	struct ebt_ip6_info *ipinfo = (struct ebt_ip6_info *)match->data;
+
+	ipinfo->invflags = 0;
+	ipinfo->bitmask = 0;
+	memset(ipinfo->saddr.s6_addr, 0, sizeof(ipinfo->saddr.s6_addr));
+	memset(ipinfo->smsk.s6_addr, 0, sizeof(ipinfo->smsk.s6_addr));
+	memset(ipinfo->daddr.s6_addr, 0, sizeof(ipinfo->daddr.s6_addr));
+	memset(ipinfo->dmsk.s6_addr, 0, sizeof(ipinfo->dmsk.s6_addr));
+}
+
+static struct in6_addr *numeric_to_addr(const char *num)
+{
+	static struct in6_addr ap;
+	int err;
+
+	if ((err=inet_pton(AF_INET6, num, &ap)) == 1)
+		return &ap;
+	return (struct in6_addr *)NULL;
+}
+
+static struct in6_addr *parse_ip6_mask(char *mask)
+{
+	static struct in6_addr maskaddr;
+	struct in6_addr *addrp;
+	unsigned int bits;
+
+	if (mask == NULL) {
+		/* no mask at all defaults to 128 bits */
+		memset(&maskaddr, 0xff, sizeof maskaddr);
+		return &maskaddr;
+	}
+	if ((addrp = numeric_to_addr(mask)) != NULL)
+		return addrp;
+	if (!xtables_strtoui(mask, NULL, &bits, 0, 128))
+		xtables_error(PARAMETER_PROBLEM, "Invalid IPv6 Mask '%s' specified", mask);
+	if (bits != 0) {
+		char *p = (char *)&maskaddr;
+		memset(p, 0xff, bits / 8);
+		memset(p + (bits / 8) + 1, 0, (128 - bits) / 8);
+		p[bits / 8] = 0xff << (8 - (bits & 7));
+		return &maskaddr;
+	}
+
+	memset(&maskaddr, 0, sizeof maskaddr);
+	return &maskaddr;
+}
+
+/* Set the ipv6 mask and address. Callers should check ebt_errormsg[0].
+ * The string pointed to by address can be altered. */
+static void ebt_parse_ip6_address(char *address, struct in6_addr *addr, struct in6_addr *msk)
+{
+	struct in6_addr *tmp_addr;
+	char buf[256];
+	char *p;
+	int i;
+	int err;
+
+	strncpy(buf, address, sizeof(buf) - 1);
+	/* first the mask */
+	buf[sizeof(buf) - 1] = '\0';
+	if ((p = strrchr(buf, '/')) != NULL) {
+		*p = '\0';
+		tmp_addr = parse_ip6_mask(p + 1);
+	} else
+		tmp_addr = parse_ip6_mask(NULL);
+
+	*msk = *tmp_addr;
+
+	/* if a null mask is given, the name is ignored, like in "any/0" */
+	if (!memcmp(msk, &in6addr_any, sizeof(in6addr_any)))
+		strcpy(buf, "::");
+
+	if ((err=inet_pton(AF_INET6, buf, addr)) < 1) {
+		xtables_error(PARAMETER_PROBLEM, "Invalid IPv6 Address '%s' specified", buf);
+		return;
+	}
+
+	for (i = 0; i < 4; i++)
+		addr->s6_addr32[i] &= msk->s6_addr32[i];
+}
+
+#define OPT_SOURCE 0x01
+#define OPT_DEST   0x02
+#define OPT_TCLASS 0x04
+#define OPT_PROTO  0x08
+#define OPT_SPORT  0x10
+#define OPT_DPORT  0x20
+static int
+brip6_parse(int c, char **argv, int invert, unsigned int *flags,
+	   const void *entry, struct xt_entry_match **match)
+{
+	struct ebt_ip6_info *info = (struct ebt_ip6_info *)(*match)->data;
+	unsigned int i;
+	char *end;
+
+	switch (c) {
+	case IP_SOURCE:
+		if (invert)
+			info->invflags |= EBT_IP6_SOURCE;
+		ebt_parse_ip6_address(optarg, &info->saddr, &info->smsk);
+		info->bitmask |= EBT_IP6_SOURCE;
+		break;
+	case IP_DEST:
+		if (invert)
+			info->invflags |= EBT_IP6_DEST;
+		ebt_parse_ip6_address(optarg, &info->daddr, &info->dmsk);
+		info->bitmask |= EBT_IP6_DEST;
+		break;
+	case IP_SPORT:
+		if (invert)
+			info->invflags |= EBT_IP6_SPORT;
+		parse_port_range(NULL, optarg, info->sport);
+		info->bitmask |= EBT_IP6_SPORT;
+		break;
+	case IP_DPORT:
+		if (invert)
+			info->invflags |= EBT_IP6_DPORT;
+		parse_port_range(NULL, optarg, info->dport);
+		info->bitmask |= EBT_IP6_DPORT;
+		break;
+	case IP_ICMP6:
+		if (invert)
+			info->invflags |= EBT_IP6_ICMP6;
+		if (parse_icmpv6(optarg, info->icmpv6_type, info->icmpv6_code))
+			return 0;
+		info->bitmask |= EBT_IP6_ICMP6;
+		break;
+	case IP_TCLASS:
+		if (invert)
+			info->invflags |= EBT_IP6_TCLASS;
+		if (!xtables_strtoui(optarg, &end, &i, 0, 255))
+			xtables_error(PARAMETER_PROBLEM, "Problem with specified IPv6 traffic class '%s'", optarg);
+		info->tclass = i;
+		info->bitmask |= EBT_IP6_TCLASS;
+		break;
+	case IP_PROTO:
+		if (invert)
+			info->invflags |= EBT_IP6_PROTO;
+		info->protocol = xtables_parse_protocol(optarg);
+		info->bitmask |= EBT_IP6_PROTO;
+		break;
+	default:
+		return 0;
+	}
+
+	*flags |= info->bitmask;
+	return 1;
+}
+
+static void brip6_final_check(unsigned int flags)
+{
+	if (!flags)
+		xtables_error(PARAMETER_PROBLEM,
+			      "You must specify proper arguments");
+}
+
+static void brip6_print(const void *ip, const struct xt_entry_match *match,
+		       int numeric)
+{
+	struct ebt_ip6_info *ipinfo = (struct ebt_ip6_info *)match->data;
+
+	if (ipinfo->bitmask & EBT_IP6_SOURCE) {
+		printf("--ip6-src ");
+		if (ipinfo->invflags & EBT_IP6_SOURCE)
+			printf("! ");
+		printf("%s", xtables_ip6addr_to_numeric(&ipinfo->saddr));
+		printf("%s ", xtables_ip6mask_to_numeric(&ipinfo->smsk));
+	}
+	if (ipinfo->bitmask & EBT_IP6_DEST) {
+		printf("--ip6-dst ");
+		if (ipinfo->invflags & EBT_IP6_DEST)
+			printf("! ");
+		printf("%s", xtables_ip6addr_to_numeric(&ipinfo->daddr));
+		printf("%s ", xtables_ip6mask_to_numeric(&ipinfo->dmsk));
+	}
+	if (ipinfo->bitmask & EBT_IP6_TCLASS) {
+		printf("--ip6-tclass ");
+		if (ipinfo->invflags & EBT_IP6_TCLASS)
+			printf("! ");
+		printf("0x%02X ", ipinfo->tclass);
+	}
+	if (ipinfo->bitmask & EBT_IP6_PROTO) {
+		struct protoent *pe;
+
+		printf("--ip6-proto ");
+		if (ipinfo->invflags & EBT_IP6_PROTO)
+			printf("! ");
+		pe = getprotobynumber(ipinfo->protocol);
+		if (pe == NULL) {
+			printf("%d ", ipinfo->protocol);
+		} else {
+			printf("%s ", pe->p_name);
+		}
+	}
+	if (ipinfo->bitmask & EBT_IP6_SPORT) {
+		printf("--ip6-sport ");
+		if (ipinfo->invflags & EBT_IP6_SPORT)
+			printf("! ");
+		print_port_range(ipinfo->sport);
+	}
+	if (ipinfo->bitmask & EBT_IP6_DPORT) {
+		printf("--ip6-dport ");
+		if (ipinfo->invflags & EBT_IP6_DPORT)
+			printf("! ");
+		print_port_range(ipinfo->dport);
+	}
+	if (ipinfo->bitmask & EBT_IP6_ICMP6) {
+		printf("--ip6-icmp-type ");
+		if (ipinfo->invflags & EBT_IP6_ICMP6)
+			printf("! ");
+		print_icmp_type(ipinfo->icmpv6_type, ipinfo->icmpv6_code);
+	}
+}
+
+static void brip_xlate_th(struct xt_xlate *xl,
+			  const struct ebt_ip6_info *info, int bit,
+			  const char *pname)
+{
+	const uint16_t *ports;
+
+	if ((info->bitmask & bit) == 0)
+		return;
+
+	switch (bit) {
+	case EBT_IP6_SPORT:
+		if (pname)
+			xt_xlate_add(xl, "%s sport ", pname);
+		else
+			xt_xlate_add(xl, "@th,0,16 ");
+
+		ports = info->sport;
+		break;
+	case EBT_IP6_DPORT:
+		if (pname)
+			xt_xlate_add(xl, "%s dport ", pname);
+		else
+			xt_xlate_add(xl, "@th,16,16 ");
+
+		ports = info->dport;
+		break;
+	default:
+		return;
+	}
+
+	if (info->invflags & bit)
+		xt_xlate_add(xl, "!= ");
+
+	if (ports[0] == ports[1])
+		xt_xlate_add(xl, "%d ", ports[0]);
+	else
+		xt_xlate_add(xl, "%d-%d ", ports[0], ports[1]);
+}
+
+static void brip_xlate_nh(struct xt_xlate *xl,
+			  const struct ebt_ip6_info *info, int bit)
+{
+	struct in6_addr *addrp, *maskp;
+
+	if ((info->bitmask & bit) == 0)
+		return;
+
+	switch (bit) {
+	case EBT_IP6_SOURCE:
+		xt_xlate_add(xl, "ip6 saddr ");
+		addrp = (struct in6_addr *)&info->saddr;
+		maskp = (struct in6_addr *)&info->smsk;
+		break;
+	case EBT_IP6_DEST:
+		xt_xlate_add(xl, "ip6 daddr ");
+		addrp = (struct in6_addr *)&info->daddr;
+		maskp = (struct in6_addr *)&info->dmsk;
+		break;
+	default:
+		return;
+	}
+
+	if (info->invflags & bit)
+		xt_xlate_add(xl, "!= ");
+
+	xt_xlate_add(xl, "%s%s ", xtables_ip6addr_to_numeric(addrp),
+				  xtables_ip6mask_to_numeric(maskp));
+}
+
+static const char *brip6_xlate_proto_to_name(uint8_t proto)
+{
+	switch (proto) {
+	case IPPROTO_TCP:
+		return "tcp";
+	case IPPROTO_UDP:
+		return "udp";
+	case IPPROTO_UDPLITE:
+		return "udplite";
+	case IPPROTO_SCTP:
+		return "sctp";
+	case IPPROTO_DCCP:
+		return "dccp";
+	default:
+		return NULL;
+	}
+}
+
+static int brip6_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_mt_params *params)
+{
+	const struct ebt_ip6_info *info = (const void *)params->match->data;
+	const char *pname = NULL;
+
+	if ((info->bitmask & (EBT_IP6_SOURCE|EBT_IP6_DEST|EBT_IP6_ICMP6|EBT_IP6_TCLASS)) == 0)
+		xt_xlate_add(xl, "ether type ip6 ");
+
+	brip_xlate_nh(xl, info, EBT_IP6_SOURCE);
+	brip_xlate_nh(xl, info, EBT_IP6_DEST);
+
+	if (info->bitmask & EBT_IP6_TCLASS) {
+		xt_xlate_add(xl, "ip6 dscp ");
+		if (info->invflags & EBT_IP6_TCLASS)
+			xt_xlate_add(xl, "!= ");
+		xt_xlate_add(xl, "0x%02x ", info->tclass & 0x3f); /* remove ECN bits */
+	}
+
+	if (info->bitmask & EBT_IP6_PROTO) {
+		struct protoent *pe;
+
+		if (info->bitmask & (EBT_IP6_SPORT|EBT_IP6_DPORT|EBT_IP6_ICMP6) &&
+		    (info->invflags & EBT_IP6_PROTO) == 0) {
+			/* port number given and not inverted, no need to
+			 * add explicit 'meta l4proto'.
+			 */
+			pname = brip6_xlate_proto_to_name(info->protocol);
+		} else {
+			xt_xlate_add(xl, "meta l4proto ");
+			if (info->invflags & EBT_IP6_PROTO)
+				xt_xlate_add(xl, "!= ");
+			pe = getprotobynumber(info->protocol);
+			if (pe == NULL)
+				xt_xlate_add(xl, "%d ", info->protocol);
+			else
+				xt_xlate_add(xl, "%s ", pe->p_name);
+		}
+	}
+
+	brip_xlate_th(xl, info, EBT_IP6_SPORT, pname);
+	brip_xlate_th(xl, info, EBT_IP6_DPORT, pname);
+
+	if (info->bitmask & EBT_IP6_ICMP6) {
+		xt_xlate_add(xl, "icmpv6 type ");
+		if (info->invflags & EBT_IP6_ICMP6)
+			xt_xlate_add(xl, "!= ");
+
+		if (info->icmpv6_type[0] == info->icmpv6_type[1])
+			xt_xlate_add(xl, "%d ", info->icmpv6_type[0]);
+		else
+			xt_xlate_add(xl, "%d-%d ", info->icmpv6_type[0],
+						   info->icmpv6_type[1]);
+
+		if (info->icmpv6_code[0] == 0 &&
+		    info->icmpv6_code[1] == 0xff)
+			return 1;
+
+		xt_xlate_add(xl, "icmpv6 code ");
+		if (info->invflags & EBT_IP6_ICMP6)
+			xt_xlate_add(xl, "!= ");
+
+		if (info->icmpv6_code[0] == info->icmpv6_code[1])
+			xt_xlate_add(xl, "%d ", info->icmpv6_code[0]);
+		else
+			xt_xlate_add(xl, "%d-%d ", info->icmpv6_code[0],
+						   info->icmpv6_code[1]);
+	}
+
+	return 1;
+}
+
+static struct xtables_match brip6_match = {
+	.name		= "ip6",
+	.revision	= 0,
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size		= XT_ALIGN(sizeof(struct ebt_ip6_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_ip6_info)),
+	.init		= brip6_init,
+	.help		= brip6_print_help,
+	.parse		= brip6_parse,
+	.final_check	= brip6_final_check,
+	.print		= brip6_print,
+	.xlate		= brip6_xlate,
+	.extra_opts	= brip6_opts,
+};
+
+void _init(void)
+{
+	xtables_register_match(&brip6_match);
+}
diff --git a/extensions/libebt_ip6.t b/extensions/libebt_ip6.t
new file mode 100644
index 0000000..fa1038a
--- /dev/null
+++ b/extensions/libebt_ip6.t
@@ -0,0 +1,15 @@
+:INPUT,FORWARD,OUTPUT
+-p ip6 --ip6-src ! dead::beef/64 -j ACCEPT;-p IPv6 --ip6-src ! dead::/64 -j ACCEPT;OK
+-p IPv6 --ip6-dst dead:beef::/64 -j ACCEPT;=;OK
+-p IPv6 --ip6-dst f00:ba::;=;OK
+-p IPv6 --ip6-tclass 0xFF;=;OK
+-p IPv6 --ip6-proto tcp --ip6-dport 22;=;OK
+-p IPv6 --ip6-proto tcp --ip6-dport ! 22;=;OK
+-p IPv6 --ip6-proto udp --ip6-sport 1024:65535;=;OK
+-p IPv6 --ip6-proto 253;=;OK
+-p IPv6 --ip6-proto ipv6-icmp --ip6-icmp-type echo-request -j CONTINUE;=;OK
+-p IPv6 --ip6-proto ipv6-icmp --ip6-icmp-type echo-request;=;OK
+-p ip6 --ip6-protocol icmpv6 --ip6-icmp-type 1/1;-p IPv6 --ip6-proto ipv6-icmp --ip6-icmp-type communication-prohibited -j CONTINUE;OK
+-p IPv6 --ip6-proto ipv6-icmp --ip6-icmp-type ! 1:10/0:255;=;OK
+--ip6-proto ipv6-icmp ! --ip6-icmp-type 1:10/0:255;=;FAIL
+! -p IPv6 --ip6-proto ipv6-icmp ! --ip6-icmp-type 1:10/0:255;=;FAIL
diff --git a/extensions/libebt_ip6.txlate b/extensions/libebt_ip6.txlate
new file mode 100644
index 0000000..0271734
--- /dev/null
+++ b/extensions/libebt_ip6.txlate
@@ -0,0 +1,29 @@
+ebtables-translate -A FORWARD -p ip6 --ip6-src ! dead::beef/64 -j ACCEPT
+nft add rule bridge filter FORWARD ip6 saddr != dead::/64 counter accept
+
+ebtables-translate -A FORWARD -p ip6 ! --ip6-dst dead:beef::/64 -j ACCEPT
+nft add rule bridge filter FORWARD ip6 daddr != dead:beef::/64 counter accept
+
+ebtables-translate -I FORWARD -p ip6 --ip6-dst f00:ba::
+nft insert rule bridge filter FORWARD ip6 daddr f00:ba:: counter
+
+ebtables-translate -I OUTPUT -o eth0 -p ip6 --ip6-tclass 0xff
+nft insert rule bridge filter OUTPUT oifname "eth0" ip6 dscp 0x3f counter
+
+ebtables-translate -A FORWARD -p ip6 --ip6-proto tcp --ip6-dport 22
+nft add rule bridge filter FORWARD ether type ip6 tcp dport 22 counter
+
+ebtables-translate -A FORWARD -p ip6 --ip6-proto udp --ip6-sport 1024:65535
+nft add rule bridge filter FORWARD ether type ip6 udp sport 1024-65535 counter
+
+ebtables-translate -A FORWARD -p ip6 --ip6-proto 253
+nft add rule bridge filter FORWARD ether type ip6 meta l4proto 253 counter
+
+ebtables-translate -A FORWARD -p ip6  --ip6-protocol icmpv6 --ip6-icmp-type "echo-request"
+nft add rule bridge filter FORWARD icmpv6 type 128 counter
+
+ebtables-translate -A FORWARD -p ip6 --ip6-protocol icmpv6  --ip6-icmp-type 1/1
+nft add rule bridge filter FORWARD icmpv6 type 1 icmpv6 code 1 counter
+
+ebtables-translate -A FORWARD -p ip6 --ip6-protocol icmpv6 --ip6-icmp-type ! 1:10
+nft add rule bridge filter FORWARD icmpv6 type != 1-10 counter
diff --git a/extensions/libebt_limit.txlate b/extensions/libebt_limit.txlate
new file mode 100644
index 0000000..b6af15d
--- /dev/null
+++ b/extensions/libebt_limit.txlate
@@ -0,0 +1,8 @@
+ebtables-translate -A INPUT --limit 3/m --limit-burst 3
+nft add rule bridge filter INPUT limit rate 3/minute burst 3 packets counter
+
+ebtables-translate -A INPUT --limit 10/s --limit-burst 5
+nft add rule bridge filter INPUT limit rate 10/second burst 5 packets counter
+
+ebtables-translate -A INPUT --limit 10/s --limit-burst 0
+nft add rule bridge filter INPUT limit rate 10/second counter
diff --git a/extensions/libebt_log.c b/extensions/libebt_log.c
new file mode 100644
index 0000000..8858cf0
--- /dev/null
+++ b/extensions/libebt_log.c
@@ -0,0 +1,217 @@
+/*
+ * Bart De Schuymer <bdschuym@pandora.be>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Giuseppe Longo <giuseppelng@gmail.com> adapted the original code to the
+ * xtables-compat environment in 2015.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <string.h>
+#include <getopt.h>
+#include <xtables.h>
+#include <linux/netfilter_bridge/ebt_log.h>
+
+#define LOG_DEFAULT_LEVEL LOG_INFO
+
+#define LOG_PREFIX '1'
+#define LOG_LEVEL  '2'
+#define LOG_ARP    '3'
+#define LOG_IP     '4'
+#define LOG_LOG    '5'
+#define LOG_IP6    '6'
+
+struct code {
+	char *c_name;
+	int c_val;
+};
+
+static struct code eight_priority[] = {
+	{ "emerg", LOG_EMERG },
+	{ "alert", LOG_ALERT },
+	{ "crit", LOG_CRIT },
+	{ "error", LOG_ERR },
+	{ "warning", LOG_WARNING },
+	{ "notice", LOG_NOTICE },
+	{ "info", LOG_INFO },
+	{ "debug", LOG_DEBUG }
+};
+
+static int name_to_loglevel(const char *arg)
+{
+	int i;
+
+	for (i = 0; i < 8; i++)
+		if (!strcmp(arg, eight_priority[i].c_name))
+			return eight_priority[i].c_val;
+
+	/* return bad loglevel */
+	return 9;
+}
+
+static const struct option brlog_opts[] = {
+	{ .name = "log-prefix",		.has_arg = true,  .val = LOG_PREFIX },
+	{ .name = "log-level",		.has_arg = true,  .val = LOG_LEVEL  },
+	{ .name = "log-arp",		.has_arg = false, .val = LOG_ARP    },
+	{ .name = "log-ip",		.has_arg = false, .val = LOG_IP     },
+	{ .name = "log",		.has_arg = false, .val = LOG_LOG    },
+	{ .name = "log-ip6",		.has_arg = false, .val = LOG_IP6    },
+	XT_GETOPT_TABLEEND,
+};
+
+static void brlog_help(void)
+{
+	int i;
+
+	printf(
+"log options:\n"
+"--log               : use this if you're not specifying anything\n"
+"--log-level level   : level = [1-8] or a string\n"
+"--log-prefix prefix : max. %d chars.\n"
+"--log-ip            : put ip info. in the log for ip packets\n"
+"--log-arp           : put (r)arp info. in the log for (r)arp packets\n"
+"--log-ip6           : put ip6 info. in the log for ip6 packets\n"
+	, EBT_LOG_PREFIX_SIZE - 1);
+	for (i = 0; i < 8; i++)
+		printf("%d = %s\n", eight_priority[i].c_val,
+				    eight_priority[i].c_name);
+}
+
+static void brlog_init(struct xt_entry_target *t)
+{
+	struct ebt_log_info *loginfo = (struct ebt_log_info *)t->data;
+
+	loginfo->bitmask = 0;
+	loginfo->prefix[0] = '\0';
+	loginfo->loglevel = LOG_NOTICE;
+}
+
+static unsigned int log_chk_inv(int inv, unsigned int bit, const char *suffix)
+{
+	if (inv)
+		xtables_error(PARAMETER_PROBLEM,
+			      "Unexpected `!' after --log%s", suffix);
+	return bit;
+}
+
+static int brlog_parse(int c, char **argv, int invert, unsigned int *flags,
+		       const void *entry, struct xt_entry_target **target)
+{
+	struct ebt_log_info *loginfo = (struct ebt_log_info *)(*target)->data;
+	long int i;
+	char *end;
+
+	switch (c) {
+	case LOG_PREFIX:
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Unexpected `!` after --log-prefix");
+		if (strlen(optarg) > sizeof(loginfo->prefix) - 1)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Prefix too long");
+		if (strchr(optarg, '\"'))
+			xtables_error(PARAMETER_PROBLEM,
+				      "Use of \\\" is not allowed"
+				      " in the prefix");
+		strcpy((char *)loginfo->prefix, (char *)optarg);
+		break;
+	case LOG_LEVEL:
+		i = strtol(optarg, &end, 16);
+		if (*end != '\0' || i < 0 || i > 7)
+			loginfo->loglevel = name_to_loglevel(optarg);
+		else
+			loginfo->loglevel = i;
+
+		if (loginfo->loglevel == 9)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Problem with the log-level");
+		break;
+	case LOG_IP:
+		loginfo->bitmask |= log_chk_inv(invert, EBT_LOG_IP, "-ip");
+		break;
+	case LOG_ARP:
+		loginfo->bitmask |= log_chk_inv(invert, EBT_LOG_ARP, "-arp");
+		break;
+	case LOG_LOG:
+		loginfo->bitmask |= log_chk_inv(invert, 0, "");
+		break;
+	case LOG_IP6:
+		loginfo->bitmask |= log_chk_inv(invert, EBT_LOG_IP6, "-ip6");
+		break;
+	default:
+		return 0;
+	}
+
+	*flags |= loginfo->bitmask;
+	return 1;
+}
+
+static void brlog_final_check(unsigned int flags)
+{
+}
+
+static void brlog_print(const void *ip, const struct xt_entry_target *target,
+			int numeric)
+{
+	struct ebt_log_info *loginfo = (struct ebt_log_info *)target->data;
+
+	printf("--log-level %s --log-prefix \"%s\"",
+		eight_priority[loginfo->loglevel].c_name,
+		loginfo->prefix);
+
+	if (loginfo->bitmask & EBT_LOG_IP)
+		printf(" --log-ip");
+	if (loginfo->bitmask & EBT_LOG_ARP)
+		printf(" --log-arp");
+	if (loginfo->bitmask & EBT_LOG_IP6)
+		printf(" --log-ip6");
+	printf(" ");
+}
+
+static int brlog_xlate(struct xt_xlate *xl,
+		       const struct xt_xlate_tg_params *params)
+{
+	const struct ebt_log_info *loginfo = (const void *)params->target->data;
+
+	xt_xlate_add(xl, "log");
+	if (loginfo->prefix[0]) {
+		if (params->escape_quotes)
+			xt_xlate_add(xl, " prefix \\\"%s\\\"", loginfo->prefix);
+		else
+			xt_xlate_add(xl, " prefix \"%s\"", loginfo->prefix);
+	}
+
+	if (loginfo->loglevel != LOG_DEFAULT_LEVEL)
+		xt_xlate_add(xl, " level %s", eight_priority[loginfo->loglevel].c_name);
+
+	xt_xlate_add(xl, " flags ether ");
+
+	return 1;
+}
+
+static struct xtables_target brlog_target = {
+	.name		= "log",
+	.revision	= 0,
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size		= XT_ALIGN(sizeof(struct ebt_log_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_log_info)),
+	.init		= brlog_init,
+	.help		= brlog_help,
+	.parse		= brlog_parse,
+	.final_check	= brlog_final_check,
+	.print		= brlog_print,
+	.xlate		= brlog_xlate,
+	.extra_opts	= brlog_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&brlog_target);
+}
diff --git a/extensions/libebt_log.t b/extensions/libebt_log.t
new file mode 100644
index 0000000..a0df616
--- /dev/null
+++ b/extensions/libebt_log.t
@@ -0,0 +1,6 @@
+:INPUT,FORWARD,OUTPUT
+--log;=;OK
+--log-level crit;=;OK
+--log-level 1;--log-level alert --log-prefix "";OK
+--log-level emerg --log-ip --log-arp --log-ip6;--log-level emerg --log-prefix "" --log-ip --log-arp --log-ip6 -j CONTINUE;OK
+--log-level crit --log-ip --log-arp --log-ip6 --log-prefix foo;--log-level crit --log-prefix "foo" --log-ip --log-arp --log-ip6 -j CONTINUE;OK
diff --git a/extensions/libebt_log.txlate b/extensions/libebt_log.txlate
new file mode 100644
index 0000000..7ef8d5e
--- /dev/null
+++ b/extensions/libebt_log.txlate
@@ -0,0 +1,15 @@
+ebtables-translate -A INPUT --log
+nft add rule bridge filter INPUT log level notice flags ether counter
+
+ebtables-translate -A INPUT --log-level 1
+nft add rule bridge filter INPUT log level alert flags ether counter
+
+ebtables-translate -A INPUT --log-level crit
+nft add rule bridge filter INPUT log level crit flags ether counter
+
+ebtables-translate -A INPUT --log-level emerg --log-ip --log-arp --log-ip6
+nft add rule bridge filter INPUT log level emerg flags ether counter
+
+ebtables-translate -A INPUT --log-level crit --log-ip --log-arp --log-ip6 --log-prefix foo
+nft add rule bridge filter INPUT log prefix "foo" level crit flags ether counter
+
diff --git a/extensions/libebt_mark.c b/extensions/libebt_mark.c
new file mode 100644
index 0000000..423c5c9
--- /dev/null
+++ b/extensions/libebt_mark.c
@@ -0,0 +1,228 @@
+/* ebt_mark
+ *
+ * Authors:
+ * Bart De Schuymer <bdschuym@pandora.be>
+ *
+ * July, 2002, September 2006
+ *
+ * Adapted by Arturo Borrero Gonzalez <arturo@debian.org>
+ * to use libxtables for ebtables-compat in 2015.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <xtables.h>
+#include <linux/netfilter_bridge/ebt_mark_t.h>
+#include "iptables/nft.h"
+#include "iptables/nft-bridge.h"
+
+#define MARK_TARGET  '1'
+#define MARK_SETMARK '2'
+#define MARK_ORMARK  '3'
+#define MARK_ANDMARK '4'
+#define MARK_XORMARK '5'
+static const struct option brmark_opts[] = {
+	{ .name = "mark-target",.has_arg = true,	.val = MARK_TARGET },
+	/* an oldtime messup, we should have always used the scheme
+	 * <extension-name>-<option> */
+	{ .name = "set-mark",	.has_arg = true,	.val = MARK_SETMARK },
+	{ .name = "mark-set",	.has_arg = true,	.val = MARK_SETMARK },
+	{ .name = "mark-or",	.has_arg = true,	.val = MARK_ORMARK },
+	{ .name = "mark-and",	.has_arg = true,	.val = MARK_ANDMARK },
+	{ .name = "mark-xor",	.has_arg = true,	.val = MARK_XORMARK },
+	XT_GETOPT_TABLEEND,
+};
+
+static void brmark_print_help(void)
+{
+	printf(
+	"mark target options:\n"
+	" --mark-set value     : Set nfmark value\n"
+	" --mark-or  value     : Or nfmark with value (nfmark |= value)\n"
+	" --mark-and value     : And nfmark with value (nfmark &= value)\n"
+	" --mark-xor value     : Xor nfmark with value (nfmark ^= value)\n"
+	" --mark-target target : ACCEPT, DROP, RETURN or CONTINUE\n");
+}
+
+static void brmark_init(struct xt_entry_target *target)
+{
+	struct ebt_mark_t_info *info = (struct ebt_mark_t_info *)target->data;
+
+	info->target = EBT_ACCEPT;
+	info->mark = 0;
+}
+
+#define OPT_MARK_TARGET   0x01
+#define OPT_MARK_SETMARK  0x02
+#define OPT_MARK_ORMARK   0x04
+#define OPT_MARK_ANDMARK  0x08
+#define OPT_MARK_XORMARK  0x10
+
+static int
+brmark_parse(int c, char **argv, int invert, unsigned int *flags,
+	     const void *entry, struct xt_entry_target **target)
+{
+	struct ebt_mark_t_info *info = (struct ebt_mark_t_info *)
+				       (*target)->data;
+	char *end;
+	uint32_t mask;
+
+	switch (c) {
+	case MARK_TARGET:
+		{ unsigned int tmp;
+		EBT_CHECK_OPTION(flags, OPT_MARK_TARGET);
+		if (ebt_fill_target(optarg, &tmp))
+			xtables_error(PARAMETER_PROBLEM,
+				      "Illegal --mark-target target");
+		/* the 4 lsb are left to designate the target */
+		info->target = (info->target & ~EBT_VERDICT_BITS) |
+			       (tmp & EBT_VERDICT_BITS);
+		}
+		return 1;
+	case MARK_SETMARK:
+		EBT_CHECK_OPTION(flags, OPT_MARK_SETMARK);
+		mask = (OPT_MARK_ORMARK|OPT_MARK_ANDMARK|OPT_MARK_XORMARK);
+		if (*flags & mask)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--mark-set cannot be used together with"
+				      " specific --mark option");
+		info->target = (info->target & EBT_VERDICT_BITS) |
+			       MARK_SET_VALUE;
+		break;
+	case MARK_ORMARK:
+		EBT_CHECK_OPTION(flags, OPT_MARK_ORMARK);
+		mask = (OPT_MARK_SETMARK|OPT_MARK_ANDMARK|OPT_MARK_XORMARK);
+		if (*flags & mask)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--mark-or cannot be used together with"
+				      " specific --mark option");
+		info->target = (info->target & EBT_VERDICT_BITS) |
+			       MARK_OR_VALUE;
+		break;
+	case MARK_ANDMARK:
+		EBT_CHECK_OPTION(flags, OPT_MARK_ANDMARK);
+		mask = (OPT_MARK_SETMARK|OPT_MARK_ORMARK|OPT_MARK_XORMARK);
+		if (*flags & mask)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--mark-and cannot be used together with"
+				      " specific --mark option");
+		info->target = (info->target & EBT_VERDICT_BITS) |
+			       MARK_AND_VALUE;
+		break;
+	case MARK_XORMARK:
+		EBT_CHECK_OPTION(flags, OPT_MARK_XORMARK);
+		mask = (OPT_MARK_SETMARK|OPT_MARK_ANDMARK|OPT_MARK_ORMARK);
+		if (*flags & mask)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--mark-xor cannot be used together with"
+				      " specific --mark option");
+		info->target = (info->target & EBT_VERDICT_BITS) |
+			       MARK_XOR_VALUE;
+		break;
+	default:
+		return 0;
+	}
+	/* mutual code */
+	info->mark = strtoul(optarg, &end, 0);
+	if (*end != '\0' || end == optarg)
+		xtables_error(PARAMETER_PROBLEM, "Bad MARK value '%s'",
+			      optarg);
+
+	return 1;
+}
+
+static void brmark_print(const void *ip, const struct xt_entry_target *target,
+			 int numeric)
+{
+	struct ebt_mark_t_info *info = (struct ebt_mark_t_info *)target->data;
+	int tmp;
+
+	tmp = info->target & ~EBT_VERDICT_BITS;
+	if (tmp == MARK_SET_VALUE)
+		printf("--mark-set");
+	else if (tmp == MARK_OR_VALUE)
+		printf("--mark-or");
+	else if (tmp == MARK_XOR_VALUE)
+		printf("--mark-xor");
+	else if (tmp == MARK_AND_VALUE)
+		printf("--mark-and");
+	else
+		xtables_error(PARAMETER_PROBLEM, "Unknown mark action");
+
+	printf(" 0x%lx", info->mark);
+	tmp = info->target | ~EBT_VERDICT_BITS;
+	printf(" --mark-target %s", ebt_target_name(tmp));
+}
+
+static void brmark_final_check(unsigned int flags)
+{
+	if (!flags)
+		xtables_error(PARAMETER_PROBLEM,
+			      "You must specify some option");
+}
+
+static const char* brmark_verdict(int verdict)
+{
+	switch (verdict) {
+	case EBT_ACCEPT: return "accept";
+	case EBT_DROP: return "drop";
+	case EBT_CONTINUE: return "continue";
+	case EBT_RETURN: return "return";
+	}
+
+	return "";
+}
+
+static int brmark_xlate(struct xt_xlate *xl,
+			const struct xt_xlate_tg_params *params)
+{
+	const struct ebt_mark_t_info *info = (const void*)params->target->data;
+	int tmp;
+
+	tmp = info->target & ~EBT_VERDICT_BITS;
+
+	xt_xlate_add(xl, "meta mark set ");
+
+	switch (tmp) {
+	case MARK_SET_VALUE:
+		break;
+	case MARK_OR_VALUE:
+		xt_xlate_add(xl, "meta mark or ");
+		break;
+	case MARK_XOR_VALUE:
+		xt_xlate_add(xl, "meta mark xor ");
+		break;
+	case MARK_AND_VALUE:
+		xt_xlate_add(xl, "meta mark and ");
+		break;
+	default:
+		return 0;
+	}
+
+	tmp = info->target & EBT_VERDICT_BITS;
+	xt_xlate_add(xl, "0x%lx %s ", info->mark, brmark_verdict(tmp));
+	return 1;
+}
+
+static struct xtables_target brmark_target = {
+	.name		= "mark",
+	.revision	= 0,
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size		= XT_ALIGN(sizeof(struct ebt_mark_t_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_mark_t_info)),
+	.help		= brmark_print_help,
+	.init		= brmark_init,
+	.parse		= brmark_parse,
+	.final_check	= brmark_final_check,
+	.print		= brmark_print,
+	.xlate		= brmark_xlate,
+	.extra_opts	= brmark_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&brmark_target);
+}
diff --git a/extensions/libebt_mark.t b/extensions/libebt_mark.t
new file mode 100644
index 0000000..2d8f9d7
--- /dev/null
+++ b/extensions/libebt_mark.t
@@ -0,0 +1,5 @@
+:INPUT,FORWARD,OUTPUT
+-j mark --mark-set 1;-j mark --mark-set 0x1 --mark-target ACCEPT;OK
+-j mark --mark-or 0xa --mark-target CONTINUE;=;OK
+-j mark --mark-and 0x1 --mark-target RETURN;=;OK
+-j mark --mark-xor 0x1 --mark-target CONTINUE;=;OK
diff --git a/extensions/libebt_mark.xlate b/extensions/libebt_mark.xlate
new file mode 100644
index 0000000..e0982a1
--- /dev/null
+++ b/extensions/libebt_mark.xlate
@@ -0,0 +1,11 @@
+ebtables-translate -A INPUT --mark-set 42
+nft add rule bridge filter INPUT mark set 0x2a counter
+
+ebtables-translate -A INPUT --mark-or 42 --mark-target RETURN
+nft add rule bridge filter INPUT mark set mark or 0x2a counter return
+
+ebtables-translate -A INPUT --mark-and 42 --mark-target ACCEPT
+nft add rule bridge filter INPUT mark set mark and 0x2a counter accept
+
+ebtables-translate -A INPUT --mark-xor 42 --mark-target DROP
+nft add rule bridge filter INPUT mark set mark xor 0x2a counter drop
diff --git a/extensions/libebt_mark_m.c b/extensions/libebt_mark_m.c
new file mode 100644
index 0000000..2462d0a
--- /dev/null
+++ b/extensions/libebt_mark_m.c
@@ -0,0 +1,143 @@
+/* ebt_mark_m
+ *
+ * Authors:
+ * Bart De Schuymer <bdschuym@pandora.be>
+ *
+ * July, 2002
+ *
+ * Adapted by Arturo Borrero Gonzalez <arturo@debian.org>
+ * to use libxtables for ebtables-compat in 2015.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <xtables.h>
+#include <linux/netfilter_bridge/ebt_mark_m.h>
+
+#define MARK '1'
+
+static const struct option brmark_m_opts[] = {
+	{ .name = "mark",	.has_arg = true, .val = MARK },
+	XT_GETOPT_TABLEEND,
+};
+
+static void brmark_m_print_help(void)
+{
+	printf(
+"mark option:\n"
+"--mark    [!] [value][/mask]: Match nfmask value (see man page)\n");
+}
+
+static void brmark_m_init(struct xt_entry_match *match)
+{
+	struct ebt_mark_m_info *info = (struct ebt_mark_m_info *)match->data;
+
+	info->mark = 0;
+	info->mask = 0;
+	info->invert = 0;
+	info->bitmask = 0;
+}
+
+#define OPT_MARK 0x01
+static int
+brmark_m_parse(int c, char **argv, int invert, unsigned int *flags,
+	       const void *entry, struct xt_entry_match **match)
+{
+	struct ebt_mark_m_info *info = (struct ebt_mark_m_info *)
+				       (*match)->data;
+	char *end;
+
+	switch (c) {
+	case MARK:
+		if (invert)
+			info->invert = 1;
+		info->mark = strtoul(optarg, &end, 0);
+		info->bitmask = EBT_MARK_AND;
+		if (*end == '/') {
+			if (end == optarg)
+				info->bitmask = EBT_MARK_OR;
+			info->mask = strtoul(end+1, &end, 0);
+		} else {
+			info->mask = 0xffffffff;
+		}
+		if (*end != '\0' || end == optarg)
+			xtables_error(PARAMETER_PROBLEM, "Bad mark value '%s'",
+				      optarg);
+		break;
+	default:
+		return 0;
+	}
+
+	*flags |= info->bitmask;
+	return 1;
+}
+
+static void brmark_m_final_check(unsigned int flags)
+{
+	if (!flags)
+		xtables_error(PARAMETER_PROBLEM,
+			      "You must specify proper arguments");
+}
+
+static void brmark_m_print(const void *ip, const struct xt_entry_match *match,
+			   int numeric)
+{
+	struct ebt_mark_m_info *info = (struct ebt_mark_m_info *)match->data;
+
+	printf("--mark ");
+	if (info->invert)
+		printf("! ");
+	if (info->bitmask == EBT_MARK_OR)
+		printf("/0x%lx ", info->mask);
+	else if (info->mask != 0xffffffff)
+		printf("0x%lx/0x%lx ", info->mark, info->mask);
+	else
+		printf("0x%lx ", info->mark);
+}
+
+static int brmark_m_xlate(struct xt_xlate *xl,
+			  const struct xt_xlate_mt_params *params)
+{
+	const struct ebt_mark_m_info *info = (const void*)params->match->data;
+	enum xt_op op = XT_OP_EQ;
+
+	if (info->invert)
+		op = XT_OP_NEQ;
+
+	xt_xlate_add(xl, "meta mark ");
+
+	if (info->bitmask == EBT_MARK_OR) {
+		xt_xlate_add(xl, "and 0x%x %s0 ", (uint32_t)info->mask,
+			     info->invert ? "" : "!= ");
+	} else if (info->mask != 0xffffffffU) {
+		xt_xlate_add(xl, "and 0x%x %s0x%x ", (uint32_t)info->mask,
+			   op == XT_OP_EQ ? "" : "!= ", (uint32_t)info->mark);
+	} else {
+		xt_xlate_add(xl, "%s0x%x ",
+			   op == XT_OP_EQ ? "" : "!= ", (uint32_t)info->mark);
+	}
+
+	return 1;
+}
+static struct xtables_match brmark_m_match = {
+	.name		= "mark_m",
+	.revision	= 0,
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size		= XT_ALIGN(sizeof(struct ebt_mark_m_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_mark_m_info)),
+	.init		= brmark_m_init,
+	.help		= brmark_m_print_help,
+	.parse		= brmark_m_parse,
+	.final_check	= brmark_m_final_check,
+	.print		= brmark_m_print,
+	.xlate		= brmark_m_xlate,
+	.extra_opts	= brmark_m_opts,
+};
+
+void _init(void)
+{
+	xtables_register_match(&brmark_m_match);
+}
diff --git a/extensions/libebt_mark_m.t b/extensions/libebt_mark_m.t
new file mode 100644
index 0000000..0003542
--- /dev/null
+++ b/extensions/libebt_mark_m.t
@@ -0,0 +1,6 @@
+:INPUT,FORWARD,OUTPUT
+--mark 42;--mark 0x2a;OK
+--mark ! 42;--mark ! 0x2a;OK
+--mark 42/0xff;--mark 0x2a/0xff;OK
+--mark ! 0x1/0xff;=;OK
+--mark /0x2;=;OK
diff --git a/extensions/libebt_mark_m.txlate b/extensions/libebt_mark_m.txlate
new file mode 100644
index 0000000..7b44425
--- /dev/null
+++ b/extensions/libebt_mark_m.txlate
@@ -0,0 +1,14 @@
+ebtables-translate -A INPUT --mark 42
+nft add rule bridge filter INPUT meta mark 0x2a counter
+
+ebtables-translate -A INPUT ! --mark 42
+nft add rule bridge filter INPUT meta mark != 0x2a counter
+
+ebtables-translate -A INPUT --mark ! 42
+nft add rule bridge filter INPUT meta mark != 0x2a counter
+
+ebtables-translate -A INPUT --mark ! 0x1/0xff
+nft add rule bridge filter INPUT meta mark and 0xff != 0x1 counter
+
+ebtables-translate -A INPUT --mark /0x02
+nft add rule bridge filter INPUT meta mark and 0x2 != 0 counter
diff --git a/extensions/libebt_nflog.c b/extensions/libebt_nflog.c
new file mode 100644
index 0000000..9801f35
--- /dev/null
+++ b/extensions/libebt_nflog.c
@@ -0,0 +1,168 @@
+/* ebt_nflog
+ *
+ * Authors:
+ * Peter Warasin <peter@endian.com>
+ *
+ *  February, 2008
+ *
+ * Based on:
+ *  ebt_ulog.c, (C) 2004, Bart De Schuymer <bdschuym@pandora.be>
+ *  libxt_NFLOG.c
+ *
+ * Adapted to libxtables for ebtables-compat in 2015 by
+ * Arturo Borrero Gonzalez <arturo@debian.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <xtables.h>
+#include "iptables/nft.h"
+#include "iptables/nft-bridge.h"
+#include <linux/netfilter_bridge/ebt_nflog.h>
+
+enum {
+	NFLOG_GROUP	= 0x1,
+	NFLOG_PREFIX	= 0x2,
+	NFLOG_RANGE	= 0x4,
+	NFLOG_THRESHOLD	= 0x8,
+	NFLOG_NFLOG	= 0x16,
+};
+
+static const struct option brnflog_opts[] = {
+	{ .name = "nflog-group",     .has_arg = true,  .val = NFLOG_GROUP},
+	{ .name = "nflog-prefix",    .has_arg = true,  .val = NFLOG_PREFIX},
+	{ .name = "nflog-range",     .has_arg = true,  .val = NFLOG_RANGE},
+	{ .name = "nflog-threshold", .has_arg = true,  .val = NFLOG_THRESHOLD},
+	{ .name = "nflog",           .has_arg = false, .val = NFLOG_NFLOG},
+	XT_GETOPT_TABLEEND,
+};
+
+static void brnflog_help(void)
+{
+	printf("nflog options:\n"
+	       "--nflog               : use the default nflog parameters\n"
+	       "--nflog-prefix prefix : Prefix string for log message\n"
+	       "--nflog-group group   : NETLINK group used for logging\n"
+	       "--nflog-range range   : Number of byte to copy\n"
+	       "--nflog-threshold     : Message threshold of"
+	       "in-kernel queue\n");
+}
+
+static void brnflog_init(struct xt_entry_target *t)
+{
+	struct ebt_nflog_info *info = (struct ebt_nflog_info *)t->data;
+
+	info->prefix[0]	= '\0';
+	info->group	= EBT_NFLOG_DEFAULT_GROUP;
+	info->threshold = EBT_NFLOG_DEFAULT_THRESHOLD;
+}
+
+static int brnflog_parse(int c, char **argv, int invert, unsigned int *flags,
+			 const void *entry, struct xt_entry_target **target)
+{
+	struct ebt_nflog_info *info = (struct ebt_nflog_info *)(*target)->data;
+	unsigned int i;
+
+	if (invert)
+		xtables_error(PARAMETER_PROBLEM,
+			      "The use of '!' makes no sense for the"
+			      " nflog watcher");
+
+	switch (c) {
+	case NFLOG_PREFIX:
+		EBT_CHECK_OPTION(flags, NFLOG_PREFIX);
+		if (strlen(optarg) > EBT_NFLOG_PREFIX_SIZE - 1)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Prefix too long for nflog-prefix");
+		strncpy(info->prefix, optarg, EBT_NFLOG_PREFIX_SIZE);
+		break;
+	case NFLOG_GROUP:
+		EBT_CHECK_OPTION(flags, NFLOG_GROUP);
+		if (!xtables_strtoui(optarg, NULL, &i, 1, UINT32_MAX))
+			xtables_error(PARAMETER_PROBLEM,
+				      "--nflog-group must be a number!");
+		info->group = i;
+		break;
+	case NFLOG_RANGE:
+		EBT_CHECK_OPTION(flags, NFLOG_RANGE);
+		if (!xtables_strtoui(optarg, NULL, &i, 1, UINT32_MAX))
+			xtables_error(PARAMETER_PROBLEM,
+				      "--nflog-range must be a number!");
+		info->len = i;
+		break;
+	case NFLOG_THRESHOLD:
+		EBT_CHECK_OPTION(flags, NFLOG_THRESHOLD);
+		if (!xtables_strtoui(optarg, NULL, &i, 1, UINT32_MAX))
+			xtables_error(PARAMETER_PROBLEM,
+				      "--nflog-threshold must be a number!");
+		info->threshold = i;
+		break;
+	case NFLOG_NFLOG:
+		EBT_CHECK_OPTION(flags, NFLOG_NFLOG);
+		break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static void
+brnflog_print(const void *ip, const struct xt_entry_target *target,
+	      int numeric)
+{
+	struct ebt_nflog_info *info = (struct ebt_nflog_info *)target->data;
+
+	if (info->prefix[0] != '\0')
+		printf("--nflog-prefix \"%s\" ", info->prefix);
+	if (info->group)
+		printf("--nflog-group %d ", info->group);
+	if (info->len)
+		printf("--nflog-range %d ", info->len);
+	if (info->threshold != EBT_NFLOG_DEFAULT_THRESHOLD)
+		printf("--nflog-threshold %d ", info->threshold);
+}
+
+static int brnflog_xlate(struct xt_xlate *xl,
+			 const struct xt_xlate_tg_params *params)
+{
+	const struct ebt_nflog_info *info = (void *)params->target->data;
+
+	xt_xlate_add(xl, "log ");
+	if (info->prefix[0] != '\0') {
+		if (params->escape_quotes)
+			xt_xlate_add(xl, "prefix \\\"%s\\\" ", info->prefix);
+		else
+			xt_xlate_add(xl, "prefix \"%s\" ", info->prefix);
+	}
+
+	xt_xlate_add(xl, "group %u ", info->group);
+
+	if (info->len)
+		xt_xlate_add(xl, "snaplen %u ", info->len);
+	if (info->threshold != EBT_NFLOG_DEFAULT_THRESHOLD)
+		xt_xlate_add(xl, "queue-threshold %u ", info->threshold);
+
+	return 1;
+}
+
+static struct xtables_target brnflog_watcher = {
+	.name		= "nflog",
+	.revision	= 0,
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size		= XT_ALIGN(sizeof(struct ebt_nflog_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_nflog_info)),
+	.init		= brnflog_init,
+	.help		= brnflog_help,
+	.parse		= brnflog_parse,
+	.print		= brnflog_print,
+	.xlate		= brnflog_xlate,
+	.extra_opts	= brnflog_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&brnflog_watcher);
+}
diff --git a/extensions/libebt_nflog.t b/extensions/libebt_nflog.t
new file mode 100644
index 0000000..f867df3
--- /dev/null
+++ b/extensions/libebt_nflog.t
@@ -0,0 +1,5 @@
+:INPUT,FORWARD,OUTPUT
+--nflog;=;OK
+--nflog-group 42;=;OK
+--nflog-range 42;--nflog-group 1 --nflog-range 42 -j CONTINUE;OK
+--nflog-threshold 100 --nflog-prefix foo;--nflog-prefix "foo" --nflog-group 1 --nflog-threshold 100 -j CONTINUE;OK
diff --git a/extensions/libebt_nflog.txlate b/extensions/libebt_nflog.txlate
new file mode 100644
index 0000000..bc3f536
--- /dev/null
+++ b/extensions/libebt_nflog.txlate
@@ -0,0 +1,11 @@
+ebtables-translate -A INPUT --nflog
+nft add rule bridge filter INPUT log group 1 counter
+
+ebtables-translate -A INPUT --nflog-group 42
+nft add rule bridge filter INPUT log group 42 counter
+
+ebtables-translate -A INPUT --nflog-range 42
+nft add rule bridge filter INPUT log group 1 snaplen 42 counter
+
+ebtables-translate -A INPUT --nflog-threshold 100 --nflog-prefix foo
+nft add rule bridge filter INPUT log prefix "foo" group 1 queue-threshold 100 counter
diff --git a/extensions/libebt_pkttype.c b/extensions/libebt_pkttype.c
new file mode 100644
index 0000000..4e2d19d
--- /dev/null
+++ b/extensions/libebt_pkttype.c
@@ -0,0 +1,119 @@
+/* ebt_pkttype
+ *
+ * Authors:
+ * Bart De Schuymer <bdschuym@pandora.be>
+ *
+ * April, 2003
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <netdb.h>
+#include <xtables.h>
+#include <linux/if_packet.h>
+#include <linux/netfilter_bridge/ebt_pkttype.h>
+
+static const char *classes[] = {
+	"host",
+	"broadcast",
+	"multicast",
+	"otherhost",
+	"outgoing",
+	"loopback",
+	"fastroute",
+};
+
+static const struct option brpkttype_opts[] =
+{
+	{ "pkttype-type"        , required_argument, 0, '1' },
+	{ 0 }
+};
+
+static void brpkttype_print_help(void)
+{
+	printf(
+"pkttype options:\n"
+"--pkttype-type    [!] type: class the packet belongs to\n"
+"Possible values: broadcast, multicast, host, otherhost, or any other byte value (which would be pretty useless).\n");
+}
+
+
+static int brpkttype_parse(int c, char **argv, int invert, unsigned int *flags,
+			   const void *entry, struct xt_entry_match **match)
+{
+	struct ebt_pkttype_info *ptinfo = (struct ebt_pkttype_info *)(*match)->data;
+	char *end;
+	long int i;
+
+	switch (c) {
+	case '1':
+		if (invert)
+			ptinfo->invert = 1;
+		i = strtol(optarg, &end, 16);
+		if (*end != '\0') {
+			for (i = 0; i < ARRAY_SIZE(classes); i++) {
+				if (!strcasecmp(optarg, classes[i]))
+					break;
+			}
+			if (i >= ARRAY_SIZE(classes))
+				xtables_error(PARAMETER_PROBLEM, "Could not parse class '%s'", optarg);
+		}
+		if (i < 0 || i > 255)
+			xtables_error(PARAMETER_PROBLEM, "Problem with specified pkttype class");
+		ptinfo->pkt_type = (uint8_t)i;
+		break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+
+static void brpkttype_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	struct ebt_pkttype_info *pt = (struct ebt_pkttype_info *)match->data;
+
+	printf("--pkttype-type %s", pt->invert ? "! " : "");
+
+	if (pt->pkt_type < ARRAY_SIZE(classes))
+		printf("%s ", classes[pt->pkt_type]);
+	else
+		printf("%d ", pt->pkt_type);
+}
+
+static int brpkttype_xlate(struct xt_xlate *xl,
+			  const struct xt_xlate_mt_params *params)
+{
+	const struct ebt_pkttype_info *info = (const void*)params->match->data;
+
+	xt_xlate_add(xl, "meta pkttype %s", info->invert ? "!= " : "");
+
+	if (info->pkt_type < 3)
+		xt_xlate_add(xl, "%s ", classes[info->pkt_type]);
+	else if (info->pkt_type == 3)
+		xt_xlate_add(xl, "other ");
+	else
+		xt_xlate_add(xl, "%d ", info->pkt_type);
+
+	return 1;
+}
+
+static struct xtables_match brpkttype_match = {
+	.name		= "pkttype",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size		= XT_ALIGN(sizeof(struct ebt_pkttype_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_pkttype_info)),
+	.help		= brpkttype_print_help,
+	.parse		= brpkttype_parse,
+	.print		= brpkttype_print,
+	.xlate		= brpkttype_xlate,
+	.extra_opts	= brpkttype_opts,
+};
+
+void _init(void)
+{
+	xtables_register_match(&brpkttype_match);
+}
diff --git a/extensions/libebt_pkttype.t b/extensions/libebt_pkttype.t
new file mode 100644
index 0000000..e3b95de
--- /dev/null
+++ b/extensions/libebt_pkttype.t
@@ -0,0 +1,14 @@
+:INPUT,FORWARD,OUTPUT
+! --pkttype-type host;--pkttype-type ! host -j CONTINUE;OK
+--pkttype-type host;=;OK
+--pkttype-type ! host;=;OK
+--pkttype-type broadcast;=;OK
+--pkttype-type ! broadcast;=;OK
+--pkttype-type multicast;=;OK
+--pkttype-type ! multicast;=;OK
+--pkttype-type otherhost;=;OK
+--pkttype-type ! otherhost;=;OK
+--pkttype-type outgoing;=;OK
+--pkttype-type ! outgoing;=;OK
+--pkttype-type loopback;=;OK
+--pkttype-type ! loopback;=;OK
diff --git a/extensions/libebt_pkttype.txlate b/extensions/libebt_pkttype.txlate
new file mode 100644
index 0000000..94d016d
--- /dev/null
+++ b/extensions/libebt_pkttype.txlate
@@ -0,0 +1,20 @@
+ebtables-translate -A INPUT --pkttype-type host
+nft add rule bridge filter INPUT meta pkttype host counter
+
+ebtables-translate -A INPUT ! --pkttype-type broadcast
+nft add rule bridge filter INPUT meta pkttype != broadcast counter
+
+ebtables-translate -A INPUT --pkttype-type ! multicast
+nft add rule bridge filter INPUT meta pkttype != multicast counter
+
+ebtables-translate -A INPUT --pkttype-type otherhost
+nft add rule bridge filter INPUT meta pkttype other counter
+
+ebtables-translate -A INPUT --pkttype-type outgoing
+nft add rule bridge filter INPUT meta pkttype 4 counter
+
+ebtables-translate -A INPUT --pkttype-type loopback
+nft add rule bridge filter INPUT meta pkttype 5 counter
+
+ebtables-translate -A INPUT --pkttype-type fastroute
+nft add rule bridge filter INPUT meta pkttype 6 counter
diff --git a/extensions/libebt_redirect.c b/extensions/libebt_redirect.c
new file mode 100644
index 0000000..6e65399
--- /dev/null
+++ b/extensions/libebt_redirect.c
@@ -0,0 +1,109 @@
+/* ebt_redirect
+ *
+ * Authors:
+ * Bart De Schuymer <bdschuym@pandora.be>
+ *
+ * April, 2002
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <xtables.h>
+#include <linux/netfilter_bridge/ebt_redirect.h>
+#include "iptables/nft.h"
+#include "iptables/nft-bridge.h"
+
+#define REDIRECT_TARGET '1'
+static const struct option brredir_opts[] =
+{
+	{ "redirect-target", required_argument, 0, REDIRECT_TARGET },
+	{ 0 }
+};
+
+static void brredir_print_help(void)
+{
+	printf(
+	"redirect option:\n"
+	" --redirect-target target   : ACCEPT, DROP, RETURN or CONTINUE\n");
+}
+
+static void brredir_init(struct xt_entry_target *target)
+{
+	struct ebt_redirect_info *redirectinfo =
+	   (struct ebt_redirect_info *)target->data;
+
+	redirectinfo->target = EBT_ACCEPT;
+}
+
+#define OPT_REDIRECT_TARGET  0x01
+static int brredir_parse(int c, char **argv, int invert, unsigned int *flags,
+			 const void *entry, struct xt_entry_target **target)
+{
+	struct ebt_redirect_info *redirectinfo =
+	   (struct ebt_redirect_info *)(*target)->data;
+
+	switch (c) {
+	case REDIRECT_TARGET:
+		EBT_CHECK_OPTION(flags, OPT_REDIRECT_TARGET);
+		if (ebt_fill_target(optarg, (unsigned int *)&redirectinfo->target))
+			xtables_error(PARAMETER_PROBLEM, "Illegal --redirect-target target");
+		break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static void brredir_print(const void *ip, const struct xt_entry_target *target, int numeric)
+{
+	struct ebt_redirect_info *redirectinfo =
+	   (struct ebt_redirect_info *)target->data;
+
+	if (redirectinfo->target == EBT_ACCEPT)
+		return;
+	printf("--redirect-target %s", ebt_target_name(redirectinfo->target));
+}
+
+static const char* brredir_verdict(int verdict)
+{
+	switch (verdict) {
+	case EBT_ACCEPT: return "accept";
+	case EBT_DROP: return "drop";
+	case EBT_CONTINUE: return "continue";
+	case EBT_RETURN: return "return";
+	}
+
+	return "";
+}
+
+static int brredir_xlate(struct xt_xlate *xl,
+			 const struct xt_xlate_tg_params *params)
+{
+	const struct ebt_redirect_info *red = (const void*)params->target->data;
+
+	xt_xlate_add(xl, "meta set pkttype host");
+	if (red->target != EBT_ACCEPT)
+		xt_xlate_add(xl, " %s ", brredir_verdict(red->target));
+	return 0;
+}
+
+static struct xtables_target brredirect_target = {
+	.name		= "redirect",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size		= XT_ALIGN(sizeof(struct ebt_redirect_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_redirect_info)),
+	.help		= brredir_print_help,
+	.init		= brredir_init,
+	.parse		= brredir_parse,
+	.print		= brredir_print,
+	.xlate		= brredir_xlate,
+	.extra_opts	= brredir_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&brredirect_target);
+}
diff --git a/extensions/libebt_redirect.t b/extensions/libebt_redirect.t
new file mode 100644
index 0000000..23858af
--- /dev/null
+++ b/extensions/libebt_redirect.t
@@ -0,0 +1,4 @@
+:PREROUTING
+*nat
+-j redirect;=;OK
+-j redirect --redirect-target RETURN;=;OK
diff --git a/extensions/libebt_snat.c b/extensions/libebt_snat.c
new file mode 100644
index 0000000..c1124bf
--- /dev/null
+++ b/extensions/libebt_snat.c
@@ -0,0 +1,146 @@
+/* ebt_nat
+ *
+ * Authors:
+ * Bart De Schuymer <bdschuym@pandora.be>
+ *
+ * June, 2002
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <netinet/ether.h>
+#include <xtables.h>
+#include <linux/netfilter_bridge/ebt_nat.h>
+#include "iptables/nft.h"
+#include "iptables/nft-bridge.h"
+
+#define NAT_S '1'
+#define NAT_S_TARGET '2'
+#define NAT_S_ARP '3'
+static const struct option brsnat_opts[] =
+{
+	{ "to-source"     , required_argument, 0, NAT_S },
+	{ "to-src"        , required_argument, 0, NAT_S },
+	{ "snat-target"   , required_argument, 0, NAT_S_TARGET },
+	{ "snat-arp"      ,       no_argument, 0, NAT_S_ARP },
+	{ 0 }
+};
+
+static void brsnat_print_help(void)
+{
+	printf(
+	"snat options:\n"
+	" --to-src address       : MAC address to map source to\n"
+	" --snat-target target   : ACCEPT, DROP, RETURN or CONTINUE\n"
+	" --snat-arp             : also change src address in arp msg\n");
+}
+
+static void brsnat_init(struct xt_entry_target *target)
+{
+	struct ebt_nat_info *natinfo = (struct ebt_nat_info *)target->data;
+
+	natinfo->target = EBT_ACCEPT;
+}
+
+#define OPT_SNAT         0x01
+#define OPT_SNAT_TARGET  0x02
+#define OPT_SNAT_ARP     0x04
+static int brsnat_parse(int c, char **argv, int invert, unsigned int *flags,
+			 const void *entry, struct xt_entry_target **target)
+{
+	struct ebt_nat_info *natinfo = (struct ebt_nat_info *)(*target)->data;
+	struct ether_addr *addr;
+
+	switch (c) {
+	case NAT_S:
+		EBT_CHECK_OPTION(flags, OPT_SNAT);
+		if (!(addr = ether_aton(optarg)))
+			xtables_error(PARAMETER_PROBLEM, "Problem with specified --to-source mac");
+		memcpy(natinfo->mac, addr, ETH_ALEN);
+		break;
+	case NAT_S_TARGET:
+		{ unsigned int tmp;
+		EBT_CHECK_OPTION(flags, OPT_SNAT_TARGET);
+		if (ebt_fill_target(optarg, &tmp))
+			xtables_error(PARAMETER_PROBLEM, "Illegal --snat-target target");
+		natinfo->target = (natinfo->target & ~EBT_VERDICT_BITS) | (tmp & EBT_VERDICT_BITS);
+		}
+		break;
+	case NAT_S_ARP:
+		EBT_CHECK_OPTION(flags, OPT_SNAT_ARP);
+		natinfo->target ^= NAT_ARP_BIT;
+		break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static void brsnat_final_check(unsigned int flags)
+{
+	if (!flags)
+		xtables_error(PARAMETER_PROBLEM,
+			      "You must specify proper arguments");
+}
+
+static void brsnat_print(const void *ip, const struct xt_entry_target *target, int numeric)
+{
+	struct ebt_nat_info *natinfo = (struct ebt_nat_info *)target->data;
+
+	printf("--to-src ");
+	xtables_print_mac(natinfo->mac);
+	if (!(natinfo->target&NAT_ARP_BIT))
+		printf(" --snat-arp");
+	printf(" --snat-target %s", ebt_target_name((natinfo->target|~EBT_VERDICT_BITS)));
+}
+
+static const char* brsnat_verdict(int verdict)
+{
+	switch (verdict) {
+	case EBT_ACCEPT: return "accept";
+	case EBT_DROP: return "drop";
+	case EBT_CONTINUE: return "continue";
+	case EBT_RETURN: return "return";
+	}
+
+	return "";
+}
+
+static int brsnat_xlate(struct xt_xlate *xl,
+			 const struct xt_xlate_tg_params *params)
+{
+	const struct ebt_nat_info *natinfo = (const void*)params->target->data;
+
+	xt_xlate_add(xl, "ether saddr set %s ",
+		     ether_ntoa((struct ether_addr *)natinfo->mac));
+
+	/* NAT_ARP_BIT set -> no arp mangling, not set -> arp mangling (yes, its inverted) */
+	if (!(natinfo->target&NAT_ARP_BIT))
+		return 0;
+
+	xt_xlate_add(xl, "%s ", brsnat_verdict(natinfo->target | ~EBT_VERDICT_BITS));
+	return 1;
+}
+
+static struct xtables_target brsnat_target =
+{
+	.name		= "snat",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size           = XT_ALIGN(sizeof(struct ebt_nat_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_nat_info)),
+	.help		= brsnat_print_help,
+	.init		= brsnat_init,
+	.parse		= brsnat_parse,
+	.final_check	= brsnat_final_check,
+	.print		= brsnat_print,
+	.xlate		= brsnat_xlate,
+	.extra_opts	= brsnat_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&brsnat_target);
+}
diff --git a/extensions/libebt_snat.t b/extensions/libebt_snat.t
new file mode 100644
index 0000000..639b13f
--- /dev/null
+++ b/extensions/libebt_snat.t
@@ -0,0 +1,4 @@
+:POSTROUTING
+*nat
+-o someport -j snat --to-source a:b:c:d:e:f;-o someport -j snat --to-src 0a:0b:0c:0d:0e:0f --snat-target ACCEPT;OK
+-o someport+ -j snat --to-src de:ad:00:be:ee:ff --snat-target CONTINUE;=;OK
diff --git a/extensions/libebt_snat.txlate b/extensions/libebt_snat.txlate
new file mode 100644
index 0000000..0d84602
--- /dev/null
+++ b/extensions/libebt_snat.txlate
@@ -0,0 +1,5 @@
+ebtables-translate -t nat -A POSTROUTING -s 0:0:0:0:0:0 -o someport+ --to-source de:ad:00:be:ee:ff
+nft add rule bridge nat POSTROUTING oifname "someport*" ether saddr 00:00:00:00:00:00 ether saddr set de:ad:0:be:ee:ff accept counter
+
+ebtables-translate -t nat -A POSTROUTING -o someport --to-src de:ad:00:be:ee:ff --snat-target CONTINUE
+nft add rule bridge nat POSTROUTING oifname "someport" ether saddr set de:ad:0:be:ee:ff continue counter
diff --git a/extensions/libebt_standard.t b/extensions/libebt_standard.t
new file mode 100644
index 0000000..c6c3172
--- /dev/null
+++ b/extensions/libebt_standard.t
@@ -0,0 +1,28 @@
+:INPUT,FORWARD,OUTPUT
+-d de:ad:be:ef:00:00;=;OK
+-s 0:0:0:0:0:0;-s 00:00:00:00:00:00;OK
+-d 00:00:00:00:00:00;=;OK
+-s de:ad:be:ef:0:00 -j RETURN;-s de:ad:be:ef:00:00 -j RETURN;OK
+-d de:ad:be:ef:00:00 -j CONTINUE;=;OK
+-d de:ad:be:ef:0:00/ff:ff:ff:ff:0:0 -j DROP;-d de:ad:be:ef:00:00/ff:ff:ff:ff:00:00 -j DROP;OK
+-p ARP -j ACCEPT;=;OK
+-p ! ARP -j ACCEPT;=;OK
+-p 0 -j ACCEPT;=;FAIL
+-p ! 0 -j ACCEPT;=;FAIL
+:INPUT
+-i foobar;=;OK
+-o foobar;=;FAIL
+:FORWARD
+-i foobar;=;OK
+-o foobar;=;OK
+:OUTPUT
+-i foobar;=;FAIL
+-o foobar;=;OK
+:PREROUTING
+*nat
+-i foobar;=;OK
+-o foobar;=;FAIL
+:POSTROUTING
+*nat
+-i foobar;=;FAIL
+-o foobar;=;OK
diff --git a/extensions/libebt_stp.c b/extensions/libebt_stp.c
new file mode 100644
index 0000000..81ba572
--- /dev/null
+++ b/extensions/libebt_stp.c
@@ -0,0 +1,318 @@
+/* ebt_stp
+ *
+ * Authors:
+ * Bart De Schuymer <bdschuym@pandora.be>
+ *
+ * July, 2003
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <netinet/ether.h>
+#include <linux/netfilter_bridge/ebt_stp.h>
+#include <xtables.h>
+
+#include "iptables/nft.h"
+#include "iptables/nft-bridge.h"
+
+#define STP_TYPE	'a'
+#define STP_FLAGS	'b'
+#define STP_ROOTPRIO	'c'
+#define STP_ROOTADDR	'd'
+#define STP_ROOTCOST	'e'
+#define STP_SENDERPRIO	'f'
+#define STP_SENDERADDR	'g'
+#define STP_PORT	'h'
+#define STP_MSGAGE	'i'
+#define STP_MAXAGE	'j'
+#define STP_HELLOTIME	'k'
+#define STP_FWDD	'l'
+#define STP_NUMOPS 12
+
+static const struct option brstp_opts[] =
+{
+	{ "stp-type"         , required_argument, 0, STP_TYPE},
+	{ "stp-flags"        , required_argument, 0, STP_FLAGS},
+	{ "stp-root-prio"    , required_argument, 0, STP_ROOTPRIO},
+	{ "stp-root-addr"    , required_argument, 0, STP_ROOTADDR},
+	{ "stp-root-cost"    , required_argument, 0, STP_ROOTCOST},
+	{ "stp-sender-prio"  , required_argument, 0, STP_SENDERPRIO},
+	{ "stp-sender-addr"  , required_argument, 0, STP_SENDERADDR},
+	{ "stp-port"         , required_argument, 0, STP_PORT},
+	{ "stp-msg-age"      , required_argument, 0, STP_MSGAGE},
+	{ "stp-max-age"      , required_argument, 0, STP_MAXAGE},
+	{ "stp-hello-time"   , required_argument, 0, STP_HELLOTIME},
+	{ "stp-forward-delay", required_argument, 0, STP_FWDD},
+	{ 0 }
+};
+
+#define BPDU_TYPE_CONFIG 0
+#define BPDU_TYPE_TCN 0x80
+#define BPDU_TYPE_CONFIG_STRING "config"
+#define BPDU_TYPE_TCN_STRING "tcn"
+
+#define FLAG_TC 0x01
+#define FLAG_TC_ACK 0x80
+#define FLAG_TC_STRING "topology-change"
+#define FLAG_TC_ACK_STRING "topology-change-ack"
+
+static void brstp_print_help(void)
+{
+	printf(
+"stp options:\n"
+"--stp-type type                  : BPDU type\n"
+"--stp-flags flag                 : control flag\n"
+"--stp-root-prio prio[:prio]      : root priority (16-bit) range\n"
+"--stp-root-addr address[/mask]   : MAC address of root\n"
+"--stp-root-cost cost[:cost]      : root cost (32-bit) range\n"
+"--stp-sender-prio prio[:prio]    : sender priority (16-bit) range\n"
+"--stp-sender-addr address[/mask] : MAC address of sender\n"
+"--stp-port port[:port]           : port id (16-bit) range\n"
+"--stp-msg-age age[:age]          : message age timer (16-bit) range\n"
+"--stp-max-age age[:age]          : maximum age timer (16-bit) range\n"
+"--stp-hello-time time[:time]     : hello time timer (16-bit) range\n"
+"--stp-forward-delay delay[:delay]: forward delay timer (16-bit) range\n"
+" Recognized BPDU type strings:\n"
+"   \"config\": configuration BPDU (=0)\n"
+"   \"tcn\"   : topology change notification BPDU (=0x80)\n"
+" Recognized control flag strings:\n"
+"   \"topology-change\"    : topology change flag (0x01)\n"
+"   \"topology-change-ack\": topology change acknowledgement flag (0x80)");
+}
+
+static int parse_range(const char *portstring, void *lower, void *upper,
+   int bits, uint32_t min, uint32_t max)
+{
+	char *buffer;
+	char *cp, *end;
+	uint32_t low_nr, upp_nr;
+	int ret = 0;
+
+	buffer = strdup(portstring);
+	if ((cp = strchr(buffer, ':')) == NULL) {
+		low_nr = strtoul(buffer, &end, 10);
+		if (*end || low_nr < min || low_nr > max) {
+			ret = -1;
+			goto out;
+		}
+		if (bits == 2) {
+			*(uint16_t *)lower =  low_nr;
+			*(uint16_t *)upper =  low_nr;
+		} else {
+			*(uint32_t *)lower =  low_nr;
+			*(uint32_t *)upper =  low_nr;
+		}
+	} else {
+		*cp = '\0';
+		cp++;
+		if (!*buffer)
+			low_nr = min;
+		else {
+			low_nr = strtoul(buffer, &end, 10);
+			if (*end || low_nr < min) {
+				ret = -1;
+				goto out;
+			}
+		}
+		if (!*cp)
+			upp_nr = max;
+		else {
+			upp_nr = strtoul(cp, &end, 10);
+			if (*end || upp_nr > max) {
+				ret = -1;
+				goto out;
+			}
+		}
+		if (upp_nr < low_nr) {
+			ret = -1;
+			goto out;
+		}
+		if (bits == 2) {
+			*(uint16_t *)lower = low_nr;
+			*(uint16_t *)upper = upp_nr;
+		} else {
+			*(uint32_t *)lower = low_nr;
+			*(uint32_t *)upper = upp_nr;
+		}
+	}
+out:
+	free(buffer);
+	return ret;
+}
+
+static void print_range(unsigned int l, unsigned int u)
+{
+	if (l == u)
+		printf("%u ", l);
+	else
+		printf("%u:%u ", l, u);
+}
+
+static int
+brstp_parse(int c, char **argv, int invert, unsigned int *flags,
+	    const void *entry, struct xt_entry_match **match)
+{
+	struct ebt_stp_info *stpinfo = (struct ebt_stp_info *)(*match)->data;
+	unsigned int flag;
+	long int i;
+	char *end = NULL;
+
+	if (c < 'a' || c > ('a' + STP_NUMOPS - 1))
+		return 0;
+	flag = 1 << (c - 'a');
+	EBT_CHECK_OPTION(flags, flag);
+	if (invert)
+		stpinfo->invflags |= flag;
+	stpinfo->bitmask |= flag;
+	switch (flag) {
+	case EBT_STP_TYPE:
+		i = strtol(optarg, &end, 0);
+		if (i < 0 || i > 255 || *end != '\0') {
+			if (!strcasecmp(optarg, BPDU_TYPE_CONFIG_STRING))
+				stpinfo->type = BPDU_TYPE_CONFIG;
+			else if (!strcasecmp(optarg, BPDU_TYPE_TCN_STRING))
+				stpinfo->type = BPDU_TYPE_TCN;
+			else
+				xtables_error(PARAMETER_PROBLEM, "Bad --stp-type argument");
+		} else
+			stpinfo->type = i;
+		break;
+	case EBT_STP_FLAGS:
+		i = strtol(optarg, &end, 0);
+		if (i < 0 || i > 255 || *end != '\0') {
+			if (!strcasecmp(optarg, FLAG_TC_STRING))
+				stpinfo->config.flags = FLAG_TC;
+			else if (!strcasecmp(optarg, FLAG_TC_ACK_STRING))
+				stpinfo->config.flags = FLAG_TC_ACK;
+			else
+				xtables_error(PARAMETER_PROBLEM, "Bad --stp-flags argument");
+		} else
+			stpinfo->config.flags = i;
+		break;
+	case EBT_STP_ROOTPRIO:
+		if (parse_range(argv[optind-1], &(stpinfo->config.root_priol),
+		    &(stpinfo->config.root_priou), 2, 0, 0xffff))
+			xtables_error(PARAMETER_PROBLEM, "Bad --stp-root-prio range");
+		break;
+	case EBT_STP_ROOTCOST:
+		if (parse_range(argv[optind-1], &(stpinfo->config.root_costl),
+		    &(stpinfo->config.root_costu), 4, 0, 0xffffffff))
+			xtables_error(PARAMETER_PROBLEM, "Bad --stp-root-cost range");
+		break;
+	case EBT_STP_SENDERPRIO:
+		if (parse_range(argv[optind-1], &(stpinfo->config.sender_priol),
+		    &(stpinfo->config.sender_priou), 2, 0, 0xffff))
+			xtables_error(PARAMETER_PROBLEM, "Bad --stp-sender-prio range");
+		break;
+	case EBT_STP_PORT:
+		if (parse_range(argv[optind-1], &(stpinfo->config.portl),
+		    &(stpinfo->config.portu), 2, 0, 0xffff))
+			xtables_error(PARAMETER_PROBLEM, "Bad --stp-port-range");
+		break;
+	case EBT_STP_MSGAGE:
+		if (parse_range(argv[optind-1], &(stpinfo->config.msg_agel),
+		    &(stpinfo->config.msg_ageu), 2, 0, 0xffff))
+			xtables_error(PARAMETER_PROBLEM, "Bad --stp-msg-age range");
+		break;
+	case EBT_STP_MAXAGE:
+		if (parse_range(argv[optind-1], &(stpinfo->config.max_agel),
+		    &(stpinfo->config.max_ageu), 2, 0, 0xffff))
+			xtables_error(PARAMETER_PROBLEM, "Bad --stp-max-age range");
+		break;
+	case EBT_STP_HELLOTIME:
+		if (parse_range(argv[optind-1], &(stpinfo->config.hello_timel),
+		    &(stpinfo->config.hello_timeu), 2, 0, 0xffff))
+			xtables_error(PARAMETER_PROBLEM, "Bad --stp-hello-time range");
+		break;
+	case EBT_STP_FWDD:
+		if (parse_range(argv[optind-1], &(stpinfo->config.forward_delayl),
+		    &(stpinfo->config.forward_delayu), 2, 0, 0xffff))
+			xtables_error(PARAMETER_PROBLEM, "Bad --stp-forward-delay range");
+		break;
+	case EBT_STP_ROOTADDR:
+		if (xtables_parse_mac_and_mask(argv[optind-1],
+					       stpinfo->config.root_addr,
+					       stpinfo->config.root_addrmsk))
+			xtables_error(PARAMETER_PROBLEM, "Bad --stp-root-addr address");
+		break;
+	case EBT_STP_SENDERADDR:
+		if (xtables_parse_mac_and_mask(argv[optind-1],
+					       stpinfo->config.sender_addr,
+					       stpinfo->config.sender_addrmsk))
+			xtables_error(PARAMETER_PROBLEM, "Bad --stp-sender-addr address");
+		break;
+	default:
+		xtables_error(PARAMETER_PROBLEM, "Unknown stp option");
+	}
+	return 1;
+}
+
+static void brstp_print(const void *ip, const struct xt_entry_match *match,
+			 int numeric)
+{
+	const struct ebt_stp_info *stpinfo = (struct ebt_stp_info *)match->data;
+	const struct ebt_stp_config_info *c = &(stpinfo->config);
+	int i;
+
+	for (i = 0; i < STP_NUMOPS; i++) {
+		if (!(stpinfo->bitmask & (1 << i)))
+			continue;
+		printf("--%s %s", brstp_opts[i].name,
+		       (stpinfo->invflags & (1 << i)) ? "! " : "");
+		if (EBT_STP_TYPE == (1 << i)) {
+			if (stpinfo->type == BPDU_TYPE_CONFIG)
+				printf("%s", BPDU_TYPE_CONFIG_STRING);
+			else if (stpinfo->type == BPDU_TYPE_TCN)
+				printf("%s", BPDU_TYPE_TCN_STRING);
+			else
+				printf("%d", stpinfo->type);
+		} else if (EBT_STP_FLAGS == (1 << i)) {
+			if (c->flags == FLAG_TC)
+				printf("%s", FLAG_TC_STRING);
+			else if (c->flags == FLAG_TC_ACK)
+				printf("%s", FLAG_TC_ACK_STRING);
+			else
+				printf("%d", c->flags);
+		} else if (EBT_STP_ROOTPRIO == (1 << i))
+			print_range(c->root_priol, c->root_priou);
+		else if (EBT_STP_ROOTADDR == (1 << i))
+			xtables_print_mac_and_mask((unsigned char *)c->root_addr,
+			   (unsigned char*)c->root_addrmsk);
+		else if (EBT_STP_ROOTCOST == (1 << i))
+			print_range(c->root_costl, c->root_costu);
+		else if (EBT_STP_SENDERPRIO == (1 << i))
+			print_range(c->sender_priol, c->sender_priou);
+		else if (EBT_STP_SENDERADDR == (1 << i))
+			xtables_print_mac_and_mask((unsigned char *)c->sender_addr,
+			   (unsigned char *)c->sender_addrmsk);
+		else if (EBT_STP_PORT == (1 << i))
+			print_range(c->portl, c->portu);
+		else if (EBT_STP_MSGAGE == (1 << i))
+			print_range(c->msg_agel, c->msg_ageu);
+		else if (EBT_STP_MAXAGE == (1 << i))
+			print_range(c->max_agel, c->max_ageu);
+		else if (EBT_STP_HELLOTIME == (1 << i))
+			print_range(c->hello_timel, c->hello_timeu);
+		else if (EBT_STP_FWDD == (1 << i))
+			print_range(c->forward_delayl, c->forward_delayu);
+		printf(" ");
+	}
+}
+
+static struct xtables_match brstp_match = {
+	.name		= "stp",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size		= sizeof(struct ebt_stp_info),
+	.help		= brstp_print_help,
+	.parse		= brstp_parse,
+	.print		= brstp_print,
+	.extra_opts	= brstp_opts,
+};
+
+void _init(void)
+{
+	xtables_register_match(&brstp_match);
+}
diff --git a/extensions/libebt_stp.t b/extensions/libebt_stp.t
new file mode 100644
index 0000000..0c6b77b
--- /dev/null
+++ b/extensions/libebt_stp.t
@@ -0,0 +1,13 @@
+:INPUT,FORWARD,OUTPUT
+--stp-type 1;=;OK
+--stp-flags 0x1;--stp-flags topology-change -j CONTINUE;OK
+--stp-root-prio 1  -j ACCEPT;=;OK
+--stp-root-addr 0d:ea:d0:0b:ee:f0;=;OK
+--stp-root-cost 1;=;OK
+--stp-sender-prio 1;=;OK
+--stp-sender-addr de:ad:be:ef:00:00;=;OK
+--stp-port 1;=;OK
+--stp-msg-age 1;=;OK
+--stp-max-age 1;=;OK
+--stp-hello-time 1;=;OK
+--stp-forward-delay 1;=;OK
diff --git a/extensions/libebt_vlan.c b/extensions/libebt_vlan.c
new file mode 100644
index 0000000..fa69792
--- /dev/null
+++ b/extensions/libebt_vlan.c
@@ -0,0 +1,156 @@
+/* ebt_vlan
+ *
+ * Authors:
+ * Bart De Schuymer <bdschuym@pandora.be>
+ * Nick Fedchik <nick@fedchik.org.ua>
+ * June, 2002
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <ctype.h>
+#include <xtables.h>
+#include <netinet/if_ether.h>
+#include <linux/netfilter_bridge/ebt_vlan.h>
+#include <linux/if_ether.h>
+#include "iptables/nft.h"
+#include "iptables/nft-bridge.h"
+
+#define NAME_VLAN_ID    "id"
+#define NAME_VLAN_PRIO  "prio"
+#define NAME_VLAN_ENCAP "encap"
+
+#define VLAN_ID    '1'
+#define VLAN_PRIO  '2'
+#define VLAN_ENCAP '3'
+
+static const struct option brvlan_opts[] = {
+	{"vlan-id"   , required_argument, NULL, VLAN_ID},
+	{"vlan-prio" , required_argument, NULL, VLAN_PRIO},
+	{"vlan-encap", required_argument, NULL, VLAN_ENCAP},
+	XT_GETOPT_TABLEEND,
+};
+
+/*
+ * option inverse flags definition
+ */
+#define OPT_VLAN_ID     0x01
+#define OPT_VLAN_PRIO   0x02
+#define OPT_VLAN_ENCAP  0x04
+#define OPT_VLAN_FLAGS	(OPT_VLAN_ID | OPT_VLAN_PRIO | OPT_VLAN_ENCAP)
+
+static void brvlan_print_help(void)
+{
+	printf(
+"vlan options:\n"
+"--vlan-id [!] id       : vlan-tagged frame identifier, 0,1-4096 (integer)\n"
+"--vlan-prio [!] prio   : Priority-tagged frame's user priority, 0-7 (integer)\n"
+"--vlan-encap [!] encap : Encapsulated frame protocol (hexadecimal or name)\n");
+}
+
+static int
+brvlan_parse(int c, char **argv, int invert, unsigned int *flags,
+	       const void *entry, struct xt_entry_match **match)
+{
+	struct ebt_vlan_info *vlaninfo = (struct ebt_vlan_info *) (*match)->data;
+	struct xt_ethertypeent *ethent;
+	char *end;
+	struct ebt_vlan_info local;
+
+	switch (c) {
+	case VLAN_ID:
+		EBT_CHECK_OPTION(flags, OPT_VLAN_ID);
+		if (invert)
+			vlaninfo->invflags |= EBT_VLAN_ID;
+		local.id = strtoul(optarg, &end, 10);
+		if (local.id > 4094 || *end != '\0')
+			xtables_error(PARAMETER_PROBLEM, "Invalid --vlan-id range ('%s')", optarg);
+		vlaninfo->id = local.id;
+		vlaninfo->bitmask |= EBT_VLAN_ID;
+		break;
+	case VLAN_PRIO:
+		EBT_CHECK_OPTION(flags, OPT_VLAN_PRIO);
+		if (invert)
+			vlaninfo->invflags |= EBT_VLAN_PRIO;
+		local.prio = strtoul(optarg, &end, 10);
+		if (local.prio >= 8 || *end != '\0')
+			xtables_error(PARAMETER_PROBLEM, "Invalid --vlan-prio range ('%s')", optarg);
+		vlaninfo->prio = local.prio;
+		vlaninfo->bitmask |= EBT_VLAN_PRIO;
+		break;
+	case VLAN_ENCAP:
+		EBT_CHECK_OPTION(flags, OPT_VLAN_ENCAP);
+		if (invert)
+			vlaninfo->invflags |= EBT_VLAN_ENCAP;
+		local.encap = strtoul(optarg, &end, 16);
+		if (*end != '\0') {
+			ethent = xtables_getethertypebyname(optarg);
+			if (ethent == NULL)
+				xtables_error(PARAMETER_PROBLEM, "Unknown --vlan-encap value ('%s')", optarg);
+			local.encap = ethent->e_ethertype;
+		}
+		if (local.encap < ETH_ZLEN)
+			xtables_error(PARAMETER_PROBLEM, "Invalid --vlan-encap range ('%s')", optarg);
+		vlaninfo->encap = htons(local.encap);
+		vlaninfo->bitmask |= EBT_VLAN_ENCAP;
+		break;
+	default:
+		return 0;
+
+	}
+	return 1;
+}
+
+static void brvlan_print(const void *ip, const struct xt_entry_match *match,
+			 int numeric)
+{
+	struct ebt_vlan_info *vlaninfo = (struct ebt_vlan_info *) match->data;
+
+	if (vlaninfo->bitmask & EBT_VLAN_ID) {
+		printf("--vlan-id %s%d ", (vlaninfo->invflags & EBT_VLAN_ID) ? "! " : "", vlaninfo->id);
+	}
+	if (vlaninfo->bitmask & EBT_VLAN_PRIO) {
+		printf("--vlan-prio %s%d ", (vlaninfo->invflags & EBT_VLAN_PRIO) ? "! " : "", vlaninfo->prio);
+	}
+	if (vlaninfo->bitmask & EBT_VLAN_ENCAP) {
+		printf("--vlan-encap %s", (vlaninfo->invflags & EBT_VLAN_ENCAP) ? "! " : "");
+		printf("%4.4X ", ntohs(vlaninfo->encap));
+	}
+}
+
+static int brvlan_xlate(struct xt_xlate *xl,
+			const struct xt_xlate_mt_params *params)
+{
+	const struct ebt_vlan_info *vlaninfo = (const void*)params->match->data;
+
+	if (vlaninfo->bitmask & EBT_VLAN_ID)
+		xt_xlate_add(xl, "vlan id %s%d ", (vlaninfo->invflags & EBT_VLAN_ID) ? "!= " : "", vlaninfo->id);
+
+	if (vlaninfo->bitmask & EBT_VLAN_PRIO)
+		xt_xlate_add(xl, "vlan pcp %s%d ", (vlaninfo->invflags & EBT_VLAN_PRIO) ? "!= " : "", vlaninfo->prio);
+
+	if (vlaninfo->bitmask & EBT_VLAN_ENCAP)
+		xt_xlate_add(xl, "vlan type %s0x%4.4x ", (vlaninfo->invflags & EBT_VLAN_ENCAP) ? "!= " : "", ntohs(vlaninfo->encap));
+
+	return 1;
+}
+
+static struct xtables_match brvlan_match = {
+	.name		= "vlan",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_BRIDGE,
+	.size		= XT_ALIGN(sizeof(struct ebt_vlan_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct ebt_vlan_info)),
+	.help		= brvlan_print_help,
+	.parse		= brvlan_parse,
+	.print		= brvlan_print,
+	.xlate		= brvlan_xlate,
+	.extra_opts	= brvlan_opts,
+};
+
+void _init(void)
+{
+	xtables_register_match(&brvlan_match);
+}
diff --git a/extensions/libebt_vlan.t b/extensions/libebt_vlan.t
new file mode 100644
index 0000000..81c7958
--- /dev/null
+++ b/extensions/libebt_vlan.t
@@ -0,0 +1,13 @@
+:INPUT,FORWARD,OUTPUT
+-p 802_1Q --vlan-id 42;=;OK
+-p 802_1Q --vlan-id ! 42;=;OK
+-p 802_1Q --vlan-prio 1;=;OK
+-p 802_1Q --vlan-prio ! 1;=;OK
+-p 802_1Q --vlan-encap ip;-p 802_1Q --vlan-encap 0800 -j CONTINUE;OK
+-p 802_1Q --vlan-encap 0800 ;=;OK
+-p 802_1Q --vlan-encap ! 0800 ;=;OK
+-p 802_1Q --vlan-encap IPv6 ! --vlan-id 1;-p 802_1Q --vlan-id ! 1 --vlan-encap 86DD -j CONTINUE;OK
+-p 802_1Q --vlan-id ! 1 --vlan-encap 86DD;=;OK
+--vlan-encap ip;=;FAIL
+--vlan-id 2;=;FAIL
+--vlan-prio 1;=;FAIL
diff --git a/extensions/libebt_vlan.txlate b/extensions/libebt_vlan.txlate
new file mode 100644
index 0000000..2ab62d5
--- /dev/null
+++ b/extensions/libebt_vlan.txlate
@@ -0,0 +1,11 @@
+ebtables-translate -A INPUT -p 802_1Q --vlan-id 42
+nft add rule bridge filter INPUT vlan id 42 counter
+
+ebtables-translate -A INPUT -p 802_1Q --vlan-prio ! 1
+nft add rule bridge filter INPUT vlan pcp != 1 counter
+
+ebtables-translate -A INPUT -p 802_1Q --vlan-encap ip
+nft add rule bridge filter INPUT vlan type 0x0800 counter
+
+ebtables-translate -A INPUT -p 802_1Q --vlan-encap ipv6 ! --vlan-id 1
+nft add rule bridge filter INPUT vlan id != 1 vlan type 0x86dd counter
diff --git a/extensions/libext.mk b/extensions/libext.mk
new file mode 100644
index 0000000..49bad61
--- /dev/null
+++ b/extensions/libext.mk
@@ -0,0 +1,62 @@
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS:=
+LOCAL_MODULE:=libext$(libext_suffix)
+
+# LOCAL_MODULE_CLASS must be defined before calling $(local-generated-sources-dir)
+#
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+MY_gen := $(call local-intermediates-dir)
+
+# LOCAL_PATH needed because of dirty #include "blabla.c"
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../include/ \
+	$(LOCAL_PATH)/.. \
+	$(MY_gen) \
+	$(LOCAL_PATH)
+
+LOCAL_CFLAGS:=-DNO_SHARED_LIBS=1
+# The $* does not work as expected. It ends up empty. Even with SECONDEXPANSION.
+# LOCAL_CFLAGS+=-D_INIT=lib$*_init
+LOCAL_CFLAGS+=-DXTABLES_INTERNAL
+LOCAL_CFLAGS+=-D_LARGEFILE_SOURCE=1 -D_LARGE_FILES -D_FILE_OFFSET_BITS=64 -D_REENTRANT -DENABLE_IPV4 -DENABLE_IPV6
+# Accommodate arm-eabi-4.4.3 tools that don't set __ANDROID__
+LOCAL_CFLAGS+=-D__ANDROID__
+LOCAL_CFLAGS += $(MY_warnings)
+
+MY_GEN_INITEXT:= $(MY_gen)/initext.c
+$(MY_GEN_INITEXT): MY_initext_func := $(addprefix $(libext_prefix)_,$(libext_build_mod))
+$(MY_GEN_INITEXT): MY_suffix := $(libext_suffix)
+$(MY_GEN_INITEXT):
+	@mkdir -p $(dir $@)
+	@( \
+	echo "" >$@; \
+	for i in $(MY_initext_func); do \
+		echo "extern void lib$${i}_init(void);" >>$@; \
+	done; \
+	echo "void init_extensions$(MY_suffix)(void);" >>$@; \
+	echo "void init_extensions$(MY_suffix)(void)" >>$@; \
+	echo "{" >>$@; \
+	for i in $(MY_initext_func); do \
+		echo " ""lib$${i}_init();" >>$@; \
+	done; \
+	echo "}" >>$@; \
+	);
+
+MY_lib_sources:= \
+	$(patsubst %,$(LOCAL_PATH)/lib$(libext_prefix)_%.c,$(libext_build_mod))
+
+MY_gen_lib_sources:= $(patsubst $(LOCAL_PATH)/%,${MY_gen}/%,${MY_lib_sources})
+
+${MY_gen_lib_sources}: PRIVATE_PATH := $(LOCAL_PATH)
+${MY_gen_lib_sources}: PRIVATE_CUSTOM_TOOL = $(PRIVATE_PATH)/filter_init $(PRIVATE_PATH)/$(notdir $@) > $@
+${MY_gen_lib_sources}: PRIVATE_MODULE := $(LOCAL_MODULE)
+${MY_gen_lib_sources}: PRIVATE_C_INCLUDES := $(LOCAL_C_INCLUDES)
+${MY_gen_lib_sources}: ${MY_gen}/% : $(LOCAL_PATH)/% $(LOCAL_PATH)/filter_init
+	$(transform-generated-source)
+
+$(MY_GEN_INITEXT): $(MY_gen_lib_sources)
+
+LOCAL_GENERATED_SOURCES:= $(MY_GEN_INITEXT) $(MY_gen_lib_sources)
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/extensions/libip6t_DNAT.c b/extensions/libip6t_DNAT.c
new file mode 100644
index 0000000..89c5ceb
--- /dev/null
+++ b/extensions/libip6t_DNAT.c
@@ -0,0 +1,411 @@
+/*
+ * Copyright (c) 2011 Patrick McHardy <kaber@trash.net>
+ *
+ * Based on Rusty Russell's IPv4 DNAT target. Development of IPv6 NAT
+ * funded by Astaro.
+ */
+
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <xtables.h>
+#include <iptables.h>
+#include <limits.h> /* INT_MAX in ip_tables.h */
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter/nf_nat.h>
+
+enum {
+	O_TO_DEST = 0,
+	O_RANDOM,
+	O_PERSISTENT,
+	O_X_TO_DEST,
+	F_TO_DEST   = 1 << O_TO_DEST,
+	F_RANDOM   = 1 << O_RANDOM,
+	F_X_TO_DEST = 1 << O_X_TO_DEST,
+};
+
+static void DNAT_help(void)
+{
+	printf(
+"DNAT target options:\n"
+" --to-destination [<ipaddr>[-<ipaddr>]][:port[-port]]\n"
+"				Address to map destination to.\n"
+"[--random] [--persistent]\n");
+}
+
+static void DNAT_help_v2(void)
+{
+	printf(
+"DNAT target options:\n"
+" --to-destination [<ipaddr>[-<ipaddr>]][:port[-port[/port]]]\n"
+"				Address to map destination to.\n"
+"[--random] [--persistent]\n");
+}
+
+static const struct xt_option_entry DNAT_opts[] = {
+	{.name = "to-destination", .id = O_TO_DEST, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_MULTI},
+	{.name = "random", .id = O_RANDOM, .type = XTTYPE_NONE},
+	{.name = "persistent", .id = O_PERSISTENT, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+
+/* Ranges expected in network order. */
+static void
+parse_to(const char *orig_arg, int portok, struct nf_nat_range2 *range, int rev)
+{
+	char *arg, *start, *end = NULL, *colon = NULL, *dash, *error;
+	const struct in6_addr *ip;
+
+	arg = strdup(orig_arg);
+	if (arg == NULL)
+		xtables_error(RESOURCE_PROBLEM, "strdup");
+
+	start = strchr(arg, '[');
+	if (start == NULL) {
+		start = arg;
+		/* Lets assume one colon is port information. Otherwise its an IPv6 address */
+		colon = strchr(arg, ':');
+		if (colon && strchr(colon+1, ':'))
+			colon = NULL;
+	}
+	else {
+		start++;
+		end = strchr(start, ']');
+		if (end == NULL)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Invalid address format");
+
+		*end = '\0';
+		colon = strchr(end + 1, ':');
+	}
+
+	if (colon) {
+		int port;
+
+		if (!portok)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Need TCP, UDP, SCTP or DCCP with port specification");
+
+		range->flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
+
+		port = atoi(colon+1);
+		if (port <= 0 || port > 65535)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Port `%s' not valid\n", colon+1);
+
+		error = strchr(colon+1, ':');
+		if (error)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Invalid port:port syntax - use dash\n");
+
+		dash = strchr(colon, '-');
+		if (!dash) {
+			range->min_proto.tcp.port
+				= range->max_proto.tcp.port
+				= htons(port);
+		} else {
+			int maxport;
+
+			maxport = atoi(dash + 1);
+			if (maxport <= 0 || maxport > 65535)
+				xtables_error(PARAMETER_PROBLEM,
+					   "Port `%s' not valid\n", dash+1);
+			if (maxport < port)
+				/* People are stupid. */
+				xtables_error(PARAMETER_PROBLEM,
+					   "Port range `%s' funky\n", colon+1);
+			range->min_proto.tcp.port = htons(port);
+			range->max_proto.tcp.port = htons(maxport);
+
+			if (rev >= 2) {
+				char *slash = strchr(dash, '/');
+				if (slash) {
+					int baseport;
+
+					baseport = atoi(slash + 1);
+					if (baseport <= 0 || baseport > 65535)
+						xtables_error(PARAMETER_PROBLEM,
+								 "Port `%s' not valid\n", slash+1);
+					range->flags |= NF_NAT_RANGE_PROTO_OFFSET;
+					range->base_proto.tcp.port = htons(baseport);
+				}
+			}
+		}
+		/* Starts with colon or [] colon? No IP info...*/
+		if (colon == arg || colon == arg+2) {
+			free(arg);
+			return;
+		}
+		*colon = '\0';
+	}
+
+	range->flags |= NF_NAT_RANGE_MAP_IPS;
+	dash = strchr(start, '-');
+	if (colon && dash && dash > colon)
+		dash = NULL;
+
+	if (dash)
+		*dash = '\0';
+
+	ip = xtables_numeric_to_ip6addr(start);
+	if (!ip)
+		xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
+			      start);
+	range->min_addr.in6 = *ip;
+	if (dash) {
+		ip = xtables_numeric_to_ip6addr(dash + 1);
+		if (!ip)
+			xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
+				      dash+1);
+		range->max_addr.in6 = *ip;
+	} else
+		range->max_addr = range->min_addr;
+
+	free(arg);
+	return;
+}
+
+static void _DNAT_parse(struct xt_option_call *cb,
+		struct nf_nat_range2 *range, int rev)
+{
+	const struct ip6t_entry *entry = cb->xt_entry;
+	int portok;
+
+	if (entry->ipv6.proto == IPPROTO_TCP ||
+	    entry->ipv6.proto == IPPROTO_UDP ||
+	    entry->ipv6.proto == IPPROTO_SCTP ||
+	    entry->ipv6.proto == IPPROTO_DCCP ||
+	    entry->ipv6.proto == IPPROTO_ICMP)
+		portok = 1;
+	else
+		portok = 0;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_TO_DEST:
+		if (cb->xflags & F_X_TO_DEST) {
+			xtables_error(PARAMETER_PROBLEM,
+				      "DNAT: Multiple --to-destination not supported");
+		}
+		parse_to(cb->arg, portok, range, rev);
+		cb->xflags |= F_X_TO_DEST;
+		break;
+	case O_PERSISTENT:
+		range->flags |= NF_NAT_RANGE_PERSISTENT;
+		break;
+	}
+}
+
+static void DNAT_parse(struct xt_option_call *cb)
+{
+	struct nf_nat_range *range_v1 = (void *)cb->data;
+	struct nf_nat_range2 range = {};
+
+	memcpy(&range, range_v1, sizeof(*range_v1));
+	_DNAT_parse(cb, &range, 1);
+	memcpy(range_v1, &range, sizeof(*range_v1));
+}
+
+static void DNAT_parse_v2(struct xt_option_call *cb)
+{
+	_DNAT_parse(cb, (struct nf_nat_range2 *)cb->data, 2);
+}
+
+static void _DNAT_fcheck(struct xt_fcheck_call *cb, unsigned int *flags)
+{
+	static const unsigned int f = F_TO_DEST | F_RANDOM;
+
+	if ((cb->xflags & f) == f)
+		*flags |= NF_NAT_RANGE_PROTO_RANDOM;
+}
+
+static void DNAT_fcheck(struct xt_fcheck_call *cb)
+{
+	_DNAT_fcheck(cb, &((struct nf_nat_range *)cb->data)->flags);
+}
+
+static void DNAT_fcheck_v2(struct xt_fcheck_call *cb)
+{
+	_DNAT_fcheck(cb, &((struct nf_nat_range2 *)cb->data)->flags);
+}
+
+static void print_range(const struct nf_nat_range2 *range, int rev)
+{
+	if (range->flags & NF_NAT_RANGE_MAP_IPS) {
+		if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED)
+			printf("[");
+		printf("%s", xtables_ip6addr_to_numeric(&range->min_addr.in6));
+		if (memcmp(&range->min_addr, &range->max_addr,
+			   sizeof(range->min_addr)))
+			printf("-%s", xtables_ip6addr_to_numeric(&range->max_addr.in6));
+		if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED)
+			printf("]");
+	}
+	if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		printf(":");
+		printf("%hu", ntohs(range->min_proto.tcp.port));
+		if (range->max_proto.tcp.port != range->min_proto.tcp.port)
+			printf("-%hu", ntohs(range->max_proto.tcp.port));
+		if (rev >= 2 && (range->flags & NF_NAT_RANGE_PROTO_OFFSET))
+			printf("/%hu", ntohs(range->base_proto.tcp.port));
+	}
+}
+
+static void _DNAT_print(const struct nf_nat_range2 *range, int rev)
+{
+	printf(" to:");
+	print_range(range, rev);
+	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
+		printf(" random");
+	if (range->flags & NF_NAT_RANGE_PERSISTENT)
+		printf(" persistent");
+}
+
+static void DNAT_print(const void *ip, const struct xt_entry_target *target,
+                       int numeric)
+{
+	const struct nf_nat_range *range_v1 = (const void *)target->data;
+	struct nf_nat_range2 range = {};
+
+	memcpy(&range, range_v1, sizeof(*range_v1));
+	_DNAT_print(&range, 1);
+}
+
+static void DNAT_print_v2(const void *ip, const struct xt_entry_target *target,
+                          int numeric)
+{
+	_DNAT_print((const struct nf_nat_range2 *)target->data, 2);
+}
+
+static void _DNAT_save(const struct nf_nat_range2 *range, int rev)
+{
+	printf(" --to-destination ");
+	print_range(range, rev);
+	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
+		printf(" --random");
+	if (range->flags & NF_NAT_RANGE_PERSISTENT)
+		printf(" --persistent");
+}
+
+static void DNAT_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct nf_nat_range *range_v1 = (const void *)target->data;
+	struct nf_nat_range2 range = {};
+
+	memcpy(&range, range_v1, sizeof(*range_v1));
+	_DNAT_save(&range, 1);
+}
+
+static void DNAT_save_v2(const void *ip, const struct xt_entry_target *target)
+{
+	_DNAT_save((const struct nf_nat_range2 *)target->data, 2);
+}
+
+static void print_range_xlate(const struct nf_nat_range2 *range,
+			      struct xt_xlate *xl, int rev)
+{
+	bool proto_specified = range->flags & NF_NAT_RANGE_PROTO_SPECIFIED;
+
+	if (range->flags & NF_NAT_RANGE_MAP_IPS) {
+		xt_xlate_add(xl, "%s%s%s",
+			     proto_specified ? "[" : "",
+			     xtables_ip6addr_to_numeric(&range->min_addr.in6),
+			     proto_specified ? "]" : "");
+
+		if (memcmp(&range->min_addr, &range->max_addr,
+			   sizeof(range->min_addr))) {
+			xt_xlate_add(xl, "-%s%s%s",
+				     proto_specified ? "[" : "",
+				     xtables_ip6addr_to_numeric(&range->max_addr.in6),
+				     proto_specified ? "]" : "");
+		}
+	}
+	if (proto_specified) {
+		xt_xlate_add(xl, ":%hu", ntohs(range->min_proto.tcp.port));
+
+		if (range->max_proto.tcp.port != range->min_proto.tcp.port)
+			xt_xlate_add(xl, "-%hu",
+				   ntohs(range->max_proto.tcp.port));
+	}
+}
+
+static int _DNAT_xlate(struct xt_xlate *xl,
+		      const struct nf_nat_range2 *range, int rev)
+{
+	bool sep_need = false;
+	const char *sep = " ";
+
+	xt_xlate_add(xl, "dnat to ");
+	print_range_xlate(range, xl, rev);
+	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM) {
+		xt_xlate_add(xl, " random");
+		sep_need = true;
+	}
+	if (range->flags & NF_NAT_RANGE_PERSISTENT) {
+		if (sep_need)
+			sep = ",";
+		xt_xlate_add(xl, "%spersistent", sep);
+	}
+
+	return 1;
+}
+
+static int DNAT_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_tg_params *params)
+{
+	const struct nf_nat_range *range_v1 = (const void *)params->target->data;
+	struct nf_nat_range2 range = {};
+
+	memcpy(&range, range_v1, sizeof(*range_v1));
+	_DNAT_xlate(xl, &range, 1);
+
+	return 1;
+}
+
+static int DNAT_xlate_v2(struct xt_xlate *xl,
+		      const struct xt_xlate_tg_params *params)
+{
+	_DNAT_xlate(xl, (const struct nf_nat_range2 *)params->target->data, 2);
+
+	return 1;
+}
+
+static struct xtables_target dnat_tg_reg[] = {
+	{
+		.name		= "DNAT",
+		.version	= XTABLES_VERSION,
+		.family		= NFPROTO_IPV6,
+		.revision	= 1,
+		.size		= XT_ALIGN(sizeof(struct nf_nat_range)),
+		.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_range)),
+		.help		= DNAT_help,
+		.print		= DNAT_print,
+		.save		= DNAT_save,
+		.x6_parse	= DNAT_parse,
+		.x6_fcheck	= DNAT_fcheck,
+		.x6_options	= DNAT_opts,
+		.xlate		= DNAT_xlate,
+	},
+	{
+		.name		= "DNAT",
+		.version	= XTABLES_VERSION,
+		.family		= NFPROTO_IPV6,
+		.revision	= 2,
+		.size		= XT_ALIGN(sizeof(struct nf_nat_range2)),
+		.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_range2)),
+		.help		= DNAT_help_v2,
+		.print		= DNAT_print_v2,
+		.save		= DNAT_save_v2,
+		.x6_parse	= DNAT_parse_v2,
+		.x6_fcheck	= DNAT_fcheck_v2,
+		.x6_options	= DNAT_opts,
+		.xlate		= DNAT_xlate_v2,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_targets(dnat_tg_reg, ARRAY_SIZE(dnat_tg_reg));
+}
diff --git a/extensions/libip6t_DNAT.t b/extensions/libip6t_DNAT.t
new file mode 100644
index 0000000..ec7d61f
--- /dev/null
+++ b/extensions/libip6t_DNAT.t
@@ -0,0 +1,16 @@
+:PREROUTING
+*nat
+-j DNAT --to-destination dead::beef;=;OK
+-j DNAT --to-destination dead::beef-dead::fee7;=;OK
+-j DNAT --to-destination [dead::beef]:1025-65535;;FAIL
+-j DNAT --to-destination [dead::beef] --to-destination [dead::fee7];;FAIL
+-p tcp -j DNAT --to-destination [dead::beef]:1025-65535;=;OK
+-p tcp -j DNAT --to-destination [dead::beef-dead::fee7]:1025-65535;=;OK
+-p tcp -j DNAT --to-destination [dead::beef-dead::fee7]:1025-65536;;FAIL
+-p tcp -j DNAT --to-destination [dead::beef-dead::fee7]:1025-65535 --to-destination [dead::beef-dead::fee8]:1025-65535;;FAIL
+-p tcp -j DNAT --to-destination [dead::beef-dead::fee7]:1000-2000/1000;=;OK
+-p tcp -j DNAT --to-destination [dead::beef-dead::fee7]:1000-2000/3000;=;OK
+-p tcp -j DNAT --to-destination [dead::beef-dead::fee7]:1000-2000/65535;=;OK
+-p tcp -j DNAT --to-destination [dead::beef-dead::fee7]:1000-2000/0;;FAIL
+-p tcp -j DNAT --to-destination [dead::beef-dead::fee7]:1000-2000/65536;;FAIL
+-j DNAT;;FAIL
diff --git a/extensions/libip6t_DNAT.txlate b/extensions/libip6t_DNAT.txlate
new file mode 100644
index 0000000..03c4caf
--- /dev/null
+++ b/extensions/libip6t_DNAT.txlate
@@ -0,0 +1,11 @@
+ip6tables-translate -t nat -A prerouting -i eth1 -p tcp --dport 8080 -j DNAT --to-destination [fec0::1234]:80
+nft add rule ip6 nat prerouting iifname "eth1" tcp dport 8080 counter dnat to [fec0::1234]:80
+
+ip6tables-translate -t nat -A prerouting -p tcp -j DNAT --to-destination [fec0::1234]:1-20
+nft add rule ip6 nat prerouting meta l4proto tcp counter dnat to [fec0::1234]:1-20
+
+ip6tables-translate -t nat -A prerouting -p tcp -j DNAT --to-destination [fec0::1234]:80 --persistent
+nft add rule ip6 nat prerouting meta l4proto tcp counter dnat to [fec0::1234]:80 persistent
+
+ip6tables-translate -t nat -A prerouting -p tcp -j DNAT --to-destination [fec0::1234]:80 --random --persistent
+nft add rule ip6 nat prerouting meta l4proto tcp counter dnat to [fec0::1234]:80 random,persistent
diff --git a/extensions/libip6t_DNPT.c b/extensions/libip6t_DNPT.c
new file mode 100644
index 0000000..d045e44
--- /dev/null
+++ b/extensions/libip6t_DNPT.c
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2012-2013 Patrick McHardy <kaber@trash.net>
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <xtables.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter_ipv6/ip6t_NPT.h>
+
+enum {
+	O_SRC_PFX	= 1 << 0,
+	O_DST_PFX	= 1 << 1,
+};
+
+static const struct xt_option_entry DNPT_options[] = {
+	{ .name = "src-pfx", .id = O_SRC_PFX, .type = XTTYPE_HOSTMASK,
+	  .flags = XTOPT_MAND },
+	{ .name = "dst-pfx", .id = O_DST_PFX, .type = XTTYPE_HOSTMASK,
+	  .flags = XTOPT_MAND },
+	{ }
+};
+
+static void DNPT_help(void)
+{
+	printf("DNPT target options:"
+	       "\n"
+	       " --src-pfx prefix/length\n"
+	       " --dst-pfx prefix/length\n"
+	       "\n");
+}
+
+static void DNPT_parse(struct xt_option_call *cb)
+{
+	struct ip6t_npt_tginfo *npt = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SRC_PFX:
+		npt->src_pfx = cb->val.haddr;
+		npt->src_pfx_len = cb->val.hlen;
+		break;
+	case O_DST_PFX:
+		npt->dst_pfx = cb->val.haddr;
+		npt->dst_pfx_len = cb->val.hlen;
+		break;
+	}
+}
+
+static void DNPT_print(const void *ip, const struct xt_entry_target *target,
+		       int numeric)
+{
+	const struct ip6t_npt_tginfo *npt = (const void *)target->data;
+
+	printf(" DNPT src-pfx %s/%u", xtables_ip6addr_to_numeric(&npt->src_pfx.in6),
+				 npt->src_pfx_len);
+	printf(" dst-pfx %s/%u", xtables_ip6addr_to_numeric(&npt->dst_pfx.in6),
+				 npt->dst_pfx_len);
+}
+
+static void DNPT_save(const void *ip, const struct xt_entry_target *target)
+{
+	static const struct in6_addr zero_addr;
+	const struct ip6t_npt_tginfo *info = (const void *)target->data;
+
+	if (memcmp(&info->src_pfx.in6, &zero_addr, sizeof(zero_addr)) != 0 ||
+	    info->src_pfx_len != 0)
+		printf(" --src-pfx %s/%u",
+		       xtables_ip6addr_to_numeric(&info->src_pfx.in6),
+		       info->src_pfx_len);
+	if (memcmp(&info->dst_pfx.in6, &zero_addr, sizeof(zero_addr)) != 0 ||
+	    info->dst_pfx_len != 0)
+		printf(" --dst-pfx %s/%u",
+		       xtables_ip6addr_to_numeric(&info->dst_pfx.in6),
+		       info->dst_pfx_len);
+}
+
+static struct xtables_target snpt_tg_reg = {
+	.name		= "DNPT",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV6,
+	.size		= XT_ALIGN(sizeof(struct ip6t_npt_tginfo)),
+	.userspacesize	= offsetof(struct ip6t_npt_tginfo, adjustment),
+	.help		= DNPT_help,
+	.x6_parse	= DNPT_parse,
+	.print		= DNPT_print,
+	.save		= DNPT_save,
+	.x6_options	= DNPT_options,
+};
+
+void _init(void)
+{
+	xtables_register_target(&snpt_tg_reg);
+}
diff --git a/extensions/libip6t_DNPT.man b/extensions/libip6t_DNPT.man
new file mode 100644
index 0000000..9b060f5
--- /dev/null
+++ b/extensions/libip6t_DNPT.man
@@ -0,0 +1,30 @@
+Provides stateless destination IPv6-to-IPv6 Network Prefix Translation (as
+described by RFC 6296).
+.PP
+You have to use this target in the
+.B mangle
+table, not in the
+.B nat
+table. It takes the following options:
+.TP
+\fB\-\-src\-pfx\fP [\fIprefix/\fP\fIlength]
+Set source prefix that you want to translate and length
+.TP
+\fB\-\-dst\-pfx\fP [\fIprefix/\fP\fIlength]
+Set destination prefix that you want to use in the translation and length
+.PP
+You have to use the SNPT target to undo the translation. Example:
+.IP
+ip6tables \-t mangle \-I POSTROUTING \-s fd00::/64 \! \-o vboxnet0
+\-j SNPT \-\-src-pfx fd00::/64 \-\-dst-pfx 2001:e20:2000:40f::/64
+.IP
+ip6tables \-t mangle \-I PREROUTING \-i wlan0 \-d 2001:e20:2000:40f::/64
+\-j DNPT \-\-src-pfx 2001:e20:2000:40f::/64 \-\-dst-pfx fd00::/64
+.PP
+You may need to enable IPv6 neighbor proxy:
+.IP
+sysctl \-w net.ipv6.conf.all.proxy_ndp=1
+.PP
+You also have to use the
+.B NOTRACK
+target to disable connection tracking for translated flows.
diff --git a/extensions/libip6t_DNPT.t b/extensions/libip6t_DNPT.t
new file mode 100644
index 0000000..0406dc9
--- /dev/null
+++ b/extensions/libip6t_DNPT.t
@@ -0,0 +1,7 @@
+:PREROUTING
+*mangle
+-j DNPT --src-pfx dead::/64 --dst-pfx 1c3::/64;=;OK
+-j DNPT --src-pfx dead::beef --dst-pfx 1c3::/64;;FAIL
+-j DNPT --src-pfx dead::/64;;FAIL
+-j DNPT --dst-pfx dead::/64;;FAIL
+-j DNPT;;FAIL
diff --git a/extensions/libip6t_HL.c b/extensions/libip6t_HL.c
index 254b191..52ca5d3 100644
--- a/extensions/libip6t_HL.c
+++ b/extensions/libip6t_HL.c
@@ -20,12 +20,12 @@
 
 #define s struct ip6t_HL_info
 static const struct xt_option_entry HL_opts[] = {
-	{.name = "ttl-set", .type = XTTYPE_UINT8, .id = O_HL_SET,
+	{.name = "hl-set", .type = XTTYPE_UINT8, .id = O_HL_SET,
 	 .excl = F_ANY, .flags = XTOPT_PUT, XTOPT_POINTER(s, hop_limit)},
-	{.name = "ttl-dec", .type = XTTYPE_UINT8, .id = O_HL_DEC,
+	{.name = "hl-dec", .type = XTTYPE_UINT8, .id = O_HL_DEC,
 	 .excl = F_ANY, .flags = XTOPT_PUT, XTOPT_POINTER(s, hop_limit),
 	 .min = 1},
-	{.name = "ttl-inc", .type = XTTYPE_UINT8, .id = O_HL_INC,
+	{.name = "hl-inc", .type = XTTYPE_UINT8, .id = O_HL_INC,
 	 .excl = F_ANY, .flags = XTOPT_PUT, XTOPT_POINTER(s, hop_limit),
 	 .min = 1},
 	XTOPT_TABLEEND,
diff --git a/extensions/libip6t_HL.t b/extensions/libip6t_HL.t
new file mode 100644
index 0000000..4e529f8
--- /dev/null
+++ b/extensions/libip6t_HL.t
@@ -0,0 +1,10 @@
+:PREROUTING,INPUT,FORWARD,OUTPUT,POSTROUTING
+*mangle
+-j HL --hl-set 42;=;OK
+-j HL --hl-inc 1;=;OK
+-j HL --hl-dec 1;=;OK
+-j HL --hl-set 256;;FAIL
+-j HL --hl-inc 0;;FAIL
+-j HL --hl-dec 0;;FAIL
+-j HL --hl-dec 1 --hl-inc 1;;FAIL
+-j HL --hl-set --hl-inc 1;;FAIL
diff --git a/extensions/libip6t_LOG.c b/extensions/libip6t_LOG.c
index a419ec9..40adc69 100644
--- a/extensions/libip6t_LOG.c
+++ b/extensions/libip6t_LOG.c
@@ -63,6 +63,11 @@
 	unsigned int level;
 };
 
+struct ip6t_log_xlate {
+	const char *name;
+	unsigned int level;
+};
+
 static const struct ip6t_log_names ip6t_log_names[]
 = { { .name = "alert",   .level = LOG_ALERT },
     { .name = "crit",    .level = LOG_CRIT },
@@ -87,19 +92,19 @@
 				   "Newlines not allowed in --log-prefix");
 		break;
 	case O_LOG_TCPSEQ:
-		info->logflags = IP6T_LOG_TCPSEQ;
+		info->logflags |= IP6T_LOG_TCPSEQ;
 		break;
 	case O_LOG_TCPOPTS:
-		info->logflags = IP6T_LOG_TCPOPT;
+		info->logflags |= IP6T_LOG_TCPOPT;
 		break;
 	case O_LOG_IPOPTS:
-		info->logflags = IP6T_LOG_IPOPT;
+		info->logflags |= IP6T_LOG_IPOPT;
 		break;
 	case O_LOG_UID:
-		info->logflags = IP6T_LOG_UID;
+		info->logflags |= IP6T_LOG_UID;
 		break;
 	case O_LOG_MAC:
-		info->logflags = IP6T_LOG_MACDECODE;
+		info->logflags |= IP6T_LOG_MACDECODE;
 		break;
 	}
 }
@@ -146,8 +151,10 @@
 	const struct ip6t_log_info *loginfo
 		= (const struct ip6t_log_info *)target->data;
 
-	if (strcmp(loginfo->prefix, "") != 0)
-		printf(" --log-prefix \"%s\"", loginfo->prefix);
+	if (strcmp(loginfo->prefix, "") != 0) {
+		printf(" --log-prefix");
+		xtables_save_string(loginfo->prefix);
+	}
 
 	if (loginfo->level != LOG_DEFAULT_LEVEL)
 		printf(" --log-level %d", loginfo->level);
@@ -164,6 +171,64 @@
 		printf(" --log-macdecode");
 }
 
+static const struct ip6t_log_xlate ip6t_log_xlate_names[] = {
+	{"alert",       LOG_ALERT },
+	{"crit",        LOG_CRIT },
+	{"debug",       LOG_DEBUG },
+	{"emerg",       LOG_EMERG },
+	{"err",         LOG_ERR },
+	{"info",        LOG_INFO },
+	{"notice",      LOG_NOTICE },
+	{"warn",        LOG_WARNING }
+};
+
+static int LOG_xlate(struct xt_xlate *xl,
+		     const struct xt_xlate_tg_params *params)
+{
+	const struct ip6t_log_info *loginfo =
+		(const struct ip6t_log_info *)params->target->data;
+	unsigned int i = 0;
+
+	xt_xlate_add(xl, "log");
+	if (strcmp(loginfo->prefix, "") != 0) {
+		if (params->escape_quotes)
+			xt_xlate_add(xl, " prefix \\\"%s\\\"", loginfo->prefix);
+		else
+			xt_xlate_add(xl, " prefix \"%s\"", loginfo->prefix);
+	}
+
+	for (i = 0; i < ARRAY_SIZE(ip6t_log_xlate_names); ++i)
+		if (loginfo->level == ip6t_log_xlate_names[i].level &&
+		    loginfo->level != LOG_DEFAULT_LEVEL) {
+			xt_xlate_add(xl, " level %s",
+				   ip6t_log_xlate_names[i].name);
+			break;
+		}
+
+	if ((loginfo->logflags & IP6T_LOG_MASK) == IP6T_LOG_MASK) {
+		xt_xlate_add(xl, " flags all");
+	} else {
+		if (loginfo->logflags & (IP6T_LOG_TCPSEQ | IP6T_LOG_TCPOPT)) {
+			const char *delim = " ";
+
+			xt_xlate_add(xl, " flags tcp");
+			if (loginfo->logflags & IP6T_LOG_TCPSEQ) {
+				xt_xlate_add(xl, " sequence");
+				delim = ",";
+			}
+			if (loginfo->logflags & IP6T_LOG_TCPOPT)
+				xt_xlate_add(xl, "%soptions", delim);
+		}
+		if (loginfo->logflags & IP6T_LOG_IPOPT)
+			xt_xlate_add(xl, " flags ip options");
+		if (loginfo->logflags & IP6T_LOG_UID)
+			xt_xlate_add(xl, " flags skuid");
+		if (loginfo->logflags & IP6T_LOG_MACDECODE)
+			xt_xlate_add(xl, " flags ether");
+	}
+
+	return 1;
+}
 static struct xtables_target log_tg6_reg = {
 	.name          = "LOG",
 	.version       = XTABLES_VERSION,
@@ -176,6 +241,7 @@
 	.save          = LOG_save,
 	.x6_parse      = LOG_parse,
 	.x6_options    = LOG_opts,
+	.xlate	       = LOG_xlate,
 };
 
 void _init(void)
diff --git a/extensions/libip6t_LOG.man b/extensions/libip6t_LOG.man
deleted file mode 100644
index b7803fe..0000000
--- a/extensions/libip6t_LOG.man
+++ /dev/null
@@ -1,31 +0,0 @@
-Turn on kernel logging of matching packets.  When this option is set
-for a rule, the Linux kernel will print some information on all
-matching packets (like most IPv6 IPv6-header fields) via the kernel log
-(where it can be read with
-.I dmesg
-or 
-.IR syslogd (8)).
-This is a "non-terminating target", i.e. rule traversal continues at
-the next rule.  So if you want to LOG the packets you refuse, use two
-separate rules with the same matching criteria, first using target LOG
-then DROP (or REJECT).
-.TP
-\fB\-\-log\-level\fP \fIlevel\fP
-Level of logging (numeric or see \fIsyslog.conf\fP(5)).
-.TP
-\fB\-\-log\-prefix\fP \fIprefix\fP
-Prefix log messages with the specified prefix; up to 29 letters long,
-and useful for distinguishing messages in the logs.
-.TP
-\fB\-\-log\-tcp\-sequence\fP
-Log TCP sequence numbers. This is a security risk if the log is
-readable by users.
-.TP
-\fB\-\-log\-tcp\-options\fP
-Log options from the TCP packet header.
-.TP
-\fB\-\-log\-ip\-options\fP
-Log options from the IPv6 packet header.
-.TP
-\fB\-\-log\-uid\fP
-Log the userid of the process which generated the packet.
diff --git a/extensions/libip6t_LOG.t b/extensions/libip6t_LOG.t
new file mode 100644
index 0000000..fbf5118
--- /dev/null
+++ b/extensions/libip6t_LOG.t
@@ -0,0 +1,12 @@
+:INPUT,FORWARD,OUTPUT
+-j LOG;-j LOG;OK
+-j LOG --log-prefix "test: ";=;OK
+-j LOG --log-prefix "test: " --log-level 1;=;OK
+# iptables displays the log-level output using the number; not the string
+-j LOG --log-prefix "test: " --log-level alert;-j LOG --log-prefix "test: " --log-level 1;OK
+-j LOG --log-prefix "test: " --log-tcp-sequence;=;OK
+-j LOG --log-prefix "test: " --log-tcp-options;=;OK
+-j LOG --log-prefix "test: " --log-ip-options;=;OK
+-j LOG --log-prefix "test: " --log-uid;=;OK
+-j LOG --log-prefix "test: " --log-level bad;;FAIL
+-j LOG --log-prefix;;FAIL
diff --git a/extensions/libip6t_LOG.txlate b/extensions/libip6t_LOG.txlate
new file mode 100644
index 0000000..2820a82
--- /dev/null
+++ b/extensions/libip6t_LOG.txlate
@@ -0,0 +1,8 @@
+iptables-translate -I INPUT -j LOG
+nft insert rule ip filter INPUT counter log
+
+ip6tables-translate -A FORWARD -p tcp -j LOG --log-level debug
+nft add rule ip6 filter FORWARD meta l4proto tcp counter log level debug
+
+ip6tables-translate -A FORWARD -p tcp -j LOG --log-prefix "Checking log"
+nft add rule ip6 filter FORWARD meta l4proto tcp counter log prefix \"Checking log\"
diff --git a/extensions/libip6t_MASQUERADE.c b/extensions/libip6t_MASQUERADE.c
new file mode 100644
index 0000000..f92760f
--- /dev/null
+++ b/extensions/libip6t_MASQUERADE.c
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2011 Patrick McHardy <kaber@trash.net>
+ *
+ * Based on Rusty Russell's IPv4 MASQUERADE target. Development of IPv6 NAT
+ * funded by Astaro.
+ */
+
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <xtables.h>
+#include <limits.h> /* INT_MAX in ip_tables.h */
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter/nf_nat.h>
+
+enum {
+	O_TO_PORTS = 0,
+	O_RANDOM,
+	O_RANDOM_FULLY,
+};
+
+static void MASQUERADE_help(void)
+{
+	printf(
+"MASQUERADE target options:\n"
+" --to-ports <port>[-<port>]\n"
+"				Port (range) to map to.\n"
+" --random\n"
+"				Randomize source port.\n"
+" --random-fully\n"
+"				Fully randomize source port.\n");
+}
+
+static const struct xt_option_entry MASQUERADE_opts[] = {
+	{.name = "to-ports", .id = O_TO_PORTS, .type = XTTYPE_STRING},
+	{.name = "random", .id = O_RANDOM, .type = XTTYPE_NONE},
+	{.name = "random-fully", .id = O_RANDOM_FULLY, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+
+/* Parses ports */
+static void
+parse_ports(const char *arg, struct nf_nat_range *r)
+{
+	char *end;
+	unsigned int port, maxport;
+
+	r->flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
+
+	if (!xtables_strtoui(arg, &end, &port, 0, UINT16_MAX))
+		xtables_param_act(XTF_BAD_VALUE, "MASQUERADE", "--to-ports", arg);
+
+	switch (*end) {
+	case '\0':
+		r->min_proto.tcp.port
+			= r->max_proto.tcp.port
+			= htons(port);
+		return;
+	case '-':
+		if (!xtables_strtoui(end + 1, NULL, &maxport, 0, UINT16_MAX))
+			break;
+
+		if (maxport < port)
+			break;
+
+		r->min_proto.tcp.port = htons(port);
+		r->max_proto.tcp.port = htons(maxport);
+		return;
+	default:
+		break;
+	}
+	xtables_param_act(XTF_BAD_VALUE, "MASQUERADE", "--to-ports", arg);
+}
+
+static void MASQUERADE_parse(struct xt_option_call *cb)
+{
+	const struct ip6t_entry *entry = cb->xt_entry;
+	struct nf_nat_range *r = cb->data;
+	int portok;
+
+	if (entry->ipv6.proto == IPPROTO_TCP ||
+	    entry->ipv6.proto == IPPROTO_UDP ||
+	    entry->ipv6.proto == IPPROTO_SCTP ||
+	    entry->ipv6.proto == IPPROTO_DCCP ||
+	    entry->ipv6.proto == IPPROTO_ICMP)
+		portok = 1;
+	else
+		portok = 0;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_TO_PORTS:
+		if (!portok)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Need TCP, UDP, SCTP or DCCP with port specification");
+		parse_ports(cb->arg, r);
+		break;
+	case O_RANDOM:
+		r->flags |=  NF_NAT_RANGE_PROTO_RANDOM;
+		break;
+	case O_RANDOM_FULLY:
+		r->flags |=  NF_NAT_RANGE_PROTO_RANDOM_FULLY;
+		break;
+	}
+}
+
+static void
+MASQUERADE_print(const void *ip, const struct xt_entry_target *target,
+                 int numeric)
+{
+	const struct nf_nat_range *r = (const void *)target->data;
+
+	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		printf(" masq ports: ");
+		printf("%hu", ntohs(r->min_proto.tcp.port));
+		if (r->max_proto.tcp.port != r->min_proto.tcp.port)
+			printf("-%hu", ntohs(r->max_proto.tcp.port));
+	}
+
+	if (r->flags & NF_NAT_RANGE_PROTO_RANDOM)
+		printf(" random");
+
+	if (r->flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY)
+		printf(" random-fully");
+}
+
+static void
+MASQUERADE_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct nf_nat_range *r = (const void *)target->data;
+
+	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		printf(" --to-ports %hu", ntohs(r->min_proto.tcp.port));
+		if (r->max_proto.tcp.port != r->min_proto.tcp.port)
+			printf("-%hu", ntohs(r->max_proto.tcp.port));
+	}
+
+	if (r->flags & NF_NAT_RANGE_PROTO_RANDOM)
+		printf(" --random");
+
+	if (r->flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY)
+		printf(" --random-fully");
+}
+
+static int MASQUERADE_xlate(struct xt_xlate *xl,
+			    const struct xt_xlate_tg_params *params)
+{
+	const struct nf_nat_range *r = (const void *)params->target->data;
+
+	xt_xlate_add(xl, "masquerade");
+
+	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		xt_xlate_add(xl, " to :%hu", ntohs(r->min_proto.tcp.port));
+		if (r->max_proto.tcp.port != r->min_proto.tcp.port)
+			xt_xlate_add(xl, "-%hu", ntohs(r->max_proto.tcp.port));
+	}
+
+	xt_xlate_add(xl, " ");
+	if (r->flags & NF_NAT_RANGE_PROTO_RANDOM)
+		xt_xlate_add(xl, "random ");
+
+	xt_xlate_add(xl, " ");
+	if (r->flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY)
+		xt_xlate_add(xl, "random-fully ");
+
+	return 1;
+}
+
+static struct xtables_target masquerade_tg_reg = {
+	.name		= "MASQUERADE",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV6,
+	.size		= XT_ALIGN(sizeof(struct nf_nat_range)),
+	.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_range)),
+	.help		= MASQUERADE_help,
+	.x6_parse	= MASQUERADE_parse,
+	.print		= MASQUERADE_print,
+	.save		= MASQUERADE_save,
+	.x6_options	= MASQUERADE_opts,
+	.xlate		= MASQUERADE_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_target(&masquerade_tg_reg);
+}
diff --git a/extensions/libip6t_MASQUERADE.t b/extensions/libip6t_MASQUERADE.t
new file mode 100644
index 0000000..e25d2a0
--- /dev/null
+++ b/extensions/libip6t_MASQUERADE.t
@@ -0,0 +1,9 @@
+:POSTROUTING
+*nat
+-j MASQUERADE;=;OK
+-j MASQUERADE --random;=;OK
+-j MASQUERADE --random-fully;=;OK
+-p tcp -j MASQUERADE --to-ports 1024;=;OK
+-p udp -j MASQUERADE --to-ports 1024-65535;=;OK
+-p udp -j MASQUERADE --to-ports 1024-65536;;FAIL
+-p udp -j MASQUERADE --to-ports -1;;FAIL
diff --git a/extensions/libip6t_MASQUERADE.txlate b/extensions/libip6t_MASQUERADE.txlate
new file mode 100644
index 0000000..6c289c2
--- /dev/null
+++ b/extensions/libip6t_MASQUERADE.txlate
@@ -0,0 +1,8 @@
+ip6tables-translate -t nat -A POSTROUTING -j MASQUERADE
+nft add rule ip6 nat POSTROUTING counter masquerade
+
+ip6tables-translate -t nat -A POSTROUTING -p tcp -j MASQUERADE --to-ports 10
+nft add rule ip6 nat POSTROUTING meta l4proto tcp counter masquerade to :10
+
+ip6tables-translate -t nat -A POSTROUTING -p tcp -j MASQUERADE --to-ports 10-20 --random
+nft add rule ip6 nat POSTROUTING meta l4proto tcp counter masquerade to :10-20 random
diff --git a/extensions/libip6t_NETMAP.c b/extensions/libip6t_NETMAP.c
new file mode 100644
index 0000000..579ed04
--- /dev/null
+++ b/extensions/libip6t_NETMAP.c
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2011 Patrick McHardy <kaber@trash.net>
+ *
+ * Based on Svenning Soerensen's IPv4 NETMAP target. Development of IPv6 NAT
+ * funded by Astaro.
+ */
+
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <xtables.h>
+#include <libiptc/libip6tc.h>
+#include <linux/netfilter/nf_nat.h>
+
+#define MODULENAME "NETMAP"
+
+enum {
+	O_TO = 0,
+};
+
+static const struct xt_option_entry NETMAP_opts[] = {
+	{.name = "to", .id = O_TO, .type = XTTYPE_HOSTMASK,
+	 .flags = XTOPT_MAND},
+	XTOPT_TABLEEND,
+};
+
+static void NETMAP_help(void)
+{
+	printf(MODULENAME" target options:\n"
+	       "  --%s address[/mask]\n"
+	       "				Network address to map to.\n\n",
+	       NETMAP_opts[0].name);
+}
+
+static void NETMAP_parse(struct xt_option_call *cb)
+{
+	struct nf_nat_range *range = cb->data;
+	unsigned int i;
+
+	xtables_option_parse(cb);
+	range->flags |= NF_NAT_RANGE_MAP_IPS;
+	for (i = 0; i < 4; i++) {
+		range->min_addr.ip6[i] = cb->val.haddr.ip6[i] &
+					 cb->val.hmask.ip6[i];
+		range->max_addr.ip6[i] = range->min_addr.ip6[i] |
+					 ~cb->val.hmask.ip6[i];
+	}
+}
+
+static void __NETMAP_print(const void *ip, const struct xt_entry_target *target,
+                           int numeric)
+{
+	const struct nf_nat_range *r = (const void *)target->data;
+	struct in6_addr a;
+	unsigned int i;
+	int bits;
+
+	a = r->min_addr.in6;
+	printf("%s", xtables_ip6addr_to_numeric(&a));
+	for (i = 0; i < 4; i++)
+		a.s6_addr32[i] = ~(r->min_addr.ip6[i] ^ r->max_addr.ip6[i]);
+	bits = xtables_ip6mask_to_cidr(&a);
+	if (bits < 0)
+		printf("/%s", xtables_ip6addr_to_numeric(&a));
+	else
+		printf("/%d", bits);
+}
+
+static void NETMAP_print(const void *ip, const struct xt_entry_target *target,
+                           int numeric)
+{
+	printf(" to:");
+	__NETMAP_print(ip, target, numeric);
+}
+
+static void NETMAP_save(const void *ip, const struct xt_entry_target *target)
+{
+	printf(" --%s ", NETMAP_opts[0].name);
+	__NETMAP_print(ip, target, 0);
+}
+
+static struct xtables_target netmap_tg_reg = {
+	.name		= MODULENAME,
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV6,
+	.size		= XT_ALIGN(sizeof(struct nf_nat_range)),
+	.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_range)),
+	.help		= NETMAP_help,
+	.x6_parse	= NETMAP_parse,
+	.print		= NETMAP_print,
+	.save		= NETMAP_save,
+	.x6_options	= NETMAP_opts,
+};
+
+void _init(void)
+{
+	xtables_register_target(&netmap_tg_reg);
+}
diff --git a/extensions/libip6t_NETMAP.t b/extensions/libip6t_NETMAP.t
new file mode 100644
index 0000000..043562d
--- /dev/null
+++ b/extensions/libip6t_NETMAP.t
@@ -0,0 +1,4 @@
+:PREROUTING,INPUT,OUTPUT,POSTROUTING
+*nat
+-j NETMAP --to dead::/64;=;OK
+-j NETMAP --to dead::beef;=;OK
diff --git a/extensions/libip6t_REDIRECT.c b/extensions/libip6t_REDIRECT.c
new file mode 100644
index 0000000..8e04d2c
--- /dev/null
+++ b/extensions/libip6t_REDIRECT.c
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2011 Patrick McHardy <kaber@trash.net>
+ *
+ * Based on Rusty Russell's IPv4 REDIRECT target. Development of IPv6 NAT
+ * funded by Astaro.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <xtables.h>
+#include <limits.h> /* INT_MAX in ip_tables.h */
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter/nf_nat.h>
+
+enum {
+	O_TO_PORTS = 0,
+	O_RANDOM,
+	F_TO_PORTS = 1 << O_TO_PORTS,
+	F_RANDOM   = 1 << O_RANDOM,
+};
+
+static void REDIRECT_help(void)
+{
+	printf(
+"REDIRECT target options:\n"
+" --to-ports <port>[-<port>]\n"
+"				Port (range) to map to.\n"
+" [--random]\n");
+}
+
+static const struct xt_option_entry REDIRECT_opts[] = {
+	{.name = "to-ports", .id = O_TO_PORTS, .type = XTTYPE_STRING},
+	{.name = "random", .id = O_RANDOM, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+
+/* Parses ports */
+static void
+parse_ports(const char *arg, struct nf_nat_range *range)
+{
+	char *end = "";
+	unsigned int port, maxport;
+
+	range->flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
+
+	if (!xtables_strtoui(arg, &end, &port, 0, UINT16_MAX) &&
+	    (port = xtables_service_to_port(arg, NULL)) == (unsigned)-1)
+		xtables_param_act(XTF_BAD_VALUE, "REDIRECT", "--to-ports", arg);
+
+	switch (*end) {
+	case '\0':
+		range->min_proto.tcp.port
+			= range->max_proto.tcp.port
+			= htons(port);
+		return;
+	case '-':
+		if (!xtables_strtoui(end + 1, NULL, &maxport, 0, UINT16_MAX) &&
+		    (maxport = xtables_service_to_port(end + 1, NULL)) == (unsigned)-1)
+			break;
+
+		if (maxport < port)
+			break;
+
+		range->min_proto.tcp.port = htons(port);
+		range->max_proto.tcp.port = htons(maxport);
+		return;
+	default:
+		break;
+	}
+	xtables_param_act(XTF_BAD_VALUE, "REDIRECT", "--to-ports", arg);
+}
+
+static void REDIRECT_parse(struct xt_option_call *cb)
+{
+	const struct ip6t_entry *entry = cb->xt_entry;
+	struct nf_nat_range *range = (void *)(*cb->target)->data;
+	int portok;
+
+	if (entry->ipv6.proto == IPPROTO_TCP
+	    || entry->ipv6.proto == IPPROTO_UDP
+	    || entry->ipv6.proto == IPPROTO_SCTP
+	    || entry->ipv6.proto == IPPROTO_DCCP
+	    || entry->ipv6.proto == IPPROTO_ICMP)
+		portok = 1;
+	else
+		portok = 0;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_TO_PORTS:
+		if (!portok)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Need TCP, UDP, SCTP or DCCP with port specification");
+		parse_ports(cb->arg, range);
+		if (cb->xflags & F_RANDOM)
+			range->flags |= NF_NAT_RANGE_PROTO_RANDOM;
+		break;
+	case O_RANDOM:
+		if (cb->xflags & F_TO_PORTS)
+			range->flags |= NF_NAT_RANGE_PROTO_RANDOM;
+		break;
+	}
+}
+
+static void REDIRECT_print(const void *ip, const struct xt_entry_target *target,
+                           int numeric)
+{
+	const struct nf_nat_range *range = (const void *)target->data;
+
+	if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		printf(" redir ports ");
+		printf("%hu", ntohs(range->min_proto.tcp.port));
+		if (range->max_proto.tcp.port != range->min_proto.tcp.port)
+			printf("-%hu", ntohs(range->max_proto.tcp.port));
+		if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
+			printf(" random");
+	}
+}
+
+static void REDIRECT_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct nf_nat_range *range = (const void *)target->data;
+
+	if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		printf(" --to-ports ");
+		printf("%hu", ntohs(range->min_proto.tcp.port));
+		if (range->max_proto.tcp.port != range->min_proto.tcp.port)
+			printf("-%hu", ntohs(range->max_proto.tcp.port));
+		if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
+			printf(" --random");
+	}
+}
+
+static int REDIRECT_xlate(struct xt_xlate *xl,
+			  const struct xt_xlate_tg_params *params)
+{
+	const struct nf_nat_range *range = (const void *)params->target->data;
+
+	if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		xt_xlate_add(xl, "redirect to :%hu",
+			   ntohs(range->min_proto.tcp.port));
+		if (range->max_proto.tcp.port != range->min_proto.tcp.port)
+			xt_xlate_add(xl, "-%hu ",
+				   ntohs(range->max_proto.tcp.port));
+		if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
+			xt_xlate_add(xl, " random ");
+	}
+
+	return 1;
+}
+
+static struct xtables_target redirect_tg_reg = {
+	.name		= "REDIRECT",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV6,
+	.size		= XT_ALIGN(sizeof(struct nf_nat_range)),
+	.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_range)),
+	.help		= REDIRECT_help,
+	.x6_parse	= REDIRECT_parse,
+	.print		= REDIRECT_print,
+	.save		= REDIRECT_save,
+	.x6_options	= REDIRECT_opts,
+	.xlate		= REDIRECT_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_target(&redirect_tg_reg);
+}
diff --git a/extensions/libip6t_REDIRECT.t b/extensions/libip6t_REDIRECT.t
new file mode 100644
index 0000000..a0fb0ed
--- /dev/null
+++ b/extensions/libip6t_REDIRECT.t
@@ -0,0 +1,6 @@
+:PREROUTING,OUTPUT
+*nat
+-p tcp -j REDIRECT --to-ports 42;=;OK
+-p udp -j REDIRECT --to-ports 42-1234;=;OK
+-p tcp -j REDIRECT --to-ports 42-1234 --random;=;OK
+-j REDIRECT --to-ports 42;;FAIL
diff --git a/extensions/libip6t_REDIRECT.txlate b/extensions/libip6t_REDIRECT.txlate
new file mode 100644
index 0000000..209f67a
--- /dev/null
+++ b/extensions/libip6t_REDIRECT.txlate
@@ -0,0 +1,5 @@
+ip6tables-translate -t nat -A prerouting -p tcp --dport 80 -j REDIRECT --to-ports 8080
+nft add rule ip6 nat prerouting tcp dport 80 counter redirect to :8080
+
+ip6tables-translate -t nat -A prerouting -p tcp --dport 80 -j REDIRECT --to-ports 8080 --random
+nft add rule ip6 nat prerouting tcp dport 80 counter redirect to :8080 random
diff --git a/extensions/libip6t_REJECT.c b/extensions/libip6t_REJECT.c
index 8085321..e3929d1 100644
--- a/extensions/libip6t_REJECT.c
+++ b/extensions/libip6t_REJECT.c
@@ -13,8 +13,8 @@
 struct reject_names {
 	const char *name;
 	const char *alias;
-	enum ip6t_reject_with with;
 	const char *desc;
+	const char *xlate;
 };
 
 enum {
@@ -22,20 +22,50 @@
 };
 
 static const struct reject_names reject_table[] = {
-	{"icmp6-no-route", "no-route",
-		IP6T_ICMP6_NO_ROUTE, "ICMPv6 no route"},
-	{"icmp6-adm-prohibited", "adm-prohibited",
-		IP6T_ICMP6_ADM_PROHIBITED, "ICMPv6 administratively prohibited"},
+	[IP6T_ICMP6_NO_ROUTE] = {
+		"icmp6-no-route", "no-route",
+		"ICMPv6 no route",
+		"no-route",
+	},
+	[IP6T_ICMP6_ADM_PROHIBITED] = {
+		"icmp6-adm-prohibited", "adm-prohibited",
+		"ICMPv6 administratively prohibited",
+		"admin-prohibited",
+	},
 #if 0
-	{"icmp6-not-neighbor", "not-neighbor"},
-		IP6T_ICMP6_NOT_NEIGHBOR, "ICMPv6 not a neighbor"},
+	[IP6T_ICMP6_NOT_NEIGHBOR] = {
+		"icmp6-not-neighbor", "not-neighbor",
+		"ICMPv6 not a neighbor",
+	},
 #endif
-	{"icmp6-addr-unreachable", "addr-unreach",
-		IP6T_ICMP6_ADDR_UNREACH, "ICMPv6 address unreachable"},
-	{"icmp6-port-unreachable", "port-unreach",
-		IP6T_ICMP6_PORT_UNREACH, "ICMPv6 port unreachable"},
-	{"tcp-reset", "tcp-reset",
-		IP6T_TCP_RESET, "TCP RST packet"}
+	[IP6T_ICMP6_ADDR_UNREACH] = {
+		"icmp6-addr-unreachable", "addr-unreach",
+		"ICMPv6 address unreachable",
+		"addr-unreachable",
+	},
+	[IP6T_ICMP6_PORT_UNREACH] = {
+		"icmp6-port-unreachable", "port-unreach",
+		"ICMPv6 port unreachable",
+		"port-unreachable",
+	},
+#if 0
+	[IP6T_ICMP6_ECHOREPLY] = {},
+#endif
+	[IP6T_TCP_RESET] = {
+		"tcp-reset", "tcp-reset",
+		"TCP RST packet",
+		"tcp reset",
+	},
+	[IP6T_ICMP6_POLICY_FAIL] = {
+		"icmp6-policy-fail", "policy-fail",
+		"ICMPv6 policy fail",
+		"policy-fail",
+	},
+	[IP6T_ICMP6_REJECT_ROUTE] = {
+		"icmp6-reject-route", "reject-route",
+		"ICMPv6 reject route",
+		"reject-route",
+	},
 };
 
 static void
@@ -46,6 +76,8 @@
 	printf("Valid reject types:\n");
 
 	for (i = 0; i < ARRAY_SIZE(reject_table); ++i) {
+		if (!reject_table[i].name)
+			continue;
 		printf("    %-25s\t%s\n", reject_table[i].name, reject_table[i].desc);
 		printf("    %-25s\talias\n", reject_table[i].alias);
 	}
@@ -82,14 +114,17 @@
 	unsigned int i;
 
 	xtables_option_parse(cb);
-	for (i = 0; i < ARRAY_SIZE(reject_table); ++i)
+	for (i = 0; i < ARRAY_SIZE(reject_table); ++i) {
+		if (!reject_table[i].name)
+			continue;
 		if (strncasecmp(reject_table[i].name,
 		      cb->arg, strlen(cb->arg)) == 0 ||
 		    strncasecmp(reject_table[i].alias,
 		      cb->arg, strlen(cb->arg)) == 0) {
-			reject->with = reject_table[i].with;
+			reject->with = i;
 			return;
 		}
+	}
 	xtables_error(PARAMETER_PROBLEM,
 		"unknown reject type \"%s\"", cb->arg);
 }
@@ -99,25 +134,34 @@
 {
 	const struct ip6t_reject_info *reject
 		= (const struct ip6t_reject_info *)target->data;
-	unsigned int i;
 
-	for (i = 0; i < ARRAY_SIZE(reject_table); ++i)
-		if (reject_table[i].with == reject->with)
-			break;
-	printf(" reject-with %s", reject_table[i].name);
+	printf(" reject-with %s", reject_table[reject->with].name);
 }
 
 static void REJECT_save(const void *ip, const struct xt_entry_target *target)
 {
 	const struct ip6t_reject_info *reject
 		= (const struct ip6t_reject_info *)target->data;
-	unsigned int i;
 
-	for (i = 0; i < ARRAY_SIZE(reject_table); ++i)
-		if (reject_table[i].with == reject->with)
-			break;
+	printf(" --reject-with %s", reject_table[reject->with].name);
+}
 
-	printf(" --reject-with %s", reject_table[i].name);
+static int REJECT_xlate(struct xt_xlate *xl,
+			const struct xt_xlate_tg_params *params)
+{
+	const struct ip6t_reject_info *reject =
+		(const struct ip6t_reject_info *)params->target->data;
+
+	if (reject->with == IP6T_ICMP6_PORT_UNREACH)
+		xt_xlate_add(xl, "reject");
+	else if (reject->with == IP6T_TCP_RESET)
+		xt_xlate_add(xl, "reject with %s",
+			     reject_table[reject->with].xlate);
+	else
+		xt_xlate_add(xl, "reject with icmpv6 type %s",
+			     reject_table[reject->with].xlate);
+
+	return 1;
 }
 
 static struct xtables_target reject_tg6_reg = {
@@ -132,6 +176,7 @@
 	.save		= REJECT_save,
 	.x6_parse	= REJECT_parse,
 	.x6_options	= REJECT_opts,
+	.xlate		= REJECT_xlate,
 };
 
 void _init(void)
diff --git a/extensions/libip6t_REJECT.man b/extensions/libip6t_REJECT.man
index 2d09e05..3c42768 100644
--- a/extensions/libip6t_REJECT.man
+++ b/extensions/libip6t_REJECT.man
@@ -18,10 +18,9 @@
 \fBicmp6\-adm\-prohibited\fP,
 \fBadm\-prohibited\fP,
 \fBicmp6\-addr\-unreachable\fP,
-\fBaddr\-unreach\fP,
-\fBicmp6\-port\-unreachable\fP or
-\fBport\-unreach\fP
-which return the appropriate ICMPv6 error message (\fBport\-unreach\fP is
+\fBaddr\-unreach\fP, or
+\fBicmp6\-port\-unreachable\fP,
+which return the appropriate ICMPv6 error message (\fBicmp6\-port\-unreachable\fP is
 the default). Finally, the option
 \fBtcp\-reset\fP
 can be used on rules which only match the TCP protocol: this causes a
@@ -31,3 +30,23 @@
 hosts (which won't accept your mail otherwise).
 \fBtcp\-reset\fP
 can only be used with kernel versions 2.6.14 or later.
+.PP
+\fIWarning:\fP You should not indiscriminately apply the REJECT target to
+packets whose connection state is classified as INVALID; instead, you should
+only DROP these.
+.PP
+Consider a source host transmitting a packet P, with P experiencing so much
+delay along its path that the source host issues a retransmission, P_2, with
+P_2 being successful in reaching its destination and advancing the connection
+state normally. It is conceivable that the late-arriving P may be considered
+not to be associated with any connection tracking entry. Generating a reject
+response for a packet so classed would then terminate the healthy connection.
+.PP
+So, instead of:
+.PP
+-A INPUT ... -j REJECT
+.PP
+do consider using:
+.PP
+-A INPUT ... -m conntrack --ctstate INVALID -j DROP
+-A INPUT ... -j REJECT
diff --git a/extensions/libip6t_REJECT.t b/extensions/libip6t_REJECT.t
new file mode 100644
index 0000000..d2b337d
--- /dev/null
+++ b/extensions/libip6t_REJECT.t
@@ -0,0 +1,11 @@
+:INPUT,FORWARD,OUTPUT
+-j REJECT;=;OK
+# manpage for IPv6 variant of REJECT does not show up for some reason?
+-j REJECT --reject-with icmp6-no-route;=;OK
+-j REJECT --reject-with icmp6-adm-prohibited;=;OK
+-j REJECT --reject-with icmp6-addr-unreachable;=;OK
+-j REJECT --reject-with icmp6-port-unreachable;=;OK
+-j REJECT --reject-with icmp6-policy-fail;=;OK
+-j REJECT --reject-with icmp6-reject-route;=;OK
+-p tcp -j REJECT --reject-with tcp-reset;=;OK
+-j REJECT --reject-with tcp-reset;;FAIL
diff --git a/extensions/libip6t_REJECT.txlate b/extensions/libip6t_REJECT.txlate
new file mode 100644
index 0000000..cfa35eb
--- /dev/null
+++ b/extensions/libip6t_REJECT.txlate
@@ -0,0 +1,8 @@
+ip6tables-translate -A FORWARD -p TCP --dport 22 -j REJECT
+nft add rule ip6 filter FORWARD tcp dport 22 counter reject
+
+ip6tables-translate -A FORWARD -p TCP --dport 22 -j REJECT --reject-with icmp6-reject-route
+nft add rule ip6 filter FORWARD tcp dport 22 counter reject with icmpv6 type reject-route
+
+ip6tables-translate -A FORWARD -p TCP --dport 22 -j REJECT --reject-with tcp-reset
+nft add rule ip6 filter FORWARD tcp dport 22 counter reject with tcp reset
diff --git a/extensions/libip6t_SNAT.c b/extensions/libip6t_SNAT.c
new file mode 100644
index 0000000..7d74b3d
--- /dev/null
+++ b/extensions/libip6t_SNAT.c
@@ -0,0 +1,317 @@
+/*
+ * Copyright (c) 2011 Patrick McHardy <kaber@trash.net>
+ *
+ * Based on Rusty Russell's IPv4 SNAT target. Development of IPv6 NAT
+ * funded by Astaro.
+ */
+
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <xtables.h>
+#include <iptables.h>
+#include <limits.h> /* INT_MAX in ip_tables.h */
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter/nf_nat.h>
+
+enum {
+	O_TO_SRC = 0,
+	O_RANDOM,
+	O_RANDOM_FULLY,
+	O_PERSISTENT,
+	O_X_TO_SRC,
+	F_TO_SRC       = 1 << O_TO_SRC,
+	F_RANDOM       = 1 << O_RANDOM,
+	F_RANDOM_FULLY = 1 << O_RANDOM_FULLY,
+	F_X_TO_SRC     = 1 << O_X_TO_SRC,
+};
+
+static void SNAT_help(void)
+{
+	printf(
+"SNAT target options:\n"
+" --to-source [<ipaddr>[-<ipaddr>]][:port[-port]]\n"
+"				Address to map source to.\n"
+"[--random] [--random-fully] [--persistent]\n");
+}
+
+static const struct xt_option_entry SNAT_opts[] = {
+	{.name = "to-source", .id = O_TO_SRC, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_MULTI},
+	{.name = "random", .id = O_RANDOM, .type = XTTYPE_NONE},
+	{.name = "random-fully", .id = O_RANDOM_FULLY, .type = XTTYPE_NONE},
+	{.name = "persistent", .id = O_PERSISTENT, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+
+/* Ranges expected in network order. */
+static void
+parse_to(const char *orig_arg, int portok, struct nf_nat_range *range)
+{
+	char *arg, *start, *end = NULL, *colon = NULL, *dash, *error;
+	const struct in6_addr *ip;
+
+	arg = strdup(orig_arg);
+	if (arg == NULL)
+		xtables_error(RESOURCE_PROBLEM, "strdup");
+
+	start = strchr(arg, '[');
+	if (start == NULL) {
+		start = arg;
+		/* Lets assume one colon is port information. Otherwise its an IPv6 address */
+		colon = strchr(arg, ':');
+		if (colon && strchr(colon+1, ':'))
+			colon = NULL;
+	}
+	else {
+		start++;
+		end = strchr(start, ']');
+		if (end == NULL)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Invalid address format");
+
+		*end = '\0';
+		colon = strchr(end + 1, ':');
+	}
+
+	if (colon) {
+		int port;
+
+		if (!portok)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Need TCP, UDP, SCTP or DCCP with port specification");
+
+		range->flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
+
+		port = atoi(colon+1);
+		if (port <= 0 || port > 65535)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Port `%s' not valid\n", colon+1);
+
+		error = strchr(colon+1, ':');
+		if (error)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Invalid port:port syntax - use dash\n");
+
+		dash = strchr(colon, '-');
+		if (!dash) {
+			range->min_proto.tcp.port
+				= range->max_proto.tcp.port
+				= htons(port);
+		} else {
+			int maxport;
+
+			maxport = atoi(dash + 1);
+			if (maxport <= 0 || maxport > 65535)
+				xtables_error(PARAMETER_PROBLEM,
+					   "Port `%s' not valid\n", dash+1);
+			if (maxport < port)
+				/* People are stupid. */
+				xtables_error(PARAMETER_PROBLEM,
+					   "Port range `%s' funky\n", colon+1);
+			range->min_proto.tcp.port = htons(port);
+			range->max_proto.tcp.port = htons(maxport);
+		}
+		/* Starts with colon or [] colon? No IP info...*/
+		if (colon == arg || colon == arg+2) {
+			free(arg);
+			return;
+		}
+		*colon = '\0';
+	}
+
+	range->flags |= NF_NAT_RANGE_MAP_IPS;
+	dash = strchr(start, '-');
+	if (colon && dash && dash > colon)
+		dash = NULL;
+
+	if (dash)
+		*dash = '\0';
+
+	ip = xtables_numeric_to_ip6addr(start);
+	if (!ip)
+		xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
+			      start);
+	range->min_addr.in6 = *ip;
+	if (dash) {
+		ip = xtables_numeric_to_ip6addr(dash + 1);
+		if (!ip)
+			xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
+				      dash+1);
+		range->max_addr.in6 = *ip;
+	} else
+		range->max_addr = range->min_addr;
+
+	free(arg);
+	return;
+}
+
+static void SNAT_parse(struct xt_option_call *cb)
+{
+	const struct ip6t_entry *entry = cb->xt_entry;
+	struct nf_nat_range *range = cb->data;
+	int portok;
+
+	if (entry->ipv6.proto == IPPROTO_TCP ||
+	    entry->ipv6.proto == IPPROTO_UDP ||
+	    entry->ipv6.proto == IPPROTO_SCTP ||
+	    entry->ipv6.proto == IPPROTO_DCCP ||
+	    entry->ipv6.proto == IPPROTO_ICMP)
+		portok = 1;
+	else
+		portok = 0;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_TO_SRC:
+		if (cb->xflags & F_X_TO_SRC) {
+			xtables_error(PARAMETER_PROBLEM,
+				      "SNAT: Multiple --to-source not supported");
+		}
+		parse_to(cb->arg, portok, range);
+		cb->xflags |= F_X_TO_SRC;
+		break;
+	case O_PERSISTENT:
+		range->flags |= NF_NAT_RANGE_PERSISTENT;
+		break;
+	}
+}
+
+static void SNAT_fcheck(struct xt_fcheck_call *cb)
+{
+	static const unsigned int f = F_TO_SRC | F_RANDOM;
+	static const unsigned int r = F_TO_SRC | F_RANDOM_FULLY;
+	struct nf_nat_range *range = cb->data;
+
+	if ((cb->xflags & f) == f)
+		range->flags |= NF_NAT_RANGE_PROTO_RANDOM;
+	if ((cb->xflags & r) == r)
+		range->flags |= NF_NAT_RANGE_PROTO_RANDOM_FULLY;
+}
+
+static void print_range(const struct nf_nat_range *range)
+{
+	if (range->flags & NF_NAT_RANGE_MAP_IPS) {
+		if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED)
+			printf("[");
+		printf("%s", xtables_ip6addr_to_numeric(&range->min_addr.in6));
+		if (memcmp(&range->min_addr, &range->max_addr,
+			   sizeof(range->min_addr)))
+			printf("-%s", xtables_ip6addr_to_numeric(&range->max_addr.in6));
+		if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED)
+			printf("]");
+	}
+	if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		printf(":");
+		printf("%hu", ntohs(range->min_proto.tcp.port));
+		if (range->max_proto.tcp.port != range->min_proto.tcp.port)
+			printf("-%hu", ntohs(range->max_proto.tcp.port));
+	}
+}
+
+static void SNAT_print(const void *ip, const struct xt_entry_target *target,
+                       int numeric)
+{
+	const struct nf_nat_range *range = (const void *)target->data;
+
+	printf(" to:");
+	print_range(range);
+	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
+		printf(" random");
+	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY)
+		printf(" random-fully");
+	if (range->flags & NF_NAT_RANGE_PERSISTENT)
+		printf(" persistent");
+}
+
+static void SNAT_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct nf_nat_range *range = (const void *)target->data;
+
+	printf(" --to-source ");
+	print_range(range);
+	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
+		printf(" --random");
+	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY)
+		printf(" --random-fully");
+	if (range->flags & NF_NAT_RANGE_PERSISTENT)
+		printf(" --persistent");
+}
+
+static void print_range_xlate(const struct nf_nat_range *range,
+			      struct xt_xlate *xl)
+{
+	bool proto_specified = range->flags & NF_NAT_RANGE_PROTO_SPECIFIED;
+
+	if (range->flags & NF_NAT_RANGE_MAP_IPS) {
+		xt_xlate_add(xl, "%s%s%s",
+			     proto_specified ? "[" : "",
+			     xtables_ip6addr_to_numeric(&range->min_addr.in6),
+			     proto_specified ? "]" : "");
+
+		if (memcmp(&range->min_addr, &range->max_addr,
+			   sizeof(range->min_addr))) {
+			xt_xlate_add(xl, "-%s%s%s",
+				     proto_specified ? "[" : "",
+				     xtables_ip6addr_to_numeric(&range->max_addr.in6),
+				     proto_specified ? "]" : "");
+		}
+	}
+	if (proto_specified) {
+		xt_xlate_add(xl, ":%hu", ntohs(range->min_proto.tcp.port));
+
+		if (range->max_proto.tcp.port != range->min_proto.tcp.port)
+			xt_xlate_add(xl, "-%hu",
+				   ntohs(range->max_proto.tcp.port));
+	}
+}
+
+static int SNAT_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_tg_params *params)
+{
+	const struct nf_nat_range *range = (const void *)params->target->data;
+	bool sep_need = false;
+	const char *sep = " ";
+
+	xt_xlate_add(xl, "snat to ");
+	print_range_xlate(range, xl);
+	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM) {
+		xt_xlate_add(xl, " random");
+		sep_need = true;
+	}
+	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY) {
+		if (sep_need)
+			sep = ",";
+		xt_xlate_add(xl, "%sfully-random", sep);
+		sep_need = true;
+	}
+	if (range->flags & NF_NAT_RANGE_PERSISTENT) {
+		if (sep_need)
+			sep = ",";
+		xt_xlate_add(xl, "%spersistent", sep);
+	}
+
+	return 1;
+}
+
+static struct xtables_target snat_tg_reg = {
+	.name		= "SNAT",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV6,
+	.revision	= 1,
+	.size		= XT_ALIGN(sizeof(struct nf_nat_range)),
+	.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_range)),
+	.help		= SNAT_help,
+	.x6_parse	= SNAT_parse,
+	.x6_fcheck	= SNAT_fcheck,
+	.print		= SNAT_print,
+	.save		= SNAT_save,
+	.x6_options	= SNAT_opts,
+	.xlate		= SNAT_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_target(&snat_tg_reg);
+}
diff --git a/extensions/libip6t_SNAT.t b/extensions/libip6t_SNAT.t
new file mode 100644
index 0000000..d188a6b
--- /dev/null
+++ b/extensions/libip6t_SNAT.t
@@ -0,0 +1,11 @@
+:POSTROUTING
+*nat
+-j SNAT --to-source dead::beef;=;OK
+-j SNAT --to-source dead::beef-dead::fee7;=;OK
+-j SNAT --to-source [dead::beef]:1025-65535;;FAIL
+-j SNAT --to-source [dead::beef] --to-source [dead::fee7];;FAIL
+-p tcp -j SNAT --to-source [dead::beef]:1025-65535;=;OK
+-p tcp -j SNAT --to-source [dead::beef-dead::fee7]:1025-65535;=;OK
+-p tcp -j SNAT --to-source [dead::beef-dead::fee7]:1025-65536;;FAIL
+-p tcp -j SNAT --to-source [dead::beef-dead::fee7]:1025-65535 --to-source [dead::beef-dead::fee8]:1025-65535;;FAIL
+-j SNAT;;FAIL
diff --git a/extensions/libip6t_SNAT.txlate b/extensions/libip6t_SNAT.txlate
new file mode 100644
index 0000000..44f2fce
--- /dev/null
+++ b/extensions/libip6t_SNAT.txlate
@@ -0,0 +1,11 @@
+ip6tables-translate -t nat -A postrouting -o eth0 -p tcp -j SNAT --to [fec0::1234]:80
+nft add rule ip6 nat postrouting oifname "eth0" meta l4proto tcp counter snat to [fec0::1234]:80
+
+ip6tables-translate -t nat -A postrouting -o eth0 -p tcp -j SNAT --to [fec0::1234]:1-20
+nft add rule ip6 nat postrouting oifname "eth0" meta l4proto tcp counter snat to [fec0::1234]:1-20
+
+ip6tables-translate -t nat -A postrouting -o eth0 -p tcp -j SNAT --to [fec0::1234]:123 --random
+nft add rule ip6 nat postrouting oifname "eth0" meta l4proto tcp counter snat to [fec0::1234]:123 random
+
+ip6tables-translate -t nat -A postrouting -o eth0 -p tcp -j SNAT --to [fec0::1234]:123 --random-fully --persistent
+nft add rule ip6 nat postrouting oifname "eth0" meta l4proto tcp counter snat to [fec0::1234]:123 fully-random,persistent
diff --git a/extensions/libip6t_SNPT.c b/extensions/libip6t_SNPT.c
new file mode 100644
index 0000000..65f787d
--- /dev/null
+++ b/extensions/libip6t_SNPT.c
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2012-2013 Patrick McHardy <kaber@trash.net>
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <xtables.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter_ipv6/ip6t_NPT.h>
+
+enum {
+	O_SRC_PFX	= 1 << 0,
+	O_DST_PFX	= 1 << 1,
+};
+
+static const struct xt_option_entry SNPT_options[] = {
+	{ .name = "src-pfx", .id = O_SRC_PFX, .type = XTTYPE_HOSTMASK,
+	  .flags = XTOPT_MAND },
+	{ .name = "dst-pfx", .id = O_DST_PFX, .type = XTTYPE_HOSTMASK,
+	  .flags = XTOPT_MAND },
+	{ }
+};
+
+static void SNPT_help(void)
+{
+	printf("SNPT target options:"
+	       "\n"
+	       " --src-pfx prefix/length\n"
+	       " --dst-pfx prefix/length\n"
+	       "\n");
+}
+
+static void SNPT_parse(struct xt_option_call *cb)
+{
+	struct ip6t_npt_tginfo *npt = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SRC_PFX:
+		npt->src_pfx = cb->val.haddr;
+		npt->src_pfx_len = cb->val.hlen;
+		break;
+	case O_DST_PFX:
+		npt->dst_pfx = cb->val.haddr;
+		npt->dst_pfx_len = cb->val.hlen;
+		break;
+	}
+}
+
+static void SNPT_print(const void *ip, const struct xt_entry_target *target,
+		       int numeric)
+{
+	const struct ip6t_npt_tginfo *npt = (const void *)target->data;
+
+	printf(" SNPT src-pfx %s/%u", xtables_ip6addr_to_numeric(&npt->src_pfx.in6),
+				 npt->src_pfx_len);
+	printf(" dst-pfx %s/%u", xtables_ip6addr_to_numeric(&npt->dst_pfx.in6),
+				 npt->dst_pfx_len);
+}
+
+static void SNPT_save(const void *ip, const struct xt_entry_target *target)
+{
+	static const struct in6_addr zero_addr;
+	const struct ip6t_npt_tginfo *info = (const void *)target->data;
+
+	if (memcmp(&info->src_pfx.in6, &zero_addr, sizeof(zero_addr)) != 0 ||
+	    info->src_pfx_len != 0)
+		printf(" --src-pfx %s/%u",
+		       xtables_ip6addr_to_numeric(&info->src_pfx.in6),
+		       info->src_pfx_len);
+	if (memcmp(&info->dst_pfx.in6, &zero_addr, sizeof(zero_addr)) != 0 ||
+	    info->dst_pfx_len != 0)
+		printf(" --dst-pfx %s/%u",
+		       xtables_ip6addr_to_numeric(&info->dst_pfx.in6),
+		       info->dst_pfx_len);
+}
+
+static struct xtables_target snpt_tg_reg = {
+	.name		= "SNPT",
+	.version	= XTABLES_VERSION,
+	.family		= NFPROTO_IPV6,
+	.size		= XT_ALIGN(sizeof(struct ip6t_npt_tginfo)),
+	.userspacesize	= offsetof(struct ip6t_npt_tginfo, adjustment),
+	.help		= SNPT_help,
+	.x6_parse	= SNPT_parse,
+	.print		= SNPT_print,
+	.save		= SNPT_save,
+	.x6_options	= SNPT_options,
+};
+
+void _init(void)
+{
+	xtables_register_target(&snpt_tg_reg);
+}
diff --git a/extensions/libip6t_SNPT.man b/extensions/libip6t_SNPT.man
new file mode 100644
index 0000000..97e0071
--- /dev/null
+++ b/extensions/libip6t_SNPT.man
@@ -0,0 +1,30 @@
+Provides stateless source IPv6-to-IPv6 Network Prefix Translation (as described
+by RFC 6296).
+.PP
+You have to use this target in the
+.B mangle
+table, not in the
+.B nat
+table. It takes the following options:
+.TP
+\fB\-\-src\-pfx\fP [\fIprefix/\fP\fIlength]
+Set source prefix that you want to translate and length
+.TP
+\fB\-\-dst\-pfx\fP [\fIprefix/\fP\fIlength]
+Set destination prefix that you want to use in the translation and length
+.PP
+You have to use the DNPT target to undo the translation. Example:
+.IP
+ip6tables \-t mangle \-I POSTROUTING \-s fd00::/64 \! \-o vboxnet0
+\-j SNPT \-\-src-pfx fd00::/64 \-\-dst-pfx 2001:e20:2000:40f::/64
+.IP
+ip6tables \-t mangle \-I PREROUTING \-i wlan0 \-d 2001:e20:2000:40f::/64
+\-j DNPT \-\-src-pfx 2001:e20:2000:40f::/64 \-\-dst-pfx fd00::/64
+.PP
+You may need to enable IPv6 neighbor proxy:
+.IP
+sysctl \-w net.ipv6.conf.all.proxy_ndp=1
+.PP
+You also have to use the
+.B NOTRACK
+target to disable connection tracking for translated flows.
diff --git a/extensions/libip6t_SNPT.t b/extensions/libip6t_SNPT.t
new file mode 100644
index 0000000..7ed6d0c
--- /dev/null
+++ b/extensions/libip6t_SNPT.t
@@ -0,0 +1,7 @@
+:INPUT,POSTROUTING
+*mangle
+-j SNPT --src-pfx dead::/64 --dst-pfx 1c3::/64;=;OK
+-j SNPT --src-pfx dead::beef --dst-pfx 1c3::/64;;FAIL
+-j SNPT --src-pfx dead::/64;;FAIL
+-j SNPT --dst-pfx dead::/64;;FAIL
+-j SNPT;;FAIL
diff --git a/extensions/libip6t_ah.c b/extensions/libip6t_ah.c
index 26f8140..f35982f 100644
--- a/extensions/libip6t_ah.c
+++ b/extensions/libip6t_ah.c
@@ -28,6 +28,14 @@
 };
 #undef s
 
+static void ah_init(struct xt_entry_match *m)
+{
+	struct ip6t_ah *ahinfo = (void *)m->data;
+
+	/* Defaults for when no --ahspi is used at all */
+	ahinfo->spis[1] = ~0U;
+}
+
 static void ah_parse(struct xt_option_call *cb)
 {
 	struct ip6t_ah *ahinfo = cb->data;
@@ -120,6 +128,41 @@
 		printf(" --ahres");
 }
 
+static int ah_xlate(struct xt_xlate *xl,
+		    const struct xt_xlate_mt_params *params)
+{
+	const struct ip6t_ah *ahinfo = (struct ip6t_ah *)params->match->data;
+	char *space = "";
+
+	if (!(ahinfo->spis[0] == 0 && ahinfo->spis[1] == 0xFFFFFFFF)) {
+		xt_xlate_add(xl, "ah spi%s ",
+			(ahinfo->invflags & IP6T_AH_INV_SPI) ? " !=" : "");
+		if (ahinfo->spis[0] != ahinfo->spis[1])
+			xt_xlate_add(xl, "%u-%u", ahinfo->spis[0],
+				     ahinfo->spis[1]);
+		else
+			xt_xlate_add(xl, "%u", ahinfo->spis[0]);
+		space = " ";
+	}
+
+	if (ahinfo->hdrlen != 0 || (ahinfo->invflags & IP6T_AH_INV_LEN)) {
+		xt_xlate_add(xl, "%sah hdrlength%s %u", space,
+			     (ahinfo->invflags & IP6T_AH_INV_LEN) ? " !=" : "",
+			     ahinfo->hdrlen);
+		space = " ";
+	}
+
+	if (ahinfo->hdrres != 0) {
+		xt_xlate_add(xl, "%sah reserved %u", space, ahinfo->hdrres);
+		space = " ";
+	}
+
+	if (!space[0]) /* plain '-m ah' */
+		xt_xlate_add(xl, "meta l4proto ah");
+
+	return 1;
+}
+
 static struct xtables_match ah_mt6_reg = {
 	.name          = "ah",
 	.version       = XTABLES_VERSION,
@@ -127,10 +170,12 @@
 	.size          = XT_ALIGN(sizeof(struct ip6t_ah)),
 	.userspacesize = XT_ALIGN(sizeof(struct ip6t_ah)),
 	.help          = ah_help,
+	.init          = ah_init,
 	.print         = ah_print,
 	.save          = ah_save,
 	.x6_parse      = ah_parse,
 	.x6_options    = ah_opts,
+	.xlate	       = ah_xlate,
 };
 
 void
diff --git a/extensions/libip6t_ah.t b/extensions/libip6t_ah.t
new file mode 100644
index 0000000..c1898d4
--- /dev/null
+++ b/extensions/libip6t_ah.t
@@ -0,0 +1,15 @@
+:INPUT,FORWARD,OUTPUT
+-m ah --ahspi 0;=;OK
+-m ah --ahspi 4294967295;=;OK
+-m ah --ahspi 0:4294967295;-m ah;OK
+-m ah ! --ahspi 0;=;OK
+# ERROR: should fail: iptables -A FORWARD -t mangle -j CLASSIFY --set-class 1:-1
+# -m ah --ahres;=;OK
+# ERROR: line 7 (cannot find: ip6tables -I INPUT -m ah --ahlen 32
+# -m ah --ahlen 32;=;OK
+-m ah --ahspi -1;;FAIL
+-m ah --ahspi 4294967296;;FAIL
+-m ah --ahspi invalid;;FAIL
+-m ah --ahspi 0:invalid;;FAIL
+-m ah --ahspi;;FAIL
+-m ah;=;OK
diff --git a/extensions/libip6t_ah.txlate b/extensions/libip6t_ah.txlate
new file mode 100644
index 0000000..c6b09a2
--- /dev/null
+++ b/extensions/libip6t_ah.txlate
@@ -0,0 +1,17 @@
+ip6tables-translate -A INPUT -m ah --ahspi 500 -j DROP
+nft add rule ip6 filter INPUT ah spi 500 counter drop
+
+ip6tables-translate -A INPUT -m ah --ahspi 500:550 -j DROP
+nft add rule ip6 filter INPUT ah spi 500-550 counter drop
+
+ip6tables-translate -A INPUT -m ah ! --ahlen 120
+nft add rule ip6 filter INPUT ah hdrlength != 120 counter
+
+ip6tables-translate -A INPUT -m ah --ahres
+nft add rule ip6 filter INPUT ah reserved 1 counter
+
+ip6tables-translate -A INPUT -m ah --ahspi 500 ! --ahlen 120 -j DROP
+nft add rule ip6 filter INPUT ah spi 500 ah hdrlength != 120 counter drop
+
+ip6tables-translate -A INPUT -m ah --ahspi 500 --ahlen 120 --ahres -j ACCEPT
+nft add rule ip6 filter INPUT ah spi 500 ah hdrlength 120 ah reserved 1 counter accept
diff --git a/extensions/libip6t_dst.c b/extensions/libip6t_dst.c
index 4125bd3..fe7e340 100644
--- a/extensions/libip6t_dst.c
+++ b/extensions/libip6t_dst.c
@@ -111,6 +111,11 @@
 
 	xtables_option_parse(cb);
 	switch (cb->entry->id) {
+	case O_DSTLEN:
+		if (cb->invert)
+			optinfo->invflags |= IP6T_OPTS_INV_LEN;
+		optinfo->flags |= IP6T_OPTS_LEN;
+		break;
 	case O_DSTOPTS:
 		optinfo->optsnr = parse_options(cb->arg, optinfo->opts);
 		optinfo->flags |= IP6T_OPTS_OPTS;
diff --git a/extensions/libip6t_dst.t b/extensions/libip6t_dst.t
new file mode 100644
index 0000000..0b0013b
--- /dev/null
+++ b/extensions/libip6t_dst.t
@@ -0,0 +1,5 @@
+:INPUT,FORWARD,OUTPUT
+-m dst --dst-len 0;=;OK
+-m dst --dst-opts 149:92,12:12,123:12;=;OK
+-m dst ! --dst-len 42;=;OK
+-m dst --dst-len 42 --dst-opts 149:92,12:12,123:12;=;OK
diff --git a/extensions/libip6t_eui64.t b/extensions/libip6t_eui64.t
new file mode 100644
index 0000000..e5aaaac
--- /dev/null
+++ b/extensions/libip6t_eui64.t
@@ -0,0 +1,8 @@
+:PREROUTING
+*raw
+-m eui64;=;OK
+:INPUT,FORWARD
+*filter
+-m eui64;=;OK
+:OUTPUT
+-m eui64;;FAIL
diff --git a/extensions/libip6t_frag.c b/extensions/libip6t_frag.c
index 4779386..3842496 100644
--- a/extensions/libip6t_frag.c
+++ b/extensions/libip6t_frag.c
@@ -41,6 +41,13 @@
 };
 #undef s
 
+static void frag_init(struct xt_entry_match *m)
+{
+	struct ip6t_frag *fraginfo = (void *)m->data;
+
+	fraginfo->ids[1] = ~0U;
+}
+
 static void frag_parse(struct xt_option_call *cb)
 {
 	struct ip6t_frag *fraginfo = cb->data;
@@ -50,6 +57,22 @@
 	case O_FRAGID:
 		if (cb->nvals == 1)
 			fraginfo->ids[1] = fraginfo->ids[0];
+		if (cb->invert)
+			fraginfo->invflags |= IP6T_FRAG_INV_IDS;
+		/*
+		 * Note however that IP6T_FRAG_IDS is not tested by anything,
+		 * so it is merely here for completeness.
+		 */
+		fraginfo->flags |= IP6T_FRAG_IDS;
+		break;
+	case O_FRAGLEN:
+		/*
+		 * As of Linux 3.0, the kernel does not check for
+		 * fraglen at all.
+		 */
+		if (cb->invert)
+			fraginfo->invflags |= IP6T_FRAG_INV_LEN;
+		fraginfo->flags |= IP6T_FRAG_LEN;
 		break;
 	case O_FRAGRES:
 		fraginfo->flags |= IP6T_FRAG_RES;
@@ -150,6 +173,45 @@
 		printf(" --fraglast");
 }
 
+static int frag_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_mt_params *params)
+{
+	const struct ip6t_frag *fraginfo =
+		(struct ip6t_frag *)params->match->data;
+	char *space= "";
+
+	if (!(fraginfo->ids[0] == 0 && fraginfo->ids[1] == 0xFFFFFFFF)) {
+		xt_xlate_add(xl, "frag id %s",
+			     (fraginfo->invflags & IP6T_FRAG_INV_IDS) ?
+			     "!= " : "");
+		if (fraginfo->ids[0] != fraginfo->ids[1])
+			xt_xlate_add(xl, "%u-%u", fraginfo->ids[0],
+				     fraginfo->ids[1]);
+		else
+			xt_xlate_add(xl, "%u", fraginfo->ids[0]);
+
+		space = " ";
+	}
+
+	if (fraginfo->flags & IP6T_FRAG_RES) {
+		xt_xlate_add(xl, "%sfrag reserved 1", space);
+		space = " ";
+	}
+	if (fraginfo->flags & IP6T_FRAG_FST) {
+		xt_xlate_add(xl, "%sfrag frag-off 0", space);
+		space = " ";
+	}
+	if (fraginfo->flags & IP6T_FRAG_MF) {
+		xt_xlate_add(xl, "%sfrag more-fragments 1", space);
+		space = " ";
+	}
+	if (fraginfo->flags & IP6T_FRAG_NMF) {
+		xt_xlate_add(xl, "%sfrag more-fragments 0", space);
+	}
+
+	return 1;
+}
+
 static struct xtables_match frag_mt6_reg = {
 	.name          = "frag",
 	.version       = XTABLES_VERSION,
@@ -157,10 +219,12 @@
 	.size          = XT_ALIGN(sizeof(struct ip6t_frag)),
 	.userspacesize = XT_ALIGN(sizeof(struct ip6t_frag)),
 	.help          = frag_help,
+	.init          = frag_init,
 	.print         = frag_print,
 	.save          = frag_save,
 	.x6_parse      = frag_parse,
 	.x6_options    = frag_opts,
+	.xlate	       = frag_xlate,
 };
 
 void
diff --git a/extensions/libip6t_frag.t b/extensions/libip6t_frag.t
new file mode 100644
index 0000000..299fa03
--- /dev/null
+++ b/extensions/libip6t_frag.t
@@ -0,0 +1,13 @@
+:INPUT,FORWARD,OUTPUT
+-m frag --fragid 1:42;=;OK
+-m frag --fraglen 42;=;OK
+-m frag --fragres;=;OK
+-m frag --fragfirst;=;OK
+-m frag --fragmore;=;OK
+-m frag --fraglast;=;OK
+-m frag ! --fragid 1 ! --fraglen 42 --fragres --fragfirst;=;OK
+-m frag --fragfirst --fragmore;=;OK
+-m frag --fragfirst --fraglast;=;OK
+-m frag --fraglast --fragmore;;FAIL
+-d ff02::fb/128 -p udp -m udp --dport 5353 -m frag --fragmore;=;OK
+-d fe80::/64 -p udp --dport 546 -m frag --fraglast;-d fe80::/64 -p udp -m udp --dport 546 -m frag --fraglast;OK
diff --git a/extensions/libip6t_frag.txlate b/extensions/libip6t_frag.txlate
new file mode 100644
index 0000000..e8bd9d4
--- /dev/null
+++ b/extensions/libip6t_frag.txlate
@@ -0,0 +1,17 @@
+ip6tables-translate -t filter -A INPUT -m frag --fragid 100:200 -j ACCEPT
+nft add rule ip6 filter INPUT frag id 100-200 counter accept
+
+ip6tables-translate -t filter -A INPUT -m frag --fragid 100 --fragres --fragmore -j ACCEPT
+nft add rule ip6 filter INPUT frag id 100 frag reserved 1 frag more-fragments 1 counter accept
+
+ip6tables-translate -t filter -A INPUT -m frag ! --fragid 100:200 -j ACCEPT
+nft add rule ip6 filter INPUT frag id != 100-200 counter accept
+
+ip6tables-translate -t filter -A INPUT -m frag --fragid 100:200 --fraglast -j ACCEPT
+nft add rule ip6 filter INPUT frag id 100-200 frag more-fragments 0 counter accept
+
+ip6tables-translate -t filter -A INPUT -m frag --fragid 100:200 --fragfirst -j ACCEPT
+nft add rule ip6 filter INPUT frag id 100-200 frag frag-off 0 counter accept
+
+ip6tables-translate -t filter -A INPUT -m frag --fraglast -j ACCEPT
+nft add rule ip6 filter INPUT frag more-fragments 0 counter accept
diff --git a/extensions/libip6t_hbh.c b/extensions/libip6t_hbh.c
index 809e80d..4cebecf 100644
--- a/extensions/libip6t_hbh.c
+++ b/extensions/libip6t_hbh.c
@@ -5,8 +5,6 @@
 #include <xtables.h>
 #include <linux/netfilter_ipv6/ip6t_opts.h>
 
-#define DEBUG		0
-
 enum {
 	O_HBH_LEN = 0,
 	O_HBH_OPTS,
@@ -83,7 +81,7 @@
                         opts[i] |= (0x00FF);
 		}
 
-#if DEBUG
+#ifdef DEBUG
 		printf("opts str: %s %s\n", cp, range);
 		printf("opts opt: %04X\n", opts[i]);
 #endif
@@ -92,7 +90,7 @@
 
 	free(buffer);
 
-#if DEBUG
+#ifdef DEBUG
 	printf("addr nr: %d\n", i);
 #endif
 
@@ -108,6 +106,7 @@
 	case O_HBH_LEN:
 		if (cb->invert)
 			optinfo->invflags |= IP6T_OPTS_INV_LEN;
+		optinfo->flags |= IP6T_OPTS_LEN;
 		break;
 	case O_HBH_OPTS:
 		optinfo->optsnr = parse_options(cb->arg, optinfo->opts);
@@ -163,6 +162,23 @@
 	print_options(optinfo->optsnr, (uint16_t *)optinfo->opts);
 }
 
+static int hbh_xlate(struct xt_xlate *xl,
+		     const struct xt_xlate_mt_params *params)
+{
+	const struct ip6t_opts *optinfo =
+		(struct ip6t_opts *)params->match->data;
+
+	if (!(optinfo->flags & IP6T_OPTS_LEN) ||
+	    (optinfo->flags & IP6T_OPTS_OPTS))
+		return 0;
+
+	xt_xlate_add(xl, "hbh hdrlength %s%u",
+		     (optinfo->invflags & IP6T_OPTS_INV_LEN) ? "!= " : "",
+		     optinfo->hdrlen);
+
+	return 1;
+}
+
 static struct xtables_match hbh_mt6_reg = {
 	.name 		= "hbh",
 	.version	= XTABLES_VERSION,
@@ -174,6 +190,7 @@
 	.save		= hbh_save,
 	.x6_parse	= hbh_parse,
 	.x6_options	= hbh_opts,
+	.xlate		= hbh_xlate,
 };
 
 void
diff --git a/extensions/libip6t_hbh.t b/extensions/libip6t_hbh.t
new file mode 100644
index 0000000..4b58f25
--- /dev/null
+++ b/extensions/libip6t_hbh.t
@@ -0,0 +1,5 @@
+:INPUT,FORWARD,OUTPUT
+-m hbh;=;OK
+-m hbh --hbh-len 42;=;OK
+-m hbh ! --hbh-len 42;=;OK
+-m hbh --hbh-len 42 --hbh-opts 1:2,23:42,4:6,8:10,42,23,4:5;=;OK
diff --git a/extensions/libip6t_hbh.txlate b/extensions/libip6t_hbh.txlate
new file mode 100644
index 0000000..28101fd
--- /dev/null
+++ b/extensions/libip6t_hbh.txlate
@@ -0,0 +1,5 @@
+ip6tables-translate -t filter -A INPUT -m hbh --hbh-len 22
+nft add rule ip6 filter INPUT hbh hdrlength 22 counter
+
+ip6tables-translate -t filter -A INPUT -m hbh ! --hbh-len 22
+nft add rule ip6 filter INPUT hbh hdrlength != 22 counter
diff --git a/extensions/libip6t_hl.c b/extensions/libip6t_hl.c
index 3559db4..37922f6 100644
--- a/extensions/libip6t_hl.c
+++ b/extensions/libip6t_hl.c
@@ -83,6 +83,24 @@
 	printf(" %s %u", op[info->mode], info->hop_limit);
 }
 
+static const char *const op[] = {
+	[IP6T_HL_EQ] = "",
+	[IP6T_HL_NE] = "!= ",
+	[IP6T_HL_LT] = "lt ",
+	[IP6T_HL_GT] = "gt "
+};
+
+static int hl_xlate(struct xt_xlate *xl,
+		    const struct xt_xlate_mt_params *params)
+{
+	const struct ip6t_hl_info *info =
+		(struct ip6t_hl_info *) params->match->data;
+
+	xt_xlate_add(xl, "ip6 hoplimit %s%u", op[info->mode], info->hop_limit);
+
+	return 1;
+}
+
 #define s struct ip6t_hl_info
 static const struct xt_option_entry hl_opts[] = {
 	{.name = "hl-lt", .id = O_HL_LT, .excl = F_ANY, .type = XTTYPE_UINT8,
@@ -109,6 +127,7 @@
 	.x6_parse      = hl_parse,
 	.x6_fcheck     = hl_check,
 	.x6_options    = hl_opts,
+	.xlate	       = hl_xlate,
 };
 
 
diff --git a/extensions/libip6t_hl.t b/extensions/libip6t_hl.t
new file mode 100644
index 0000000..b02816a
--- /dev/null
+++ b/extensions/libip6t_hl.t
@@ -0,0 +1,8 @@
+:INPUT,FORWARD,OUTPUT
+-m hl;;FAIL
+-m hl --hl-eq 42;=;OK
+-m hl ! --hl-eq 42;=;OK
+-m hl --hl-lt 42;=;OK
+-m hl --hl-gt 42;=;OK
+-m hl --hl-gt 42 --hl-eq 42;;FAIL
+-m hl --hl-gt;;FAIL
diff --git a/extensions/libip6t_hl.txlate b/extensions/libip6t_hl.txlate
new file mode 100644
index 0000000..1756393
--- /dev/null
+++ b/extensions/libip6t_hl.txlate
@@ -0,0 +1,5 @@
+ip6tables-translate -t nat -A postrouting -m hl --hl-gt 3
+nft add rule ip6 nat postrouting ip6 hoplimit gt 3 counter
+
+ip6tables-translate -t nat -A postrouting -m hl ! --hl-eq 3
+nft add rule ip6 nat postrouting ip6 hoplimit != 3 counter
diff --git a/extensions/libip6t_icmp6.c b/extensions/libip6t_icmp6.c
index 68b940b..cc7bfae 100644
--- a/extensions/libip6t_icmp6.c
+++ b/extensions/libip6t_icmp6.c
@@ -4,23 +4,23 @@
 #include <xtables.h>
 #include <limits.h> /* INT_MAX in ip6_tables.h */
 #include <linux/netfilter_ipv6/ip6_tables.h>
+#include <netinet/icmp6.h>
+
+#include "libxt_icmp.h"
 
 enum {
 	O_ICMPV6_TYPE = 0,
 };
 
-struct icmpv6_names {
-	const char *name;
-	uint8_t type;
-	uint8_t code_min, code_max;
-};
-
-static const struct icmpv6_names icmpv6_codes[] = {
+static const struct xt_icmp_names icmpv6_codes[] = {
 	{ "destination-unreachable", 1, 0, 0xFF },
 	{   "no-route", 1, 0, 0 },
 	{   "communication-prohibited", 1, 1, 1 },
+	{   "beyond-scope", 1, 2, 2 },
 	{   "address-unreachable", 1, 3, 3 },
 	{   "port-unreachable", 1, 4, 4 },
+	{   "failed-policy", 1, 5, 5 },
+	{   "reject-route", 1, 6, 6 },
 
 	{ "packet-too-big", 2, 0, 0xFF },
 
@@ -54,34 +54,14 @@
 
 };
 
-static void
-print_icmpv6types(void)
-{
-	unsigned int i;
-	printf("Valid ICMPv6 Types:");
-
-	for (i = 0; i < ARRAY_SIZE(icmpv6_codes); ++i) {
-		if (i && icmpv6_codes[i].type == icmpv6_codes[i-1].type) {
-			if (icmpv6_codes[i].code_min == icmpv6_codes[i-1].code_min
-			    && (icmpv6_codes[i].code_max
-				== icmpv6_codes[i-1].code_max))
-				printf(" (%s)", icmpv6_codes[i].name);
-			else
-				printf("\n   %s", icmpv6_codes[i].name);
-		}
-		else
-			printf("\n%s", icmpv6_codes[i].name);
-	}
-	printf("\n");
-}
-
 static void icmp6_help(void)
 {
 	printf(
 "icmpv6 match options:\n"
 "[!] --icmpv6-type typename	match icmpv6 type\n"
 "				(or numeric type or type/code)\n");
-	print_icmpv6types();
+	printf("Valid ICMPv6 Types:");
+	xt_print_icmp_types(icmpv6_codes, ARRAY_SIZE(icmpv6_codes));
 }
 
 static const struct xt_option_entry icmp6_opts[] = {
@@ -219,6 +199,68 @@
 		printf("/%u", icmpv6->code[0]);
 }
 
+#define XT_ICMPV6_TYPE(type)	(type - ND_ROUTER_SOLICIT)
+
+static const char *icmp6_type_xlate_array[] = {
+	[XT_ICMPV6_TYPE(ND_ROUTER_SOLICIT)]	= "nd-router-solicit",
+	[XT_ICMPV6_TYPE(ND_ROUTER_ADVERT)]	= "nd-router-advert",
+	[XT_ICMPV6_TYPE(ND_NEIGHBOR_SOLICIT)]	= "nd-neighbor-solicit",
+	[XT_ICMPV6_TYPE(ND_NEIGHBOR_ADVERT)]	= "nd-neighbor-advert",
+	[XT_ICMPV6_TYPE(ND_REDIRECT)]		= "nd-redirect",
+};
+
+static const char *icmp6_type_xlate(unsigned int type)
+{
+	if (type < ND_ROUTER_SOLICIT || type > ND_REDIRECT)
+		return NULL;
+
+	return icmp6_type_xlate_array[XT_ICMPV6_TYPE(type)];
+}
+
+static unsigned int type_xlate_print(struct xt_xlate *xl, unsigned int icmptype,
+				     unsigned int code_min,
+				     unsigned int code_max)
+{
+	unsigned int i;
+	const char *type_name;
+
+	if (code_min == code_max)
+		return 0;
+
+	type_name = icmp6_type_xlate(icmptype);
+
+	if (type_name) {
+		xt_xlate_add(xl, "%s", type_name);
+	} else {
+		for (i = 0; i < ARRAY_SIZE(icmpv6_codes); ++i)
+			if (icmpv6_codes[i].type == icmptype &&
+			    icmpv6_codes[i].code_min == code_min &&
+			    icmpv6_codes[i].code_max == code_max)
+				break;
+
+		if (i != ARRAY_SIZE(icmpv6_codes))
+			xt_xlate_add(xl, "%s", icmpv6_codes[i].name);
+		else
+			return 0;
+	}
+
+	return 1;
+}
+
+static int icmp6_xlate(struct xt_xlate *xl,
+		       const struct xt_xlate_mt_params *params)
+{
+	const struct ip6t_icmp *info = (struct ip6t_icmp *)params->match->data;
+
+	xt_xlate_add(xl, "icmpv6 type%s ",
+		     (info->invflags & IP6T_ICMP_INV) ? " !=" : "");
+
+	if (!type_xlate_print(xl, info->type, info->code[0], info->code[1]))
+		return 0;
+
+	return 1;
+}
+
 static struct xtables_match icmp6_mt6_reg = {
 	.name 		= "icmp6",
 	.version 	= XTABLES_VERSION,
@@ -231,6 +273,7 @@
 	.save		= icmp6_save,
 	.x6_parse	= icmp6_parse,
 	.x6_options	= icmp6_opts,
+	.xlate		= icmp6_xlate,
 };
 
 void _init(void)
diff --git a/extensions/libip6t_icmp6.t b/extensions/libip6t_icmp6.t
new file mode 100644
index 0000000..028cfc1
--- /dev/null
+++ b/extensions/libip6t_icmp6.t
@@ -0,0 +1,6 @@
+:INPUT,FORWARD,OUTPUT
+-m icmpv6;;FAIL
+-p ipv6-icmp -m icmp6 --icmpv6-type 1/0;=;OK
+-p ipv6-icmp -m icmp6 --icmpv6-type 2;=;OK
+# cannot use option twice:
+-p ipv6-icmp -m icmp6 --icmpv6-type no-route --icmpv6-type packet-too-big;;FAIL
diff --git a/extensions/libip6t_icmp6.txlate b/extensions/libip6t_icmp6.txlate
new file mode 100644
index 0000000..15481ad
--- /dev/null
+++ b/extensions/libip6t_icmp6.txlate
@@ -0,0 +1,8 @@
+ip6tables-translate -t filter -A INPUT -m icmp6 --icmpv6-type 1 -j LOG
+nft add rule ip6 filter INPUT icmpv6 type destination-unreachable counter log
+
+ip6tables-translate -t filter -A INPUT -m icmp6 --icmpv6-type neighbour-advertisement -j LOG
+nft add rule ip6 filter INPUT icmpv6 type nd-neighbor-advert counter log
+
+ip6tables-translate -t filter -A INPUT -m icmp6 ! --icmpv6-type packet-too-big -j LOG
+nft add rule ip6 filter INPUT icmpv6 type != packet-too-big counter log
diff --git a/extensions/libip6t_ipv6header.c b/extensions/libip6t_ipv6header.c
index 00d5d5b..6f03087 100644
--- a/extensions/libip6t_ipv6header.c
+++ b/extensions/libip6t_ipv6header.c
@@ -127,7 +127,7 @@
 	printf(
 "ipv6header match options:\n"
 "[!] --header headers     Type of header to match, by name\n"
-"                         names: hop,dst,route,frag,auth,esp,none,proto\n"
+"                         names: hop,dst,route,frag,auth,esp,none,prot\n"
 "                    long names: hop-by-hop,ipv6-opts,ipv6-route,\n"
 "                                ipv6-frag,ah,esp,ipv6-nonxt,protocol\n"
 "                       numbers: 0,60,43,44,51,50,59\n"
diff --git a/extensions/libip6t_ipv6header.man b/extensions/libip6t_ipv6header.man
index a998861..807d9ab 100644
--- a/extensions/libip6t_ipv6header.man
+++ b/extensions/libip6t_ipv6header.man
@@ -31,7 +31,7 @@
 No Next header which matches 59 in the 'Next Header field' of IPv6 header or
 any IPv6 extension headers
 .TP
-\fBproto\fP
+\fBprot\fP
 which matches any upper layer protocol header. A protocol name from
 /etc/protocols and numeric value also allowed. The number 255 is equivalent to
-\fBproto\fP.
+\fBprot\fP.
diff --git a/extensions/libip6t_ipv6header.t b/extensions/libip6t_ipv6header.t
new file mode 100644
index 0000000..67fa479
--- /dev/null
+++ b/extensions/libip6t_ipv6header.t
@@ -0,0 +1,4 @@
+:INPUT,FORWARD,OUTPUT
+-m ipv6header --header hop-by-hop;=;OK
+-m ipv6header --header hop-by-hop --soft;=;OK
+-m ipv6header --header ipv6-nonxt;=;OK
diff --git a/extensions/libip6t_mh.c b/extensions/libip6t_mh.c
index 686a293..f4c0fd9 100644
--- a/extensions/libip6t_mh.c
+++ b/extensions/libip6t_mh.c
@@ -202,6 +202,26 @@
 		printf(" --mh-type %u", mhinfo->types[0]);
 }
 
+static int mh_xlate(struct xt_xlate *xl,
+		    const struct xt_xlate_mt_params *params)
+{
+	const struct ip6t_mh *mhinfo = (struct ip6t_mh *)params->match->data;
+
+	if (mhinfo->types[0] == 0 && mhinfo->types[1] == 0xff)
+		return 1;
+
+	if (mhinfo->types[0] != mhinfo->types[1])
+		xt_xlate_add(xl, "mh type %s%u-%u",
+			     mhinfo->invflags & IP6T_MH_INV_TYPE ? "!= " : "",
+			     mhinfo->types[0], mhinfo->types[1]);
+	else
+		xt_xlate_add(xl, "mh type %s%u",
+			     mhinfo->invflags & IP6T_MH_INV_TYPE ? "!= " : "",
+			     mhinfo->types[0]);
+
+	return 1;
+}
+
 static const struct xt_option_entry mh_opts[] = {
 	{.name = "mh-type", .id = O_MH_TYPE, .type = XTTYPE_STRING,
 	 .flags = XTOPT_INVERT},
@@ -220,6 +240,7 @@
 	.print		= mh_print,
 	.save		= mh_save,
 	.x6_options	= mh_opts,
+	.xlate		= mh_xlate,
 };
 
 void _init(void)
diff --git a/extensions/libip6t_mh.man b/extensions/libip6t_mh.man
index 4559e78..8ec08c6 100644
--- a/extensions/libip6t_mh.man
+++ b/extensions/libip6t_mh.man
@@ -8,5 +8,5 @@
 .IR type
 or one of the MH type names shown by the command
 .nf
- ip6tables \-p ipv6\-mh \-h
+ ip6tables \-p mh \-h
 .fi
diff --git a/extensions/libip6t_mh.t b/extensions/libip6t_mh.t
new file mode 100644
index 0000000..6b76d13
--- /dev/null
+++ b/extensions/libip6t_mh.t
@@ -0,0 +1,6 @@
+:INPUT,FORWARD,OUTPUT
+-m mh;;FAIL
+-p mobility-header -m mh;=;OK
+-p mobility-header -m mh --mh-type 1;=;OK
+-p mobility-header -m mh ! --mh-type 4;=;OK
+-p mobility-header -m mh --mh-type 4:123;=;OK
diff --git a/extensions/libip6t_mh.txlate b/extensions/libip6t_mh.txlate
new file mode 100644
index 0000000..f5d638c
--- /dev/null
+++ b/extensions/libip6t_mh.txlate
@@ -0,0 +1,5 @@
+ip6tables-translate -A INPUT -p mh --mh-type 1 -j ACCEPT
+nft add rule ip6 filter INPUT meta l4proto mobility-header mh type 1 counter accept
+
+ip6tables-translate -A INPUT -p mh --mh-type 1:3 -j ACCEPT
+nft add rule ip6 filter INPUT meta l4proto mobility-header mh type 1-3 counter accept
diff --git a/extensions/libip6t_rt.c b/extensions/libip6t_rt.c
index d470488..3cb3b24 100644
--- a/extensions/libip6t_rt.c
+++ b/extensions/libip6t_rt.c
@@ -99,6 +99,13 @@
 	return i;
 }
 
+static void rt_init(struct xt_entry_match *m)
+{
+	struct ip6t_rt *rtinfo = (void *)m->data;
+
+	rtinfo->segsleft[1] = ~0U;
+}
+
 static void rt_parse(struct xt_option_call *cb)
 {
 	struct ip6t_rt *rtinfo = cb->data;
@@ -238,6 +245,43 @@
 
 }
 
+static int rt_xlate(struct xt_xlate *xl,
+		    const struct xt_xlate_mt_params *params)
+{
+	const struct ip6t_rt *rtinfo = (struct ip6t_rt *)params->match->data;
+	char *space = "";
+
+	if (rtinfo->flags & IP6T_RT_TYP) {
+		xt_xlate_add(xl, "rt type%s %u",
+			     (rtinfo->invflags & IP6T_RT_INV_TYP) ? " !=" : "",
+			      rtinfo->rt_type);
+		space = " ";
+	}
+
+	if (!(rtinfo->segsleft[0] == 0 && rtinfo->segsleft[1] == 0xFFFFFFFF)) {
+		xt_xlate_add(xl, "%srt seg-left%s ", space,
+			     (rtinfo->invflags & IP6T_RT_INV_SGS) ? " !=" : "");
+
+		if (rtinfo->segsleft[0] != rtinfo->segsleft[1])
+			xt_xlate_add(xl, "%u-%u", rtinfo->segsleft[0],
+					rtinfo->segsleft[1]);
+		else
+			xt_xlate_add(xl, "%u", rtinfo->segsleft[0]);
+		space = " ";
+	}
+
+	if (rtinfo->flags & IP6T_RT_LEN) {
+		xt_xlate_add(xl, "%srt hdrlength%s %u", space,
+			     (rtinfo->invflags & IP6T_RT_INV_LEN) ? " !=" : "",
+			      rtinfo->hdrlen);
+	}
+
+	if (rtinfo->flags & (IP6T_RT_RES | IP6T_RT_FST | IP6T_RT_FST_NSTRICT))
+		return 0;
+
+	return 1;
+}
+
 static struct xtables_match rt_mt6_reg = {
 	.name		= "rt",
 	.version	= XTABLES_VERSION,
@@ -245,10 +289,12 @@
 	.size		= XT_ALIGN(sizeof(struct ip6t_rt)),
 	.userspacesize	= XT_ALIGN(sizeof(struct ip6t_rt)),
 	.help		= rt_help,
+	.init		= rt_init,
 	.x6_parse	= rt_parse,
 	.print		= rt_print,
 	.save		= rt_save,
 	.x6_options	= rt_opts,
+	.xlate		= rt_xlate,
 };
 
 void
diff --git a/extensions/libip6t_rt.t b/extensions/libip6t_rt.t
new file mode 100644
index 0000000..3c7b2d9
--- /dev/null
+++ b/extensions/libip6t_rt.t
@@ -0,0 +1,5 @@
+:INPUT,FORWARD,OUTPUT
+-m rt --rt-type 0 --rt-segsleft 1:23 --rt-len 42 --rt-0-res;=;OK
+-m rt --rt-type 0 ! --rt-segsleft 1:23 ! --rt-len 42 --rt-0-res;=;OK
+-m rt ! --rt-type 1 ! --rt-segsleft 12:23 ! --rt-len 42;=;OK
+-m rt;=;OK
diff --git a/extensions/libip6t_rt.txlate b/extensions/libip6t_rt.txlate
new file mode 100644
index 0000000..6464cf9
--- /dev/null
+++ b/extensions/libip6t_rt.txlate
@@ -0,0 +1,14 @@
+ip6tables-translate -A INPUT -m rt --rt-type 0 -j DROP
+nft add rule ip6 filter INPUT rt type 0 counter drop
+
+ip6tables-translate -A INPUT -m rt ! --rt-len 22 -j DROP
+nft add rule ip6 filter INPUT rt hdrlength != 22 counter drop
+
+ip6tables-translate -A INPUT -m rt --rt-segsleft 26 -j ACCEPT
+nft add rule ip6 filter INPUT rt seg-left 26 counter accept
+
+ip6tables-translate -A INPUT -m rt --rt-type 0 --rt-len 22 -j DROP
+nft add rule ip6 filter INPUT rt type 0 rt hdrlength 22 counter drop
+
+ip6tables-translate -A INPUT -m rt --rt-type 0 --rt-len 22 ! --rt-segsleft 26 -j ACCEPT
+nft add rule ip6 filter INPUT rt type 0 rt seg-left != 26 rt hdrlength 22 counter accept
diff --git a/extensions/libip6t_srh.c b/extensions/libip6t_srh.c
new file mode 100644
index 0000000..94db6f1
--- /dev/null
+++ b/extensions/libip6t_srh.c
@@ -0,0 +1,501 @@
+/* Shared library to add Segment Routing Header (SRH) matching support.
+ *
+ * Author:
+ *       Ahmed Abdelsalam       <amsalam20@gmail.com>
+ */
+
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter_ipv6/ip6t_srh.h>
+#include <string.h>
+
+/* srh command-line options */
+enum {
+	O_SRH_NEXTHDR,
+	O_SRH_LEN_EQ,
+	O_SRH_LEN_GT,
+	O_SRH_LEN_LT,
+	O_SRH_SEGS_EQ,
+	O_SRH_SEGS_GT,
+	O_SRH_SEGS_LT,
+	O_SRH_LAST_EQ,
+	O_SRH_LAST_GT,
+	O_SRH_LAST_LT,
+	O_SRH_TAG,
+	O_SRH_PSID,
+	O_SRH_NSID,
+	O_SRH_LSID,
+};
+
+static void srh_help(void)
+{
+	printf(
+"srh match options:\n"
+"[!] --srh-next-hdr		next-hdr        Next Header value of SRH\n"
+"[!] --srh-hdr-len-eq		hdr_len         Hdr Ext Len value of SRH\n"
+"[!] --srh-hdr-len-gt		hdr_len         Hdr Ext Len value of SRH\n"
+"[!] --srh-hdr-len-lt		hdr_len         Hdr Ext Len value of SRH\n"
+"[!] --srh-segs-left-eq		segs_left       Segments Left value of SRH\n"
+"[!] --srh-segs-left-gt		segs_left       Segments Left value of SRH\n"
+"[!] --srh-segs-left-lt		segs_left       Segments Left value of SRH\n"
+"[!] --srh-last-entry-eq 	last_entry      Last Entry value of SRH\n"
+"[!] --srh-last-entry-gt 	last_entry      Last Entry value of SRH\n"
+"[!] --srh-last-entry-lt 	last_entry      Last Entry value of SRH\n"
+"[!] --srh-tag			tag             Tag value of SRH\n"
+"[!] --srh-psid			addr[/mask]	SRH previous SID\n"
+"[!] --srh-nsid			addr[/mask]	SRH next SID\n"
+"[!] --srh-lsid			addr[/mask]	SRH Last SID\n");
+}
+
+#define s struct ip6t_srh
+static const struct xt_option_entry srh_opts[] = {
+	{ .name = "srh-next-hdr", .id = O_SRH_NEXTHDR, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, next_hdr)},
+	{ .name = "srh-hdr-len-eq", .id = O_SRH_LEN_EQ, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, hdr_len)},
+	{ .name = "srh-hdr-len-gt", .id = O_SRH_LEN_GT, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, hdr_len)},
+	{ .name = "srh-hdr-len-lt", .id = O_SRH_LEN_LT, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, hdr_len)},
+	{ .name = "srh-segs-left-eq", .id = O_SRH_SEGS_EQ, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, segs_left)},
+	{ .name = "srh-segs-left-gt", .id = O_SRH_SEGS_GT, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, segs_left)},
+	{ .name = "srh-segs-left-lt", .id = O_SRH_SEGS_LT, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, segs_left)},
+	{ .name = "srh-last-entry-eq", .id = O_SRH_LAST_EQ, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, last_entry)},
+	{ .name = "srh-last-entry-gt", .id = O_SRH_LAST_GT, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, last_entry)},
+	{ .name = "srh-last-entry-lt", .id = O_SRH_LAST_LT, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, last_entry)},
+	{ .name = "srh-tag", .id = O_SRH_TAG, .type = XTTYPE_UINT16,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, tag)},
+	{ }
+};
+#undef s
+
+#define s struct ip6t_srh1
+static const struct xt_option_entry srh1_opts[] = {
+	{ .name = "srh-next-hdr", .id = O_SRH_NEXTHDR, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, next_hdr)},
+	{ .name = "srh-hdr-len-eq", .id = O_SRH_LEN_EQ, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, hdr_len)},
+	{ .name = "srh-hdr-len-gt", .id = O_SRH_LEN_GT, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, hdr_len)},
+	{ .name = "srh-hdr-len-lt", .id = O_SRH_LEN_LT, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, hdr_len)},
+	{ .name = "srh-segs-left-eq", .id = O_SRH_SEGS_EQ, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, segs_left)},
+	{ .name = "srh-segs-left-gt", .id = O_SRH_SEGS_GT, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, segs_left)},
+	{ .name = "srh-segs-left-lt", .id = O_SRH_SEGS_LT, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, segs_left)},
+	{ .name = "srh-last-entry-eq", .id = O_SRH_LAST_EQ, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, last_entry)},
+	{ .name = "srh-last-entry-gt", .id = O_SRH_LAST_GT, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, last_entry)},
+	{ .name = "srh-last-entry-lt", .id = O_SRH_LAST_LT, .type = XTTYPE_UINT8,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, last_entry)},
+	{ .name = "srh-tag", .id = O_SRH_TAG, .type = XTTYPE_UINT16,
+	.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, tag)},
+	{ .name = "srh-psid", .id = O_SRH_PSID, .type = XTTYPE_HOSTMASK,
+	.flags = XTOPT_INVERT},
+	{ .name = "srh-nsid", .id = O_SRH_NSID, .type = XTTYPE_HOSTMASK,
+	.flags = XTOPT_INVERT},
+	{ .name = "srh-lsid", .id = O_SRH_LSID, .type = XTTYPE_HOSTMASK,
+	.flags = XTOPT_INVERT},
+	{ }
+};
+#undef s
+
+static void srh_init(struct xt_entry_match *m)
+{
+	struct ip6t_srh *srhinfo = (void *)m->data;
+
+	srhinfo->mt_flags = 0;
+	srhinfo->mt_invflags = 0;
+}
+
+static void srh1_init(struct xt_entry_match *m)
+{
+	struct ip6t_srh1 *srhinfo = (void *)m->data;
+
+	srhinfo->mt_flags = 0;
+	srhinfo->mt_invflags = 0;
+	memset(srhinfo->psid_addr.s6_addr, 0, sizeof(srhinfo->psid_addr.s6_addr));
+	memset(srhinfo->nsid_addr.s6_addr, 0, sizeof(srhinfo->nsid_addr.s6_addr));
+	memset(srhinfo->lsid_addr.s6_addr, 0, sizeof(srhinfo->lsid_addr.s6_addr));
+	memset(srhinfo->psid_msk.s6_addr, 0, sizeof(srhinfo->psid_msk.s6_addr));
+	memset(srhinfo->nsid_msk.s6_addr, 0, sizeof(srhinfo->nsid_msk.s6_addr));
+	memset(srhinfo->lsid_msk.s6_addr, 0, sizeof(srhinfo->lsid_msk.s6_addr));
+}
+
+static void srh_parse(struct xt_option_call *cb)
+{
+	struct ip6t_srh *srhinfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SRH_NEXTHDR:
+		srhinfo->mt_flags |= IP6T_SRH_NEXTHDR;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_NEXTHDR;
+		break;
+	case O_SRH_LEN_EQ:
+		srhinfo->mt_flags |= IP6T_SRH_LEN_EQ;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_LEN_EQ;
+		break;
+	case O_SRH_LEN_GT:
+		srhinfo->mt_flags |= IP6T_SRH_LEN_GT;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_LEN_GT;
+		break;
+	case O_SRH_LEN_LT:
+		srhinfo->mt_flags |= IP6T_SRH_LEN_LT;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_LEN_LT;
+		break;
+	case O_SRH_SEGS_EQ:
+		srhinfo->mt_flags |= IP6T_SRH_SEGS_EQ;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_SEGS_EQ;
+		break;
+	case O_SRH_SEGS_GT:
+		srhinfo->mt_flags |= IP6T_SRH_SEGS_GT;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_SEGS_GT;
+		break;
+	case O_SRH_SEGS_LT:
+		srhinfo->mt_flags |= IP6T_SRH_SEGS_LT;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_SEGS_LT;
+		break;
+	case O_SRH_LAST_EQ:
+		srhinfo->mt_flags |= IP6T_SRH_LAST_EQ;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_LAST_EQ;
+		break;
+	case O_SRH_LAST_GT:
+		srhinfo->mt_flags |= IP6T_SRH_LAST_GT;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_LAST_GT;
+		break;
+	case O_SRH_LAST_LT:
+		srhinfo->mt_flags |= IP6T_SRH_LAST_LT;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_LAST_LT;
+		break;
+	case O_SRH_TAG:
+		srhinfo->mt_flags |= IP6T_SRH_TAG;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_TAG;
+		break;
+	}
+}
+
+static void srh1_parse(struct xt_option_call *cb)
+{
+	struct ip6t_srh1 *srhinfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SRH_NEXTHDR:
+		srhinfo->mt_flags |= IP6T_SRH_NEXTHDR;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_NEXTHDR;
+		break;
+	case O_SRH_LEN_EQ:
+		srhinfo->mt_flags |= IP6T_SRH_LEN_EQ;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_LEN_EQ;
+		break;
+	case O_SRH_LEN_GT:
+		srhinfo->mt_flags |= IP6T_SRH_LEN_GT;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_LEN_GT;
+		break;
+	case O_SRH_LEN_LT:
+		srhinfo->mt_flags |= IP6T_SRH_LEN_LT;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_LEN_LT;
+		break;
+	case O_SRH_SEGS_EQ:
+		srhinfo->mt_flags |= IP6T_SRH_SEGS_EQ;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_SEGS_EQ;
+		break;
+	case O_SRH_SEGS_GT:
+		srhinfo->mt_flags |= IP6T_SRH_SEGS_GT;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_SEGS_GT;
+		break;
+	case O_SRH_SEGS_LT:
+		srhinfo->mt_flags |= IP6T_SRH_SEGS_LT;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_SEGS_LT;
+		break;
+	case O_SRH_LAST_EQ:
+		srhinfo->mt_flags |= IP6T_SRH_LAST_EQ;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_LAST_EQ;
+		break;
+	case O_SRH_LAST_GT:
+		srhinfo->mt_flags |= IP6T_SRH_LAST_GT;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_LAST_GT;
+		break;
+	case O_SRH_LAST_LT:
+		srhinfo->mt_flags |= IP6T_SRH_LAST_LT;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_LAST_LT;
+		break;
+	case O_SRH_TAG:
+		srhinfo->mt_flags |= IP6T_SRH_TAG;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_TAG;
+		break;
+	case O_SRH_PSID:
+		srhinfo->mt_flags |= IP6T_SRH_PSID;
+		srhinfo->psid_addr = cb->val.haddr.in6;
+		srhinfo->psid_msk  = cb->val.hmask.in6;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_PSID;
+		break;
+	case O_SRH_NSID:
+		srhinfo->mt_flags |= IP6T_SRH_NSID;
+		srhinfo->nsid_addr = cb->val.haddr.in6;
+		srhinfo->nsid_msk  = cb->val.hmask.in6;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_NSID;
+		break;
+	case O_SRH_LSID:
+		srhinfo->mt_flags |= IP6T_SRH_LSID;
+		srhinfo->lsid_addr = cb->val.haddr.in6;
+		srhinfo->lsid_msk  = cb->val.hmask.in6;
+		if (cb->invert)
+			srhinfo->mt_invflags |= IP6T_SRH_INV_LSID;
+		break;
+	}
+}
+
+static void srh_print(const void *ip, const struct xt_entry_match *match,
+			int numeric)
+{
+	const struct ip6t_srh *srhinfo = (struct ip6t_srh *)match->data;
+
+	printf(" srh");
+	if (srhinfo->mt_flags & IP6T_SRH_NEXTHDR)
+		printf(" next-hdr:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_NEXTHDR ? "!" : "",
+			srhinfo->next_hdr);
+	if (srhinfo->mt_flags & IP6T_SRH_LEN_EQ)
+		printf(" hdr-len-eq:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_LEN_EQ ? "!" : "",
+			srhinfo->hdr_len);
+	if (srhinfo->mt_flags & IP6T_SRH_LEN_GT)
+		printf(" hdr-len-gt:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_LEN_GT ? "!" : "",
+			srhinfo->hdr_len);
+	if (srhinfo->mt_flags & IP6T_SRH_LEN_LT)
+		printf(" hdr-len-lt:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_LEN_LT ? "!" : "",
+			srhinfo->hdr_len);
+	if (srhinfo->mt_flags & IP6T_SRH_SEGS_EQ)
+		printf(" segs-left-eq:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_SEGS_EQ ? "!" : "",
+			srhinfo->segs_left);
+	if (srhinfo->mt_flags & IP6T_SRH_SEGS_GT)
+		printf(" segs-left-gt:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_SEGS_GT ? "!" : "",
+			srhinfo->segs_left);
+	if (srhinfo->mt_flags & IP6T_SRH_SEGS_LT)
+		printf(" segs-left-lt:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_SEGS_LT ? "!" : "",
+			srhinfo->segs_left);
+	if (srhinfo->mt_flags & IP6T_SRH_LAST_EQ)
+		printf(" last-entry-eq:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_LAST_EQ ? "!" : "",
+			srhinfo->last_entry);
+	if (srhinfo->mt_flags & IP6T_SRH_LAST_GT)
+		printf(" last-entry-gt:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_LAST_GT ? "!" : "",
+			srhinfo->last_entry);
+	if (srhinfo->mt_flags & IP6T_SRH_LAST_LT)
+		printf(" last-entry-lt:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_LAST_LT ? "!" : "",
+			srhinfo->last_entry);
+	if (srhinfo->mt_flags & IP6T_SRH_TAG)
+		printf(" tag:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_TAG ? "!" : "",
+			srhinfo->tag);
+}
+
+static void srh1_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct ip6t_srh1 *srhinfo = (struct ip6t_srh1 *)match->data;
+
+	printf(" srh");
+	if (srhinfo->mt_flags & IP6T_SRH_NEXTHDR)
+		printf(" next-hdr:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_NEXTHDR ? "!" : "",
+			srhinfo->next_hdr);
+	if (srhinfo->mt_flags & IP6T_SRH_LEN_EQ)
+		printf(" hdr-len-eq:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_LEN_EQ ? "!" : "",
+			srhinfo->hdr_len);
+	if (srhinfo->mt_flags & IP6T_SRH_LEN_GT)
+		printf(" hdr-len-gt:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_LEN_GT ? "!" : "",
+			srhinfo->hdr_len);
+	if (srhinfo->mt_flags & IP6T_SRH_LEN_LT)
+		printf(" hdr-len-lt:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_LEN_LT ? "!" : "",
+			srhinfo->hdr_len);
+	if (srhinfo->mt_flags & IP6T_SRH_SEGS_EQ)
+		printf(" segs-left-eq:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_SEGS_EQ ? "!" : "",
+			srhinfo->segs_left);
+	if (srhinfo->mt_flags & IP6T_SRH_SEGS_GT)
+		printf(" segs-left-gt:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_SEGS_GT ? "!" : "",
+			srhinfo->segs_left);
+	if (srhinfo->mt_flags & IP6T_SRH_SEGS_LT)
+		printf(" segs-left-lt:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_SEGS_LT ? "!" : "",
+			srhinfo->segs_left);
+	if (srhinfo->mt_flags & IP6T_SRH_LAST_EQ)
+		printf(" last-entry-eq:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_LAST_EQ ? "!" : "",
+			srhinfo->last_entry);
+	if (srhinfo->mt_flags & IP6T_SRH_LAST_GT)
+		printf(" last-entry-gt:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_LAST_GT ? "!" : "",
+			srhinfo->last_entry);
+	if (srhinfo->mt_flags & IP6T_SRH_LAST_LT)
+		printf(" last-entry-lt:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_LAST_LT ? "!" : "",
+			srhinfo->last_entry);
+	if (srhinfo->mt_flags & IP6T_SRH_TAG)
+		printf(" tag:%s%d", srhinfo->mt_invflags & IP6T_SRH_INV_TAG ? "!" : "",
+			srhinfo->tag);
+	if (srhinfo->mt_flags & IP6T_SRH_PSID)
+		printf(" psid %s %s/%u", srhinfo->mt_invflags & IP6T_SRH_INV_PSID ? "!" : "",
+			xtables_ip6addr_to_numeric(&srhinfo->psid_addr),
+			xtables_ip6mask_to_cidr(&srhinfo->psid_msk));
+	if (srhinfo->mt_flags & IP6T_SRH_NSID)
+		printf(" nsid %s %s/%u", srhinfo->mt_invflags & IP6T_SRH_INV_NSID ? "!" : "",
+			xtables_ip6addr_to_numeric(&srhinfo->nsid_addr),
+			xtables_ip6mask_to_cidr(&srhinfo->nsid_msk));
+	if (srhinfo->mt_flags & IP6T_SRH_LSID)
+		printf(" lsid %s %s/%u", srhinfo->mt_invflags & IP6T_SRH_INV_LSID ? "!" : "",
+			xtables_ip6addr_to_numeric(&srhinfo->lsid_addr),
+			xtables_ip6mask_to_cidr(&srhinfo->lsid_msk));
+}
+
+static void srh_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct ip6t_srh *srhinfo = (struct ip6t_srh *)match->data;
+
+	if (srhinfo->mt_flags & IP6T_SRH_NEXTHDR)
+		printf("%s --srh-next-hdr %u", (srhinfo->mt_invflags & IP6T_SRH_INV_NEXTHDR) ? " !" : "",
+			srhinfo->next_hdr);
+	if (srhinfo->mt_flags & IP6T_SRH_LEN_EQ)
+		printf("%s --srh-hdr-len-eq %u", (srhinfo->mt_invflags & IP6T_SRH_INV_LEN_EQ) ? " !" : "",
+			srhinfo->hdr_len);
+	if (srhinfo->mt_flags & IP6T_SRH_LEN_GT)
+		printf("%s --srh-hdr-len-gt %u", (srhinfo->mt_invflags & IP6T_SRH_INV_LEN_GT) ? " !" : "",
+			srhinfo->hdr_len);
+	if (srhinfo->mt_flags & IP6T_SRH_LEN_LT)
+		printf("%s --srh-hdr-len-lt %u", (srhinfo->mt_invflags & IP6T_SRH_INV_LEN_LT) ? " !" : "",
+			srhinfo->hdr_len);
+	if (srhinfo->mt_flags & IP6T_SRH_SEGS_EQ)
+		printf("%s --srh-segs-left-eq %u", (srhinfo->mt_invflags & IP6T_SRH_INV_SEGS_EQ) ? " !" : "",
+			srhinfo->segs_left);
+	if (srhinfo->mt_flags & IP6T_SRH_SEGS_GT)
+		printf("%s --srh-segs-left-gt %u", (srhinfo->mt_invflags & IP6T_SRH_INV_SEGS_GT) ? " !" : "",
+			srhinfo->segs_left);
+	if (srhinfo->mt_flags & IP6T_SRH_SEGS_LT)
+		printf("%s --srh-segs-left-lt %u", (srhinfo->mt_invflags & IP6T_SRH_INV_SEGS_LT) ? " !" : "",
+			srhinfo->segs_left);
+	if (srhinfo->mt_flags & IP6T_SRH_LAST_EQ)
+		printf("%s --srh-last-entry-eq %u", (srhinfo->mt_invflags & IP6T_SRH_INV_LAST_EQ) ? " !" : "",
+			srhinfo->last_entry);
+	if (srhinfo->mt_flags & IP6T_SRH_LAST_GT)
+		printf("%s --srh-last-entry-gt %u", (srhinfo->mt_invflags & IP6T_SRH_INV_LAST_GT) ? " !" : "",
+			srhinfo->last_entry);
+	if (srhinfo->mt_flags & IP6T_SRH_LAST_LT)
+		printf("%s --srh-last-entry-lt %u", (srhinfo->mt_invflags & IP6T_SRH_INV_LAST_LT) ? " !" : "",
+			srhinfo->last_entry);
+	if (srhinfo->mt_flags & IP6T_SRH_TAG)
+		printf("%s --srh-tag %u", (srhinfo->mt_invflags & IP6T_SRH_INV_TAG) ? " !" : "",
+			srhinfo->tag);
+}
+
+static void srh1_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct ip6t_srh1 *srhinfo = (struct ip6t_srh1 *)match->data;
+
+	if (srhinfo->mt_flags & IP6T_SRH_NEXTHDR)
+		printf("%s --srh-next-hdr %u", (srhinfo->mt_invflags & IP6T_SRH_INV_NEXTHDR) ? " !" : "",
+			srhinfo->next_hdr);
+	if (srhinfo->mt_flags & IP6T_SRH_LEN_EQ)
+		printf("%s --srh-hdr-len-eq %u", (srhinfo->mt_invflags & IP6T_SRH_INV_LEN_EQ) ? " !" : "",
+			srhinfo->hdr_len);
+	if (srhinfo->mt_flags & IP6T_SRH_LEN_GT)
+		printf("%s --srh-hdr-len-gt %u", (srhinfo->mt_invflags & IP6T_SRH_INV_LEN_GT) ? " !" : "",
+			srhinfo->hdr_len);
+	if (srhinfo->mt_flags & IP6T_SRH_LEN_LT)
+		printf("%s --srh-hdr-len-lt %u", (srhinfo->mt_invflags & IP6T_SRH_INV_LEN_LT) ? " !" : "",
+			srhinfo->hdr_len);
+	if (srhinfo->mt_flags & IP6T_SRH_SEGS_EQ)
+		printf("%s --srh-segs-left-eq %u", (srhinfo->mt_invflags & IP6T_SRH_INV_SEGS_EQ) ? " !" : "",
+			srhinfo->segs_left);
+	if (srhinfo->mt_flags & IP6T_SRH_SEGS_GT)
+		printf("%s --srh-segs-left-gt %u", (srhinfo->mt_invflags & IP6T_SRH_INV_SEGS_GT) ? " !" : "",
+			srhinfo->segs_left);
+	if (srhinfo->mt_flags & IP6T_SRH_SEGS_LT)
+		printf("%s --srh-segs-left-lt %u", (srhinfo->mt_invflags & IP6T_SRH_INV_SEGS_LT) ? " !" : "",
+			srhinfo->segs_left);
+	if (srhinfo->mt_flags & IP6T_SRH_LAST_EQ)
+		printf("%s --srh-last-entry-eq %u", (srhinfo->mt_invflags & IP6T_SRH_INV_LAST_EQ) ? " !" : "",
+			srhinfo->last_entry);
+	if (srhinfo->mt_flags & IP6T_SRH_LAST_GT)
+		printf("%s --srh-last-entry-gt %u", (srhinfo->mt_invflags & IP6T_SRH_INV_LAST_GT) ? " !" : "",
+			srhinfo->last_entry);
+	if (srhinfo->mt_flags & IP6T_SRH_LAST_LT)
+		printf("%s --srh-last-entry-lt %u", (srhinfo->mt_invflags & IP6T_SRH_INV_LAST_LT) ? " !" : "",
+			srhinfo->last_entry);
+	if (srhinfo->mt_flags & IP6T_SRH_TAG)
+		printf("%s --srh-tag %u", (srhinfo->mt_invflags & IP6T_SRH_INV_TAG) ? " !" : "",
+			srhinfo->tag);
+	if (srhinfo->mt_flags & IP6T_SRH_PSID)
+		printf("%s --srh-psid %s/%u", srhinfo->mt_invflags & IP6T_SRH_INV_PSID ? " !" : "",
+			xtables_ip6addr_to_numeric(&srhinfo->psid_addr),
+			xtables_ip6mask_to_cidr(&srhinfo->psid_msk));
+	if (srhinfo->mt_flags & IP6T_SRH_NSID)
+		printf("%s --srh-nsid %s/%u", srhinfo->mt_invflags & IP6T_SRH_INV_NSID ? " !" : "",
+			xtables_ip6addr_to_numeric(&srhinfo->nsid_addr),
+			xtables_ip6mask_to_cidr(&srhinfo->nsid_msk));
+	if (srhinfo->mt_flags & IP6T_SRH_LSID)
+		printf("%s --srh-lsid %s/%u", srhinfo->mt_invflags & IP6T_SRH_INV_LSID ? " !" : "",
+			xtables_ip6addr_to_numeric(&srhinfo->lsid_addr),
+			xtables_ip6mask_to_cidr(&srhinfo->lsid_msk));
+}
+
+static struct xtables_match srh_mt6_reg[] = {
+	{
+		.name		= "srh",
+		.version	= XTABLES_VERSION,
+		.revision	= 0,
+		.family		= NFPROTO_IPV6,
+		.size		= XT_ALIGN(sizeof(struct ip6t_srh)),
+		.userspacesize	= XT_ALIGN(sizeof(struct ip6t_srh)),
+		.help		= srh_help,
+		.init		= srh_init,
+		.print		= srh_print,
+		.save		= srh_save,
+		.x6_parse	= srh_parse,
+		.x6_options	= srh_opts,
+	},
+	{
+		.name		= "srh",
+		.version	= XTABLES_VERSION,
+		.revision	= 1,
+		.family		= NFPROTO_IPV6,
+		.size		= XT_ALIGN(sizeof(struct ip6t_srh1)),
+		.userspacesize	= XT_ALIGN(sizeof(struct ip6t_srh1)),
+		.help		= srh_help,
+		.init		= srh1_init,
+		.print		= srh1_print,
+		.save		= srh1_save,
+		.x6_parse	= srh1_parse,
+		.x6_options	= srh1_opts,
+	}
+};
+
+void
+_init(void)
+{
+	xtables_register_matches(srh_mt6_reg, ARRAY_SIZE(srh_mt6_reg));
+}
diff --git a/extensions/libip6t_srh.t b/extensions/libip6t_srh.t
new file mode 100644
index 0000000..5b02a71
--- /dev/null
+++ b/extensions/libip6t_srh.t
@@ -0,0 +1,28 @@
+:INPUT,FORWARD,OUTPUT
+-m srh --srh-next-hdr 17;=;OK
+-m srh --srh-hdr-len-eq 8;=;OK
+-m srh --srh-hdr-len-gt 8;=;OK
+-m srh --srh-hdr-len-lt 8;=;OK
+-m srh --srh-segs-left-eq 1;=;OK
+-m srh --srh-segs-left-gt 1;=;OK
+-m srh --srh-segs-left-lt 1;=;OK
+-m srh --srh-last-entry-eq 4;=;OK
+-m srh --srh-last-entry-gt 4;=;OK
+-m srh --srh-last-entry-lt 4;=;OK
+-m srh --srh-tag 0;=;OK
+-m srh ! --srh-next-hdr 17;=;OK
+-m srh ! --srh-hdr-len-eq 8;=;OK
+-m srh ! --srh-hdr-len-gt 8;=;OK
+-m srh ! --srh-hdr-len-lt 8;=;OK
+-m srh ! --srh-segs-left-eq 1;=;OK
+-m srh ! --srh-segs-left-gt 1;=;OK
+-m srh ! --srh-segs-left-lt 1;=;OK
+-m srh ! --srh-last-entry-eq 4;=;OK
+-m srh ! --srh-last-entry-gt 4;=;OK
+-m srh ! --srh-last-entry-lt 4;=;OK
+-m srh ! --srh-tag 0;=;OK
+-m srh --srh-next-hdr 17 --srh-segs-left-eq 1 --srh-last-entry-eq 4 --srh-tag 0;=;OK
+-m srh ! --srh-next-hdr 17 ! --srh-segs-left-eq 0 --srh-tag 0;=;OK
+-m srh --srh-psid a::/64 --srh-nsid b::/128 --srh-lsid c::/0;=;OK
+-m srh ! --srh-psid a::/64 ! --srh-nsid b::/128 ! --srh-lsid c::/0;=;OK
+-m srh;=;OK
diff --git a/extensions/libip6t_standard.t b/extensions/libip6t_standard.t
new file mode 100644
index 0000000..a528af1
--- /dev/null
+++ b/extensions/libip6t_standard.t
@@ -0,0 +1,5 @@
+:INPUT,FORWARD,OUTPUT
+-s ::/128;=;OK
+! -d ::;! -d ::/128;OK
+! -s ::;! -s ::/128;OK
+-s ::/64;=;OK
diff --git a/extensions/libipt_CLUSTERIP.man b/extensions/libipt_CLUSTERIP.man
index 8ec6d6b..768bb23 100644
--- a/extensions/libipt_CLUSTERIP.man
+++ b/extensions/libipt_CLUSTERIP.man
@@ -2,6 +2,9 @@
 a certain IP and MAC address without an explicit load balancer in front of
 them.  Connections are statically distributed between the nodes in this
 cluster.
+.PP
+Please note that CLUSTERIP target is considered deprecated in favour of cluster
+match which is more flexible and not limited to IPv4.
 .TP
 \fB\-\-new\fP
 Create a new ClusterIP.  You always have to set this on the first rule
diff --git a/extensions/libipt_CLUSTERIP.t b/extensions/libipt_CLUSTERIP.t
new file mode 100644
index 0000000..5af555e
--- /dev/null
+++ b/extensions/libipt_CLUSTERIP.t
@@ -0,0 +1,4 @@
+:INPUT
+-d 10.31.3.236/32 -i lo -j CLUSTERIP --new --hashmode sourceip --clustermac 01:AA:7B:47:F7:D7 --total-nodes 2 --local-node 0 --hash-init 1;=;FAIL
+-d 10.31.3.236/32 -i lo -j CLUSTERIP --new --hashmode sourceip --clustermac 01:AA:7B:47:F7:D7 --total-nodes 2 --local-node 1 --hash-init 1;=;OK
+-d 10.31.3.236/32 -i lo -j CLUSTERIP --new --hashmode sourceip --clustermac 01:AA:7B:47:F7:D7 --total-nodes 2 --local-node 2 --hash-init 1;=;OK
diff --git a/extensions/libipt_DNAT.c b/extensions/libipt_DNAT.c
index 3b55c69..4907a2e 100644
--- a/extensions/libipt_DNAT.c
+++ b/extensions/libipt_DNAT.c
@@ -6,7 +6,7 @@
 #include <iptables.h> /* get_kernel_version */
 #include <limits.h> /* INT_MAX in ip_tables.h */
 #include <linux/netfilter_ipv4/ip_tables.h>
-#include <net/netfilter/nf_nat.h>
+#include <linux/netfilter/nf_nat.h>
 
 enum {
 	O_TO_DEST = 0,
@@ -23,7 +23,7 @@
 struct ipt_natinfo
 {
 	struct xt_entry_target t;
-	struct nf_nat_multi_range mr;
+	struct nf_nat_ipv4_multi_range_compat mr;
 };
 
 static void DNAT_help(void)
@@ -35,6 +35,15 @@
 "[--random] [--persistent]\n");
 }
 
+static void DNAT_help_v2(void)
+{
+	printf(
+"DNAT target options:\n"
+" --to-destination [<ipaddr>[-<ipaddr>]][:port[-port[/port]]]\n"
+"				Address to map destination to.\n"
+"[--random] [--persistent]\n");
+}
+
 static const struct xt_option_entry DNAT_opts[] = {
 	{.name = "to-destination", .id = O_TO_DEST, .type = XTTYPE_STRING,
 	 .flags = XTOPT_MAND | XTOPT_MULTI},
@@ -44,7 +53,7 @@
 };
 
 static struct ipt_natinfo *
-append_range(struct ipt_natinfo *info, const struct nf_nat_range *range)
+append_range(struct ipt_natinfo *info, const struct nf_nat_ipv4_range *range)
 {
 	unsigned int size;
 
@@ -66,7 +75,7 @@
 static struct xt_entry_target *
 parse_to(const char *orig_arg, int portok, struct ipt_natinfo *info)
 {
-	struct nf_nat_range range;
+	struct nf_nat_ipv4_range range;
 	char *arg, *colon, *dash, *error;
 	const struct in_addr *ip;
 
@@ -83,7 +92,7 @@
 			xtables_error(PARAMETER_PROBLEM,
 				   "Need TCP, UDP, SCTP or DCCP with port specification");
 
-		range.flags |= IP_NAT_RANGE_PROTO_SPECIFIED;
+		range.flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
 
 		port = atoi(colon+1);
 		if (port <= 0 || port > 65535)
@@ -122,7 +131,7 @@
 		*colon = '\0';
 	}
 
-	range.flags |= IP_NAT_RANGE_MAP_IPS;
+	range.flags |= NF_NAT_RANGE_MAP_IPS;
 	dash = strchr(arg, '-');
 	if (colon && dash && dash > colon)
 		dash = NULL;
@@ -174,24 +183,26 @@
 					   "DNAT: Multiple --to-destination not supported");
 		}
 		*cb->target = parse_to(cb->arg, portok, info);
-		/* WTF do we need this for?? */
-		if (cb->xflags & F_RANDOM)
-			info->mr.range[0].flags |= IP_NAT_RANGE_PROTO_RANDOM;
 		cb->xflags |= F_X_TO_DEST;
 		break;
-	case O_RANDOM:
-		if (cb->xflags & F_TO_DEST)
-			info->mr.range[0].flags |= IP_NAT_RANGE_PROTO_RANDOM;
-		break;
 	case O_PERSISTENT:
-		info->mr.range[0].flags |= IP_NAT_RANGE_PERSISTENT;
+		info->mr.range[0].flags |= NF_NAT_RANGE_PERSISTENT;
 		break;
 	}
 }
 
-static void print_range(const struct nf_nat_range *r)
+static void DNAT_fcheck(struct xt_fcheck_call *cb)
 {
-	if (r->flags & IP_NAT_RANGE_MAP_IPS) {
+	static const unsigned int f = F_TO_DEST | F_RANDOM;
+	struct nf_nat_ipv4_multi_range_compat *mr = cb->data;
+
+	if ((cb->xflags & f) == f)
+		mr->range[0].flags |= NF_NAT_RANGE_PROTO_RANDOM;
+}
+
+static void print_range(const struct nf_nat_ipv4_range *r)
+{
+	if (r->flags & NF_NAT_RANGE_MAP_IPS) {
 		struct in_addr a;
 
 		a.s_addr = r->min_ip;
@@ -201,7 +212,7 @@
 			printf("-%s", xtables_ipaddr_to_numeric(&a));
 		}
 	}
-	if (r->flags & IP_NAT_RANGE_PROTO_SPECIFIED) {
+	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
 		printf(":");
 		printf("%hu", ntohs(r->min.tcp.port));
 		if (r->max.tcp.port != r->min.tcp.port)
@@ -218,9 +229,9 @@
 	printf(" to:");
 	for (i = 0; i < info->mr.rangesize; i++) {
 		print_range(&info->mr.range[i]);
-		if (info->mr.range[i].flags & IP_NAT_RANGE_PROTO_RANDOM)
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PROTO_RANDOM)
 			printf(" random");
-		if (info->mr.range[i].flags & IP_NAT_RANGE_PERSISTENT)
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PERSISTENT)
 			printf(" persistent");
 	}
 }
@@ -233,27 +244,312 @@
 	for (i = 0; i < info->mr.rangesize; i++) {
 		printf(" --to-destination ");
 		print_range(&info->mr.range[i]);
-		if (info->mr.range[i].flags & IP_NAT_RANGE_PROTO_RANDOM)
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PROTO_RANDOM)
 			printf(" --random");
-		if (info->mr.range[i].flags & IP_NAT_RANGE_PERSISTENT)
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PERSISTENT)
 			printf(" --persistent");
 	}
 }
 
-static struct xtables_target dnat_tg_reg = {
-	.name		= "DNAT",
-	.version	= XTABLES_VERSION,
-	.family		= NFPROTO_IPV4,
-	.size		= XT_ALIGN(sizeof(struct nf_nat_multi_range)),
-	.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_multi_range)),
-	.help		= DNAT_help,
-	.x6_parse	= DNAT_parse,
-	.print		= DNAT_print,
-	.save		= DNAT_save,
-	.x6_options	= DNAT_opts,
+static void print_range_xlate(const struct nf_nat_ipv4_range *r,
+			struct xt_xlate *xl)
+{
+	if (r->flags & NF_NAT_RANGE_MAP_IPS) {
+		struct in_addr a;
+
+		a.s_addr = r->min_ip;
+		xt_xlate_add(xl, "%s", xtables_ipaddr_to_numeric(&a));
+		if (r->max_ip != r->min_ip) {
+			a.s_addr = r->max_ip;
+			xt_xlate_add(xl, "-%s", xtables_ipaddr_to_numeric(&a));
+		}
+	}
+	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		xt_xlate_add(xl, ":%hu", ntohs(r->min.tcp.port));
+		if (r->max.tcp.port != r->min.tcp.port)
+			xt_xlate_add(xl, "-%hu", ntohs(r->max.tcp.port));
+	}
+}
+
+static int DNAT_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_tg_params *params)
+{
+	const struct ipt_natinfo *info = (const void *)params->target;
+	unsigned int i = 0;
+	bool sep_need = false;
+	const char *sep = " ";
+
+	for (i = 0; i < info->mr.rangesize; i++) {
+		xt_xlate_add(xl, "dnat to ");
+		print_range_xlate(&info->mr.range[i], xl);
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PROTO_RANDOM) {
+			xt_xlate_add(xl, " random");
+			sep_need = true;
+		}
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PERSISTENT) {
+			if (sep_need)
+				sep = ",";
+			xt_xlate_add(xl, "%spersistent", sep);
+		}
+	}
+
+	return 1;
+}
+
+static void
+parse_to_v2(const char *orig_arg, int portok, struct nf_nat_range2 *range)
+{
+	char *arg, *colon, *dash, *error;
+	const struct in_addr *ip;
+
+	arg = strdup(orig_arg);
+	if (arg == NULL)
+		xtables_error(RESOURCE_PROBLEM, "strdup");
+
+	colon = strchr(arg, ':');
+	if (colon) {
+		int port;
+
+		if (!portok)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Need TCP, UDP, SCTP or DCCP with port specification");
+
+		range->flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
+
+		port = atoi(colon+1);
+		if (port <= 0 || port > 65535)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Port `%s' not valid\n", colon+1);
+
+		error = strchr(colon+1, ':');
+		if (error)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Invalid port:port syntax - use dash\n");
+
+		dash = strchr(colon, '-');
+		if (!dash) {
+			range->min_proto.tcp.port
+				= range->max_proto.tcp.port
+				= htons(port);
+		} else {
+			int maxport;
+			char *slash;
+
+			maxport = atoi(dash + 1);
+			if (maxport <= 0 || maxport > 65535)
+				xtables_error(PARAMETER_PROBLEM,
+					   "Port `%s' not valid\n", dash+1);
+			if (maxport < port)
+				/* People are stupid. */
+				xtables_error(PARAMETER_PROBLEM,
+					   "Port range `%s' funky\n", colon+1);
+			range->min_proto.tcp.port = htons(port);
+			range->max_proto.tcp.port = htons(maxport);
+
+			slash = strchr(dash, '/');
+			if (slash) {
+				int baseport;
+
+				baseport = atoi(slash + 1);
+				if (baseport <= 0 || baseport > 65535)
+					xtables_error(PARAMETER_PROBLEM,
+							 "Port `%s' not valid\n", slash+1);
+				range->flags |= NF_NAT_RANGE_PROTO_OFFSET;
+				range->base_proto.tcp.port = htons(baseport);
+			}
+		}
+		/* Starts with a colon? No IP info...*/
+		if (colon == arg) {
+			free(arg);
+			return;
+		}
+		*colon = '\0';
+	}
+
+	range->flags |= NF_NAT_RANGE_MAP_IPS;
+	dash = strchr(arg, '-');
+	if (colon && dash && dash > colon)
+		dash = NULL;
+
+	if (dash)
+		*dash = '\0';
+
+	ip = xtables_numeric_to_ipaddr(arg);
+	if (!ip)
+		xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
+			   arg);
+	range->min_addr.in = *ip;
+	if (dash) {
+		ip = xtables_numeric_to_ipaddr(dash+1);
+		if (!ip)
+			xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
+				   dash+1);
+		range->max_addr.in = *ip;
+	} else
+		range->max_addr = range->min_addr;
+
+	free(arg);
+	return;
+}
+
+static void DNAT_parse_v2(struct xt_option_call *cb)
+{
+	const struct ipt_entry *entry = cb->xt_entry;
+	struct nf_nat_range2 *range = cb->data;
+	int portok;
+
+	if (entry->ip.proto == IPPROTO_TCP
+	    || entry->ip.proto == IPPROTO_UDP
+	    || entry->ip.proto == IPPROTO_SCTP
+	    || entry->ip.proto == IPPROTO_DCCP
+	    || entry->ip.proto == IPPROTO_ICMP)
+		portok = 1;
+	else
+		portok = 0;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_TO_DEST:
+		if (cb->xflags & F_X_TO_DEST) {
+			xtables_error(PARAMETER_PROBLEM,
+				   "DNAT: Multiple --to-destination not supported");
+		}
+		parse_to_v2(cb->arg, portok, range);
+		cb->xflags |= F_X_TO_DEST;
+		break;
+	case O_PERSISTENT:
+		range->flags |= NF_NAT_RANGE_PERSISTENT;
+		break;
+	}
+}
+
+static void DNAT_fcheck_v2(struct xt_fcheck_call *cb)
+{
+	static const unsigned int f = F_TO_DEST | F_RANDOM;
+	struct nf_nat_range2 *range = cb->data;
+
+	if ((cb->xflags & f) == f)
+		range->flags |= NF_NAT_RANGE_PROTO_RANDOM;
+}
+
+static void print_range_v2(const struct nf_nat_range2 *range)
+{
+	if (range->flags & NF_NAT_RANGE_MAP_IPS) {
+		printf("%s", xtables_ipaddr_to_numeric(&range->min_addr.in));
+		if (memcmp(&range->min_addr, &range->max_addr,
+			   sizeof(range->min_addr)))
+			printf("-%s", xtables_ipaddr_to_numeric(&range->max_addr.in));
+	}
+	if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		printf(":");
+		printf("%hu", ntohs(range->min_proto.tcp.port));
+		if (range->max_proto.tcp.port != range->min_proto.tcp.port)
+			printf("-%hu", ntohs(range->max_proto.tcp.port));
+		if (range->flags & NF_NAT_RANGE_PROTO_OFFSET)
+			printf("/%hu", ntohs(range->base_proto.tcp.port));
+	}
+}
+
+static void DNAT_print_v2(const void *ip, const struct xt_entry_target *target,
+                       int numeric)
+{
+	const struct nf_nat_range2 *range = (const void *)target->data;
+
+	printf(" to:");
+	print_range_v2(range);
+	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
+		printf(" random");
+	if (range->flags & NF_NAT_RANGE_PERSISTENT)
+		printf(" persistent");
+}
+
+static void DNAT_save_v2(const void *ip, const struct xt_entry_target *target)
+{
+	const struct nf_nat_range2 *range = (const void *)target->data;
+
+	printf(" --to-destination ");
+	print_range_v2(range);
+	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
+		printf(" --random");
+	if (range->flags & NF_NAT_RANGE_PERSISTENT)
+		printf(" --persistent");
+}
+
+static void print_range_xlate_v2(const struct nf_nat_range2 *range,
+			      struct xt_xlate *xl)
+{
+	if (range->flags & NF_NAT_RANGE_MAP_IPS) {
+		xt_xlate_add(xl, "%s", xtables_ipaddr_to_numeric(&range->min_addr.in));
+		if (memcmp(&range->min_addr, &range->max_addr,
+			   sizeof(range->min_addr))) {
+			xt_xlate_add(xl, "-%s", xtables_ipaddr_to_numeric(&range->max_addr.in));
+		}
+	}
+	if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		xt_xlate_add(xl, ":%hu", ntohs(range->min_proto.tcp.port));
+		if (range->max_proto.tcp.port != range->min_proto.tcp.port)
+			xt_xlate_add(xl, "-%hu", ntohs(range->max_proto.tcp.port));
+		if (range->flags & NF_NAT_RANGE_PROTO_OFFSET)
+			xt_xlate_add(xl, ";%hu", ntohs(range->base_proto.tcp.port));
+	}
+}
+
+static int DNAT_xlate_v2(struct xt_xlate *xl,
+		      const struct xt_xlate_tg_params *params)
+{
+	const struct nf_nat_range2 *range = (const void *)params->target->data;
+	bool sep_need = false;
+	const char *sep = " ";
+
+	xt_xlate_add(xl, "dnat to ");
+	print_range_xlate_v2(range, xl);
+	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM) {
+		xt_xlate_add(xl, " random");
+		sep_need = true;
+	}
+	if (range->flags & NF_NAT_RANGE_PERSISTENT) {
+		if (sep_need)
+			sep = ",";
+		xt_xlate_add(xl, "%spersistent", sep);
+	}
+
+	return 1;
+}
+
+static struct xtables_target dnat_tg_reg[] = {
+	{
+		.name		= "DNAT",
+		.version	= XTABLES_VERSION,
+		.family		= NFPROTO_IPV4,
+		.revision	= 0,
+		.size		= XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)),
+		.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)),
+		.help		= DNAT_help,
+		.print		= DNAT_print,
+		.save		= DNAT_save,
+		.x6_parse	= DNAT_parse,
+		.x6_fcheck	= DNAT_fcheck,
+		.x6_options	= DNAT_opts,
+		.xlate		= DNAT_xlate,
+	},
+	{
+		.name		= "DNAT",
+		.version	= XTABLES_VERSION,
+		.family		= NFPROTO_IPV4,
+		.revision	= 2,
+		.size		= XT_ALIGN(sizeof(struct nf_nat_range2)),
+		.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_range2)),
+		.help		= DNAT_help_v2,
+		.print		= DNAT_print_v2,
+		.save		= DNAT_save_v2,
+		.x6_parse	= DNAT_parse_v2,
+		.x6_fcheck	= DNAT_fcheck_v2,
+		.x6_options	= DNAT_opts,
+		.xlate		= DNAT_xlate_v2,
+	},
 };
 
 void _init(void)
 {
-	xtables_register_target(&dnat_tg_reg);
+	xtables_register_targets(dnat_tg_reg, ARRAY_SIZE(dnat_tg_reg));
 }
diff --git a/extensions/libipt_DNAT.man b/extensions/libipt_DNAT.man
deleted file mode 100644
index d5ded35..0000000
--- a/extensions/libipt_DNAT.man
+++ /dev/null
@@ -1,39 +0,0 @@
-This target is only valid in the
-.B nat
-table, in the
-.B PREROUTING
-and
-.B OUTPUT
-chains, and user-defined chains which are only called from those
-chains.  It specifies that the destination address of the packet
-should be modified (and all future packets in this connection will
-also be mangled), and rules should cease being examined.  It takes one
-type of option:
-.TP
-\fB\-\-to\-destination\fP [\fIipaddr\fP[\fB\-\fP\fIipaddr\fP]][\fB:\fP\fIport\fP[\fB\-\fP\fIport\fP]]
-which can specify a single new destination IP address, an inclusive
-range of IP addresses, and optionally, a port range (which is only
-valid if the rule also specifies
-\fB\-p tcp\fP
-or
-\fB\-p udp\fP).
-If no port range is specified, then the destination port will never be
-modified. If no IP address is specified then only the destination port
-will be modified.
-
-In Kernels up to 2.6.10 you can add several \-\-to\-destination options. For
-those kernels, if you specify more than one destination address, either via an
-address range or multiple \-\-to\-destination options, a simple round-robin (one
-after another in cycle) load balancing takes place between these addresses.
-Later Kernels (>= 2.6.11-rc1) don't have the ability to NAT to multiple ranges
-anymore.
-.TP
-\fB\-\-random\fP
-If option
-\fB\-\-random\fP
-is used then port mapping will be randomized (kernel >= 2.6.22).
-.TP
-\fB\-\-persistent\fP
-Gives a client the same source-/destination-address for each connection.
-This supersedes the SAME target. Support for persistent mappings is available
-from 2.6.29-rc2.
diff --git a/extensions/libipt_DNAT.t b/extensions/libipt_DNAT.t
new file mode 100644
index 0000000..1c4413b
--- /dev/null
+++ b/extensions/libipt_DNAT.t
@@ -0,0 +1,16 @@
+:PREROUTING
+*nat
+-j DNAT --to-destination 1.1.1.1;=;OK
+-j DNAT --to-destination 1.1.1.1-1.1.1.10;=;OK
+-j DNAT --to-destination 1.1.1.1:1025-65535;;FAIL
+-j DNAT --to-destination 1.1.1.1 --to-destination 2.2.2.2;;FAIL
+-p tcp -j DNAT --to-destination 1.1.1.1:1025-65535;=;OK
+-p tcp -j DNAT --to-destination 1.1.1.1-1.1.1.10:1025-65535;=;OK
+-p tcp -j DNAT --to-destination 1.1.1.1-1.1.1.10:1025-65536;;FAIL
+-p tcp -j DNAT --to-destination 1.1.1.1-1.1.1.10:1025-65535 --to-destination 2.2.2.2-2.2.2.20:1025-65535;;FAIL
+-p tcp -j DNAT --to-destination 1.1.1.1:1000-2000/1000;=;OK
+-p tcp -j DNAT --to-destination 1.1.1.1:1000-2000/3000;=;OK
+-p tcp -j DNAT --to-destination 1.1.1.1:1000-2000/65535;=;OK
+-p tcp -j DNAT --to-destination 1.1.1.1:1000-2000/0;;FAIL
+-p tcp -j DNAT --to-destination 1.1.1.1:1000-2000/65536;;FAIL
+-j DNAT;;FAIL
diff --git a/extensions/libipt_DNAT.txlate b/extensions/libipt_DNAT.txlate
new file mode 100644
index 0000000..e88314d
--- /dev/null
+++ b/extensions/libipt_DNAT.txlate
@@ -0,0 +1,14 @@
+iptables-translate -t nat -A prerouting -p tcp -o eth0 -j DNAT --to-destination 1.2.3.4
+nft add rule ip nat prerouting oifname "eth0" ip protocol tcp counter dnat to 1.2.3.4
+
+iptables-translate -t nat -A prerouting -p tcp -d 15.45.23.67 --dport 80 -j DNAT --to-destination 192.168.1.1-192.168.1.10
+nft add rule ip nat prerouting ip daddr 15.45.23.67 tcp dport 80 counter dnat to 192.168.1.1-192.168.1.10
+
+iptables-translate -t nat -A prerouting -p tcp -o eth0 -j DNAT --to-destination 1.2.3.4:1-1023
+nft add rule ip nat prerouting oifname "eth0" ip protocol tcp counter dnat to 1.2.3.4:1-1023
+
+iptables-translate -t nat -A prerouting -p tcp -o eth0 -j DNAT --to-destination 1.2.3.4 --random
+nft add rule ip nat prerouting oifname "eth0" ip protocol tcp counter dnat to 1.2.3.4 random
+
+iptables-translate -t nat -A prerouting -p tcp -o eth0 -j DNAT --to-destination 1.2.3.4 --random --persistent
+nft add rule ip nat prerouting oifname "eth0" ip protocol tcp counter dnat to 1.2.3.4 random,persistent
diff --git a/extensions/libipt_ECN.man b/extensions/libipt_ECN.man
index a9cbe10..8ae7996 100644
--- a/extensions/libipt_ECN.man
+++ b/extensions/libipt_ECN.man
@@ -1,4 +1,4 @@
-This target allows to selectively work around known ECN blackholes.
+This target selectively works around known ECN blackholes.
 It can only be used in the mangle table.
 .TP
 \fB\-\-ecn\-tcp\-remove\fP
diff --git a/extensions/libipt_ECN.t b/extensions/libipt_ECN.t
new file mode 100644
index 0000000..2e09205
--- /dev/null
+++ b/extensions/libipt_ECN.t
@@ -0,0 +1,5 @@
+:PREROUTING,FORWARD,OUTPUT,POSTROUTING
+*mangle
+-j ECN;;FAIL
+-p tcp -j ECN;;FAIL
+-p tcp -j ECN --ecn-tcp-remove;=;OK
diff --git a/extensions/libipt_LOG.c b/extensions/libipt_LOG.c
index b270bcf..36e2e73 100644
--- a/extensions/libipt_LOG.c
+++ b/extensions/libipt_LOG.c
@@ -63,6 +63,11 @@
 	unsigned int level;
 };
 
+struct ipt_log_xlate {
+	const char *name;
+	unsigned int level;
+};
+
 static const struct ipt_log_names ipt_log_names[]
 = { { .name = "alert",   .level = LOG_ALERT },
     { .name = "crit",    .level = LOG_CRIT },
@@ -87,19 +92,19 @@
 				   "Newlines not allowed in --log-prefix");
 		break;
 	case O_LOG_TCPSEQ:
-		info->logflags = IPT_LOG_TCPSEQ;
+		info->logflags |= IPT_LOG_TCPSEQ;
 		break;
 	case O_LOG_TCPOPTS:
-		info->logflags = IPT_LOG_TCPOPT;
+		info->logflags |= IPT_LOG_TCPOPT;
 		break;
 	case O_LOG_IPOPTS:
-		info->logflags = IPT_LOG_IPOPT;
+		info->logflags |= IPT_LOG_IPOPT;
 		break;
 	case O_LOG_UID:
-		info->logflags = IPT_LOG_UID;
+		info->logflags |= IPT_LOG_UID;
 		break;
 	case O_LOG_MAC:
-		info->logflags = IPT_LOG_MACDECODE;
+		info->logflags |= IPT_LOG_MACDECODE;
 		break;
 	}
 }
@@ -166,6 +171,64 @@
 		printf(" --log-macdecode");
 }
 
+static const struct ipt_log_xlate ipt_log_xlate_names[] = {
+	{"alert",	LOG_ALERT },
+	{"crit",	LOG_CRIT },
+	{"debug",	LOG_DEBUG },
+	{"emerg",	LOG_EMERG },
+	{"err",		LOG_ERR },
+	{"info",	LOG_INFO },
+	{"notice",	LOG_NOTICE },
+	{"warn",	LOG_WARNING }
+};
+
+static int LOG_xlate(struct xt_xlate *xl,
+		     const struct xt_xlate_tg_params *params)
+{
+	const struct ipt_log_info *loginfo =
+		(const struct ipt_log_info *)params->target->data;
+	unsigned int i = 0;
+
+	xt_xlate_add(xl, "log");
+	if (strcmp(loginfo->prefix, "") != 0) {
+		if (params->escape_quotes)
+			xt_xlate_add(xl, " prefix \\\"%s\\\"", loginfo->prefix);
+		else
+			xt_xlate_add(xl, " prefix \"%s\"", loginfo->prefix);
+	}
+
+	for (i = 0; i < ARRAY_SIZE(ipt_log_xlate_names); ++i)
+		if (loginfo->level != LOG_DEFAULT_LEVEL &&
+		    loginfo->level == ipt_log_xlate_names[i].level) {
+			xt_xlate_add(xl, " level %s",
+				   ipt_log_xlate_names[i].name);
+			break;
+		}
+
+	if ((loginfo->logflags & IPT_LOG_MASK) == IPT_LOG_MASK) {
+		xt_xlate_add(xl, " flags all");
+	} else {
+		if (loginfo->logflags & (IPT_LOG_TCPSEQ | IPT_LOG_TCPOPT)) {
+			const char *delim = " ";
+
+			xt_xlate_add(xl, " flags tcp");
+			if (loginfo->logflags & IPT_LOG_TCPSEQ) {
+				xt_xlate_add(xl, " sequence");
+				delim = ",";
+			}
+			if (loginfo->logflags & IPT_LOG_TCPOPT)
+				xt_xlate_add(xl, "%soptions", delim);
+		}
+		if (loginfo->logflags & IPT_LOG_IPOPT)
+			xt_xlate_add(xl, " flags ip options");
+		if (loginfo->logflags & IPT_LOG_UID)
+			xt_xlate_add(xl, " flags skuid");
+		if (loginfo->logflags & IPT_LOG_MACDECODE)
+			xt_xlate_add(xl, " flags ether");
+	}
+
+	return 1;
+}
 static struct xtables_target log_tg_reg = {
 	.name          = "LOG",
 	.version       = XTABLES_VERSION,
@@ -178,6 +241,7 @@
 	.save          = LOG_save,
 	.x6_parse      = LOG_parse,
 	.x6_options    = LOG_opts,
+	.xlate	       = LOG_xlate,
 };
 
 void _init(void)
diff --git a/extensions/libipt_LOG.man b/extensions/libipt_LOG.man
deleted file mode 100644
index 47c35e0..0000000
--- a/extensions/libipt_LOG.man
+++ /dev/null
@@ -1,31 +0,0 @@
-Turn on kernel logging of matching packets.  When this option is set
-for a rule, the Linux kernel will print some information on all
-matching packets (like most IP header fields) via the kernel log
-(where it can be read with
-.I dmesg
-or 
-.IR syslogd (8)).
-This is a "non-terminating target", i.e. rule traversal continues at
-the next rule.  So if you want to LOG the packets you refuse, use two
-separate rules with the same matching criteria, first using target LOG
-then DROP (or REJECT).
-.TP
-\fB\-\-log\-level\fP \fIlevel\fP
-Level of logging (numeric or see \fIsyslog.conf\fP(5)).
-.TP
-\fB\-\-log\-prefix\fP \fIprefix\fP
-Prefix log messages with the specified prefix; up to 29 letters long,
-and useful for distinguishing messages in the logs.
-.TP
-\fB\-\-log\-tcp\-sequence\fP
-Log TCP sequence numbers. This is a security risk if the log is
-readable by users.
-.TP
-\fB\-\-log\-tcp\-options\fP
-Log options from the TCP packet header.
-.TP
-\fB\-\-log\-ip\-options\fP
-Log options from the IP packet header.
-.TP
-\fB\-\-log\-uid\fP
-Log the userid of the process which generated the packet.
diff --git a/extensions/libipt_LOG.t b/extensions/libipt_LOG.t
new file mode 100644
index 0000000..fbf5118
--- /dev/null
+++ b/extensions/libipt_LOG.t
@@ -0,0 +1,12 @@
+:INPUT,FORWARD,OUTPUT
+-j LOG;-j LOG;OK
+-j LOG --log-prefix "test: ";=;OK
+-j LOG --log-prefix "test: " --log-level 1;=;OK
+# iptables displays the log-level output using the number; not the string
+-j LOG --log-prefix "test: " --log-level alert;-j LOG --log-prefix "test: " --log-level 1;OK
+-j LOG --log-prefix "test: " --log-tcp-sequence;=;OK
+-j LOG --log-prefix "test: " --log-tcp-options;=;OK
+-j LOG --log-prefix "test: " --log-ip-options;=;OK
+-j LOG --log-prefix "test: " --log-uid;=;OK
+-j LOG --log-prefix "test: " --log-level bad;;FAIL
+-j LOG --log-prefix;;FAIL
diff --git a/extensions/libipt_LOG.txlate b/extensions/libipt_LOG.txlate
new file mode 100644
index 0000000..81f64fb
--- /dev/null
+++ b/extensions/libipt_LOG.txlate
@@ -0,0 +1,5 @@
+iptables-translate -A FORWARD -p tcp -j LOG --log-level error
+nft add rule ip filter FORWARD ip protocol tcp counter log level err
+
+iptables-translate -A FORWARD -p tcp -j LOG --log-prefix "Random prefix"
+nft add rule ip filter FORWARD ip protocol tcp counter log prefix \"Random prefix\"
diff --git a/extensions/libipt_MASQUERADE.c b/extensions/libipt_MASQUERADE.c
index 7ba42df..90bf606 100644
--- a/extensions/libipt_MASQUERADE.c
+++ b/extensions/libipt_MASQUERADE.c
@@ -6,11 +6,12 @@
 #include <xtables.h>
 #include <limits.h> /* INT_MAX in ip_tables.h */
 #include <linux/netfilter_ipv4/ip_tables.h>
-#include <net/netfilter/nf_nat.h>
+#include <linux/netfilter/nf_nat.h>
 
 enum {
 	O_TO_PORTS = 0,
 	O_RANDOM,
+	O_RANDOM_FULLY,
 };
 
 static void MASQUERADE_help(void)
@@ -20,18 +21,21 @@
 " --to-ports <port>[-<port>]\n"
 "				Port (range) to map to.\n"
 " --random\n"
-"				Randomize source port.\n");
+"				Randomize source port.\n"
+" --random-fully\n"
+"				Fully randomize source port.\n");
 }
 
 static const struct xt_option_entry MASQUERADE_opts[] = {
 	{.name = "to-ports", .id = O_TO_PORTS, .type = XTTYPE_STRING},
 	{.name = "random", .id = O_RANDOM, .type = XTTYPE_NONE},
+	{.name = "random-fully", .id = O_RANDOM_FULLY, .type = XTTYPE_NONE},
 	XTOPT_TABLEEND,
 };
 
 static void MASQUERADE_init(struct xt_entry_target *t)
 {
-	struct nf_nat_multi_range *mr = (struct nf_nat_multi_range *)t->data;
+	struct nf_nat_ipv4_multi_range_compat *mr = (struct nf_nat_ipv4_multi_range_compat *)t->data;
 
 	/* Actually, it's 0, but it's ignored at the moment. */
 	mr->rangesize = 1;
@@ -39,12 +43,12 @@
 
 /* Parses ports */
 static void
-parse_ports(const char *arg, struct nf_nat_multi_range *mr)
+parse_ports(const char *arg, struct nf_nat_ipv4_multi_range_compat *mr)
 {
 	char *end;
 	unsigned int port, maxport;
 
-	mr->range[0].flags |= IP_NAT_RANGE_PROTO_SPECIFIED;
+	mr->range[0].flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
 
 	if (!xtables_strtoui(arg, &end, &port, 0, UINT16_MAX))
 		xtables_param_act(XTF_BAD_VALUE, "MASQUERADE", "--to-ports", arg);
@@ -75,7 +79,7 @@
 {
 	const struct ipt_entry *entry = cb->xt_entry;
 	int portok;
-	struct nf_nat_multi_range *mr = cb->data;
+	struct nf_nat_ipv4_multi_range_compat *mr = cb->data;
 
 	if (entry->ip.proto == IPPROTO_TCP
 	    || entry->ip.proto == IPPROTO_UDP
@@ -95,7 +99,10 @@
 		parse_ports(cb->arg, mr);
 		break;
 	case O_RANDOM:
-		mr->range[0].flags |=  IP_NAT_RANGE_PROTO_RANDOM;
+		mr->range[0].flags |=  NF_NAT_RANGE_PROTO_RANDOM;
+		break;
+	case O_RANDOM_FULLY:
+		mr->range[0].flags |=  NF_NAT_RANGE_PROTO_RANDOM_FULLY;
 		break;
 	}
 }
@@ -104,48 +111,77 @@
 MASQUERADE_print(const void *ip, const struct xt_entry_target *target,
                  int numeric)
 {
-	const struct nf_nat_multi_range *mr = (const void *)target->data;
-	const struct nf_nat_range *r = &mr->range[0];
+	const struct nf_nat_ipv4_multi_range_compat *mr = (const void *)target->data;
+	const struct nf_nat_ipv4_range *r = &mr->range[0];
 
-	if (r->flags & IP_NAT_RANGE_PROTO_SPECIFIED) {
+	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
 		printf(" masq ports: ");
 		printf("%hu", ntohs(r->min.tcp.port));
 		if (r->max.tcp.port != r->min.tcp.port)
 			printf("-%hu", ntohs(r->max.tcp.port));
 	}
 
-	if (r->flags & IP_NAT_RANGE_PROTO_RANDOM)
+	if (r->flags & NF_NAT_RANGE_PROTO_RANDOM)
 		printf(" random");
+
+	if (r->flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY)
+		printf(" random-fully");
 }
 
 static void
 MASQUERADE_save(const void *ip, const struct xt_entry_target *target)
 {
-	const struct nf_nat_multi_range *mr = (const void *)target->data;
-	const struct nf_nat_range *r = &mr->range[0];
+	const struct nf_nat_ipv4_multi_range_compat *mr = (const void *)target->data;
+	const struct nf_nat_ipv4_range *r = &mr->range[0];
 
-	if (r->flags & IP_NAT_RANGE_PROTO_SPECIFIED) {
+	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
 		printf(" --to-ports %hu", ntohs(r->min.tcp.port));
 		if (r->max.tcp.port != r->min.tcp.port)
 			printf("-%hu", ntohs(r->max.tcp.port));
 	}
 
-	if (r->flags & IP_NAT_RANGE_PROTO_RANDOM)
+	if (r->flags & NF_NAT_RANGE_PROTO_RANDOM)
 		printf(" --random");
+
+	if (r->flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY)
+		printf(" --random-fully");
+}
+
+static int MASQUERADE_xlate(struct xt_xlate *xl,
+			    const struct xt_xlate_tg_params *params)
+{
+	const struct nf_nat_ipv4_multi_range_compat *mr =
+		(const void *)params->target->data;
+	const struct nf_nat_ipv4_range *r = &mr->range[0];
+
+	xt_xlate_add(xl, "masquerade");
+
+	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		xt_xlate_add(xl, " to :%hu", ntohs(r->min.tcp.port));
+		if (r->max.tcp.port != r->min.tcp.port)
+			xt_xlate_add(xl, "-%hu", ntohs(r->max.tcp.port));
+        }
+
+	xt_xlate_add(xl, " ");
+	if (r->flags & NF_NAT_RANGE_PROTO_RANDOM)
+		xt_xlate_add(xl, "random ");
+
+	return 1;
 }
 
 static struct xtables_target masquerade_tg_reg = {
 	.name		= "MASQUERADE",
 	.version	= XTABLES_VERSION,
 	.family		= NFPROTO_IPV4,
-	.size		= XT_ALIGN(sizeof(struct nf_nat_multi_range)),
-	.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_multi_range)),
+	.size		= XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)),
+	.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)),
 	.help		= MASQUERADE_help,
 	.init		= MASQUERADE_init,
 	.x6_parse	= MASQUERADE_parse,
 	.print		= MASQUERADE_print,
 	.save		= MASQUERADE_save,
 	.x6_options	= MASQUERADE_opts,
+	.xlate		= MASQUERADE_xlate,
 };
 
 void _init(void)
diff --git a/extensions/libipt_MASQUERADE.man b/extensions/libipt_MASQUERADE.man
deleted file mode 100644
index 2dae964..0000000
--- a/extensions/libipt_MASQUERADE.man
+++ /dev/null
@@ -1,30 +0,0 @@
-This target is only valid in the
-.B nat
-table, in the
-.B POSTROUTING
-chain.  It should only be used with dynamically assigned IP (dialup)
-connections: if you have a static IP address, you should use the SNAT
-target.  Masquerading is equivalent to specifying a mapping to the IP
-address of the interface the packet is going out, but also has the
-effect that connections are
-.I forgotten
-when the interface goes down.  This is the correct behavior when the
-next dialup is unlikely to have the same interface address (and hence
-any established connections are lost anyway).
-.TP
-\fB\-\-to\-ports\fP \fIport\fP[\fB\-\fP\fIport\fP]
-This specifies a range of source ports to use, overriding the default
-.B SNAT
-source port-selection heuristics (see above).  This is only valid
-if the rule also specifies
-\fB\-p tcp\fP
-or
-\fB\-p udp\fP.
-.TP
-\fB\-\-random\fP
-Randomize source port mapping
-If option
-\fB\-\-random\fP
-is used then port mapping will be randomized (kernel >= 2.6.21).
-.RS
-.PP
diff --git a/extensions/libipt_MASQUERADE.t b/extensions/libipt_MASQUERADE.t
new file mode 100644
index 0000000..e25d2a0
--- /dev/null
+++ b/extensions/libipt_MASQUERADE.t
@@ -0,0 +1,9 @@
+:POSTROUTING
+*nat
+-j MASQUERADE;=;OK
+-j MASQUERADE --random;=;OK
+-j MASQUERADE --random-fully;=;OK
+-p tcp -j MASQUERADE --to-ports 1024;=;OK
+-p udp -j MASQUERADE --to-ports 1024-65535;=;OK
+-p udp -j MASQUERADE --to-ports 1024-65536;;FAIL
+-p udp -j MASQUERADE --to-ports -1;;FAIL
diff --git a/extensions/libipt_MASQUERADE.txlate b/extensions/libipt_MASQUERADE.txlate
new file mode 100644
index 0000000..40b6958
--- /dev/null
+++ b/extensions/libipt_MASQUERADE.txlate
@@ -0,0 +1,8 @@
+iptables-translate -t nat -A POSTROUTING -j MASQUERADE
+nft add rule ip nat POSTROUTING counter masquerade
+
+iptables-translate -t nat -A POSTROUTING -p tcp -j MASQUERADE --to-ports 10
+nft add rule ip nat POSTROUTING ip protocol tcp counter masquerade to :10
+
+iptables-translate -t nat -A POSTROUTING -p tcp -j MASQUERADE --to-ports 10-20 --random
+nft add rule ip nat POSTROUTING ip protocol tcp counter masquerade to :10-20 random
diff --git a/extensions/libipt_MIRROR.c b/extensions/libipt_MIRROR.c
deleted file mode 100644
index fb78751..0000000
--- a/extensions/libipt_MIRROR.c
+++ /dev/null
@@ -1,15 +0,0 @@
-/* Shared library add-on to iptables to add MIRROR target support. */
-#include <xtables.h>
-
-static struct xtables_target mirror_tg_reg = {
-	.name		= "MIRROR",
-	.version	= XTABLES_VERSION,
-	.family		= NFPROTO_IPV4,
-	.size		= XT_ALIGN(0),
-	.userspacesize	= XT_ALIGN(0),
-};
-
-void _init(void)
-{
-	xtables_register_target(&mirror_tg_reg);
-}
diff --git a/extensions/libipt_MIRROR.man b/extensions/libipt_MIRROR.man
deleted file mode 100644
index 7b720bc..0000000
--- a/extensions/libipt_MIRROR.man
+++ /dev/null
@@ -1,12 +0,0 @@
-This is an experimental demonstration target which inverts the source
-and destination fields in the IP header and retransmits the packet.
-It is only valid in the
-.BR INPUT ,
-.B FORWARD
-and
-.B PREROUTING
-chains, and user-defined chains which are only called from those
-chains.  Note that the outgoing packets are
-.B NOT
-seen by any packet filtering chains, connection tracking or NAT, to
-avoid loops and other problems.
diff --git a/extensions/libipt_NETMAP.c b/extensions/libipt_NETMAP.c
index 5c4471a..f30615a 100644
--- a/extensions/libipt_NETMAP.c
+++ b/extensions/libipt_NETMAP.c
@@ -7,7 +7,7 @@
 #include <stdlib.h>
 #include <getopt.h>
 #include <xtables.h>
-#include <net/netfilter/nf_nat.h>
+#include <linux/netfilter/nf_nat.h>
 
 #define MODULENAME "NETMAP"
 
@@ -45,7 +45,7 @@
 
 static void NETMAP_init(struct xt_entry_target *t)
 {
-	struct nf_nat_multi_range *mr = (struct nf_nat_multi_range *)t->data;
+	struct nf_nat_ipv4_multi_range_compat *mr = (struct nf_nat_ipv4_multi_range_compat *)t->data;
 
 	/* Actually, it's 0, but it's ignored at the moment. */
 	mr->rangesize = 1;
@@ -53,20 +53,20 @@
 
 static void NETMAP_parse(struct xt_option_call *cb)
 {
-	struct nf_nat_multi_range *mr = cb->data;
-	struct nf_nat_range *range = &mr->range[0];
+	struct nf_nat_ipv4_multi_range_compat *mr = cb->data;
+	struct nf_nat_ipv4_range *range = &mr->range[0];
 
 	xtables_option_parse(cb);
-	range->flags |= IP_NAT_RANGE_MAP_IPS;
+	range->flags |= NF_NAT_RANGE_MAP_IPS;
 	range->min_ip = cb->val.haddr.ip & cb->val.hmask.ip;
 	range->max_ip = range->min_ip | ~cb->val.hmask.ip;
 }
 
-static void NETMAP_print(const void *ip, const struct xt_entry_target *target,
-                         int numeric)
+static void __NETMAP_print(const void *ip, const struct xt_entry_target *target,
+			   int numeric)
 {
-	const struct nf_nat_multi_range *mr = (const void *)target->data;
-	const struct nf_nat_range *r = &mr->range[0];
+	const struct nf_nat_ipv4_multi_range_compat *mr = (const void *)target->data;
+	const struct nf_nat_ipv4_range *r = &mr->range[0];
 	struct in_addr a;
 	int bits;
 
@@ -80,18 +80,25 @@
 		printf("/%d", bits);
 }
 
+static void NETMAP_print(const void *ip, const struct xt_entry_target *target,
+			 int numeric)
+{
+	printf(" to:");
+	__NETMAP_print(ip, target, numeric);
+}
+
 static void NETMAP_save(const void *ip, const struct xt_entry_target *target)
 {
 	printf(" --%s ", NETMAP_opts[0].name);
-	NETMAP_print(ip, target, 0);
+	__NETMAP_print(ip, target, 0);
 }
 
 static struct xtables_target netmap_tg_reg = {
 	.name		= MODULENAME,
 	.version	= XTABLES_VERSION,
 	.family		= NFPROTO_IPV4,
-	.size		= XT_ALIGN(sizeof(struct nf_nat_multi_range)),
-	.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_multi_range)),
+	.size		= XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)),
+	.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)),
 	.help		= NETMAP_help,
 	.init		= NETMAP_init,
 	.x6_parse	= NETMAP_parse,
diff --git a/extensions/libipt_NETMAP.man b/extensions/libipt_NETMAP.man
deleted file mode 100644
index a7e90b8..0000000
--- a/extensions/libipt_NETMAP.man
+++ /dev/null
@@ -1,9 +0,0 @@
-This target allows you to statically map a whole network of addresses onto
-another network of addresses.  It can only be used from rules in the
-.B nat
-table.
-.TP
-\fB\-\-to\fP \fIaddress\fP[\fB/\fP\fImask\fP]
-Network address to map to.  The resulting address will be constructed in the
-following way: All 'one' bits in the mask are filled in from the new `address'.
-All bits that are zero in the mask are filled in from the original address.
diff --git a/extensions/libipt_NETMAP.t b/extensions/libipt_NETMAP.t
new file mode 100644
index 0000000..31924b9
--- /dev/null
+++ b/extensions/libipt_NETMAP.t
@@ -0,0 +1,4 @@
+:PREROUTING,INPUT,OUTPUT,POSTROUTING
+*nat
+-j NETMAP --to 1.2.3.0/24;=;OK
+-j NETMAP --to 1.2.3.4;=;OK
diff --git a/extensions/libipt_REDIRECT.c b/extensions/libipt_REDIRECT.c
index e67360a..7850306 100644
--- a/extensions/libipt_REDIRECT.c
+++ b/extensions/libipt_REDIRECT.c
@@ -4,7 +4,7 @@
 #include <xtables.h>
 #include <limits.h> /* INT_MAX in ip_tables.h */
 #include <linux/netfilter_ipv4/ip_tables.h>
-#include <net/netfilter/nf_nat.h>
+#include <linux/netfilter/nf_nat.h>
 
 enum {
 	O_TO_PORTS = 0,
@@ -30,7 +30,7 @@
 
 static void REDIRECT_init(struct xt_entry_target *t)
 {
-	struct nf_nat_multi_range *mr = (struct nf_nat_multi_range *)t->data;
+	struct nf_nat_ipv4_multi_range_compat *mr = (struct nf_nat_ipv4_multi_range_compat *)t->data;
 
 	/* Actually, it's 0, but it's ignored at the moment. */
 	mr->rangesize = 1;
@@ -38,12 +38,12 @@
 
 /* Parses ports */
 static void
-parse_ports(const char *arg, struct nf_nat_multi_range *mr)
+parse_ports(const char *arg, struct nf_nat_ipv4_multi_range_compat *mr)
 {
 	char *end = "";
 	unsigned int port, maxport;
 
-	mr->range[0].flags |= IP_NAT_RANGE_PROTO_SPECIFIED;
+	mr->range[0].flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
 
 	if (!xtables_strtoui(arg, &end, &port, 0, UINT16_MAX) &&
 	    (port = xtables_service_to_port(arg, NULL)) == (unsigned)-1)
@@ -75,7 +75,7 @@
 static void REDIRECT_parse(struct xt_option_call *cb)
 {
 	const struct ipt_entry *entry = cb->xt_entry;
-	struct nf_nat_multi_range *mr = (void *)(*cb->target)->data;
+	struct nf_nat_ipv4_multi_range_compat *mr = (void *)(*cb->target)->data;
 	int portok;
 
 	if (entry->ip.proto == IPPROTO_TCP
@@ -95,11 +95,11 @@
 				   "Need TCP, UDP, SCTP or DCCP with port specification");
 		parse_ports(cb->arg, mr);
 		if (cb->xflags & F_RANDOM)
-			mr->range[0].flags |= IP_NAT_RANGE_PROTO_RANDOM;
+			mr->range[0].flags |= NF_NAT_RANGE_PROTO_RANDOM;
 		break;
 	case O_RANDOM:
 		if (cb->xflags & F_TO_PORTS)
-			mr->range[0].flags |= IP_NAT_RANGE_PROTO_RANDOM;
+			mr->range[0].flags |= NF_NAT_RANGE_PROTO_RANDOM;
 		break;
 	}
 }
@@ -107,46 +107,65 @@
 static void REDIRECT_print(const void *ip, const struct xt_entry_target *target,
                            int numeric)
 {
-	const struct nf_nat_multi_range *mr = (const void *)target->data;
-	const struct nf_nat_range *r = &mr->range[0];
+	const struct nf_nat_ipv4_multi_range_compat *mr = (const void *)target->data;
+	const struct nf_nat_ipv4_range *r = &mr->range[0];
 
-	if (r->flags & IP_NAT_RANGE_PROTO_SPECIFIED) {
+	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
 		printf(" redir ports ");
 		printf("%hu", ntohs(r->min.tcp.port));
 		if (r->max.tcp.port != r->min.tcp.port)
 			printf("-%hu", ntohs(r->max.tcp.port));
-		if (mr->range[0].flags & IP_NAT_RANGE_PROTO_RANDOM)
+		if (mr->range[0].flags & NF_NAT_RANGE_PROTO_RANDOM)
 			printf(" random");
 	}
 }
 
 static void REDIRECT_save(const void *ip, const struct xt_entry_target *target)
 {
-	const struct nf_nat_multi_range *mr = (const void *)target->data;
-	const struct nf_nat_range *r = &mr->range[0];
+	const struct nf_nat_ipv4_multi_range_compat *mr = (const void *)target->data;
+	const struct nf_nat_ipv4_range *r = &mr->range[0];
 
-	if (r->flags & IP_NAT_RANGE_PROTO_SPECIFIED) {
+	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
 		printf(" --to-ports ");
 		printf("%hu", ntohs(r->min.tcp.port));
 		if (r->max.tcp.port != r->min.tcp.port)
 			printf("-%hu", ntohs(r->max.tcp.port));
-		if (mr->range[0].flags & IP_NAT_RANGE_PROTO_RANDOM)
+		if (mr->range[0].flags & NF_NAT_RANGE_PROTO_RANDOM)
 			printf(" --random");
 	}
 }
 
+static int REDIRECT_xlate(struct xt_xlate *xl,
+			  const struct xt_xlate_tg_params *params)
+{
+	const struct nf_nat_ipv4_multi_range_compat *mr =
+		(const void *)params->target->data;
+	const struct nf_nat_ipv4_range *r = &mr->range[0];
+
+	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		xt_xlate_add(xl, "redirect to :%hu", ntohs(r->min.tcp.port));
+		if (r->max.tcp.port != r->min.tcp.port)
+			xt_xlate_add(xl, "-%hu ", ntohs(r->max.tcp.port));
+		if (mr->range[0].flags & NF_NAT_RANGE_PROTO_RANDOM)
+			xt_xlate_add(xl, " random ");
+	}
+
+	return 1;
+}
+
 static struct xtables_target redirect_tg_reg = {
 	.name		= "REDIRECT",
 	.version	= XTABLES_VERSION,
 	.family		= NFPROTO_IPV4,
-	.size		= XT_ALIGN(sizeof(struct nf_nat_multi_range)),
-	.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_multi_range)),
+	.size		= XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)),
+	.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)),
 	.help		= REDIRECT_help,
 	.init		= REDIRECT_init,
  	.x6_parse	= REDIRECT_parse,
 	.print		= REDIRECT_print,
 	.save		= REDIRECT_save,
 	.x6_options	= REDIRECT_opts,
+	.xlate		= REDIRECT_xlate,
 };
 
 void _init(void)
diff --git a/extensions/libipt_REDIRECT.man b/extensions/libipt_REDIRECT.man
deleted file mode 100644
index 90ab19d..0000000
--- a/extensions/libipt_REDIRECT.man
+++ /dev/null
@@ -1,25 +0,0 @@
-This target is only valid in the
-.B nat
-table, in the
-.B PREROUTING
-and
-.B OUTPUT
-chains, and user-defined chains which are only called from those
-chains.  It redirects the packet to the machine itself by changing the
-destination IP to the primary address of the incoming interface
-(locally-generated packets are mapped to the 127.0.0.1 address).
-.TP
-\fB\-\-to\-ports\fP \fIport\fP[\fB\-\fP\fIport\fP]
-This specifies a destination port or range of ports to use: without
-this, the destination port is never altered.  This is only valid
-if the rule also specifies
-\fB\-p tcp\fP
-or
-\fB\-p udp\fP.
-.TP
-\fB\-\-random\fP
-If option
-\fB\-\-random\fP
-is used then port mapping will be randomized (kernel >= 2.6.22).
-.RS
-.PP
diff --git a/extensions/libipt_REDIRECT.t b/extensions/libipt_REDIRECT.t
new file mode 100644
index 0000000..a0fb0ed
--- /dev/null
+++ b/extensions/libipt_REDIRECT.t
@@ -0,0 +1,6 @@
+:PREROUTING,OUTPUT
+*nat
+-p tcp -j REDIRECT --to-ports 42;=;OK
+-p udp -j REDIRECT --to-ports 42-1234;=;OK
+-p tcp -j REDIRECT --to-ports 42-1234 --random;=;OK
+-j REDIRECT --to-ports 42;;FAIL
diff --git a/extensions/libipt_REDIRECT.txlate b/extensions/libipt_REDIRECT.txlate
new file mode 100644
index 0000000..815bb77
--- /dev/null
+++ b/extensions/libipt_REDIRECT.txlate
@@ -0,0 +1,5 @@
+iptables-translate -t nat -A prerouting -p tcp --dport 80 -j REDIRECT --to-ports 8080
+nft add rule ip nat prerouting tcp dport 80 counter redirect to :8080
+
+iptables-translate -t nat -A prerouting -p tcp --dport 80 -j REDIRECT --to-ports 8080 --random
+nft add rule ip nat prerouting tcp dport 80 counter redirect to :8080 random
diff --git a/extensions/libipt_REJECT.c b/extensions/libipt_REJECT.c
index 362c65e..743dfff 100644
--- a/extensions/libipt_REJECT.c
+++ b/extensions/libipt_REJECT.c
@@ -20,8 +20,8 @@
 struct reject_names {
 	const char *name;
 	const char *alias;
-	enum ipt_reject_with with;
 	const char *desc;
+	const char *xlate;
 };
 
 enum {
@@ -29,26 +29,53 @@
 };
 
 static const struct reject_names reject_table[] = {
-	{"icmp-net-unreachable", "net-unreach",
-		IPT_ICMP_NET_UNREACHABLE, "ICMP network unreachable"},
-	{"icmp-host-unreachable", "host-unreach",
-		IPT_ICMP_HOST_UNREACHABLE, "ICMP host unreachable"},
-	{"icmp-proto-unreachable", "proto-unreach",
-		IPT_ICMP_PROT_UNREACHABLE, "ICMP protocol unreachable"},
-	{"icmp-port-unreachable", "port-unreach",
-		IPT_ICMP_PORT_UNREACHABLE, "ICMP port unreachable (default)"},
+	[IPT_ICMP_NET_UNREACHABLE] = {
+		"icmp-net-unreachable", "net-unreach",
+		"ICMP network unreachable",
+		"net-unreachable",
+	},
+	[IPT_ICMP_HOST_UNREACHABLE] = {
+		"icmp-host-unreachable", "host-unreach",
+		"ICMP host unreachable",
+		"host-unreachable",
+	},
+	[IPT_ICMP_PROT_UNREACHABLE] = {
+		"icmp-proto-unreachable", "proto-unreach",
+		"ICMP protocol unreachable",
+		"prot-unreachable",
+	},
+	[IPT_ICMP_PORT_UNREACHABLE] = {
+		"icmp-port-unreachable", "port-unreach",
+		"ICMP port unreachable (default)",
+		"port-unreachable",
+	},
 #if 0
-	{"echo-reply", "echoreply",
-	 IPT_ICMP_ECHOREPLY, "for ICMP echo only: faked ICMP echo reply"},
+	[IPT_ICMP_ECHOREPLY] = {
+		"echo-reply", "echoreply",
+		"for ICMP echo only: faked ICMP echo reply",
+		"echo-reply",
+	},
 #endif
-	{"icmp-net-prohibited", "net-prohib",
-	 IPT_ICMP_NET_PROHIBITED, "ICMP network prohibited"},
-	{"icmp-host-prohibited", "host-prohib",
-	 IPT_ICMP_HOST_PROHIBITED, "ICMP host prohibited"},
-	{"tcp-reset", "tcp-rst",
-	 IPT_TCP_RESET, "TCP RST packet"},
-	{"icmp-admin-prohibited", "admin-prohib",
-	 IPT_ICMP_ADMIN_PROHIBITED, "ICMP administratively prohibited (*)"}
+	[IPT_ICMP_NET_PROHIBITED] = {
+		"icmp-net-prohibited", "net-prohib",
+		"ICMP network prohibited",
+		"net-prohibited",
+	},
+	[IPT_ICMP_HOST_PROHIBITED] = {
+		"icmp-host-prohibited", "host-prohib",
+		"ICMP host prohibited",
+		"host-prohibited",
+	},
+	[IPT_TCP_RESET] = {
+		"tcp-reset", "tcp-rst",
+		"TCP RST packet",
+		"tcp reset",
+	},
+	[IPT_ICMP_ADMIN_PROHIBITED] = {
+		"icmp-admin-prohibited", "admin-prohib",
+		"ICMP administratively prohibited (*)",
+		"admin-prohibited",
+	},
 };
 
 static void
@@ -59,6 +86,8 @@
 	printf("Valid reject types:\n");
 
 	for (i = 0; i < ARRAY_SIZE(reject_table); ++i) {
+		if (!reject_table[i].name)
+			continue;
 		printf("    %-25s\t%s\n", reject_table[i].name, reject_table[i].desc);
 		printf("    %-25s\talias\n", reject_table[i].alias);
 	}
@@ -97,14 +126,17 @@
 	unsigned int i;
 
 	xtables_option_parse(cb);
-	for (i = 0; i < ARRAY_SIZE(reject_table); ++i)
+	for (i = 0; i < ARRAY_SIZE(reject_table); ++i) {
+		if (!reject_table[i].name)
+			continue;
 		if (strncasecmp(reject_table[i].name,
 		      cb->arg, strlen(cb->arg)) == 0 ||
 		    strncasecmp(reject_table[i].alias,
 		      cb->arg, strlen(cb->arg)) == 0) {
-			reject->with = reject_table[i].with;
+			reject->with = i;
 			return;
 		}
+	}
 	/* This due to be dropped late in 2.4 pre-release cycle --RR */
 	if (strncasecmp("echo-reply", cb->arg, strlen(cb->arg)) == 0 ||
 	    strncasecmp("echoreply", cb->arg, strlen(cb->arg)) == 0)
@@ -119,27 +151,37 @@
 {
 	const struct ipt_reject_info *reject
 		= (const struct ipt_reject_info *)target->data;
-	unsigned int i;
 
-	for (i = 0; i < ARRAY_SIZE(reject_table); ++i)
-		if (reject_table[i].with == reject->with)
-			break;
-	printf(" reject-with %s", reject_table[i].name);
+	printf(" reject-with %s", reject_table[reject->with].name);
 }
 
 static void REJECT_save(const void *ip, const struct xt_entry_target *target)
 {
-	const struct ipt_reject_info *reject
-		= (const struct ipt_reject_info *)target->data;
-	unsigned int i;
+	const struct ipt_reject_info *reject =
+		(const struct ipt_reject_info *)target->data;
 
-	for (i = 0; i < ARRAY_SIZE(reject_table); ++i)
-		if (reject_table[i].with == reject->with)
-			break;
-
-	printf(" --reject-with %s", reject_table[i].name);
+	printf(" --reject-with %s", reject_table[reject->with].name);
 }
 
+static int REJECT_xlate(struct xt_xlate *xl,
+			const struct xt_xlate_tg_params *params)
+{
+	const struct ipt_reject_info *reject =
+		(const struct ipt_reject_info *)params->target->data;
+
+	if (reject->with == IPT_ICMP_PORT_UNREACHABLE)
+		xt_xlate_add(xl, "reject");
+	else if (reject->with == IPT_TCP_RESET)
+		xt_xlate_add(xl, "reject with %s",
+			     reject_table[reject->with].xlate);
+	else
+		xt_xlate_add(xl, "reject with icmp type %s",
+			     reject_table[reject->with].xlate);
+
+	return 1;
+}
+
+
 static struct xtables_target reject_tg_reg = {
 	.name		= "REJECT",
 	.version	= XTABLES_VERSION,
@@ -152,6 +194,7 @@
 	.save		= REJECT_save,
 	.x6_parse	= REJECT_parse,
 	.x6_options	= REJECT_opts,
+	.xlate		= REJECT_xlate,
 };
 
 void _init(void)
diff --git a/extensions/libipt_REJECT.man b/extensions/libipt_REJECT.man
index c419a85..cc47aea 100644
--- a/extensions/libipt_REJECT.man
+++ b/extensions/libipt_REJECT.man
@@ -18,9 +18,9 @@
 \fBicmp\-port\-unreachable\fP,
 \fBicmp\-proto\-unreachable\fP,
 \fBicmp\-net\-prohibited\fP,
-\fBicmp\-host\-prohibited\fP or
-\fBicmp\-admin\-prohibited\fP (*)
-which return the appropriate ICMP error message (\fBport\-unreachable\fP is
+\fBicmp\-host\-prohibited\fP, or
+\fBicmp\-admin\-prohibited\fP (*),
+which return the appropriate ICMP error message (\fBicmp\-port\-unreachable\fP is
 the default).  The option
 \fBtcp\-reset\fP
 can be used on rules which only match the TCP protocol: this causes a
@@ -28,5 +28,25 @@
 .I ident
 (113/tcp) probes which frequently occur when sending mail to broken mail
 hosts (which won't accept your mail otherwise).
-.PP
+.IP
 (*) Using icmp\-admin\-prohibited with kernels that do not support it will result in a plain DROP instead of REJECT
+.PP
+\fIWarning:\fP You should not indiscriminately apply the REJECT target to
+packets whose connection state is classified as INVALID; instead, you should
+only DROP these.
+.PP
+Consider a source host transmitting a packet P, with P experiencing so much
+delay along its path that the source host issues a retransmission, P_2, with
+P_2 being successful in reaching its destination and advancing the connection
+state normally. It is conceivable that the late-arriving P may be considered
+not to be associated with any connection tracking entry. Generating a reject
+response for a packet so classed would then terminate the healthy connection.
+.PP
+So, instead of:
+.PP
+-A INPUT ... -j REJECT
+.PP
+do consider using:
+.PP
+-A INPUT ... -m conntrack --ctstate INVALID -j DROP
+-A INPUT ... -j REJECT
diff --git a/extensions/libipt_REJECT.t b/extensions/libipt_REJECT.t
new file mode 100644
index 0000000..5b26b10
--- /dev/null
+++ b/extensions/libipt_REJECT.t
@@ -0,0 +1,9 @@
+:INPUT,FORWARD,OUTPUT
+-j REJECT;=;OK
+-j REJECT --reject-with icmp-net-unreachable;=;OK
+-j REJECT --reject-with icmp-host-unreachable;=;OK
+-j REJECT --reject-with icmp-port-unreachable;=;OK
+-j REJECT --reject-with icmp-proto-unreachable;=;OK
+-j REJECT --reject-with icmp-net-prohibited;=;OK
+-j REJECT --reject-with icmp-host-prohibited;=;OK
+-j REJECT --reject-with icmp-admin-prohibited;=;OK
diff --git a/extensions/libipt_REJECT.txlate b/extensions/libipt_REJECT.txlate
new file mode 100644
index 0000000..a1bfb5f
--- /dev/null
+++ b/extensions/libipt_REJECT.txlate
@@ -0,0 +1,8 @@
+iptables-translate -A FORWARD -p TCP --dport 22 -j REJECT
+nft add rule ip filter FORWARD tcp dport 22 counter reject
+
+iptables-translate -A FORWARD -p TCP --dport 22 -j REJECT --reject-with icmp-net-unreachable
+nft add rule ip filter FORWARD tcp dport 22 counter reject with icmp type net-unreachable
+
+iptables-translate -A FORWARD -p TCP --dport 22 -j REJECT --reject-with tcp-reset
+nft add rule ip filter FORWARD tcp dport 22 counter reject with tcp reset
diff --git a/extensions/libipt_SAME.c b/extensions/libipt_SAME.c
deleted file mode 100644
index 2ff6c82..0000000
--- a/extensions/libipt_SAME.c
+++ /dev/null
@@ -1,177 +0,0 @@
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <xtables.h>
-#include <net/netfilter/nf_nat.h>
-#include <linux/netfilter_ipv4/ipt_SAME.h>
-
-enum {
-	O_TO_ADDR = 0,
-	O_NODST,
-	O_RANDOM,
-	F_RANDOM = 1 << O_RANDOM,
-};
-
-static void SAME_help(void)
-{
-	printf(
-"SAME target options:\n"
-" --to <ipaddr>-<ipaddr>\n"
-"				Addresses to map source to.\n"
-"				 May be specified more than\n"
-"				  once for multiple ranges.\n"
-" --nodst\n"
-"				Don't use destination-ip in\n"
-"				           source selection\n"
-" --random\n"
-"				Randomize source port\n");
-}
-
-static const struct xt_option_entry SAME_opts[] = {
-	{.name = "to", .id = O_TO_ADDR, .type = XTTYPE_STRING,
-	 .flags = XTOPT_MAND},
-	{.name = "nodst", .id = O_NODST, .type = XTTYPE_NONE},
-	{.name = "random", .id = O_RANDOM, .type = XTTYPE_NONE},
-	XTOPT_TABLEEND,
-};
-
-/* Parses range of IPs */
-static void parse_to(const char *orig_arg, struct nf_nat_range *range)
-{
-	char *dash, *arg;
-	const struct in_addr *ip;
-
-	arg = strdup(orig_arg);
-	if (arg == NULL)
-		xtables_error(RESOURCE_PROBLEM, "strdup");
-	range->flags |= IP_NAT_RANGE_MAP_IPS;
-	dash = strchr(arg, '-');
-
-	if (dash)
-		*dash = '\0';
-
-	ip = xtables_numeric_to_ipaddr(arg);
-	if (!ip)
-		xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
-			   arg);
-	range->min_ip = ip->s_addr;
-
-	if (dash) {
-		ip = xtables_numeric_to_ipaddr(dash+1);
-		if (!ip)
-			xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
-				   dash+1);
-	}
-	range->max_ip = ip->s_addr;
-	if (dash)
-		if (range->min_ip > range->max_ip)
-			xtables_error(PARAMETER_PROBLEM, "Bad IP range \"%s-%s\"\n",
-				   arg, dash+1);
-	free(arg);
-}
-
-static void SAME_parse(struct xt_option_call *cb)
-{
-	struct ipt_same_info *mr = cb->data;
-	unsigned int count;
-
-	xtables_option_parse(cb);
-	switch (cb->entry->id) {
-	case O_TO_ADDR:
-		if (mr->rangesize == IPT_SAME_MAX_RANGE)
-			xtables_error(PARAMETER_PROBLEM,
-				   "Too many ranges specified, maximum "
-				   "is %i ranges.\n",
-				   IPT_SAME_MAX_RANGE);
-		parse_to(cb->arg, &mr->range[mr->rangesize]);
-		/* WTF do we need this for? */
-		if (cb->xflags & F_RANDOM)
-			mr->range[mr->rangesize].flags 
-				|= IP_NAT_RANGE_PROTO_RANDOM;
-		mr->rangesize++;
-		break;
-	case O_NODST:
-		mr->info |= IPT_SAME_NODST;
-		break;
-	case O_RANDOM:
-		for (count=0; count < mr->rangesize; count++)
-			mr->range[count].flags |= IP_NAT_RANGE_PROTO_RANDOM;
-		break;
-	}
-}
-
-static void SAME_print(const void *ip, const struct xt_entry_target *target,
-                       int numeric)
-{
-	unsigned int count;
-	const struct ipt_same_info *mr = (const void *)target->data;
-	int random_selection = 0;
-	
-	printf(" same:");
-
-	for (count = 0; count < mr->rangesize; count++) {
-		const struct nf_nat_range *r = &mr->range[count];
-		struct in_addr a;
-
-		a.s_addr = r->min_ip;
-
-		printf("%s", xtables_ipaddr_to_numeric(&a));
-		a.s_addr = r->max_ip;
-		
-		if (r->min_ip != r->max_ip)
-			printf("-%s", xtables_ipaddr_to_numeric(&a));
-		if (r->flags & IP_NAT_RANGE_PROTO_RANDOM) 
-			random_selection = 1;
-	}
-	
-	if (mr->info & IPT_SAME_NODST)
-		printf(" nodst");
-
-	if (random_selection)
-		printf(" random");
-}
-
-static void SAME_save(const void *ip, const struct xt_entry_target *target)
-{
-	unsigned int count;
-	const struct ipt_same_info *mr = (const void *)target->data;
-	int random_selection = 0;
-
-	for (count = 0; count < mr->rangesize; count++) {
-		const struct nf_nat_range *r = &mr->range[count];
-		struct in_addr a;
-
-		a.s_addr = r->min_ip;
-		printf(" --to %s", xtables_ipaddr_to_numeric(&a));
-		a.s_addr = r->max_ip;
-
-		if (r->min_ip != r->max_ip)
-			printf("-%s", xtables_ipaddr_to_numeric(&a));
-		if (r->flags & IP_NAT_RANGE_PROTO_RANDOM) 
-			random_selection = 1;
-	}
-	
-	if (mr->info & IPT_SAME_NODST)
-		printf(" --nodst");
-
-	if (random_selection)
-		printf(" --random");
-}
-
-static struct xtables_target same_tg_reg = {
-	.name		= "SAME",
-	.version	= XTABLES_VERSION,
-	.family		= NFPROTO_IPV4,
-	.size		= XT_ALIGN(sizeof(struct ipt_same_info)),
-	.userspacesize	= XT_ALIGN(sizeof(struct ipt_same_info)),
-	.help		= SAME_help,
-	.x6_parse	= SAME_parse,
-	.print		= SAME_print,
-	.save		= SAME_save,
-	.x6_options	= SAME_opts,
-};
-
-void _init(void)
-{
-	xtables_register_target(&same_tg_reg);
-}
diff --git a/extensions/libipt_SAME.man b/extensions/libipt_SAME.man
deleted file mode 100644
index a99dc73..0000000
--- a/extensions/libipt_SAME.man
+++ /dev/null
@@ -1,17 +0,0 @@
-Similar to SNAT/DNAT depending on chain: it takes a range of addresses
-(`\-\-to 1.2.3.4\-1.2.3.7') and gives a client the same
-source-/destination-address for each connection.
-.PP
-N.B.: The DNAT target's \fB\-\-persistent\fP option replaced the SAME target.
-.TP
-\fB\-\-to\fP \fIipaddr\fP[\fB\-\fP\fIipaddr\fP]
-Addresses to map source to. May be specified more than once for
-multiple ranges.
-.TP
-\fB\-\-nodst\fP
-Don't use the destination-ip in the calculations when selecting the
-new source-ip
-.TP
-\fB\-\-random\fP
-Port mapping will be forcibly randomized to avoid attacks based on 
-port prediction (kernel >= 2.6.21).
diff --git a/extensions/libipt_SNAT.c b/extensions/libipt_SNAT.c
index 8023306..e92d811 100644
--- a/extensions/libipt_SNAT.c
+++ b/extensions/libipt_SNAT.c
@@ -6,16 +6,18 @@
 #include <iptables.h>
 #include <limits.h> /* INT_MAX in ip_tables.h */
 #include <linux/netfilter_ipv4/ip_tables.h>
-#include <net/netfilter/nf_nat.h>
+#include <linux/netfilter/nf_nat.h>
 
 enum {
 	O_TO_SRC = 0,
 	O_RANDOM,
+	O_RANDOM_FULLY,
 	O_PERSISTENT,
 	O_X_TO_SRC,
-	F_TO_SRC   = 1 << O_TO_SRC,
-	F_RANDOM   = 1 << O_RANDOM,
-	F_X_TO_SRC = 1 << O_X_TO_SRC,
+	F_TO_SRC       = 1 << O_TO_SRC,
+	F_RANDOM       = 1 << O_RANDOM,
+	F_RANDOM_FULLY = 1 << O_RANDOM_FULLY,
+	F_X_TO_SRC     = 1 << O_X_TO_SRC,
 };
 
 /* Source NAT data consists of a multi-range, indicating where to map
@@ -23,7 +25,7 @@
 struct ipt_natinfo
 {
 	struct xt_entry_target t;
-	struct nf_nat_multi_range mr;
+	struct nf_nat_ipv4_multi_range_compat mr;
 };
 
 static void SNAT_help(void)
@@ -32,19 +34,20 @@
 "SNAT target options:\n"
 " --to-source [<ipaddr>[-<ipaddr>]][:port[-port]]\n"
 "				Address to map source to.\n"
-"[--random] [--persistent]\n");
+"[--random] [--random-fully] [--persistent]\n");
 }
 
 static const struct xt_option_entry SNAT_opts[] = {
 	{.name = "to-source", .id = O_TO_SRC, .type = XTTYPE_STRING,
 	 .flags = XTOPT_MAND | XTOPT_MULTI},
 	{.name = "random", .id = O_RANDOM, .type = XTTYPE_NONE},
+	{.name = "random-fully", .id = O_RANDOM_FULLY, .type = XTTYPE_NONE},
 	{.name = "persistent", .id = O_PERSISTENT, .type = XTTYPE_NONE},
 	XTOPT_TABLEEND,
 };
 
 static struct ipt_natinfo *
-append_range(struct ipt_natinfo *info, const struct nf_nat_range *range)
+append_range(struct ipt_natinfo *info, const struct nf_nat_ipv4_range *range)
 {
 	unsigned int size;
 
@@ -66,7 +69,7 @@
 static struct xt_entry_target *
 parse_to(const char *orig_arg, int portok, struct ipt_natinfo *info)
 {
-	struct nf_nat_range range;
+	struct nf_nat_ipv4_range range;
 	char *arg, *colon, *dash, *error;
 	const struct in_addr *ip;
 
@@ -83,7 +86,7 @@
 			xtables_error(PARAMETER_PROBLEM,
 				   "Need TCP, UDP, SCTP or DCCP with port specification");
 
-		range.flags |= IP_NAT_RANGE_PROTO_SPECIFIED;
+		range.flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
 
 		port = atoi(colon+1);
 		if (port <= 0 || port > 65535)
@@ -122,7 +125,7 @@
 		*colon = '\0';
 	}
 
-	range.flags |= IP_NAT_RANGE_MAP_IPS;
+	range.flags |= NF_NAT_RANGE_MAP_IPS;
 	dash = strchr(arg, '-');
 	if (colon && dash && dash > colon)
 		dash = NULL;
@@ -174,24 +177,29 @@
 					   "SNAT: Multiple --to-source not supported");
 		}
 		*cb->target = parse_to(cb->arg, portok, info);
-		/* WTF do we need this for?? */
-		if (cb->xflags & F_RANDOM)
-			info->mr.range[0].flags |= IP_NAT_RANGE_PROTO_RANDOM;
 		cb->xflags |= F_X_TO_SRC;
 		break;
-	case O_RANDOM:
-		if (cb->xflags & F_TO_SRC)
-			info->mr.range[0].flags |= IP_NAT_RANGE_PROTO_RANDOM;
-		break;
 	case O_PERSISTENT:
-		info->mr.range[0].flags |= IP_NAT_RANGE_PERSISTENT;
+		info->mr.range[0].flags |= NF_NAT_RANGE_PERSISTENT;
 		break;
 	}
 }
 
-static void print_range(const struct nf_nat_range *r)
+static void SNAT_fcheck(struct xt_fcheck_call *cb)
 {
-	if (r->flags & IP_NAT_RANGE_MAP_IPS) {
+	static const unsigned int f = F_TO_SRC | F_RANDOM;
+	static const unsigned int r = F_TO_SRC | F_RANDOM_FULLY;
+	struct nf_nat_ipv4_multi_range_compat *mr = cb->data;
+
+	if ((cb->xflags & f) == f)
+		mr->range[0].flags |= NF_NAT_RANGE_PROTO_RANDOM;
+	if ((cb->xflags & r) == r)
+		mr->range[0].flags |= NF_NAT_RANGE_PROTO_RANDOM_FULLY;
+}
+
+static void print_range(const struct nf_nat_ipv4_range *r)
+{
+	if (r->flags & NF_NAT_RANGE_MAP_IPS) {
 		struct in_addr a;
 
 		a.s_addr = r->min_ip;
@@ -201,7 +209,7 @@
 			printf("-%s", xtables_ipaddr_to_numeric(&a));
 		}
 	}
-	if (r->flags & IP_NAT_RANGE_PROTO_SPECIFIED) {
+	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
 		printf(":");
 		printf("%hu", ntohs(r->min.tcp.port));
 		if (r->max.tcp.port != r->min.tcp.port)
@@ -218,9 +226,11 @@
 	printf(" to:");
 	for (i = 0; i < info->mr.rangesize; i++) {
 		print_range(&info->mr.range[i]);
-		if (info->mr.range[i].flags & IP_NAT_RANGE_PROTO_RANDOM)
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PROTO_RANDOM)
 			printf(" random");
-		if (info->mr.range[i].flags & IP_NAT_RANGE_PERSISTENT)
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY)
+			printf(" random-fully");
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PERSISTENT)
 			printf(" persistent");
 	}
 }
@@ -233,24 +243,80 @@
 	for (i = 0; i < info->mr.rangesize; i++) {
 		printf(" --to-source ");
 		print_range(&info->mr.range[i]);
-		if (info->mr.range[i].flags & IP_NAT_RANGE_PROTO_RANDOM)
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PROTO_RANDOM)
 			printf(" --random");
-		if (info->mr.range[i].flags & IP_NAT_RANGE_PERSISTENT)
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY)
+			printf(" --random-fully");
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PERSISTENT)
 			printf(" --persistent");
 	}
 }
 
+static void print_range_xlate(const struct nf_nat_ipv4_range *r,
+			      struct xt_xlate *xl)
+{
+	if (r->flags & NF_NAT_RANGE_MAP_IPS) {
+		struct in_addr a;
+
+		a.s_addr = r->min_ip;
+		xt_xlate_add(xl, "%s", xtables_ipaddr_to_numeric(&a));
+		if (r->max_ip != r->min_ip) {
+			a.s_addr = r->max_ip;
+			xt_xlate_add(xl, "-%s", xtables_ipaddr_to_numeric(&a));
+		}
+	}
+	if (r->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
+		xt_xlate_add(xl, ":");
+		xt_xlate_add(xl, "%hu", ntohs(r->min.tcp.port));
+		if (r->max.tcp.port != r->min.tcp.port)
+			xt_xlate_add(xl, "-%hu", ntohs(r->max.tcp.port));
+	}
+}
+
+static int SNAT_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_tg_params *params)
+{
+	const struct ipt_natinfo *info = (const void *)params->target;
+	unsigned int i = 0;
+	bool sep_need = false;
+	const char *sep = " ";
+
+	for (i = 0; i < info->mr.rangesize; i++) {
+		xt_xlate_add(xl, "snat to ");
+		print_range_xlate(&info->mr.range[i], xl);
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PROTO_RANDOM) {
+			xt_xlate_add(xl, " random");
+			sep_need = true;
+		}
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY) {
+			if (sep_need)
+				sep = ",";
+			xt_xlate_add(xl, "%sfully-random", sep);
+			sep_need = true;
+		}
+		if (info->mr.range[i].flags & NF_NAT_RANGE_PERSISTENT) {
+			if (sep_need)
+				sep = ",";
+			xt_xlate_add(xl, "%spersistent", sep);
+		}
+	}
+
+	return 1;
+}
+
 static struct xtables_target snat_tg_reg = {
 	.name		= "SNAT",
 	.version	= XTABLES_VERSION,
 	.family		= NFPROTO_IPV4,
-	.size		= XT_ALIGN(sizeof(struct nf_nat_multi_range)),
-	.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_multi_range)),
+	.size		= XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)),
+	.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)),
 	.help		= SNAT_help,
 	.x6_parse	= SNAT_parse,
+	.x6_fcheck	= SNAT_fcheck,
 	.print		= SNAT_print,
 	.save		= SNAT_save,
 	.x6_options	= SNAT_opts,
+	.xlate		= SNAT_xlate,
 };
 
 void _init(void)
diff --git a/extensions/libipt_SNAT.man b/extensions/libipt_SNAT.man
deleted file mode 100644
index 626b592..0000000
--- a/extensions/libipt_SNAT.man
+++ /dev/null
@@ -1,37 +0,0 @@
-This target is only valid in the
-.B nat
-table, in the
-.B POSTROUTING
-chain.  It specifies that the source address of the packet should be
-modified (and all future packets in this connection will also be
-mangled), and rules should cease being examined.  It takes one type
-of option:
-.TP
-\fB\-\-to\-source\fP [\fIipaddr\fP[\fB\-\fP\fIipaddr\fP]][\fB:\fP\fIport\fP[\fB\-\fP\fIport\fP]]
-which can specify a single new source IP address, an inclusive range
-of IP addresses, and optionally, a port range (which is only valid if
-the rule also specifies
-\fB\-p tcp\fP
-or
-\fB\-p udp\fP).
-If no port range is specified, then source ports below 512 will be
-mapped to other ports below 512: those between 512 and 1023 inclusive
-will be mapped to ports below 1024, and other ports will be mapped to
-1024 or above. Where possible, no port alteration will occur.
-
-In Kernels up to 2.6.10, you can add several \-\-to\-source options. For those
-kernels, if you specify more than one source address, either via an address
-range or multiple \-\-to\-source options, a simple round-robin (one after another
-in cycle) takes place between these addresses.
-Later Kernels (>= 2.6.11-rc1) don't have the ability to NAT to multiple ranges
-anymore.
-.TP
-\fB\-\-random\fP
-If option
-\fB\-\-random\fP
-is used then port mapping will be randomized (kernel >= 2.6.21).
-.TP
-\fB\-\-persistent\fP
-Gives a client the same source-/destination-address for each connection.
-This supersedes the SAME target. Support for persistent mappings is available
-from 2.6.29-rc2.
diff --git a/extensions/libipt_SNAT.t b/extensions/libipt_SNAT.t
new file mode 100644
index 0000000..186e1cb
--- /dev/null
+++ b/extensions/libipt_SNAT.t
@@ -0,0 +1,11 @@
+:POSTROUTING
+*nat
+-j SNAT --to-source 1.1.1.1;=;OK
+-j SNAT --to-source 1.1.1.1-1.1.1.10;=;OK
+-j SNAT --to-source 1.1.1.1:1025-65535;;FAIL
+-j SNAT --to-source 1.1.1.1 --to-source 2.2.2.2;;FAIL
+-p tcp -j SNAT --to-source 1.1.1.1:1025-65535;=;OK
+-p tcp -j SNAT --to-source 1.1.1.1-1.1.1.10:1025-65535;=;OK
+-p tcp -j SNAT --to-source 1.1.1.1-1.1.1.10:1025-65536;;FAIL
+-p tcp -j SNAT --to-source 1.1.1.1-1.1.1.10:1025-65535 --to-source 2.2.2.2-2.2.2.20:1025-65535;;FAIL
+-j SNAT;;FAIL
diff --git a/extensions/libipt_SNAT.txlate b/extensions/libipt_SNAT.txlate
new file mode 100644
index 0000000..01592fa
--- /dev/null
+++ b/extensions/libipt_SNAT.txlate
@@ -0,0 +1,14 @@
+iptables-translate -t nat -A postrouting -o eth0 -j SNAT --to 1.2.3.4
+nft add rule ip nat postrouting oifname "eth0" counter snat to 1.2.3.4
+
+iptables-translate -t nat -A postrouting -o eth0 -j SNAT --to 1.2.3.4-1.2.3.6
+nft add rule ip nat postrouting oifname "eth0" counter snat to 1.2.3.4-1.2.3.6
+
+iptables-translate -t nat -A postrouting -p tcp -o eth0 -j SNAT --to 1.2.3.4:1-1023
+nft add rule ip nat postrouting oifname "eth0" ip protocol tcp counter snat to 1.2.3.4:1-1023
+
+iptables-translate -t nat -A postrouting -o eth0 -j SNAT --to 1.2.3.4 --random
+nft add rule ip nat postrouting oifname "eth0" counter snat to 1.2.3.4 random
+
+iptables-translate -t nat -A postrouting -o eth0 -j SNAT --to 1.2.3.4 --random --persistent
+nft add rule ip nat postrouting oifname "eth0" counter snat to 1.2.3.4 random,persistent
diff --git a/extensions/libipt_TTL.t b/extensions/libipt_TTL.t
new file mode 100644
index 0000000..3680979
--- /dev/null
+++ b/extensions/libipt_TTL.t
@@ -0,0 +1,10 @@
+:PREROUTING,INPUT,FORWARD,OUTPUT,POSTROUTING
+*mangle
+-j TTL --ttl-set 42;=;OK
+-j TTL --ttl-inc 1;=;OK
+-j TTL --ttl-dec 1;=;OK
+-j TTL --ttl-set 256;;FAIL
+-j TTL --ttl-inc 0;;FAIL
+-j TTL --ttl-dec 0;;FAIL
+-j TTL --ttl-dec 1 --ttl-inc 1;;FAIL
+-j TTL --ttl-set --ttl-inc 1;;FAIL
diff --git a/extensions/libipt_ULOG.c b/extensions/libipt_ULOG.c
index e08ae05..5163eea 100644
--- a/extensions/libipt_ULOG.c
+++ b/extensions/libipt_ULOG.c
@@ -11,6 +11,7 @@
  */
 #include <stdio.h>
 #include <string.h>
+#include <strings.h>
 #include <xtables.h>
 /* For 64bit kernel / 32bit userspace */
 #include <linux/netfilter_ipv4/ipt_ULOG.h>
@@ -37,9 +38,9 @@
 	{.name = "ulog-prefix", .id = O_ULOG_PREFIX, .type = XTTYPE_STRING,
 	 .flags = XTOPT_PUT, XTOPT_POINTER(struct ipt_ulog_info, prefix),
 	 .min = 1},
-	{.name = "ulog-cprange", .id = O_ULOG_CPRANGE, .type = XTTYPE_UINT64,
+	{.name = "ulog-cprange", .id = O_ULOG_CPRANGE, .type = XTTYPE_UINT64},
+	{.name = "ulog-qthreshold", .id = O_ULOG_QTHR, .type = XTTYPE_UINT64,
 	 .min = 1, .max = ULOG_MAX_QLEN},
-	{.name = "ulog-qthreshold", .id = O_ULOG_QTHR, .type = XTTYPE_UINT64},
 	XTOPT_TABLEEND,
 };
 
diff --git a/extensions/libipt_ULOG.man b/extensions/libipt_ULOG.man
index 649b6e3..c91f776 100644
--- a/extensions/libipt_ULOG.man
+++ b/extensions/libipt_ULOG.man
@@ -1,4 +1,5 @@
-This target provides userspace logging of matching packets.  When this
+This is the deprecated ipv4-only predecessor of the NFLOG target.
+It provides userspace logging of matching packets.  When this
 target is set for a rule, the Linux kernel will multicast this packet
 through a
 .IR netlink 
diff --git a/extensions/libipt_addrtype.c b/extensions/libipt_addrtype.c
deleted file mode 100644
index 3dec626..0000000
--- a/extensions/libipt_addrtype.c
+++ /dev/null
@@ -1,308 +0,0 @@
-/* Shared library add-on to iptables to add addrtype matching support 
- * 
- * This program is released under the terms of GNU GPL */
-#include <stdio.h>
-#include <string.h>
-#include <xtables.h>
-#include <linux/netfilter_ipv4/ipt_addrtype.h>
-
-enum {
-	O_SRC_TYPE = 0,
-	O_DST_TYPE,
-	O_LIMIT_IFACE_IN,
-	O_LIMIT_IFACE_OUT,
-	F_SRC_TYPE        = 1 << O_SRC_TYPE,
-	F_DST_TYPE        = 1 << O_DST_TYPE,
-	F_LIMIT_IFACE_IN  = 1 << O_LIMIT_IFACE_IN,
-	F_LIMIT_IFACE_OUT = 1 << O_LIMIT_IFACE_OUT,
-};
-
-/* from linux/rtnetlink.h, must match order of enumeration */
-static const char *const rtn_names[] = {
-	"UNSPEC",
-	"UNICAST",
-	"LOCAL",
-	"BROADCAST",
-	"ANYCAST",
-	"MULTICAST",
-	"BLACKHOLE",
-	"UNREACHABLE",
-	"PROHIBIT",
-	"THROW",
-	"NAT",
-	"XRESOLVE",
-	NULL
-};
-
-static void addrtype_help_types(void)
-{
-	int i;
-
-	for (i = 0; rtn_names[i]; i++)
-		printf("                                %s\n", rtn_names[i]);
-}
-
-static void addrtype_help_v0(void)
-{
-	printf(
-"Address type match options:\n"
-" [!] --src-type type[,...]      Match source address type\n"
-" [!] --dst-type type[,...]      Match destination address type\n"
-"\n"
-"Valid types:           \n");
-	addrtype_help_types();
-}
-
-static void addrtype_help_v1(void)
-{
-	printf(
-"Address type match options:\n"
-" [!] --src-type type[,...]      Match source address type\n"
-" [!] --dst-type type[,...]      Match destination address type\n"
-"     --limit-iface-in           Match only on the packet's incoming device\n"
-"     --limit-iface-out          Match only on the packet's incoming device\n"
-"\n"
-"Valid types:           \n");
-	addrtype_help_types();
-}
-
-static int
-parse_type(const char *name, size_t len, uint16_t *mask)
-{
-	int i;
-
-	for (i = 0; rtn_names[i]; i++)
-		if (strncasecmp(name, rtn_names[i], len) == 0) {
-			/* build up bitmask for kernel module */
-			*mask |= (1 << i);
-			return 1;
-		}
-
-	return 0;
-}
-
-static void parse_types(const char *arg, uint16_t *mask)
-{
-	const char *comma;
-
-	while ((comma = strchr(arg, ',')) != NULL) {
-		if (comma == arg || !parse_type(arg, comma-arg, mask))
-			xtables_error(PARAMETER_PROBLEM,
-			           "addrtype: bad type `%s'", arg);
-		arg = comma + 1;
-	}
-
-	if (strlen(arg) == 0 || !parse_type(arg, strlen(arg), mask))
-		xtables_error(PARAMETER_PROBLEM, "addrtype: bad type \"%s\"", arg);
-}
-	
-static void addrtype_parse_v0(struct xt_option_call *cb)
-{
-	struct ipt_addrtype_info *info = cb->data;
-
-	xtables_option_parse(cb);
-	switch (cb->entry->id) {
-	case O_SRC_TYPE:
-		parse_types(cb->arg, &info->source);
-		if (cb->invert)
-			info->invert_source = 1;
-		break;
-	case O_DST_TYPE:
-		parse_types(cb->arg, &info->dest);
-		if (cb->invert)
-			info->invert_dest = 1;
-		break;
-	}
-}
-
-static void addrtype_parse_v1(struct xt_option_call *cb)
-{
-	struct ipt_addrtype_info_v1 *info = cb->data;
-
-	xtables_option_parse(cb);
-	switch (cb->entry->id) {
-	case O_SRC_TYPE:
-		parse_types(cb->arg, &info->source);
-		if (cb->invert)
-			info->flags |= IPT_ADDRTYPE_INVERT_SOURCE;
-		break;
-	case O_DST_TYPE:
-		parse_types(cb->arg, &info->dest);
-		if (cb->invert)
-			info->flags |= IPT_ADDRTYPE_INVERT_DEST;
-		break;
-	case O_LIMIT_IFACE_IN:
-		info->flags |= IPT_ADDRTYPE_LIMIT_IFACE_IN;
-		break;
-	case O_LIMIT_IFACE_OUT:
-		info->flags |= IPT_ADDRTYPE_LIMIT_IFACE_OUT;
-		break;
-	}
-}
-
-static void addrtype_check(struct xt_fcheck_call *cb)
-{
-	if (!(cb->xflags & (F_SRC_TYPE | F_DST_TYPE)))
-		xtables_error(PARAMETER_PROBLEM,
-			   "addrtype: you must specify --src-type or --dst-type");
-}
-
-static void print_types(uint16_t mask)
-{
-	const char *sep = "";
-	int i;
-
-	for (i = 0; rtn_names[i]; i++)
-		if (mask & (1 << i)) {
-			printf("%s%s", sep, rtn_names[i]);
-			sep = ",";
-		}
-}
-
-static void addrtype_print_v0(const void *ip, const struct xt_entry_match *match,
-                              int numeric)
-{
-	const struct ipt_addrtype_info *info = 
-		(struct ipt_addrtype_info *) match->data;
-
-	printf(" ADDRTYPE match");
-	if (info->source) {
-		printf(" src-type ");
-		if (info->invert_source)
-			printf("!");
-		print_types(info->source);
-	}
-	if (info->dest) {
-		printf(" dst-type");
-		if (info->invert_dest)
-			printf("!");
-		print_types(info->dest);
-	}
-}
-
-static void addrtype_print_v1(const void *ip, const struct xt_entry_match *match,
-                              int numeric)
-{
-	const struct ipt_addrtype_info_v1 *info = 
-		(struct ipt_addrtype_info_v1 *) match->data;
-
-	printf(" ADDRTYPE match");
-	if (info->source) {
-		printf(" src-type ");
-		if (info->flags & IPT_ADDRTYPE_INVERT_SOURCE)
-			printf("!");
-		print_types(info->source);
-	}
-	if (info->dest) {
-		printf(" dst-type ");
-		if (info->flags & IPT_ADDRTYPE_INVERT_DEST)
-			printf("!");
-		print_types(info->dest);
-	}
-	if (info->flags & IPT_ADDRTYPE_LIMIT_IFACE_IN) {
-		printf(" limit-in");
-	}
-	if (info->flags & IPT_ADDRTYPE_LIMIT_IFACE_OUT) {
-		printf(" limit-out");
-	}
-}
-
-static void addrtype_save_v0(const void *ip, const struct xt_entry_match *match)
-{
-	const struct ipt_addrtype_info *info =
-		(struct ipt_addrtype_info *) match->data;
-
-	if (info->source) {
-		if (info->invert_source)
-			printf(" !");
-		printf(" --src-type ");
-		print_types(info->source);
-	}
-	if (info->dest) {
-		if (info->invert_dest)
-			printf(" !");
-		printf(" --dst-type ");
-		print_types(info->dest);
-	}
-}
-
-static void addrtype_save_v1(const void *ip, const struct xt_entry_match *match)
-{
-	const struct ipt_addrtype_info_v1 *info =
-		(struct ipt_addrtype_info_v1 *) match->data;
-
-	if (info->source) {
-		if (info->flags & IPT_ADDRTYPE_INVERT_SOURCE)
-			printf(" !");
-		printf(" --src-type ");
-		print_types(info->source);
-	}
-	if (info->dest) {
-		if (info->flags & IPT_ADDRTYPE_INVERT_DEST)
-			printf(" !");
-		printf(" --dst-type ");
-		print_types(info->dest);
-	}
-	if (info->flags & IPT_ADDRTYPE_LIMIT_IFACE_IN) {
-		printf(" --limit-iface-in");
-	}
-	if (info->flags & IPT_ADDRTYPE_LIMIT_IFACE_OUT) {
-		printf(" --limit-iface-out");
-	}
-}
-
-static const struct xt_option_entry addrtype_opts_v0[] = {
-	{.name = "src-type", .id = O_SRC_TYPE, .type = XTTYPE_STRING,
-	 .flags = XTOPT_INVERT},
-	{.name = "dst-type", .id = O_DST_TYPE, .type = XTTYPE_STRING,
-	 .flags = XTOPT_INVERT},
-	XTOPT_TABLEEND,
-};
-
-static const struct xt_option_entry addrtype_opts_v1[] = {
-	{.name = "src-type", .id = O_SRC_TYPE, .type = XTTYPE_STRING,
-	 .flags = XTOPT_INVERT},
-	{.name = "dst-type", .id = O_DST_TYPE, .type = XTTYPE_STRING,
-	 .flags = XTOPT_INVERT},
-	{.name = "limit-iface-in", .id = O_LIMIT_IFACE_IN,
-	 .type = XTTYPE_NONE, .excl = F_LIMIT_IFACE_OUT},
-	{.name = "limit-iface-out", .id = O_LIMIT_IFACE_OUT,
-	 .type = XTTYPE_NONE, .excl = F_LIMIT_IFACE_IN},
-	XTOPT_TABLEEND,
-};
-
-static struct xtables_match addrtype_mt_reg[] = {
-	{
-		.name          = "addrtype",
-		.version       = XTABLES_VERSION,
-		.family        = NFPROTO_IPV4,
-		.size          = XT_ALIGN(sizeof(struct ipt_addrtype_info)),
-		.userspacesize = XT_ALIGN(sizeof(struct ipt_addrtype_info)),
-		.help          = addrtype_help_v0,
-		.print         = addrtype_print_v0,
-		.save          = addrtype_save_v0,
-		.x6_parse      = addrtype_parse_v0,
-		.x6_fcheck     = addrtype_check,
-		.x6_options    = addrtype_opts_v0,
-	},
-	{
-		.name          = "addrtype",
-		.revision      = 1,
-		.version       = XTABLES_VERSION,
-		.family        = NFPROTO_IPV4,
-		.size          = XT_ALIGN(sizeof(struct ipt_addrtype_info_v1)),
-		.userspacesize = XT_ALIGN(sizeof(struct ipt_addrtype_info_v1)),
-		.help          = addrtype_help_v1,
-		.print         = addrtype_print_v1,
-		.save          = addrtype_save_v1,
-		.x6_parse      = addrtype_parse_v1,
-		.x6_fcheck     = addrtype_check,
-		.x6_options    = addrtype_opts_v1,
-	},
-};
-
-
-void _init(void) 
-{
-	xtables_register_matches(addrtype_mt_reg, ARRAY_SIZE(addrtype_mt_reg));
-}
diff --git a/extensions/libipt_ah.c b/extensions/libipt_ah.c
index 8cf167c..fec5705 100644
--- a/extensions/libipt_ah.c
+++ b/extensions/libipt_ah.c
@@ -21,6 +21,13 @@
 	XTOPT_TABLEEND,
 };
 
+static void ah_init(struct xt_entry_match *m)
+{
+	struct ipt_ah *ahinfo = (void *)m->data;
+
+	ahinfo->spis[1] = ~0U;
+}
+
 static void ah_parse(struct xt_option_call *cb)
 {
 	struct ipt_ah *ahinfo = cb->data;
@@ -85,17 +92,37 @@
 
 }
 
+static int ah_xlate(struct xt_xlate *xl,
+		    const struct xt_xlate_mt_params *params)
+{
+	const struct ipt_ah *ahinfo = (struct ipt_ah *)params->match->data;
+
+	if (!(ahinfo->spis[0] == 0 && ahinfo->spis[1] == 0xFFFFFFFF)) {
+		xt_xlate_add(xl, "ah spi%s ",
+			   (ahinfo->invflags & IPT_AH_INV_SPI) ? " !=" : "");
+		if (ahinfo->spis[0] != ahinfo->spis[1])
+			xt_xlate_add(xl, "%u-%u", ahinfo->spis[0],
+				   ahinfo->spis[1]);
+		else
+			xt_xlate_add(xl, "%u", ahinfo->spis[0]);
+	}
+
+	return 1;
+}
+
 static struct xtables_match ah_mt_reg = {
-	.name 		= "ah",
-	.version 	= XTABLES_VERSION,
+	.name		= "ah",
+	.version	= XTABLES_VERSION,
 	.family		= NFPROTO_IPV4,
 	.size		= XT_ALIGN(sizeof(struct ipt_ah)),
-	.userspacesize 	= XT_ALIGN(sizeof(struct ipt_ah)),
-	.help 		= ah_help,
-	.print 		= ah_print,
-	.save 		= ah_save,
+	.userspacesize	= XT_ALIGN(sizeof(struct ipt_ah)),
+	.help		= ah_help,
+	.init		= ah_init,
+	.print		= ah_print,
+	.save		= ah_save,
 	.x6_parse	= ah_parse,
 	.x6_options	= ah_opts,
+	.xlate		= ah_xlate,
 };
 
 void
diff --git a/extensions/libipt_ah.t b/extensions/libipt_ah.t
new file mode 100644
index 0000000..cd85386
--- /dev/null
+++ b/extensions/libipt_ah.t
@@ -0,0 +1,13 @@
+:INPUT,FORWARD,OUTPUT
+-p ah -m ah --ahspi 0;=;OK
+-p ah -m ah --ahspi 4294967295;=;OK
+-p ah -m ah --ahspi 0:4294967295;-p ah -m ah;OK
+-p ah -m ah ! --ahspi 0;=;OK
+-p ah -m ah --ahspi -1;;FAIL
+-p ah -m ah --ahspi 4294967296;;FAIL
+-p ah -m ah --ahspi invalid;;FAIL
+-p ah -m ah --ahspi 0:invalid;;FAIL
+-m ah --ahspi 0;;FAIL
+-m ah --ahspi;;FAIL
+-m ah;;FAIL
+-p ah -m ah;=;OK
diff --git a/extensions/libipt_ah.txlate b/extensions/libipt_ah.txlate
new file mode 100644
index 0000000..ea3ef3e
--- /dev/null
+++ b/extensions/libipt_ah.txlate
@@ -0,0 +1,8 @@
+iptables-translate -A INPUT -p 51 -m ah --ahspi 500 -j DROP
+nft add rule ip filter INPUT ah spi 500 counter drop
+
+iptables-translate -A INPUT -p 51 -m ah --ahspi 500:600 -j DROP
+nft add rule ip filter INPUT ah spi 500-600 counter drop
+
+iptables-translate -A INPUT -p 51 -m ah ! --ahspi 50 -j DROP
+nft add rule ip filter INPUT ah spi != 50 counter drop
diff --git a/extensions/libipt_ecn.c b/extensions/libipt_ecn.c
deleted file mode 100644
index 56a0347..0000000
--- a/extensions/libipt_ecn.c
+++ /dev/null
@@ -1,137 +0,0 @@
-/* Shared library add-on to iptables for ECN matching
- *
- * (C) 2002 by Harald Welte <laforge@gnumonks.org>
- *
- * This program is distributed under the terms of GNU GPL v2, 1991
- *
- * libipt_ecn.c borrowed heavily from libipt_dscp.c
- *
- */
-#include <stdio.h>
-#include <xtables.h>
-#include <linux/netfilter_ipv4/ipt_ecn.h>
-
-enum {
-	O_ECN_TCP_CWR = 0,
-	O_ECN_TCP_ECE,
-	O_ECN_IP_ECT,
-};
-
-static void ecn_help(void)
-{
-	printf(
-"ECN match options\n"
-"[!] --ecn-tcp-cwr 		Match CWR bit of TCP header\n"
-"[!] --ecn-tcp-ece		Match ECE bit of TCP header\n"
-"[!] --ecn-ip-ect [0..3]	Match ECN codepoint in IPv4 header\n");
-}
-
-static const struct xt_option_entry ecn_opts[] = {
-	{.name = "ecn-tcp-cwr", .id = O_ECN_TCP_CWR, .type = XTTYPE_NONE,
-	 .flags = XTOPT_INVERT},
-	{.name = "ecn-tcp-ece", .id = O_ECN_TCP_ECE, .type = XTTYPE_NONE,
-	 .flags = XTOPT_INVERT},
-	{.name = "ecn-ip-ect", .id = O_ECN_IP_ECT, .type = XTTYPE_UINT8,
-	 .min = 0, .max = 3, .flags = XTOPT_INVERT},
-	XTOPT_TABLEEND,
-};
-
-static void ecn_parse(struct xt_option_call *cb)
-{
-	struct ipt_ecn_info *einfo = cb->data;
-
-	xtables_option_parse(cb);
-	switch (cb->entry->id) {
-	case O_ECN_TCP_CWR:
-		einfo->operation |= IPT_ECN_OP_MATCH_CWR;
-		if (cb->invert)
-			einfo->invert |= IPT_ECN_OP_MATCH_CWR;
-		break;
-	case O_ECN_TCP_ECE:
-		einfo->operation |= IPT_ECN_OP_MATCH_ECE;
-		if (cb->invert)
-			einfo->invert |= IPT_ECN_OP_MATCH_ECE;
-		break;
-	case O_ECN_IP_ECT:
-		if (cb->invert)
-			einfo->invert |= IPT_ECN_OP_MATCH_IP;
-		einfo->operation |= IPT_ECN_OP_MATCH_IP;
-		einfo->ip_ect = cb->val.u8;
-		break;
-	}
-}
-
-static void ecn_check(struct xt_fcheck_call *cb)
-{
-	if (cb->xflags == 0)
-		xtables_error(PARAMETER_PROBLEM,
-		           "ECN match: some option required");
-}
-
-static void ecn_print(const void *ip, const struct xt_entry_match *match,
-                      int numeric)
-{
-	const struct ipt_ecn_info *einfo =
-		(const struct ipt_ecn_info *)match->data;
-
-	printf(" ECN match");
-
-	if (einfo->operation & IPT_ECN_OP_MATCH_ECE) {
-		printf(" %sECE",
-		       (einfo->invert & IPT_ECN_OP_MATCH_ECE) ? "!" : "");
-	}
-
-	if (einfo->operation & IPT_ECN_OP_MATCH_CWR) {
-		printf(" %sCWR",
-		       (einfo->invert & IPT_ECN_OP_MATCH_CWR) ? "!" : "");
-	}
-
-	if (einfo->operation & IPT_ECN_OP_MATCH_IP) {
-		printf(" %sECT=%d",
-		       (einfo->invert & IPT_ECN_OP_MATCH_IP) ? "!" : "",
-		       einfo->ip_ect);
-	}
-}
-
-static void ecn_save(const void *ip, const struct xt_entry_match *match)
-{
-	const struct ipt_ecn_info *einfo =
-		(const struct ipt_ecn_info *)match->data;
-	
-	if (einfo->operation & IPT_ECN_OP_MATCH_ECE) {
-		if (einfo->invert & IPT_ECN_OP_MATCH_ECE)
-			printf(" !");
-		printf(" --ecn-tcp-ece");
-	}
-
-	if (einfo->operation & IPT_ECN_OP_MATCH_CWR) {
-		if (einfo->invert & IPT_ECN_OP_MATCH_CWR)
-			printf(" !");
-		printf(" --ecn-tcp-cwr");
-	}
-
-	if (einfo->operation & IPT_ECN_OP_MATCH_IP) {
-		if (einfo->invert & IPT_ECN_OP_MATCH_IP)
-			printf(" !");
-		printf(" --ecn-ip-ect %d", einfo->ip_ect);
-	}
-}
-
-static struct xtables_match ecn_mt_reg = {
-	.name          = "ecn",
-	.version       = XTABLES_VERSION,
-	.family        = NFPROTO_IPV4,
-	.size          = XT_ALIGN(sizeof(struct ipt_ecn_info)),
-	.userspacesize = XT_ALIGN(sizeof(struct ipt_ecn_info)),
-	.help          = ecn_help,
-	.print         = ecn_print,
-	.save          = ecn_save,
-	.x6_parse      = ecn_parse,
-	.x6_fcheck     = ecn_check,
-	.x6_options    = ecn_opts,
-};
-
-void _init(void)
-{
-	xtables_register_match(&ecn_mt_reg);
-}
diff --git a/extensions/libipt_ecn.man b/extensions/libipt_ecn.man
deleted file mode 100644
index 7f80647..0000000
--- a/extensions/libipt_ecn.man
+++ /dev/null
@@ -1,11 +0,0 @@
-This allows you to match the ECN bits of the IPv4 and TCP header.  ECN is the Explicit Congestion Notification mechanism as specified in RFC3168
-.TP
-[\fB!\fP] \fB\-\-ecn\-tcp\-cwr\fP
-This matches if the TCP ECN CWR (Congestion Window Received) bit is set.
-.TP
-[\fB!\fP] \fB\-\-ecn\-tcp\-ece\fP
-This matches if the TCP ECN ECE (ECN Echo) bit is set.
-.TP
-[\fB!\fP] \fB\-\-ecn\-ip\-ect\fP \fInum\fP
-This matches a particular IPv4 ECT (ECN-Capable Transport). You have to specify
-a number between `0' and `3'.
diff --git a/extensions/libipt_icmp.c b/extensions/libipt_icmp.c
index 666e7da..e5e2366 100644
--- a/extensions/libipt_icmp.c
+++ b/extensions/libipt_icmp.c
@@ -5,6 +5,8 @@
 #include <limits.h> /* INT_MAX in ip6_tables.h */
 #include <linux/netfilter_ipv4/ip_tables.h>
 
+#include "libxt_icmp.h"
+
 /* special hack for icmp-type 'any': 
  * Up to kernel <=2.4.20 the problem was:
  * '-p icmp ' matches all icmp packets
@@ -17,13 +19,7 @@
 	O_ICMP_TYPE = 0,
 };
 
-struct icmp_names {
-	const char *name;
-	uint8_t type;
-	uint8_t code_min, code_max;
-};
-
-static const struct icmp_names icmp_codes[] = {
+static const struct xt_icmp_names icmp_codes[] = {
 	{ "any", 0xFF, 0, 0xFF },
 	{ "echo-reply", 0, 0, 0xFF },
 	/* Alias */ { "pong", 0, 0, 0xFF },
@@ -78,34 +74,14 @@
 	{ "address-mask-reply", 18, 0, 0xFF }
 };
 
-static void
-print_icmptypes(void)
-{
-	unsigned int i;
-	printf("Valid ICMP Types:");
-
-	for (i = 0; i < ARRAY_SIZE(icmp_codes); ++i) {
-		if (i && icmp_codes[i].type == icmp_codes[i-1].type) {
-			if (icmp_codes[i].code_min == icmp_codes[i-1].code_min
-			    && (icmp_codes[i].code_max
-				== icmp_codes[i-1].code_max))
-				printf(" (%s)", icmp_codes[i].name);
-			else
-				printf("\n   %s", icmp_codes[i].name);
-		}
-		else
-			printf("\n%s", icmp_codes[i].name);
-	}
-	printf("\n");
-}
-
 static void icmp_help(void)
 {
 	printf(
 "icmp match options:\n"
 "[!] --icmp-type typename	match icmp type\n"
 "[!] --icmp-type type[/code]	(or numeric type or type/code)\n");
-	print_icmptypes();
+	printf("Valid ICMP Types:");
+	xt_print_icmp_types(icmp_codes, ARRAY_SIZE(icmp_codes));
 }
 
 static const struct xt_option_entry icmp_opts[] = {
@@ -249,6 +225,46 @@
 	}
 }
 
+static unsigned int type_xlate_print(struct xt_xlate *xl, unsigned int icmptype,
+				     unsigned int code_min,
+				     unsigned int code_max)
+{
+	unsigned int i;
+
+	if (code_min != code_max) {
+		for (i = 0; i < ARRAY_SIZE(icmp_codes); ++i)
+			if (icmp_codes[i].type == icmptype &&
+			    icmp_codes[i].code_min == code_min &&
+			    icmp_codes[i].code_max == code_max) {
+				xt_xlate_add(xl, "%s", icmp_codes[i].name);
+				return 1;
+			}
+	}
+
+	return 0;
+}
+
+static int icmp_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_mt_params *params)
+{
+	const struct ipt_icmp *info = (struct ipt_icmp *)params->match->data;
+
+	if (info->type != 0xFF) {
+		xt_xlate_add(xl, "icmp type%s ",
+			     (info->invflags & IPT_ICMP_INV) ? " !=" : "");
+
+		if (!type_xlate_print(xl, info->type, info->code[0],
+				      info->code[1]))
+			return 0;
+	} else {
+		/* '-m icmp --icmp-type any' is a noop by itself,
+		 * but it eats a (mandatory) previous '-p icmp' so
+		 * emit it here */
+		xt_xlate_add(xl, "ip protocol icmp");
+	}
+	return 1;
+}
+
 static struct xtables_match icmp_mt_reg = {
 	.name		= "icmp",
 	.version	= XTABLES_VERSION,
@@ -261,6 +277,7 @@
 	.save		= icmp_save,
 	.x6_parse	= icmp_parse,
 	.x6_options	= icmp_opts,
+	.xlate		= icmp_xlate,
 };
 
 void _init(void)
diff --git a/extensions/libipt_icmp.t b/extensions/libipt_icmp.t
new file mode 100644
index 0000000..f4ba65c
--- /dev/null
+++ b/extensions/libipt_icmp.t
@@ -0,0 +1,15 @@
+:INPUT,FORWARD,OUTPUT
+-p icmp -m icmp --icmp-type any;=;OK
+# output uses the number, better use the name?
+# ERROR: cannot find: iptables -I INPUT -p icmp -m icmp --icmp-type echo-reply
+# -p icmp -m icmp --icmp-type echo-reply;=;OK
+# output uses the number, better use the name?
+# ERROR: annot find: iptables -I INPUT -p icmp -m icmp --icmp-type destination-unreachable
+# -p icmp -m icmp --icmp-type destination-unreachable;=;OK
+# it does not acccept name/name, should we accept this?
+# ERROR: cannot load: iptables -A INPUT -p icmp -m icmp --icmp-type destination-unreachable/network-unreachable
+# -p icmp -m icmp --icmp-type destination-unreachable/network-unreachable;=;OK
+-m icmp;;FAIL
+# we accept "iptables -I INPUT -p tcp -m tcp", why not this below?
+# ERROR: cannot load: iptables -A INPUT -p icmp -m icmp
+# -p icmp -m icmp;=;OK
diff --git a/extensions/libipt_icmp.txlate b/extensions/libipt_icmp.txlate
new file mode 100644
index 0000000..a2aec8e
--- /dev/null
+++ b/extensions/libipt_icmp.txlate
@@ -0,0 +1,11 @@
+iptables-translate -t filter -A INPUT -m icmp --icmp-type echo-reply -j ACCEPT
+nft add rule ip filter INPUT icmp type echo-reply counter accept
+
+iptables-translate -t filter -A INPUT -m icmp --icmp-type 3 -j ACCEPT
+nft add rule ip filter INPUT icmp type destination-unreachable counter accept
+
+iptables-translate -t filter -A INPUT -m icmp ! --icmp-type 3 -j ACCEPT
+nft add rule ip filter INPUT icmp type != destination-unreachable counter accept
+
+iptables-translate -t filter -A INPUT -m icmp --icmp-type any -j ACCEPT
+nft add rule ip filter INPUT ip protocol icmp counter accept
diff --git a/extensions/libipt_realm.c b/extensions/libipt_realm.c
index b60c57e..e01d048 100644
--- a/extensions/libipt_realm.c
+++ b/extensions/libipt_realm.c
@@ -28,100 +28,104 @@
 	XTOPT_TABLEEND,
 };
 
-/* array of realms from /etc/iproute2/rt_realms */
+static const char f_realms[] = "/etc/iproute2/rt_realms";
+/* array of realms from f_realms[] */
 static struct xtables_lmap *realms;
 
-static void realm_init(struct xt_entry_match *m)
-{
-	const char file[] = "/etc/iproute2/rt_realms";
-	realms = xtables_lmap_init(file);
-	if (realms == NULL && errno != ENOENT)
-		fprintf(stderr, "Warning: %s: %s\n", file, strerror(errno));
-}
-
 static void realm_parse(struct xt_option_call *cb)
 {
-	struct ipt_realm_info *realminfo = cb->data;
-	int id;
-	char *end;
+	struct xt_realm_info *ri = cb->data;
+	unsigned int id, mask;
 
 	xtables_option_parse(cb);
-	realminfo->id = strtoul(cb->arg, &end, 0);
-	if (end != cb->arg && (*end == '/' || *end == '\0')) {
-		if (*end == '/')
-			realminfo->mask = strtoul(end+1, &end, 0);
-		else
-			realminfo->mask = 0xffffffff;
-		if (*end != '\0' || end == cb->arg)
-			xtables_error(PARAMETER_PROBLEM,
-				   "Bad realm value \"%s\"", cb->arg);
-	} else {
-		id = xtables_lmap_name2id(realms, cb->arg);
-		if (id == -1)
-			xtables_error(PARAMETER_PROBLEM,
-				   "Realm \"%s\" not found", cb->arg);
-		realminfo->id = id;
-		realminfo->mask = 0xffffffff;
-	}
+	xtables_parse_val_mask(cb, &id, &mask, realms);
+
+	ri->id = id;
+	ri->mask = mask;
+
 	if (cb->invert)
-		realminfo->invert = 1;
-}
-
-static void
-print_realm(unsigned long id, unsigned long mask, int numeric)
-{
-	const char* name = NULL;
-
-	if (mask != 0xffffffff)
-		printf(" 0x%lx/0x%lx", id, mask);
-	else {
-		if (numeric == 0)
-			name = xtables_lmap_id2name(realms, id);
-		if (name)
-			printf(" %s", name);
-		else
-			printf(" 0x%lx", id);
-	}
+		ri->invert = 1;
 }
 
 static void realm_print(const void *ip, const struct xt_entry_match *match,
-                        int numeric)
+			int numeric)
 {
-	const struct ipt_realm_info *ri = (const void *)match->data;
+	const struct xt_realm_info *ri = (const void *)match->data;
 
 	if (ri->invert)
 		printf(" !");
 
 	printf(" realm");
-	print_realm(ri->id, ri->mask, numeric);
+	xtables_print_val_mask(ri->id, ri->mask, numeric ? NULL : realms);
 }
 
 static void realm_save(const void *ip, const struct xt_entry_match *match)
 {
-	const struct ipt_realm_info *ri = (const void *)match->data;
+	const struct xt_realm_info *ri = (const void *)match->data;
 
 	if (ri->invert)
 		printf(" !");
 
 	printf(" --realm");
-	print_realm(ri->id, ri->mask, 0);
+	xtables_print_val_mask(ri->id, ri->mask, realms);
+}
+
+static void
+print_realm_xlate(unsigned long id, unsigned long mask,
+		  int numeric, struct xt_xlate *xl, uint32_t op)
+{
+	const char *name = NULL;
+
+	if (mask != 0xffffffff)
+		xt_xlate_add(xl, " and 0x%lx %s 0x%lx", mask,
+			   op == XT_OP_EQ ? "==" : "!=", id);
+	else {
+		if (numeric == 0)
+			name = xtables_lmap_id2name(realms, id);
+		if (name)
+			xt_xlate_add(xl, " %s%s",
+				   op == XT_OP_EQ ? "" : "!= ", name);
+		else
+			xt_xlate_add(xl, " %s0x%lx",
+				   op == XT_OP_EQ ? "" : "!= ", id);
+	}
+}
+
+static int realm_xlate(struct xt_xlate *xl,
+		       const struct xt_xlate_mt_params *params)
+{
+	const struct xt_realm_info *ri = (const void *)params->match->data;
+	enum xt_op op = XT_OP_EQ;
+
+	if (ri->invert)
+		op = XT_OP_NEQ;
+
+	xt_xlate_add(xl, "rtclassid");
+	print_realm_xlate(ri->id, ri->mask, 0, xl, op);
+
+	return 1;
 }
 
 static struct xtables_match realm_mt_reg = {
 	.name		= "realm",
 	.version	= XTABLES_VERSION,
 	.family		= NFPROTO_IPV4,
-	.size		= XT_ALIGN(sizeof(struct ipt_realm_info)),
-	.userspacesize	= XT_ALIGN(sizeof(struct ipt_realm_info)),
+	.size		= XT_ALIGN(sizeof(struct xt_realm_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_realm_info)),
 	.help		= realm_help,
-	.init		= realm_init,
 	.print		= realm_print,
 	.save		= realm_save,
 	.x6_parse	= realm_parse,
 	.x6_options	= realm_opts,
+	.xlate		= realm_xlate,
 };
 
 void _init(void)
 {
+	realms = xtables_lmap_init(f_realms);
+	if (realms == NULL && errno != ENOENT)
+		fprintf(stderr, "Warning: %s: %s\n", f_realms,
+			strerror(errno));
+
 	xtables_register_match(&realm_mt_reg);
 }
diff --git a/extensions/libipt_realm.man b/extensions/libipt_realm.man
index a40b1ad..72dff9b 100644
--- a/extensions/libipt_realm.man
+++ b/extensions/libipt_realm.man
@@ -5,3 +5,5 @@
 Matches a given realm number (and optionally mask). If not a number, value
 can be a named realm from /etc/iproute2/rt_realms (mask can not be used in
 that case).
+Both value and mask are four byte unsigned integers and may be specified in
+decimal, hex (by prefixing with "0x") or octal (if a leading zero is given).
diff --git a/extensions/libipt_realm.t b/extensions/libipt_realm.t
new file mode 100644
index 0000000..ca66640
--- /dev/null
+++ b/extensions/libipt_realm.t
@@ -0,0 +1,4 @@
+:INPUT,FORWARD,OUTPUT
+-m realm --realm 0x1/0x2a;=;OK
+-m realm --realm 0x2a;=;OK
+-m realm;;FAIL
diff --git a/extensions/libipt_realm.txlate b/extensions/libipt_realm.txlate
new file mode 100644
index 0000000..7d71029
--- /dev/null
+++ b/extensions/libipt_realm.txlate
@@ -0,0 +1,11 @@
+iptables-translate -A PREROUTING -m realm --realm 4
+nft add rule ip filter PREROUTING rtclassid 0x4 counter
+
+iptables-translate -A PREROUTING -m realm --realm 5/5
+nft add rule ip filter PREROUTING rtclassid and 0x5 == 0x5 counter
+
+iptables-translate -A PREROUTING -m realm ! --realm 50
+nft add rule ip filter PREROUTING rtclassid != 0x32 counter
+
+iptables-translate -A INPUT -m realm --realm 1/0xf
+nft add rule ip filter INPUT rtclassid and 0xf == 0x1 counter
diff --git a/extensions/libipt_ttl.c b/extensions/libipt_ttl.c
index 6370cb6..6bdd219 100644
--- a/extensions/libipt_ttl.c
+++ b/extensions/libipt_ttl.c
@@ -20,7 +20,7 @@
 {
 	printf(
 "ttl match options:\n"
-"  --ttl-eq value	Match time to live value\n"
+"[!] --ttl-eq value	Match time to live value\n"
 "  --ttl-lt value	Match TTL < value\n"
 "  --ttl-gt value	Match TTL > value\n");
 }
@@ -100,6 +100,35 @@
 	printf(" %u", info->ttl);
 }
 
+static int ttl_xlate(struct xt_xlate *xl,
+		     const struct xt_xlate_mt_params *params)
+{
+	const struct ipt_ttl_info *info =
+		(struct ipt_ttl_info *) params->match->data;
+
+		switch (info->mode) {
+		case IPT_TTL_EQ:
+			xt_xlate_add(xl, "ip ttl");
+			break;
+		case IPT_TTL_NE:
+			xt_xlate_add(xl, "ip ttl !=");
+			break;
+		case IPT_TTL_LT:
+			xt_xlate_add(xl, "ip ttl lt");
+			break;
+		case IPT_TTL_GT:
+			xt_xlate_add(xl, "ip ttl gt");
+			break;
+		default:
+			/* Should not happen. */
+			break;
+	}
+
+	xt_xlate_add(xl, " %u", info->ttl);
+
+	return 1;
+}
+
 #define s struct ipt_ttl_info
 static const struct xt_option_entry ttl_opts[] = {
 	{.name = "ttl-lt", .id = O_TTL_LT, .excl = F_ANY, .type = XTTYPE_UINT8,
@@ -126,6 +155,7 @@
 	.x6_parse	= ttl_parse,
 	.x6_fcheck	= ttl_check,
 	.x6_options	= ttl_opts,
+	.xlate		= ttl_xlate,
 };
 
 
diff --git a/extensions/libipt_ttl.man b/extensions/libipt_ttl.man
index 849f704..1f32277 100644
--- a/extensions/libipt_ttl.man
+++ b/extensions/libipt_ttl.man
@@ -1,6 +1,6 @@
 This module matches the time to live field in the IP header.
 .TP
-\fB\-\-ttl\-eq\fP \fIttl\fP
+[\fB!\fP] \fB\-\-ttl\-eq\fP \fIttl\fP
 Matches the given TTL value.
 .TP
 \fB\-\-ttl\-gt\fP \fIttl\fP
diff --git a/extensions/libipt_ttl.t b/extensions/libipt_ttl.t
new file mode 100644
index 0000000..ebe5b3a
--- /dev/null
+++ b/extensions/libipt_ttl.t
@@ -0,0 +1,15 @@
+:INPUT,FORWARD,OUTPUT
+-m ttl --ttl-eq 0;=;OK
+-m ttl --ttl-eq 255;=;OK
+-m ttl ! --ttl-eq 0;=;OK
+-m ttl ! --ttl-eq 255;=;OK
+-m ttl --ttl-gt 0;=;OK
+# not possible have anything greater than 255, TTL is 8-bit long
+# ERROR: should fail: iptables -A INPUT -m ttl --ttl-gt 255
+## -m ttl --ttl-gt 255;;FAIL
+# not possible have anything below 0
+# ERROR: should fail: iptables -A INPUT -m ttl --ttl-lt 0
+## -m ttl --ttl-lt 0;;FAIL
+-m ttl --ttl-eq 256;;FAIL
+-m ttl --ttl-eq -1;;FAIL
+-m ttl;;FAIL
diff --git a/extensions/libipt_ttl.txlate b/extensions/libipt_ttl.txlate
new file mode 100644
index 0000000..3d5d6a7
--- /dev/null
+++ b/extensions/libipt_ttl.txlate
@@ -0,0 +1,5 @@
+iptables-translate -A INPUT -m ttl --ttl-eq 3 -j ACCEPT
+nft add rule ip filter INPUT ip ttl 3 counter accept
+
+iptables-translate -A INPUT -m ttl --ttl-gt 5 -j ACCEPT
+nft add rule ip filter INPUT ip ttl gt 5 counter accept
diff --git a/extensions/libipt_unclean.c b/extensions/libipt_unclean.c
deleted file mode 100644
index bc4a4a0..0000000
--- a/extensions/libipt_unclean.c
+++ /dev/null
@@ -1,15 +0,0 @@
-/* Shared library add-on to iptables for unclean. */
-#include <xtables.h>
-
-static struct xtables_match unclean_mt_reg = {
-	.name		= "unclean",
-	.version	= XTABLES_VERSION,
-	.family		= NFPROTO_IPV4,
-	.size		= XT_ALIGN(0),
-	.userspacesize	= XT_ALIGN(0),
-};
-
-void _init(void)
-{
-	xtables_register_match(&unclean_mt_reg);
-}
diff --git a/extensions/libipt_unclean.man b/extensions/libipt_unclean.man
deleted file mode 100644
index 3fecd55..0000000
--- a/extensions/libipt_unclean.man
+++ /dev/null
@@ -1,2 +0,0 @@
-This module takes no options, but attempts to match packets which seem
-malformed or unusual.  This is regarded as experimental.
diff --git a/extensions/libxt_AUDIT.c b/extensions/libxt_AUDIT.c
index 86a61cb..f7832de 100644
--- a/extensions/libxt_AUDIT.c
+++ b/extensions/libxt_AUDIT.c
@@ -82,6 +82,16 @@
 	}
 }
 
+static int audit_xlate(struct xt_xlate *xl,
+		       const struct xt_xlate_tg_params *params)
+{
+	/* audit type is merely sanity checked by xt_AUDIT.ko,
+	 * so nftables doesn't even support it */
+
+	xt_xlate_add(xl, "log level audit");
+	return 1;
+}
+
 static struct xtables_target audit_tg_reg = {
 	.name		= "AUDIT",
 	.version	= XTABLES_VERSION,
@@ -93,6 +103,7 @@
 	.save		= audit_save,
 	.x6_parse	= audit_parse,
 	.x6_options	= audit_opts,
+	.xlate		= audit_xlate,
 };
 
 void _init(void)
diff --git a/extensions/libxt_AUDIT.man b/extensions/libxt_AUDIT.man
index cd79696..8c513d2 100644
--- a/extensions/libxt_AUDIT.man
+++ b/extensions/libxt_AUDIT.man
@@ -1,14 +1,16 @@
-This target allows to create audit records for packets hitting the target.
+This target creates audit records for packets hitting the target.
 It can be used to record accepted, dropped, and rejected packets. See
 auditd(8) for additional details.
 .TP
 \fB\-\-type\fP {\fBaccept\fP|\fBdrop\fP|\fBreject\fP}
-Set type of audit record.
+Set type of audit record. Starting with linux-4.12, this option has no effect
+on generated audit messages anymore. It is still accepted by iptables for
+compatibility reasons, but ignored.
 .PP
 Example:
 .IP
 iptables \-N AUDIT_DROP
 .IP
-iptables \-A AUDIT_DROP \-j AUDIT \-\-type drop
+iptables \-A AUDIT_DROP \-j AUDIT
 .IP
 iptables \-A AUDIT_DROP \-j DROP
diff --git a/extensions/libxt_AUDIT.t b/extensions/libxt_AUDIT.t
new file mode 100644
index 0000000..97575b0
--- /dev/null
+++ b/extensions/libxt_AUDIT.t
@@ -0,0 +1,6 @@
+:INPUT,FORWARD,OUTPUT
+-j AUDIT --type accept;=;OK
+-j AUDIT --type drop;=;OK
+-j AUDIT --type reject;=;OK
+-j AUDIT;;FAIL
+-j AUDIT --type wrong;;FAIL
diff --git a/extensions/libxt_AUDIT.txlate b/extensions/libxt_AUDIT.txlate
new file mode 100644
index 0000000..abd11ea
--- /dev/null
+++ b/extensions/libxt_AUDIT.txlate
@@ -0,0 +1,8 @@
+iptables-translate -t filter -A INPUT -j AUDIT --type accept
+nft add rule ip filter INPUT counter log level audit
+
+iptables-translate -t filter -A INPUT -j AUDIT --type drop
+nft add rule ip filter INPUT counter log level audit
+
+iptables-translate -t filter -A INPUT -j AUDIT --type reject
+nft add rule ip filter INPUT counter log level audit
diff --git a/extensions/libxt_CHECKSUM.man b/extensions/libxt_CHECKSUM.man
index 92ae700..726f4ea 100644
--- a/extensions/libxt_CHECKSUM.man
+++ b/extensions/libxt_CHECKSUM.man
@@ -1,4 +1,4 @@
-This target allows to selectively work around broken/old applications.
+This target selectively works around broken/old applications.
 It can only be used in the mangle table.
 .TP
 \fB\-\-checksum\-fill\fP
diff --git a/extensions/libxt_CHECKSUM.t b/extensions/libxt_CHECKSUM.t
new file mode 100644
index 0000000..9451ad8
--- /dev/null
+++ b/extensions/libxt_CHECKSUM.t
@@ -0,0 +1,4 @@
+:PREROUTING,FORWARD,POSTROUTING
+*mangle
+-j CHECKSUM --checksum-fill;=;OK
+-j CHECKSUM;;FAIL
diff --git a/extensions/libxt_CLASSIFY.c b/extensions/libxt_CLASSIFY.c
index ee0f9e1..75aaf0c 100644
--- a/extensions/libxt_CLASSIFY.c
+++ b/extensions/libxt_CLASSIFY.c
@@ -1,3 +1,7 @@
+/*
+ * Copyright (c) 2003-2013 Patrick McHardy <kaber@trash.net>
+ */
+
 #include <stdio.h>
 #include <xtables.h>
 #include <linux/netfilter/xt_CLASSIFY.h>
@@ -69,20 +73,79 @@
 	       TC_H_MAJ(clinfo->priority)>>16, TC_H_MIN(clinfo->priority));
 }
 
-static struct xtables_target classify_target = { 
-	.family		= NFPROTO_UNSPEC,
-	.name		= "CLASSIFY",
-	.version	= XTABLES_VERSION,
-	.size		= XT_ALIGN(sizeof(struct xt_classify_target_info)),
-	.userspacesize	= XT_ALIGN(sizeof(struct xt_classify_target_info)),
-	.help		= CLASSIFY_help,
-	.print		= CLASSIFY_print,
-	.save		= CLASSIFY_save,
-	.x6_parse	= CLASSIFY_parse,
-	.x6_options	= CLASSIFY_opts,
+static void
+CLASSIFY_arp_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_classify_target_info *clinfo =
+		(const struct xt_classify_target_info *)target->data;
+
+	printf(" --set-class %x:%x",
+	       TC_H_MAJ(clinfo->priority)>>16, TC_H_MIN(clinfo->priority));
+}
+
+static void
+CLASSIFY_arp_print(const void *ip,
+      const struct xt_entry_target *target,
+      int numeric)
+{
+	CLASSIFY_arp_save(ip, target);
+}
+
+static int CLASSIFY_xlate(struct xt_xlate *xl,
+			  const struct xt_xlate_tg_params *params)
+{
+	const struct xt_classify_target_info *clinfo =
+		(const struct xt_classify_target_info *)params->target->data;
+	__u32 handle = clinfo->priority;
+
+	xt_xlate_add(xl, "meta priority set ");
+
+	switch (handle) {
+	case TC_H_ROOT:
+		xt_xlate_add(xl, "root");
+		break;
+	case TC_H_UNSPEC:
+		xt_xlate_add(xl, "none");
+		break;
+	default:
+		xt_xlate_add(xl, "%0x:%0x", TC_H_MAJ(handle) >> 16,
+			     TC_H_MIN(handle));
+		break;
+	}
+
+	return 1;
+}
+
+static struct xtables_target classify_tg_reg[] = {
+	{
+		.family		= NFPROTO_UNSPEC,
+		.name		= "CLASSIFY",
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_classify_target_info)),
+		.userspacesize	= XT_ALIGN(sizeof(struct xt_classify_target_info)),
+		.help		= CLASSIFY_help,
+		.print		= CLASSIFY_print,
+		.save		= CLASSIFY_save,
+		.x6_parse	= CLASSIFY_parse,
+		.x6_options	= CLASSIFY_opts,
+		.xlate          = CLASSIFY_xlate,
+	},
+	{
+		.family		= NFPROTO_ARP,
+		.name		= "CLASSIFY",
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_classify_target_info)),
+		.userspacesize	= XT_ALIGN(sizeof(struct xt_classify_target_info)),
+		.help		= CLASSIFY_help,
+		.print		= CLASSIFY_arp_print,
+		.save		= CLASSIFY_arp_save,
+		.x6_parse	= CLASSIFY_parse,
+		.x6_options	= CLASSIFY_opts,
+		.xlate          = CLASSIFY_xlate,
+	}
 };
 
 void _init(void)
 {
-	xtables_register_target(&classify_target);
+	xtables_register_targets(classify_tg_reg, ARRAY_SIZE(classify_tg_reg));
 }
diff --git a/extensions/libxt_CLASSIFY.t b/extensions/libxt_CLASSIFY.t
new file mode 100644
index 0000000..7b3ddbf
--- /dev/null
+++ b/extensions/libxt_CLASSIFY.t
@@ -0,0 +1,9 @@
+:FORWARD,OUTPUT,POSTROUTING
+*mangle
+-j CLASSIFY --set-class 0000:ffff;=;OK
+# maximum handle accepted by tc is 0xffff
+# ERROR : should fail: iptables -A FORWARD -t mangle -j CLASSIFY --set-class  0000:ffffffff
+# -j CLASSIFY --set-class 0000:ffffffff;;FAIL
+# ERROR: should fail: iptables -A FORWARD -t mangle -j CLASSIFY --set-class 1:-1
+# -j CLASSIFY --set-class 1:-1;;FAIL
+-j CLASSIFY;;FAIL
diff --git a/extensions/libxt_CLASSIFY.txlate b/extensions/libxt_CLASSIFY.txlate
new file mode 100644
index 0000000..3b34923
--- /dev/null
+++ b/extensions/libxt_CLASSIFY.txlate
@@ -0,0 +1,8 @@
+iptables-translate -A OUTPUT -j CLASSIFY --set-class 0:0
+nft add rule ip filter OUTPUT counter meta priority set none
+
+iptables-translate -A OUTPUT -j CLASSIFY --set-class ffff:ffff
+nft add rule ip filter OUTPUT counter meta priority set root
+
+iptables-translate -A OUTPUT -j CLASSIFY --set-class 1:234
+nft add rule ip filter OUTPUT counter meta priority set 1:234
diff --git a/extensions/libxt_CONNMARK.c b/extensions/libxt_CONNMARK.c
index 5d5351e..21e1091 100644
--- a/extensions/libxt_CONNMARK.c
+++ b/extensions/libxt_CONNMARK.c
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 #include <stdbool.h>
 #include <stdint.h>
@@ -32,28 +32,42 @@
 };
 
 enum {
+	D_SHIFT_LEFT = 0,
+	D_SHIFT_RIGHT,
+};
+
+enum {
 	O_SET_MARK = 0,
 	O_SAVE_MARK,
 	O_RESTORE_MARK,
 	O_AND_MARK,
 	O_OR_MARK,
 	O_XOR_MARK,
+	O_LEFT_SHIFT_MARK,
+	O_RIGHT_SHIFT_MARK,
 	O_SET_XMARK,
 	O_CTMASK,
 	O_NFMASK,
 	O_MASK,
-	F_SET_MARK     = 1 << O_SET_MARK,
-	F_SAVE_MARK    = 1 << O_SAVE_MARK,
-	F_RESTORE_MARK = 1 << O_RESTORE_MARK,
-	F_AND_MARK     = 1 << O_AND_MARK,
-	F_OR_MARK      = 1 << O_OR_MARK,
-	F_XOR_MARK     = 1 << O_XOR_MARK,
-	F_SET_XMARK    = 1 << O_SET_XMARK,
-	F_CTMASK       = 1 << O_CTMASK,
-	F_NFMASK       = 1 << O_NFMASK,
-	F_MASK         = 1 << O_MASK,
-	F_OP_ANY       = F_SET_MARK | F_SAVE_MARK | F_RESTORE_MARK |
-	                 F_AND_MARK | F_OR_MARK | F_XOR_MARK | F_SET_XMARK,
+	F_SET_MARK         = 1 << O_SET_MARK,
+	F_SAVE_MARK        = 1 << O_SAVE_MARK,
+	F_RESTORE_MARK     = 1 << O_RESTORE_MARK,
+	F_AND_MARK         = 1 << O_AND_MARK,
+	F_OR_MARK          = 1 << O_OR_MARK,
+	F_XOR_MARK         = 1 << O_XOR_MARK,
+	F_LEFT_SHIFT_MARK  = 1 << O_LEFT_SHIFT_MARK,
+	F_RIGHT_SHIFT_MARK = 1 << O_RIGHT_SHIFT_MARK,
+	F_SET_XMARK        = 1 << O_SET_XMARK,
+	F_CTMASK           = 1 << O_CTMASK,
+	F_NFMASK           = 1 << O_NFMASK,
+	F_MASK             = 1 << O_MASK,
+	F_OP_ANY           = F_SET_MARK | F_SAVE_MARK | F_RESTORE_MARK |
+	                     F_AND_MARK | F_OR_MARK | F_XOR_MARK | F_SET_XMARK,
+};
+
+static const char *const xt_connmark_shift_ops[] = {
+	"left-shift-mark",
+	"right-shift-mark"
 };
 
 static void CONNMARK_help(void)
@@ -104,6 +118,36 @@
 };
 #undef s
 
+#define s struct xt_connmark_tginfo2
+static const struct xt_option_entry connmark_tg_opts_v2[] = {
+	{.name = "set-xmark", .id = O_SET_XMARK, .type = XTTYPE_MARKMASK32,
+	 .excl = F_OP_ANY},
+	{.name = "set-mark", .id = O_SET_MARK, .type = XTTYPE_MARKMASK32,
+	 .excl = F_OP_ANY},
+	{.name = "and-mark", .id = O_AND_MARK, .type = XTTYPE_UINT32,
+	 .excl = F_OP_ANY},
+	{.name = "or-mark", .id = O_OR_MARK, .type = XTTYPE_UINT32,
+	 .excl = F_OP_ANY},
+	{.name = "xor-mark", .id = O_XOR_MARK, .type = XTTYPE_UINT32,
+	 .excl = F_OP_ANY},
+	{.name = "save-mark", .id = O_SAVE_MARK, .type = XTTYPE_NONE,
+	 .excl = F_OP_ANY},
+	{.name = "restore-mark", .id = O_RESTORE_MARK, .type = XTTYPE_NONE,
+	 .excl = F_OP_ANY},
+	{.name = "left-shift-mark", .id = O_LEFT_SHIFT_MARK, .type = XTTYPE_UINT8,
+	 .min = 0, .max = 32},
+	{.name = "right-shift-mark", .id = O_RIGHT_SHIFT_MARK, .type = XTTYPE_UINT8,
+	 .min = 0, .max = 32},
+	{.name = "ctmask", .id = O_CTMASK, .type = XTTYPE_UINT32,
+	 .excl = F_MASK, .flags = XTOPT_PUT, XTOPT_POINTER(s, ctmask)},
+	{.name = "nfmask", .id = O_NFMASK, .type = XTTYPE_UINT32,
+	 .excl = F_MASK, .flags = XTOPT_PUT, XTOPT_POINTER(s, nfmask)},
+	{.name = "mask", .id = O_MASK, .type = XTTYPE_UINT32,
+	 .excl = F_CTMASK | F_NFMASK},
+	XTOPT_TABLEEND,
+};
+#undef s
+
 static void connmark_tg_help(void)
 {
 	printf(
@@ -122,6 +166,15 @@
 );
 }
 
+static void connmark_tg_help_v2(void)
+{
+	connmark_tg_help();
+	printf(
+"  --left-shift-mark value       Left shift the ctmark with bits\n"
+"  --right-shift-mark value      Right shift the ctmark with bits\n"
+);
+}
+
 static void connmark_tg_init(struct xt_entry_target *target)
 {
 	struct xt_connmark_tginfo1 *info = (void *)target->data;
@@ -134,6 +187,18 @@
 	info->nfmask = UINT32_MAX;
 }
 
+static void connmark_tg_init_v2(struct xt_entry_target *target)
+{
+	struct xt_connmark_tginfo2 *info;
+
+	connmark_tg_init(target);
+	info = (void *)target->data;
+
+	/* Left shift by zero bit by default. */
+	info->shift_dir = D_SHIFT_LEFT;
+	info->shift_bits = 0;
+}
+
 static void CONNMARK_parse(struct xt_option_call *cb)
 {
 	struct xt_connmark_target_info *markinfo = cb->data;
@@ -197,6 +262,61 @@
 	case O_MASK:
 		info->nfmask = info->ctmask = cb->val.u32;
 		break;
+	default:
+		break;
+	}
+}
+
+static void connmark_tg_parse_v2(struct xt_option_call *cb)
+{
+	struct xt_connmark_tginfo2 *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SET_XMARK:
+		info->mode   = XT_CONNMARK_SET;
+		info->ctmark = cb->val.mark;
+		info->ctmask = cb->val.mask;
+		break;
+	case O_SET_MARK:
+		info->mode   = XT_CONNMARK_SET;
+		info->ctmark = cb->val.mark;
+		info->ctmask = cb->val.mark | cb->val.mask;
+		break;
+	case O_AND_MARK:
+		info->mode   = XT_CONNMARK_SET;
+		info->ctmark = 0;
+		info->ctmask = ~cb->val.u32;
+		break;
+	case O_OR_MARK:
+		info->mode   = XT_CONNMARK_SET;
+		info->ctmark = cb->val.u32;
+		info->ctmask = cb->val.u32;
+		break;
+	case O_XOR_MARK:
+		info->mode   = XT_CONNMARK_SET;
+		info->ctmark = cb->val.u32;
+		info->ctmask = 0;
+		break;
+	case O_SAVE_MARK:
+		info->mode = XT_CONNMARK_SAVE;
+		break;
+	case O_RESTORE_MARK:
+		info->mode = XT_CONNMARK_RESTORE;
+		break;
+	case O_MASK:
+		info->nfmask = info->ctmask = cb->val.u32;
+		break;
+	case O_LEFT_SHIFT_MARK:
+		info->shift_dir = D_SHIFT_LEFT;
+		info->shift_bits = cb->val.u8;
+		break;
+	case O_RIGHT_SHIFT_MARK:
+		info->shift_dir = D_SHIFT_RIGHT;
+		info->shift_bits = cb->val.u8;
+		break;
+	default:
+		break;
 	}
 }
 
@@ -291,6 +411,58 @@
 	}
 }
 
+static void
+connmark_tg_print_v2(const void *ip, const struct xt_entry_target *target,
+                  int numeric)
+{
+	const struct xt_connmark_tginfo2 *info = (const void *)target->data;
+	const char *shift_op = xt_connmark_shift_ops[info->shift_dir];
+
+	switch (info->mode) {
+	case XT_CONNMARK_SET:
+		if (info->ctmark == 0)
+			printf(" CONNMARK and 0x%x",
+			       (unsigned int)(uint32_t)~info->ctmask);
+		else if (info->ctmark == info->ctmask)
+			printf(" CONNMARK or 0x%x", info->ctmark);
+		else if (info->ctmask == 0)
+			printf(" CONNMARK xor 0x%x", info->ctmark);
+		else if (info->ctmask == 0xFFFFFFFFU)
+			printf(" CONNMARK set 0x%x", info->ctmark);
+		else
+			printf(" CONNMARK xset 0x%x/0x%x",
+			       info->ctmark, info->ctmask);
+		break;
+	case XT_CONNMARK_SAVE:
+		if (info->nfmask == UINT32_MAX && info->ctmask == UINT32_MAX)
+			printf(" CONNMARK save");
+		else if (info->nfmask == info->ctmask)
+			printf(" CONNMARK save mask 0x%x", info->nfmask);
+		else
+			printf(" CONNMARK save nfmask 0x%x ctmask ~0x%x",
+			       info->nfmask, info->ctmask);
+		break;
+	case XT_CONNMARK_RESTORE:
+		if (info->ctmask == UINT32_MAX && info->nfmask == UINT32_MAX)
+			printf(" CONNMARK restore");
+		else if (info->ctmask == info->nfmask)
+			printf(" CONNMARK restore mask 0x%x", info->ctmask);
+		else
+			printf(" CONNMARK restore ctmask 0x%x nfmask ~0x%x",
+			       info->ctmask, info->nfmask);
+		break;
+
+	default:
+		printf(" ERROR: UNKNOWN CONNMARK MODE");
+		break;
+	}
+
+	if (info->mode <= XT_CONNMARK_RESTORE &&
+	    info->shift_bits != 0) {
+		printf(" %s %u", shift_op, info->shift_bits);
+	}
+}
+
 static void CONNMARK_save(const void *ip, const struct xt_entry_target *target)
 {
 	const struct xt_connmark_target_info *markinfo =
@@ -347,6 +519,126 @@
 	}
 }
 
+static void
+connmark_tg_save_v2(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_connmark_tginfo2 *info = (const void *)target->data;
+	const char *shift_op = xt_connmark_shift_ops[info->shift_dir];
+
+	switch (info->mode) {
+	case XT_CONNMARK_SET:
+		printf(" --set-xmark 0x%x/0x%x", info->ctmark, info->ctmask);
+		break;
+	case XT_CONNMARK_SAVE:
+		printf(" --save-mark --nfmask 0x%x --ctmask 0x%x",
+		       info->nfmask, info->ctmask);
+		break;
+	case XT_CONNMARK_RESTORE:
+		printf(" --restore-mark --nfmask 0x%x --ctmask 0x%x",
+		       info->nfmask, info->ctmask);
+		break;
+	default:
+		printf(" ERROR: UNKNOWN CONNMARK MODE");
+		break;
+	}
+
+	if (info->mode <= XT_CONNMARK_RESTORE &&
+	    info->shift_bits != 0) {
+		printf(" --%s %u", shift_op, info->shift_bits);
+	}
+}
+
+static int connmark_tg_xlate(struct xt_xlate *xl,
+			     const struct xt_xlate_tg_params *params)
+{
+	const struct xt_connmark_tginfo1 *info =
+		(const void *)params->target->data;
+
+	switch (info->mode) {
+	case XT_CONNMARK_SET:
+		xt_xlate_add(xl, "ct mark set ");
+		if (info->ctmask == 0xFFFFFFFFU)
+			xt_xlate_add(xl, "0x%x ", info->ctmark);
+		else if (info->ctmark == 0)
+			xt_xlate_add(xl, "ct mark and 0x%x", ~info->ctmask);
+		else if (info->ctmark == info->ctmask)
+			xt_xlate_add(xl, "ct mark or 0x%x",
+				     info->ctmark);
+		else if (info->ctmask == 0)
+			xt_xlate_add(xl, "ct mark xor 0x%x",
+				     info->ctmark);
+		else
+			xt_xlate_add(xl, "ct mark xor 0x%x and 0x%x",
+				     info->ctmark, ~info->ctmask);
+		break;
+	case XT_CONNMARK_SAVE:
+		if (info->nfmask == info->ctmask &&
+		    info->nfmask == UINT32_MAX)
+			xt_xlate_add(xl, "ct mark set mark");
+		else
+			return 0;
+		break;
+	case XT_CONNMARK_RESTORE:
+		if (info->nfmask == info->ctmask &&
+		    info->nfmask == UINT32_MAX)
+			xt_xlate_add(xl, "meta mark set ct mark");
+		else
+			return 0;
+		break;
+	}
+
+	return 1;
+}
+
+static int connmark_tg_xlate_v2(struct xt_xlate *xl,
+			     const struct xt_xlate_tg_params *params)
+{
+	const struct xt_connmark_tginfo2 *info =
+		(const void *)params->target->data;
+	const char *shift_op = xt_connmark_shift_ops[info->shift_dir];
+
+	switch (info->mode) {
+	case XT_CONNMARK_SET:
+		xt_xlate_add(xl, "ct mark set ");
+		if (info->ctmask == 0xFFFFFFFFU)
+			xt_xlate_add(xl, "0x%x ", info->ctmark);
+		else if (info->ctmark == 0)
+			xt_xlate_add(xl, "ct mark and 0x%x", ~info->ctmask);
+		else if (info->ctmark == info->ctmask)
+			xt_xlate_add(xl, "ct mark or 0x%x",
+				     info->ctmark);
+		else if (info->ctmask == 0)
+			xt_xlate_add(xl, "ct mark xor 0x%x",
+				     info->ctmark);
+		else
+			xt_xlate_add(xl, "ct mark xor 0x%x and 0x%x",
+				     info->ctmark, ~info->ctmask);
+		break;
+	case XT_CONNMARK_SAVE:
+		xt_xlate_add(xl, "ct mark set mark");
+		if (!(info->nfmask == UINT32_MAX &&
+		    info->ctmask == UINT32_MAX)) {
+			if (info->nfmask == info->ctmask)
+				xt_xlate_add(xl, " and 0x%x", info->nfmask);
+		}
+		break;
+	case XT_CONNMARK_RESTORE:
+		xt_xlate_add(xl, "meta mark set ct mark");
+		if (!(info->nfmask == UINT32_MAX &&
+		    info->ctmask == UINT32_MAX)) {
+			if (info->nfmask == info->ctmask)
+				xt_xlate_add(xl, " and 0x%x", info->nfmask);
+		}
+		break;
+	}
+
+	if (info->mode <= XT_CONNMARK_RESTORE &&
+	    info->shift_bits != 0) {
+		xt_xlate_add(xl, " %s %u", shift_op, info->shift_bits);
+	}
+
+	return 1;
+}
 static struct xtables_target connmark_tg_reg[] = {
 	{
 		.family        = NFPROTO_UNSPEC,
@@ -377,6 +669,23 @@
 		.x6_parse      = connmark_tg_parse,
 		.x6_fcheck     = connmark_tg_check,
 		.x6_options    = connmark_tg_opts,
+		.xlate         = connmark_tg_xlate,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "CONNMARK",
+		.revision      = 2,
+		.family        = NFPROTO_UNSPEC,
+		.size          = XT_ALIGN(sizeof(struct xt_connmark_tginfo2)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_connmark_tginfo2)),
+		.help          = connmark_tg_help_v2,
+		.init          = connmark_tg_init_v2,
+		.print         = connmark_tg_print_v2,
+		.save          = connmark_tg_save_v2,
+		.x6_parse      = connmark_tg_parse_v2,
+		.x6_fcheck     = connmark_tg_check,
+		.x6_options    = connmark_tg_opts_v2,
+		.xlate         = connmark_tg_xlate_v2,
 	},
 };
 
diff --git a/extensions/libxt_CONNMARK.t b/extensions/libxt_CONNMARK.t
new file mode 100644
index 0000000..79a838f
--- /dev/null
+++ b/extensions/libxt_CONNMARK.t
@@ -0,0 +1,7 @@
+:PREROUTING,FORWARD,OUTPUT,POSTROUTING
+*mangle
+-j CONNMARK --restore-mark;=;OK
+-j CONNMARK --save-mark;=;OK
+-j CONNMARK --save-mark --nfmask 0xfffffff --ctmask 0xffffffff;-j CONNMARK --save-mark;OK
+-j CONNMARK --restore-mark --nfmask 0xfffffff --ctmask 0xffffffff;-j CONNMARK --restore-mark;OK
+-j CONNMARK;;FAIL
diff --git a/extensions/libxt_CONNMARK.txlate b/extensions/libxt_CONNMARK.txlate
new file mode 100644
index 0000000..ce40ae5
--- /dev/null
+++ b/extensions/libxt_CONNMARK.txlate
@@ -0,0 +1,20 @@
+iptables-translate -t mangle -A PREROUTING -j CONNMARK --set-mark 0
+nft add rule ip mangle PREROUTING counter ct mark set 0x0
+
+iptables-translate -t mangle -A PREROUTING -j CONNMARK --set-mark 0x16
+nft add rule ip mangle PREROUTING counter ct mark set 0x16
+
+iptables-translate -t mangle -A PREROUTING -j CONNMARK --set-xmark 0x16/0x12
+nft add rule ip mangle PREROUTING counter ct mark set ct mark xor 0x16 and 0xffffffed
+
+iptables-translate -t mangle -A PREROUTING -j CONNMARK --and-mark 0x16
+nft add rule ip mangle PREROUTING counter ct mark set ct mark and 0x16
+
+iptables-translate -t mangle -A PREROUTING -j CONNMARK --or-mark 0x16
+nft add rule ip mangle PREROUTING counter ct mark set ct mark or 0x16
+
+iptables-translate -t mangle -A PREROUTING -j CONNMARK --save-mark
+nft add rule ip mangle PREROUTING counter ct mark set mark
+
+iptables-translate -t mangle -A PREROUTING -j CONNMARK --restore-mark
+nft add rule ip mangle PREROUTING counter meta mark set ct mark
diff --git a/extensions/libxt_CONNSECMARK.c b/extensions/libxt_CONNSECMARK.c
index df2e6b8..0b3cd79 100644
--- a/extensions/libxt_CONNSECMARK.c
+++ b/extensions/libxt_CONNSECMARK.c
@@ -87,7 +87,7 @@
 	const struct xt_connsecmark_target_info *info =
 		(struct xt_connsecmark_target_info*)target->data;
 
-	printf("--");
+	printf(" --");
 	print_connsecmark(info);
 }
 
diff --git a/extensions/libxt_CONNSECMARK.t b/extensions/libxt_CONNSECMARK.t
new file mode 100644
index 0000000..2751b25
--- /dev/null
+++ b/extensions/libxt_CONNSECMARK.t
@@ -0,0 +1,5 @@
+:PREROUTING,FORWARD,OUTPUT,POSTROUTING
+*mangle
+-j CONNSECMARK --restore;=;OK
+-j CONNSECMARK --save;=;OK
+-j CONNSECMARK;;FAIL
diff --git a/extensions/libxt_CT.c b/extensions/libxt_CT.c
index 7b93bfa..fbbbe26 100644
--- a/extensions/libxt_CT.c
+++ b/extensions/libxt_CT.c
@@ -1,3 +1,7 @@
+/*
+ * Copyright (c) 2010-2013 Patrick McHardy <kaber@trash.net>
+ */
+
 #include <stdio.h>
 #include <string.h>
 #include <xtables.h>
@@ -12,16 +16,36 @@
 " --helper name			Use conntrack helper 'name' for connection\n"
 " --ctevents event[,event...]	Generate specified conntrack events for connection\n"
 " --expevents event[,event...]	Generate specified expectation events for connection\n"
-" --zone ID			Assign/Lookup connection in zone ID\n"
+" --zone {ID|mark}		Assign/Lookup connection in zone ID/packet nfmark\n"
+" --zone-orig {ID|mark}		Same as 'zone' option, but only applies to ORIGINAL direction\n"
+" --zone-reply {ID|mark} 	Same as 'zone' option, but only applies to REPLY direction\n"
+	);
+}
+
+static void ct_help_v1(void)
+{
+	printf(
+"CT target options:\n"
+" --notrack			Don't track connection\n"
+" --helper name			Use conntrack helper 'name' for connection\n"
+" --timeout name 		Use timeout policy 'name' for connection\n"
+" --ctevents event[,event...]	Generate specified conntrack events for connection\n"
+" --expevents event[,event...]	Generate specified expectation events for connection\n"
+" --zone {ID|mark}		Assign/Lookup connection in zone ID/packet nfmark\n"
+" --zone-orig {ID|mark}		Same as 'zone' option, but only applies to ORIGINAL direction\n"
+" --zone-reply {ID|mark} 	Same as 'zone' option, but only applies to REPLY direction\n"
 	);
 }
 
 enum {
 	O_NOTRACK = 0,
 	O_HELPER,
+	O_TIMEOUT,
 	O_CTEVENTS,
 	O_EXPEVENTS,
 	O_ZONE,
+	O_ZONE_ORIG,
+	O_ZONE_REPLY,
 };
 
 #define s struct xt_ct_target_info
@@ -31,8 +55,25 @@
 	 .flags = XTOPT_PUT, XTOPT_POINTER(s, helper)},
 	{.name = "ctevents", .id = O_CTEVENTS, .type = XTTYPE_STRING},
 	{.name = "expevents", .id = O_EXPEVENTS, .type = XTTYPE_STRING},
-	{.name = "zone", .id = O_ZONE, .type = XTTYPE_UINT16,
-	 .flags = XTOPT_PUT, XTOPT_POINTER(s, zone)},
+	{.name = "zone-orig", .id = O_ZONE_ORIG, .type = XTTYPE_STRING},
+	{.name = "zone-reply", .id = O_ZONE_REPLY, .type = XTTYPE_STRING},
+	{.name = "zone", .id = O_ZONE, .type = XTTYPE_STRING},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+#define s struct xt_ct_target_info_v1
+static const struct xt_option_entry ct_opts_v1[] = {
+	{.name = "notrack", .id = O_NOTRACK, .type = XTTYPE_NONE},
+	{.name = "helper", .id = O_HELPER, .type = XTTYPE_STRING,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, helper)},
+	{.name = "timeout", .id = O_TIMEOUT, .type = XTTYPE_STRING,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, timeout)},
+	{.name = "ctevents", .id = O_CTEVENTS, .type = XTTYPE_STRING},
+	{.name = "expevents", .id = O_EXPEVENTS, .type = XTTYPE_STRING},
+	{.name = "zone-orig", .id = O_ZONE_ORIG, .type = XTTYPE_STRING},
+	{.name = "zone-reply", .id = O_ZONE_REPLY, .type = XTTYPE_STRING},
+	{.name = "zone", .id = O_ZONE, .type = XTTYPE_STRING},
 	XTOPT_TABLEEND,
 };
 #undef s
@@ -59,6 +100,45 @@
 	{ "new",		IPEXP_NEW },
 };
 
+static void ct_parse_zone_id(const char *opt, unsigned int opt_id,
+			     uint16_t *zone_id, uint16_t *flags)
+{
+	if (opt_id == O_ZONE_ORIG)
+		*flags |= XT_CT_ZONE_DIR_ORIG;
+	if (opt_id == O_ZONE_REPLY)
+		*flags |= XT_CT_ZONE_DIR_REPL;
+
+	*zone_id = 0;
+
+	if (strcasecmp(opt, "mark") == 0) {
+		*flags |= XT_CT_ZONE_MARK;
+	} else {
+		uintmax_t val;
+
+		if (!xtables_strtoul(opt, NULL, &val, 0, UINT16_MAX))
+			xtables_error(PARAMETER_PROBLEM,
+				      "Cannot parse %s as a zone ID\n", opt);
+
+		*zone_id = (uint16_t)val;
+	}
+}
+
+static void ct_print_zone_id(const char *pfx, uint16_t zone_id, uint16_t flags)
+{
+	printf(" %s", pfx);
+
+	if ((flags & (XT_CT_ZONE_DIR_ORIG |
+		      XT_CT_ZONE_DIR_REPL)) == XT_CT_ZONE_DIR_ORIG)
+		printf("-orig");
+	if ((flags & (XT_CT_ZONE_DIR_ORIG |
+		      XT_CT_ZONE_DIR_REPL)) == XT_CT_ZONE_DIR_REPL)
+		printf("-reply");
+	if (flags & XT_CT_ZONE_MARK)
+		printf(" mark");
+	else
+		printf(" %u", zone_id);
+}
+
 static uint32_t ct_parse_events(const struct event_tbl *tbl, unsigned int size,
 				const char *events)
 {
@@ -105,6 +185,12 @@
 	case O_NOTRACK:
 		info->flags |= XT_CT_NOTRACK;
 		break;
+	case O_ZONE_ORIG:
+	case O_ZONE_REPLY:
+	case O_ZONE:
+		ct_parse_zone_id(cb->arg, cb->entry->id, &info->zone,
+				 &info->flags);
+		break;
 	case O_CTEVENTS:
 		info->ct_events = ct_parse_events(ct_event_tbl, ARRAY_SIZE(ct_event_tbl), cb->arg);
 		break;
@@ -114,6 +200,34 @@
 	}
 }
 
+static void ct_parse_v1(struct xt_option_call *cb)
+{
+	struct xt_ct_target_info_v1 *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_NOTRACK:
+		info->flags |= XT_CT_NOTRACK;
+		break;
+	case O_ZONE_ORIG:
+	case O_ZONE_REPLY:
+	case O_ZONE:
+		ct_parse_zone_id(cb->arg, cb->entry->id, &info->zone,
+				 &info->flags);
+		break;
+	case O_CTEVENTS:
+		info->ct_events = ct_parse_events(ct_event_tbl,
+						  ARRAY_SIZE(ct_event_tbl),
+						  cb->arg);
+		break;
+	case O_EXPEVENTS:
+		info->exp_events = ct_parse_events(exp_event_tbl,
+						   ARRAY_SIZE(exp_event_tbl),
+						   cb->arg);
+		break;
+	}
+}
+
 static void ct_print(const void *ip, const struct xt_entry_target *target, int numeric)
 {
 	const struct xt_ct_target_info *info =
@@ -130,8 +244,35 @@
 	if (info->exp_events)
 		ct_print_events("expevents", exp_event_tbl,
 				ARRAY_SIZE(exp_event_tbl), info->exp_events);
-	if (info->zone)
-		printf("zone %u ", info->zone);
+	if (info->flags & XT_CT_ZONE_MARK || info->zone)
+		ct_print_zone_id("zone", info->zone, info->flags);
+}
+
+static void
+ct_print_v1(const void *ip, const struct xt_entry_target *target, int numeric)
+{
+	const struct xt_ct_target_info_v1 *info =
+		(const struct xt_ct_target_info_v1 *)target->data;
+
+	if (info->flags & XT_CT_NOTRACK_ALIAS) {
+		printf (" NOTRACK");
+		return;
+	}
+	printf(" CT");
+	if (info->flags & XT_CT_NOTRACK)
+		printf(" notrack");
+	if (info->helper[0])
+		printf(" helper %s", info->helper);
+	if (info->timeout[0])
+		printf(" timeout %s", info->timeout);
+	if (info->ct_events)
+		ct_print_events("ctevents", ct_event_tbl,
+				ARRAY_SIZE(ct_event_tbl), info->ct_events);
+	if (info->exp_events)
+		ct_print_events("expevents", exp_event_tbl,
+				ARRAY_SIZE(exp_event_tbl), info->exp_events);
+	if (info->flags & XT_CT_ZONE_MARK || info->zone)
+		ct_print_zone_id("zone", info->zone, info->flags);
 }
 
 static void ct_save(const void *ip, const struct xt_entry_target *target)
@@ -139,6 +280,8 @@
 	const struct xt_ct_target_info *info =
 		(const struct xt_ct_target_info *)target->data;
 
+	if (info->flags & XT_CT_NOTRACK_ALIAS)
+		return;
 	if (info->flags & XT_CT_NOTRACK)
 		printf(" --notrack");
 	if (info->helper[0])
@@ -149,24 +292,158 @@
 	if (info->exp_events)
 		ct_print_events("--expevents", exp_event_tbl,
 				ARRAY_SIZE(exp_event_tbl), info->exp_events);
-	if (info->zone)
-		printf(" --zone %u", info->zone);
+	if (info->flags & XT_CT_ZONE_MARK || info->zone)
+		ct_print_zone_id("--zone", info->zone, info->flags);
 }
 
-static struct xtables_target ct_target = {
-	.family		= NFPROTO_UNSPEC,
-	.name		= "CT",
-	.version	= XTABLES_VERSION,
-	.size		= XT_ALIGN(sizeof(struct xt_ct_target_info)),
-	.userspacesize	= offsetof(struct xt_ct_target_info, ct),
-	.help		= ct_help,
-	.print		= ct_print,
-	.save		= ct_save,
-	.x6_parse	= ct_parse,
-	.x6_options	= ct_opts,
+static void ct_save_v1(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_ct_target_info_v1 *info =
+		(const struct xt_ct_target_info_v1 *)target->data;
+
+	if (info->flags & XT_CT_NOTRACK_ALIAS)
+		return;
+	if (info->flags & XT_CT_NOTRACK)
+		printf(" --notrack");
+	if (info->helper[0])
+		printf(" --helper %s", info->helper);
+	if (info->timeout[0])
+		printf(" --timeout %s", info->timeout);
+	if (info->ct_events)
+		ct_print_events("--ctevents", ct_event_tbl,
+				ARRAY_SIZE(ct_event_tbl), info->ct_events);
+	if (info->exp_events)
+		ct_print_events("--expevents", exp_event_tbl,
+				ARRAY_SIZE(exp_event_tbl), info->exp_events);
+	if (info->flags & XT_CT_ZONE_MARK || info->zone)
+		ct_print_zone_id("--zone", info->zone, info->flags);
+}
+
+static const char *
+ct_print_name_alias(const struct xt_entry_target *target)
+{
+	struct xt_ct_target_info *info = (void *)target->data;
+
+	return info->flags & XT_CT_NOTRACK_ALIAS ? "NOTRACK" : "CT";
+}
+
+static void notrack_ct0_tg_init(struct xt_entry_target *target)
+{
+	struct xt_ct_target_info *info = (void *)target->data;
+
+	info->flags = XT_CT_NOTRACK;
+}
+
+static void notrack_ct1_tg_init(struct xt_entry_target *target)
+{
+	struct xt_ct_target_info_v1 *info = (void *)target->data;
+
+	info->flags = XT_CT_NOTRACK;
+}
+
+static void notrack_ct2_tg_init(struct xt_entry_target *target)
+{
+	struct xt_ct_target_info_v1 *info = (void *)target->data;
+
+	info->flags = XT_CT_NOTRACK | XT_CT_NOTRACK_ALIAS;
+}
+
+static int xlate_ct1_tg(struct xt_xlate *xl,
+			const struct xt_xlate_tg_params *params)
+{
+	struct xt_ct_target_info_v1 *info =
+		(struct xt_ct_target_info_v1 *)params->target->data;
+
+	if (info->flags & XT_CT_NOTRACK)
+		xt_xlate_add(xl, "notrack");
+	else
+		return 0;
+
+	return 1;
+}
+
+static struct xtables_target ct_target_reg[] = {
+	{
+		.family		= NFPROTO_UNSPEC,
+		.name		= "CT",
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_ct_target_info)),
+		.userspacesize	= offsetof(struct xt_ct_target_info, ct),
+		.help		= ct_help,
+		.print		= ct_print,
+		.save		= ct_save,
+		.x6_parse	= ct_parse,
+		.x6_options	= ct_opts,
+	},
+	{
+		.family		= NFPROTO_UNSPEC,
+		.name		= "CT",
+		.revision	= 1,
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_ct_target_info_v1)),
+		.userspacesize	= offsetof(struct xt_ct_target_info_v1, ct),
+		.help		= ct_help_v1,
+		.print		= ct_print_v1,
+		.save		= ct_save_v1,
+		.x6_parse	= ct_parse_v1,
+		.x6_options	= ct_opts_v1,
+	},
+	{
+		.family		= NFPROTO_UNSPEC,
+		.name		= "CT",
+		.revision	= 2,
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_ct_target_info_v1)),
+		.userspacesize	= offsetof(struct xt_ct_target_info_v1, ct),
+		.help		= ct_help_v1,
+		.print		= ct_print_v1,
+		.save		= ct_save_v1,
+		.alias		= ct_print_name_alias,
+		.x6_parse	= ct_parse_v1,
+		.x6_options	= ct_opts_v1,
+		.xlate		= xlate_ct1_tg,
+	},
+	{
+		.family        = NFPROTO_UNSPEC,
+		.name          = "NOTRACK",
+		.real_name     = "CT",
+		.revision      = 0,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_ct_target_info)),
+		.userspacesize = offsetof(struct xt_ct_target_info, ct),
+		.init          = notrack_ct0_tg_init,
+	},
+	{
+		.family        = NFPROTO_UNSPEC,
+		.name          = "NOTRACK",
+		.real_name     = "CT",
+		.revision      = 1,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_ct_target_info_v1)),
+		.userspacesize = offsetof(struct xt_ct_target_info_v1, ct),
+		.init          = notrack_ct1_tg_init,
+	},
+	{
+		.family        = NFPROTO_UNSPEC,
+		.name          = "NOTRACK",
+		.real_name     = "CT",
+		.revision      = 2,
+		.ext_flags     = XTABLES_EXT_ALIAS,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_ct_target_info_v1)),
+		.userspacesize = offsetof(struct xt_ct_target_info_v1, ct),
+		.init          = notrack_ct2_tg_init,
+		.xlate	       = xlate_ct1_tg,
+	},
+	{
+		.family        = NFPROTO_UNSPEC,
+		.name          = "NOTRACK",
+		.revision      = 0,
+		.version       = XTABLES_VERSION,
+	},
 };
 
 void _init(void)
 {
-	xtables_register_target(&ct_target);
+	xtables_register_targets(ct_target_reg, ARRAY_SIZE(ct_target_reg));
 }
diff --git a/extensions/libxt_CT.man b/extensions/libxt_CT.man
index ff258b7..fc692f9 100644
--- a/extensions/libxt_CT.man
+++ b/extensions/libxt_CT.man
@@ -1,4 +1,4 @@
-The CT target allows to set parameters for a packet or its associated
+The CT target sets parameters for a packet or its associated
 connection. The target attaches a "template" connection tracking entry to
 the packet, which is then used by the conntrack core when initializing
 a new ct entry. This target is thus only valid in the "raw" table.
@@ -20,6 +20,23 @@
 Only generate the specified expectation events for this connection.
 Possible event types are: \fBnew\fP.
 .TP
-\fB\-\-zone\fP \fIid\fP
+\fB\-\-zone-orig\fP {\fIid\fP|\fBmark\fP}
+For traffic coming from ORIGINAL direction, assign this packet to zone
+\fIid\fP and only have lookups done in that zone. If \fBmark\fP is used
+instead of \fIid\fP, the zone is derived from the packet nfmark.
+.TP
+\fB\-\-zone-reply\fP {\fIid\fP|\fBmark\fP}
+For traffic coming from REPLY direction, assign this packet to zone
+\fIid\fP and only have lookups done in that zone. If \fBmark\fP is used
+instead of \fIid\fP, the zone is derived from the packet nfmark.
+.TP
+\fB\-\-zone\fP {\fIid\fP|\fBmark\fP}
 Assign this packet to zone \fIid\fP and only have lookups done in that zone.
-By default, packets have zone 0.
+If \fBmark\fP is used instead of \fIid\fP, the zone is derived from the
+packet nfmark. By default, packets have zone 0. This option applies to both
+directions.
+.TP
+\fB\-\-timeout\fP \fIname\fP
+Use the timeout policy identified by \fIname\fP for the connection. This is
+provides more flexible timeout policy definition than global timeout values
+available at /proc/sys/net/netfilter/nf_conntrack_*_timeout_*.
diff --git a/extensions/libxt_CT.t b/extensions/libxt_CT.t
new file mode 100644
index 0000000..3c28534
--- /dev/null
+++ b/extensions/libxt_CT.t
@@ -0,0 +1,20 @@
+:PREROUTING,OUTPUT
+*raw
+-j CT --notrack;=;OK
+-j CT --ctevents new,related,destroy,reply,assured,protoinfo,helper,mark;=;OK
+-j CT --expevents new;=;OK
+# ERROR: cannot find: iptables -I PREROUTING -t raw -j CT --zone 0
+# -j CT --zone 0;=;OK
+-j CT --zone 65535;=;OK
+-j CT --zone 65536;;FAIL
+-j CT --zone -1;;FAIL
+# ERROR: should fail: iptables -A PREROUTING -t raw -j CT
+# -j CT;;FAIL
+@nfct timeout add test inet tcp ESTABLISHED 100
+# cannot load: iptables -A PREROUTING -t raw -j CT --timeout test
+# -j CT --timeout test;=;OK
+@nfct timeout del test
+@nfct helper add rpc inet tcp
+# cannot load: iptables -A PREROUTING -t raw -j CT --helper rpc
+# -j CT --helper rpc;=;OK
+@nfct helper del rpc
diff --git a/extensions/libxt_DNAT.man b/extensions/libxt_DNAT.man
new file mode 100644
index 0000000..225274f
--- /dev/null
+++ b/extensions/libxt_DNAT.man
@@ -0,0 +1,38 @@
+This target is only valid in the
+.B nat
+table, in the
+.B PREROUTING
+and
+.B OUTPUT
+chains, and user-defined chains which are only called from those
+chains.  It specifies that the destination address of the packet
+should be modified (and all future packets in this connection will
+also be mangled), and rules should cease being examined.  It takes the
+following options:
+.TP
+\fB\-\-to\-destination\fP [\fIipaddr\fP[\fB\-\fP\fIipaddr\fP]][\fB:\fP\fIport\fP[\fB\-\fP\fIport\fP]]
+which can specify a single new destination IP address, an inclusive
+range of IP addresses. Optionally a port range,
+if the rule also specifies one of the following protocols:
+\fBtcp\fP, \fBudp\fP, \fBdccp\fP or \fBsctp\fP.
+If no port range is specified, then the destination port will never be
+modified. If no IP address is specified then only the destination port
+will be modified.
+In Kernels up to 2.6.10 you can add several \-\-to\-destination options. For
+those kernels, if you specify more than one destination address, either via an
+address range or multiple \-\-to\-destination options, a simple round-robin (one
+after another in cycle) load balancing takes place between these addresses.
+Later Kernels (>= 2.6.11-rc1) don't have the ability to NAT to multiple ranges
+anymore.
+.TP
+\fB\-\-random\fP
+If option
+\fB\-\-random\fP
+is used then port mapping will be randomized (kernel >= 2.6.22).
+.TP
+\fB\-\-persistent\fP
+Gives a client the same source-/destination-address for each connection.
+This supersedes the SAME target. Support for persistent mappings is available
+from 2.6.29-rc2.
+.TP
+IPv6 support available since Linux kernels >= 3.7.
diff --git a/extensions/libxt_DSCP.c b/extensions/libxt_DSCP.c
index e16e93c..cae0d83 100644
--- a/extensions/libxt_DSCP.c
+++ b/extensions/libxt_DSCP.c
@@ -92,21 +92,59 @@
 	printf(" --set-dscp 0x%02x", dinfo->dscp);
 }
 
-static struct xtables_target dscp_target = {
-	.family		= NFPROTO_UNSPEC,
-	.name		= "DSCP",
-	.version	= XTABLES_VERSION,
-	.size		= XT_ALIGN(sizeof(struct xt_DSCP_info)),
-	.userspacesize	= XT_ALIGN(sizeof(struct xt_DSCP_info)),
-	.help		= DSCP_help,
-	.print		= DSCP_print,
-	.save		= DSCP_save,
-	.x6_parse	= DSCP_parse,
-	.x6_fcheck	= DSCP_check,
-	.x6_options	= DSCP_opts,
+
+static int DSCP_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_tg_params *params)
+{
+	const struct xt_DSCP_info *dinfo =
+		(struct xt_DSCP_info *)params->target->data;
+
+	xt_xlate_add(xl, "ip dscp set 0x%02x", dinfo->dscp);
+	return 1;
+}
+
+static int DSCP_xlate6(struct xt_xlate *xl,
+		       const struct xt_xlate_tg_params *params)
+{
+	const struct xt_DSCP_info *dinfo =
+		(struct xt_DSCP_info *)params->target->data;
+
+	xt_xlate_add(xl, "ip6 dscp set 0x%02x", dinfo->dscp);
+	return 1;
+}
+
+static struct xtables_target dscp_target[] = {
+	{
+		.family		= NFPROTO_IPV4,
+		.name		= "DSCP",
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_DSCP_info)),
+		.userspacesize	= XT_ALIGN(sizeof(struct xt_DSCP_info)),
+		.help		= DSCP_help,
+		.print		= DSCP_print,
+		.save		= DSCP_save,
+		.x6_parse	= DSCP_parse,
+		.x6_fcheck	= DSCP_check,
+		.x6_options	= DSCP_opts,
+		.xlate		= DSCP_xlate,
+	},
+	{
+		.family		= NFPROTO_IPV6,
+		.name		= "DSCP",
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_DSCP_info)),
+		.userspacesize	= XT_ALIGN(sizeof(struct xt_DSCP_info)),
+		.help		= DSCP_help,
+		.print		= DSCP_print,
+		.save		= DSCP_save,
+		.x6_parse	= DSCP_parse,
+		.x6_fcheck	= DSCP_check,
+		.x6_options	= DSCP_opts,
+		.xlate		= DSCP_xlate6,
+	},
 };
 
 void _init(void)
 {
-	xtables_register_target(&dscp_target);
+	xtables_register_targets(dscp_target, ARRAY_SIZE(dscp_target));
 }
diff --git a/extensions/libxt_DSCP.man b/extensions/libxt_DSCP.man
index 551ba2e..5385c97 100644
--- a/extensions/libxt_DSCP.man
+++ b/extensions/libxt_DSCP.man
@@ -1,4 +1,4 @@
-This target allows to alter the value of the DSCP bits within the TOS
+This target alters the value of the DSCP bits within the TOS
 header of the IPv4 packet.  As this manipulates a packet, it can only
 be used in the mangle table.
 .TP
diff --git a/extensions/libxt_DSCP.t b/extensions/libxt_DSCP.t
new file mode 100644
index 0000000..fcc5598
--- /dev/null
+++ b/extensions/libxt_DSCP.t
@@ -0,0 +1,11 @@
+:PREROUTING,INPUT,FORWARD,OUTPUT,POSTROUTING
+*mangle
+-j DSCP --set-dscp 0;=;OK
+-j DSCP --set-dscp 0x3f;=;OK
+-j DSCP --set-dscp -1;;FAIL
+-j DSCP --set-dscp 0x40;;FAIL
+-j DSCP --set-dscp 0x3f --set-dscp-class CS0;;FAIL
+-j DSCP --set-dscp-class CS0;-j DSCP --set-dscp 0x00;OK
+-j DSCP --set-dscp-class BE;-j DSCP --set-dscp 0x00;OK
+-j DSCP --set-dscp-class EF;-j DSCP --set-dscp 0x2e;OK
+-j DSCP;;FAIL
diff --git a/extensions/libxt_DSCP.txlate b/extensions/libxt_DSCP.txlate
new file mode 100644
index 0000000..442742e
--- /dev/null
+++ b/extensions/libxt_DSCP.txlate
@@ -0,0 +1,5 @@
+iptables-translate -A OUTPUT -j DSCP --set-dscp 1
+nft add rule ip filter OUTPUT counter ip dscp set 0x01
+
+ip6tables-translate -A OUTPUT -j DSCP --set-dscp 6
+nft add rule ip6 filter OUTPUT counter ip6 dscp set 0x06
diff --git a/extensions/libxt_HMARK.c b/extensions/libxt_HMARK.c
new file mode 100644
index 0000000..94aebe9
--- /dev/null
+++ b/extensions/libxt_HMARK.c
@@ -0,0 +1,450 @@
+/*
+ * (C) 2012 by Hans Schillstrom <hans.schillstrom@ericsson.com>
+ * (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 version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Description: shared library add-on to iptables to add HMARK target support
+ *
+ * Initial development by Hans Schillstrom. Pablo's improvements to this piece
+ * of software has been sponsored by Sophos Astaro <http://www.sophos.com>.
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "xtables.h"
+#include <linux/netfilter/xt_HMARK.h>
+
+static void HMARK_help(void)
+{
+	printf(
+"HMARK target options, i.e. modify hash calculation by:\n"
+"  --hmark-tuple [src|dst|sport|dport|spi|proto|ct][,...]\n"
+"  --hmark-mod value		    nfmark modulus value\n"
+"  --hmark-offset value		    Last action add value to nfmark\n\n"
+"  --hmark-rnd			    Random see for hashing\n"
+" Alternatively, fine tuning of what will be included in hash calculation\n"
+"  --hmark-src-prefix length	    Source address mask CIDR prefix\n"
+"  --hmark-dst-prefix length	    Dest address mask CIDR prefix\n"
+"  --hmark-sport-mask value	    Mask src port with value\n"
+"  --hmark-dport-mask value	    Mask dst port with value\n"
+"  --hmark-spi-mask value	    For esp and ah AND spi with value\n"
+"  --hmark-sport value		    OR src port with value\n"
+"  --hmark-dport value		    OR dst port with value\n"
+"  --hmark-spi value		    For esp and ah OR spi with value\n"
+"  --hmark-proto-mask value	    Mask Protocol with value\n");
+}
+
+#define hi struct xt_hmark_info
+
+enum {
+	O_HMARK_SADDR_MASK,
+	O_HMARK_DADDR_MASK,
+	O_HMARK_SPI,
+	O_HMARK_SPI_MASK,
+	O_HMARK_SPORT,
+	O_HMARK_DPORT,
+	O_HMARK_SPORT_MASK,
+	O_HMARK_DPORT_MASK,
+	O_HMARK_PROTO_MASK,
+	O_HMARK_RND,
+	O_HMARK_MODULUS,
+	O_HMARK_OFFSET,
+	O_HMARK_CT,
+	O_HMARK_TYPE,
+};
+
+#define HMARK_OPT_PKT_MASK			\
+	((1 << O_HMARK_SADDR_MASK)		| \
+	 (1 << O_HMARK_DADDR_MASK)		| \
+	 (1 << O_HMARK_SPI_MASK)		| \
+	 (1 << O_HMARK_SPORT_MASK)		| \
+	 (1 << O_HMARK_DPORT_MASK)		| \
+	 (1 << O_HMARK_PROTO_MASK)		| \
+	 (1 << O_HMARK_SPI_MASK)		| \
+	 (1 << O_HMARK_SPORT)			| \
+	 (1 << O_HMARK_DPORT)			| \
+	 (1 << O_HMARK_SPI))
+
+static const struct xt_option_entry HMARK_opts[] = {
+	{ .name  = "hmark-tuple",
+	  .type  = XTTYPE_STRING,
+	  .id	 = O_HMARK_TYPE,
+	},
+	{ .name  = "hmark-src-prefix",
+	  .type  = XTTYPE_PLENMASK,
+	  .id	 = O_HMARK_SADDR_MASK,
+	  .flags = XTOPT_PUT, XTOPT_POINTER(hi, src_mask)
+	},
+	{ .name  = "hmark-dst-prefix",
+	  .type  = XTTYPE_PLENMASK,
+	  .id	 = O_HMARK_DADDR_MASK,
+	  .flags = XTOPT_PUT, XTOPT_POINTER(hi, dst_mask)
+	},
+	{ .name  = "hmark-sport-mask",
+	  .type  = XTTYPE_UINT16,
+	  .id	 = O_HMARK_SPORT_MASK,
+	  .flags = XTOPT_PUT, XTOPT_POINTER(hi, port_mask.p16.src)
+	},
+	{ .name  = "hmark-dport-mask",
+	  .type  = XTTYPE_UINT16,
+	  .id	 = O_HMARK_DPORT_MASK,
+	  .flags = XTOPT_PUT, XTOPT_POINTER(hi, port_mask.p16.dst)
+	},
+	{ .name  = "hmark-spi-mask",
+	  .type  = XTTYPE_UINT32,
+	  .id	 = O_HMARK_SPI_MASK,
+	  .flags = XTOPT_PUT, XTOPT_POINTER(hi, port_mask.v32)
+	},
+	{ .name  = "hmark-sport",
+	  .type  = XTTYPE_UINT16,
+	  .id	 = O_HMARK_SPORT,
+	  .flags = XTOPT_PUT, XTOPT_POINTER(hi, port_set.p16.src)
+	},
+	{ .name  = "hmark-dport",
+	  .type  = XTTYPE_UINT16,
+	  .id	 = O_HMARK_DPORT,
+	  .flags = XTOPT_PUT, XTOPT_POINTER(hi, port_set.p16.dst)
+	},
+	{ .name  = "hmark-spi",
+	  .type  = XTTYPE_UINT32,
+	  .id	 = O_HMARK_SPI,
+	  .flags = XTOPT_PUT, XTOPT_POINTER(hi, port_set.v32)
+	},
+	{ .name  = "hmark-proto-mask",
+	  .type  = XTTYPE_UINT16,
+	  .id	 = O_HMARK_PROTO_MASK,
+	  .flags = XTOPT_PUT, XTOPT_POINTER(hi, proto_mask)
+	},
+	{ .name  = "hmark-rnd",
+	  .type  = XTTYPE_UINT32,
+	  .id	 = O_HMARK_RND,
+	  .flags = XTOPT_PUT, XTOPT_POINTER(hi, hashrnd)
+	},
+	{ .name = "hmark-mod",
+	  .type = XTTYPE_UINT32,
+	  .id = O_HMARK_MODULUS,
+	  .min = 1,
+	  .flags = XTOPT_PUT | XTOPT_MAND, XTOPT_POINTER(hi, hmodulus)
+	},
+	{ .name  = "hmark-offset",
+	  .type  = XTTYPE_UINT32,
+	  .id	 = O_HMARK_OFFSET,
+	  .flags = XTOPT_PUT, XTOPT_POINTER(hi, hoffset)
+	},
+	XTOPT_TABLEEND,
+};
+
+static int
+hmark_parse(const char *type, size_t len, struct xt_hmark_info *info,
+	    unsigned int *xflags)
+{
+	if (strncasecmp(type, "ct", len) == 0) {
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_CT);
+		*xflags |= (1 << O_HMARK_CT);
+	} else if (strncasecmp(type, "src", len) == 0) {
+		memset(&info->src_mask, 0xff, sizeof(info->src_mask));
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_SADDR_MASK);
+		*xflags |= (1 << O_HMARK_SADDR_MASK);
+	} else if (strncasecmp(type, "dst", len) == 0) {
+		memset(&info->dst_mask, 0xff, sizeof(info->dst_mask));
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_DADDR_MASK);
+		*xflags |= (1 << O_HMARK_DADDR_MASK);
+	} else if (strncasecmp(type, "sport", len) == 0) {
+		memset(&info->port_mask.p16.src, 0xff,
+			sizeof(info->port_mask.p16.src));
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_SPORT_MASK);
+		*xflags |= (1 << O_HMARK_SPORT_MASK);
+	} else if (strncasecmp(type, "dport", len) == 0) {
+		memset(&info->port_mask.p16.dst, 0xff,
+			sizeof(info->port_mask.p16.dst));
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_DPORT_MASK);
+		*xflags |= (1 << O_HMARK_DPORT_MASK);
+	} else if (strncasecmp(type, "proto", len) == 0) {
+		memset(&info->proto_mask, 0xff, sizeof(info->proto_mask));
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_PROTO_MASK);
+		*xflags |= (1 << O_HMARK_PROTO_MASK);
+	} else if (strncasecmp(type, "spi", len) == 0) {
+		memset(&info->port_mask.v32, 0xff, sizeof(info->port_mask.v32));
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_SPI_MASK);
+		*xflags |= (1 << O_HMARK_SPI_MASK);
+	} else
+		return 0;
+
+	return 1;
+}
+
+static void
+hmark_parse_type(struct xt_option_call *cb)
+{
+	const char *arg = cb->arg;
+	struct xt_hmark_info *info = cb->data;
+	const char *comma;
+
+	while ((comma = strchr(arg, ',')) != NULL) {
+		if (comma == arg ||
+		    !hmark_parse(arg, comma-arg, info, &cb->xflags))
+			xtables_error(PARAMETER_PROBLEM, "Bad type \"%s\"", arg);
+		arg = comma+1;
+	}
+	if (!*arg)
+		xtables_error(PARAMETER_PROBLEM, "\"--hmark-tuple\" requires "
+						 "a list of types with no "
+						 "spaces, e.g. "
+						 "src,dst,sport,dport,proto");
+	if (strlen(arg) == 0 ||
+	    !hmark_parse(arg, strlen(arg), info, &cb->xflags))
+		xtables_error(PARAMETER_PROBLEM, "Bad type \"%s\"", arg);
+}
+
+static void HMARK_parse(struct xt_option_call *cb, int plen)
+{
+	struct xt_hmark_info *info = cb->data;
+
+	xtables_option_parse(cb);
+
+	switch (cb->entry->id) {
+	case O_HMARK_TYPE:
+		hmark_parse_type(cb);
+		break;
+	case O_HMARK_SADDR_MASK:
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_SADDR_MASK);
+		break;
+	case O_HMARK_DADDR_MASK:
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_DADDR_MASK);
+		break;
+	case O_HMARK_SPI:
+		info->port_set.v32 = htonl(cb->val.u32);
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_SPI);
+		break;
+	case O_HMARK_SPORT:
+		info->port_set.p16.src = htons(cb->val.u16);
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_SPORT);
+		break;
+	case O_HMARK_DPORT:
+		info->port_set.p16.dst = htons(cb->val.u16);
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_DPORT);
+		break;
+	case O_HMARK_SPORT_MASK:
+		info->port_mask.p16.src = htons(cb->val.u16);
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_SPORT_MASK);
+		break;
+	case O_HMARK_DPORT_MASK:
+		info->port_mask.p16.dst = htons(cb->val.u16);
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_DPORT_MASK);
+		break;
+	case O_HMARK_SPI_MASK:
+		info->port_mask.v32 = htonl(cb->val.u32);
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_SPI_MASK);
+		break;
+	case O_HMARK_PROTO_MASK:
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_PROTO_MASK);
+		break;
+	case O_HMARK_RND:
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_RND);
+		break;
+	case O_HMARK_MODULUS:
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_MODULUS);
+		break;
+	case O_HMARK_OFFSET:
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_OFFSET);
+		break;
+	case O_HMARK_CT:
+		info->flags |= XT_HMARK_FLAG(XT_HMARK_CT);
+		break;
+	}
+	cb->xflags |= (1 << cb->entry->id);
+}
+
+static void HMARK_ip4_parse(struct xt_option_call *cb)
+{
+	HMARK_parse(cb, 32);
+}
+static void HMARK_ip6_parse(struct xt_option_call *cb)
+{
+	HMARK_parse(cb, 128);
+}
+
+static void HMARK_check(struct xt_fcheck_call *cb)
+{
+	if (!(cb->xflags & (1 << O_HMARK_MODULUS)))
+		xtables_error(PARAMETER_PROBLEM, "--hmark-mod is mandatory");
+	if (!(cb->xflags & (1 << O_HMARK_RND)))
+		xtables_error(PARAMETER_PROBLEM, "--hmark-rnd is mandatory");
+	if (cb->xflags & (1 << O_HMARK_SPI_MASK) &&
+	    (cb->xflags & ((1 << O_HMARK_SPORT_MASK) |
+			   (1 << O_HMARK_DPORT_MASK))))
+		xtables_error(PARAMETER_PROBLEM, "you cannot use "
+				"--hmark-spi-mask and --hmark-?port-mask,"
+				"at the same time");
+	if (!((cb->xflags & HMARK_OPT_PKT_MASK) ||
+	       cb->xflags & (1 << O_HMARK_CT)))
+		xtables_error(PARAMETER_PROBLEM, "you have to specify "
+				"--hmark-tuple at least");
+}
+
+static void HMARK_print(const struct xt_hmark_info *info)
+{
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_SPORT_MASK))
+		printf("sport-mask 0x%x ", htons(info->port_mask.p16.src));
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_DPORT_MASK))
+		printf("dport-mask 0x%x ", htons(info->port_mask.p16.dst));
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_SPI_MASK))
+		printf("spi-mask 0x%x ", htonl(info->port_mask.v32));
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_SPORT))
+		printf("sport 0x%x ", htons(info->port_set.p16.src));
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_DPORT))
+		printf("dport 0x%x ", htons(info->port_set.p16.dst));
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_SPI))
+		printf("spi 0x%x ", htonl(info->port_set.v32));
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_PROTO_MASK))
+		printf("proto-mask 0x%x ", info->proto_mask);
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_RND))
+		printf("rnd 0x%x ", info->hashrnd);
+}
+
+static void HMARK_ip6_print(const void *ip,
+			    const struct xt_entry_target *target, int numeric)
+{
+	const struct xt_hmark_info *info =
+			(const struct xt_hmark_info *)target->data;
+
+	printf(" HMARK ");
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_MODULUS))
+		printf("mod %u ", info->hmodulus);
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_OFFSET))
+		printf("+ 0x%x ", info->hoffset);
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_CT))
+		printf("ct, ");
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_SADDR_MASK))
+		printf("src-prefix %s ",
+		       xtables_ip6mask_to_numeric(&info->src_mask.in6) + 1);
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_DADDR_MASK))
+		printf("dst-prefix %s ",
+		       xtables_ip6mask_to_numeric(&info->dst_mask.in6) + 1);
+	HMARK_print(info);
+}
+static void HMARK_ip4_print(const void *ip,
+			    const struct xt_entry_target *target, int numeric)
+{
+	const struct xt_hmark_info *info =
+		(const struct xt_hmark_info *)target->data;
+
+	printf(" HMARK ");
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_MODULUS))
+		printf("mod %u ", info->hmodulus);
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_OFFSET))
+		printf("+ 0x%x ", info->hoffset);
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_CT))
+		printf("ct, ");
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_SADDR_MASK))
+		printf("src-prefix %u ",
+		       xtables_ipmask_to_cidr(&info->src_mask.in));
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_DADDR_MASK))
+		printf("dst-prefix %u ",
+		       xtables_ipmask_to_cidr(&info->dst_mask.in));
+	HMARK_print(info);
+}
+
+static void HMARK_save(const struct xt_hmark_info *info)
+{
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_SPORT_MASK))
+		printf(" --hmark-sport-mask 0x%04x",
+		       htons(info->port_mask.p16.src));
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_DPORT_MASK))
+		printf(" --hmark-dport-mask 0x%04x",
+		       htons(info->port_mask.p16.dst));
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_SPI_MASK))
+		printf(" --hmark-spi-mask 0x%08x",
+		       htonl(info->port_mask.v32));
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_SPORT))
+		printf(" --hmark-sport 0x%04x",
+		       htons(info->port_set.p16.src));
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_DPORT))
+		printf(" --hmark-dport 0x%04x",
+		       htons(info->port_set.p16.dst));
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_SPI))
+		printf(" --hmark-spi 0x%08x", htonl(info->port_set.v32));
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_PROTO_MASK))
+		printf(" --hmark-proto-mask 0x%02x", info->proto_mask);
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_RND))
+		printf(" --hmark-rnd 0x%08x", info->hashrnd);
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_MODULUS))
+		printf(" --hmark-mod %u", info->hmodulus);
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_OFFSET))
+		printf(" --hmark-offset %u", info->hoffset);
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_CT))
+		printf(" --hmark-tuple ct");
+}
+
+static void HMARK_ip6_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_hmark_info *info =
+		(const struct xt_hmark_info *)target->data;
+	int ret;
+
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_SADDR_MASK)) {
+		ret = xtables_ip6mask_to_cidr(&info->src_mask.in6);
+		printf(" --hmark-src-prefix %d", ret);
+	}
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_DADDR_MASK)) {
+		ret = xtables_ip6mask_to_cidr(&info->dst_mask.in6);
+		printf(" --hmark-dst-prefix %d", ret);
+	}
+	HMARK_save(info);
+}
+
+static void HMARK_ip4_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_hmark_info *info =
+		(const struct xt_hmark_info *)target->data;
+	int ret;
+
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_SADDR_MASK)) {
+		ret = xtables_ipmask_to_cidr(&info->src_mask.in);
+		printf(" --hmark-src-prefix %d", ret);
+	}
+	if (info->flags & XT_HMARK_FLAG(XT_HMARK_DADDR_MASK)) {
+		ret = xtables_ipmask_to_cidr(&info->dst_mask.in);
+		printf(" --hmark-dst-prefix %d", ret);
+	}
+	HMARK_save(info);
+}
+
+static struct xtables_target mark_tg_reg[] = {
+	{
+		.family        = NFPROTO_IPV4,
+		.name	       = "HMARK",
+		.version       = XTABLES_VERSION,
+		.size	       = XT_ALIGN(sizeof(struct xt_hmark_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_hmark_info)),
+		.help	       = HMARK_help,
+		.print	       = HMARK_ip4_print,
+		.save	       = HMARK_ip4_save,
+		.x6_parse      = HMARK_ip4_parse,
+		.x6_fcheck     = HMARK_check,
+		.x6_options    = HMARK_opts,
+	},
+	{
+		.family        = NFPROTO_IPV6,
+		.name	       = "HMARK",
+		.version       = XTABLES_VERSION,
+		.size	       = XT_ALIGN(sizeof(struct xt_hmark_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_hmark_info)),
+		.help	       = HMARK_help,
+		.print	       = HMARK_ip6_print,
+		.save	       = HMARK_ip6_save,
+		.x6_parse      = HMARK_ip6_parse,
+		.x6_fcheck     = HMARK_check,
+		.x6_options    = HMARK_opts,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_targets(mark_tg_reg, ARRAY_SIZE(mark_tg_reg));
+}
diff --git a/extensions/libxt_HMARK.man b/extensions/libxt_HMARK.man
new file mode 100644
index 0000000..cd7ffd5
--- /dev/null
+++ b/extensions/libxt_HMARK.man
@@ -0,0 +1,60 @@
+Like MARK, i.e. set the fwmark, but the mark is calculated from hashing
+packet selector at choice. You have also to specify the mark range and,
+optionally, the offset to start from. ICMP error messages are inspected
+and used to calculate the hashing.
+.PP
+Existing options are:
+.TP
+\fB\-\-hmark\-tuple\fP tuple\fI\fP
+Possible tuple members are:
+.B src
+meaning source address (IPv4, IPv6 address),
+.B dst
+meaning destination address (IPv4, IPv6 address),
+.B sport
+meaning source port (TCP, UDP, UDPlite, SCTP, DCCP),
+.B dport
+meaning destination port (TCP, UDP, UDPlite, SCTP, DCCP),
+.B spi
+meaning Security Parameter Index (AH, ESP), and
+.B ct
+meaning the usage of the conntrack tuple instead of the packet selectors.
+.TP
+\fB\-\-hmark\-mod\fP \fIvalue (must be > 0)\fP
+Modulus for hash calculation (to limit the range of possible marks)
+.TP
+\fB\-\-hmark\-offset\fP \fIvalue\fP
+Offset to start marks from.
+.TP
+For advanced usage, instead of using \-\-hmark\-tuple, you can specify custom
+prefixes and masks:
+.TP
+\fB\-\-hmark\-src\-prefix\fP \fIcidr\fP
+The source address mask in CIDR notation.
+.TP
+\fB\-\-hmark\-dst\-prefix\fP \fIcidr\fP
+The destination address mask in CIDR notation.
+.TP
+\fB\-\-hmark\-sport\-mask\fP \fIvalue\fP
+A 16 bit source port mask in hexadecimal.
+.TP
+\fB\-\-hmark\-dport\-mask\fP \fIvalue\fP
+A 16 bit destination port mask in hexadecimal.
+.TP
+\fB\-\-hmark\-spi\-mask\fP \fIvalue\fP
+A 32 bit field with spi mask.
+.TP
+\fB\-\-hmark\-proto\-mask\fP \fIvalue\fP
+An 8 bit field with layer 4 protocol number.
+.TP
+\fB\-\-hmark\-rnd\fP \fIvalue\fP
+A 32 bit random custom value to feed hash calculation.
+.PP
+\fIExamples:\fP
+.PP
+iptables \-t mangle \-A PREROUTING \-m conntrack \-\-ctstate NEW
+ \-j HMARK \-\-hmark-tuple ct,src,dst,proto \-\-hmark-offset 10000
+\-\-hmark\-mod 10 \-\-hmark\-rnd 0xfeedcafe
+.PP
+iptables \-t mangle \-A PREROUTING \-j HMARK \-\-hmark\-offset 10000
+\-\-hmark-tuple src,dst,proto \-\-hmark-mod 10 \-\-hmark\-rnd 0xdeafbeef
diff --git a/extensions/libxt_HMARK.t b/extensions/libxt_HMARK.t
new file mode 100644
index 0000000..3bcf1da
--- /dev/null
+++ b/extensions/libxt_HMARK.t
@@ -0,0 +1,8 @@
+:INPUT,FORWARD,OUTPUT
+-j HMARK;;FAIL
+-j HMARK --hmark-src-prefix 32 --hmark-rnd 0x00000004 --hmark-mod 42;=;OK
+-j HMARK --hmark-src-prefix 32 --hmark-dst-prefix 32 --hmark-sport-mask 0xffff --hmark-dport-mask 0xffff --hmark-proto-mask 0xffff --hmark-rnd 0x00000004 --hmark-mod 42 --hmark-offset 1 --hmark-tuple ct;=;OK
+-j HMARK --hmark-src-prefix 32 --hmark-dst-prefix 32 --hmark-spi-mask 0x00000004 --hmark-proto-mask 0xffff --hmark-rnd 0x00000004 --hmark-mod 42 --hmark-offset 1 --hmark-tuple ct;=;OK
+-j HMARK --hmark-src-prefix 1 --hmark-dst-prefix 2 --hmark-sport-mask 0x0003 --hmark-dport-mask 0x0004 --hmark-proto-mask 0x05 --hmark-rnd 0x00000004 --hmark-mod 42 --hmark-offset 1 --hmark-tuple ct;=;OK
+# cannot mix in spi mask:
+-j HMARK --hmark-src-prefix 32 --hmark-dst-prefix 32 --hmark-sport-mask 0xffff --hmark-dport-mask 0xffff --hmark-proto-mask 0xffff --hmark-rnd 0x00000004 --hmark-mod 42 --hmark-offset 1 --hmark-tuple ct --hmark-spi-mask 4;;FAIL
diff --git a/extensions/libxt_IDLETIMER.c b/extensions/libxt_IDLETIMER.c
index 5f1b9fe..c414801 100644
--- a/extensions/libxt_IDLETIMER.c
+++ b/extensions/libxt_IDLETIMER.c
@@ -27,6 +27,7 @@
 enum {
 	O_TIMEOUT = 0,
 	O_LABEL,
+	O_ALARM,
 	O_NETLINK,
 };
 
@@ -36,8 +37,18 @@
 	 .flags = XTOPT_MAND | XTOPT_PUT, XTOPT_POINTER(s, timeout)},
 	{.name = "label", .id = O_LABEL, .type = XTTYPE_STRING,
 	 .flags = XTOPT_MAND | XTOPT_PUT, XTOPT_POINTER(s, label)},
-	{.name = "send_nl_msg", .id = O_NETLINK, .type = XTTYPE_UINT8,
-	 .flags = XTOPT_PUT, XTOPT_POINTER(s, send_nl_msg)},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+#define s struct idletimer_tg_info_v1
+static const struct xt_option_entry idletimer_tg_opts_v1[] = {
+	{.name = "timeout", .id = O_TIMEOUT, .type = XTTYPE_UINT32,
+	 .flags = XTOPT_MAND | XTOPT_PUT, XTOPT_POINTER(s, timeout)},
+	{.name = "label", .id = O_LABEL, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_PUT, XTOPT_POINTER(s, label)},
+	{.name = "alarm", .id = O_ALARM, .type = XTTYPE_NONE},
+	{.name = "send_nl_msg", .id = O_NETLINK, .type = XTTYPE_NONE},
 	XTOPT_TABLEEND,
 };
 #undef s
@@ -48,8 +59,17 @@
 "IDLETIMER target options:\n"
 " --timeout time	Timeout until the notification is sent (in seconds)\n"
 " --label string	Unique rule identifier\n"
-" --send_nl_msg		(0/1) Enable netlink messages,"
-			" and show remaining time in sysfs. Defaults to 0.\n"
+"\n");
+}
+
+static void idletimer_tg_help_v1(void)
+{
+	printf(
+"IDLETIMER target options:\n"
+" --timeout time	Timeout until the notification is sent (in seconds)\n"
+" --label string	Unique rule identifier\n"
+" --alarm	Use alarm instead of default timer\n"
+" --send_nl_msg		Enable netlink messages and show remaining time in sysfs.\n"
 "\n");
 }
 
@@ -62,9 +82,24 @@
 
 	printf(" timeout:%u", info->timeout);
 	printf(" label:%s", info->label);
-	printf(" send_nl_msg:%u", info->send_nl_msg);
 }
 
+static void idletimer_tg_print_v1(const void *ip,
+			       const struct xt_entry_target *target,
+			       int numeric)
+{
+	struct idletimer_tg_info_v1 *info =
+		(struct idletimer_tg_info_v1 *) target->data;
+
+	printf(" timeout:%u", info->timeout);
+	printf(" label:%s", info->label);
+	if (info->timer_type == XT_IDLETIMER_ALARM)
+		printf(" alarm");
+	if (info->send_nl_msg)
+		printf(" send_nl_msg");
+}
+
+
 static void idletimer_tg_save(const void *ip,
 			      const struct xt_entry_target *target)
 {
@@ -73,24 +108,64 @@
 
 	printf(" --timeout %u", info->timeout);
 	printf(" --label %s", info->label);
-	printf(" --send_nl_msg %u", info->send_nl_msg);
 }
 
-static struct xtables_target idletimer_tg_reg = {
-	.family	       = NFPROTO_UNSPEC,
-	.name	       = "IDLETIMER",
-	.version       = XTABLES_VERSION,
-	.revision      = 1,
-	.size	       = XT_ALIGN(sizeof(struct idletimer_tg_info)),
-	.userspacesize = offsetof(struct idletimer_tg_info, timer),
-	.help	       = idletimer_tg_help,
-	.x6_parse      = xtables_option_parse,
-	.print	       = idletimer_tg_print,
-	.save	       = idletimer_tg_save,
-	.x6_options    = idletimer_tg_opts,
+static void idletimer_tg_save_v1(const void *ip,
+			      const struct xt_entry_target *target)
+{
+	struct idletimer_tg_info_v1 *info =
+		(struct idletimer_tg_info_v1 *) target->data;
+
+	printf(" --timeout %u", info->timeout);
+	printf(" --label %s", info->label);
+	if (info->timer_type == XT_IDLETIMER_ALARM)
+		printf(" --alarm");
+	if (info->send_nl_msg)
+		printf(" --send_nl_msg");
+}
+
+static void idletimer_tg_parse_v1(struct xt_option_call *cb)
+{
+	struct idletimer_tg_info_v1 *info = cb->data;
+
+	xtables_option_parse(cb);
+	if (cb->entry->id == O_ALARM)
+		info->timer_type = XT_IDLETIMER_ALARM;
+	if (cb->entry->id == O_NETLINK)
+		info->send_nl_msg = 1;
+}
+
+static struct xtables_target idletimer_tg_reg[] = {
+	{
+		.family	       = NFPROTO_UNSPEC,
+		.name	       = "IDLETIMER",
+		.version       = XTABLES_VERSION,
+		.revision      = 0,
+		.size	       = XT_ALIGN(sizeof(struct idletimer_tg_info)),
+		.userspacesize = offsetof(struct idletimer_tg_info, timer),
+		.help	       = idletimer_tg_help,
+		.x6_parse      = xtables_option_parse,
+		.print	       = idletimer_tg_print,
+		.save	       = idletimer_tg_save,
+		.x6_options    = idletimer_tg_opts,
+	},
+	{
+		.family	       = NFPROTO_UNSPEC,
+		.name	       = "IDLETIMER",
+		.version       = XTABLES_VERSION,
+		.revision      = 1,
+		.size	       = XT_ALIGN(sizeof(struct idletimer_tg_info_v1)),
+		.userspacesize = offsetof(struct idletimer_tg_info_v1, timer),
+		.help	       = idletimer_tg_help_v1,
+		.x6_parse      = idletimer_tg_parse_v1,
+		.print	       = idletimer_tg_print_v1,
+		.save	       = idletimer_tg_save_v1,
+		.x6_options    = idletimer_tg_opts_v1,
+	},
+
 };
 
 void _init(void)
 {
-	xtables_register_target(&idletimer_tg_reg);
+	xtables_register_targets(idletimer_tg_reg, ARRAY_SIZE(idletimer_tg_reg));
 }
diff --git a/extensions/libxt_IDLETIMER.man b/extensions/libxt_IDLETIMER.man
index 3b5188d..bd4add9 100644
--- a/extensions/libxt_IDLETIMER.man
+++ b/extensions/libxt_IDLETIMER.man
@@ -19,6 +19,6 @@
 This is a unique identifier for the timer.  The maximum length for the
 label string is 27 characters.
 .TP
-\fB\-\---send_nl_msg\fP \fI(0/1)\fP
+\fB\-\---send_nl_msg\fP
 Send netlink messages in addition to sysfs notifications and show remaining
-time. Defaults to 0.
+time.
diff --git a/extensions/libxt_IDLETIMER.t b/extensions/libxt_IDLETIMER.t
new file mode 100644
index 0000000..e8f306d
--- /dev/null
+++ b/extensions/libxt_IDLETIMER.t
@@ -0,0 +1,5 @@
+:INPUT,FORWARD,OUTPUT
+-j IDLETIMER --timeout;;FAIL
+-j IDLETIMER --timeout 42;;FAIL
+-j IDLETIMER --timeout 42 --label foo;=;OK
+-j IDLETIMER --timeout 42 --label foo --alarm;;OK
diff --git a/extensions/libxt_LED.c b/extensions/libxt_LED.c
index 9d68fa2..6ada795 100644
--- a/extensions/libxt_LED.c
+++ b/extensions/libxt_LED.c
@@ -53,14 +53,15 @@
 	xtables_option_parse(cb);
 	switch (cb->entry->id) {
 	case O_LED_TRIGGER_ID:
-		strcpy(led->id, "netfilter-");
-		strcat(led->id, cb->arg);
+		snprintf(led->id, sizeof(led->id), "netfilter-%s", cb->arg);
 		break;
 	case O_LED_DELAY:
 		if (strncasecmp(cb->arg, "inf", 3) == 0)
 			led->delay = -1;
-		else
-			led->delay = strtoul(cb->arg, NULL, 0);
+		else if (!xtables_strtoui(cb->arg, NULL, &led->delay, 0, UINT32_MAX))
+			xtables_error(PARAMETER_PROBLEM,
+				"Delay value must be within range 0..%u",
+				UINT32_MAX);
 		break;
 	case O_LED_ALWAYS_BLINK:
 		led->always_blink = 1;
diff --git a/extensions/libxt_LED.t b/extensions/libxt_LED.t
new file mode 100644
index 0000000..1f6705f
--- /dev/null
+++ b/extensions/libxt_LED.t
@@ -0,0 +1,4 @@
+:INPUT,FORWARD,OUTPUT
+-j LED;;FAIL
+-j LED --led-trigger-id "foo";=;OK
+-j LED --led-trigger-id "foo" --led-delay 42 --led-always-blink;=;OK
diff --git a/extensions/libxt_LOG.man b/extensions/libxt_LOG.man
new file mode 100644
index 0000000..354edf4
--- /dev/null
+++ b/extensions/libxt_LOG.man
@@ -0,0 +1,32 @@
+Turn on kernel logging of matching packets.  When this option is set
+for a rule, the Linux kernel will print some information on all
+matching packets (like most IP/IPv6 header fields) via the kernel log
+(where it can be read with \fIdmesg(1)\fP or read in the syslog).
+.PP
+This is a "non-terminating target", i.e. rule traversal continues at
+the next rule.  So if you want to LOG the packets you refuse, use two
+separate rules with the same matching criteria, first using target LOG
+then DROP (or REJECT).
+.TP
+\fB\-\-log\-level\fP \fIlevel\fP
+Level of logging, which can be (system-specific) numeric or a mnemonic.
+Possible values are (in decreasing order of priority): \fBemerg\fP,
+\fBalert\fP, \fBcrit\fP, \fBerror\fP, \fBwarning\fP, \fBnotice\fP, \fBinfo\fP
+or \fBdebug\fP.
+.TP
+\fB\-\-log\-prefix\fP \fIprefix\fP
+Prefix log messages with the specified prefix; up to 29 letters long,
+and useful for distinguishing messages in the logs.
+.TP
+\fB\-\-log\-tcp\-sequence\fP
+Log TCP sequence numbers. This is a security risk if the log is
+readable by users.
+.TP
+\fB\-\-log\-tcp\-options\fP
+Log options from the TCP packet header.
+.TP
+\fB\-\-log\-ip\-options\fP
+Log options from the IP/IPv6 packet header.
+.TP
+\fB\-\-log\-uid\fP
+Log the userid of the process which generated the packet.
diff --git a/extensions/libxt_MARK.c b/extensions/libxt_MARK.c
index 556dbde..b765af6 100644
--- a/extensions/libxt_MARK.c
+++ b/extensions/libxt_MARK.c
@@ -1,3 +1,4 @@
+#include <getopt.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <xtables.h>
@@ -76,7 +77,7 @@
 "  --set-mark value[/mask]   Clear bits in mask and OR value into nfmark\n"
 "  --and-mark bits           Binary AND the nfmark with bits\n"
 "  --or-mark bits            Binary OR the nfmark with bits\n"
-"  --xor-mask bits           Binary XOR the nfmark with bits\n"
+"  --xor-mark bits           Binary XOR the nfmark with bits\n"
 "\n");
 }
 
@@ -195,7 +196,7 @@
 	case XT_MARK_AND:
 		printf(" MARK and");
 		break;
-	case XT_MARK_OR: 
+	case XT_MARK_OR:
 		printf(" MARK or");
 		break;
 	}
@@ -231,7 +232,7 @@
 	case XT_MARK_AND:
 		printf(" --and-mark");
 		break;
-	case XT_MARK_OR: 
+	case XT_MARK_OR:
 		printf(" --or-mark");
 		break;
 	}
@@ -245,6 +246,132 @@
 	printf(" --set-xmark 0x%x/0x%x", info->mark, info->mask);
 }
 
+static void mark_tg_arp_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_mark_tginfo2 *info = (const void *)target->data;
+
+	if (info->mark == 0)
+		printf(" --and-mark %x", (unsigned int)(uint32_t)~info->mask);
+	else if (info->mark == info->mask)
+		printf(" --or-mark %x", info->mark);
+	else
+		printf(" --set-mark %x", info->mark);
+}
+
+static void mark_tg_arp_print(const void *ip,
+			      const struct xt_entry_target *target, int numeric)
+{
+	mark_tg_arp_save(ip, target);
+}
+
+#define MARK_OPT 1
+#define AND_MARK_OPT 2
+#define OR_MARK_OPT 3
+
+static struct option mark_tg_arp_opts[] = {
+	{ .name = "set-mark", .has_arg = required_argument, .flag = 0, .val = MARK_OPT },
+	{ .name = "and-mark", .has_arg = required_argument, .flag = 0, .val = AND_MARK_OPT },
+	{ .name = "or-mark", .has_arg = required_argument, .flag = 0, .val =  OR_MARK_OPT },
+	{ .name = NULL}
+};
+
+static int
+mark_tg_arp_parse(int c, char **argv, int invert, unsigned int *flags,
+		  const void *entry, struct xt_entry_target **target)
+{
+	struct xt_mark_tginfo2 *info =
+		(struct xt_mark_tginfo2 *)(*target)->data;
+	int i;
+
+	switch (c) {
+	case MARK_OPT:
+		if (sscanf(argv[optind-1], "%x", &i) != 1) {
+			xtables_error(PARAMETER_PROBLEM,
+				"Bad mark value `%s'", optarg);
+			return 0;
+		}
+		info->mark = i;
+		if (*flags)
+			xtables_error(PARAMETER_PROBLEM,
+				"MARK: Can't specify --set-mark twice");
+		*flags = 1;
+		break;
+	case AND_MARK_OPT:
+		if (sscanf(argv[optind-1], "%x", &i) != 1) {
+			xtables_error(PARAMETER_PROBLEM,
+				"Bad mark value `%s'", optarg);
+			return 0;
+		}
+		info->mark = 0;
+		info->mask = ~i;
+		if (*flags)
+			xtables_error(PARAMETER_PROBLEM,
+				"MARK: Can't specify --and-mark twice");
+		*flags = 1;
+		break;
+	case OR_MARK_OPT:
+		if (sscanf(argv[optind-1], "%x", &i) != 1) {
+			xtables_error(PARAMETER_PROBLEM,
+				"Bad mark value `%s'", optarg);
+			return 0;
+		}
+		info->mark = info->mask = i;
+		if (*flags)
+			xtables_error(PARAMETER_PROBLEM,
+				"MARK: Can't specify --or-mark twice");
+		*flags = 1;
+		break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static int mark_tg_xlate(struct xt_xlate *xl,
+			 const struct xt_xlate_tg_params *params)
+{
+	const struct xt_mark_tginfo2 *info = (const void *)params->target->data;
+
+	xt_xlate_add(xl, "meta mark set ");
+
+	if (info->mask == 0xffffffffU)
+		xt_xlate_add(xl, "0x%x ", info->mark);
+	else if (info->mark == 0)
+		xt_xlate_add(xl, "mark and 0x%x ", ~info->mask);
+	else if (info->mark == info->mask)
+		xt_xlate_add(xl, "mark or 0x%x ", info->mark);
+	else if (info->mask == 0)
+		xt_xlate_add(xl, "mark xor 0x%x ", info->mark);
+	else
+		xt_xlate_add(xl, "mark and 0x%x xor 0x%x ", ~info->mask,
+			     info->mark);
+
+	return 1;
+}
+
+static int MARK_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_tg_params *params)
+{
+	const struct xt_mark_target_info_v1 *markinfo =
+		(const struct xt_mark_target_info_v1 *)params->target->data;
+
+	xt_xlate_add(xl, "meta mark set ");
+
+	switch(markinfo->mode) {
+	case XT_MARK_SET:
+		xt_xlate_add(xl, "0x%x ", (uint32_t)markinfo->mark);
+		break;
+	case XT_MARK_AND:
+		xt_xlate_add(xl, "mark and 0x%x ", (uint32_t)markinfo->mark);
+		break;
+	case XT_MARK_OR:
+		xt_xlate_add(xl, "mark or 0x%x ", (uint32_t)markinfo->mark);
+		break;
+	}
+
+	return 1;
+}
+
 static struct xtables_target mark_tg_reg[] = {
 	{
 		.family        = NFPROTO_UNSPEC,
@@ -273,6 +400,7 @@
 		.x6_parse      = MARK_parse_v1,
 		.x6_fcheck     = MARK_check,
 		.x6_options    = MARK_opts,
+		.xlate	       = MARK_xlate,
 	},
 	{
 		.version       = XTABLES_VERSION,
@@ -287,6 +415,20 @@
 		.x6_parse      = mark_tg_parse,
 		.x6_fcheck     = mark_tg_check,
 		.x6_options    = mark_tg_opts,
+		.xlate	       = mark_tg_xlate,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "MARK",
+		.revision      = 2,
+		.family        = NFPROTO_ARP,
+		.size          = XT_ALIGN(sizeof(struct xt_mark_tginfo2)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_mark_tginfo2)),
+		.help          = mark_tg_help,
+		.print         = mark_tg_arp_print,
+		.save          = mark_tg_arp_save,
+		.parse         = mark_tg_arp_parse,
+		.extra_opts    = mark_tg_arp_opts,
 	},
 };
 
diff --git a/extensions/libxt_MARK.man b/extensions/libxt_MARK.man
index 712fb76..b240859 100644
--- a/extensions/libxt_MARK.man
+++ b/extensions/libxt_MARK.man
@@ -1,7 +1,7 @@
 This target is used to set the Netfilter mark value associated with the packet.
 It can, for example, be used in conjunction with routing based on fwmark (needs
-iproute2). If you plan on doing so, note that the mark needs to be set in the
-PREROUTING chain of the mangle table to affect routing.
+iproute2). If you plan on doing so, note that the mark needs to be set in
+either the PREROUTING or the OUTPUT chain of the mangle table to affect routing.
 The mark field is 32 bits wide.
 .TP
 \fB\-\-set\-xmark\fP \fIvalue\fP[\fB/\fP\fImask\fP]
diff --git a/extensions/libxt_MARK.t b/extensions/libxt_MARK.t
new file mode 100644
index 0000000..9d1aa7d
--- /dev/null
+++ b/extensions/libxt_MARK.t
@@ -0,0 +1,7 @@
+:INPUT,FORWARD,OUTPUT
+-j MARK --set-xmark 0xfeedcafe/0xfeedcafe;=;OK
+-j MARK --set-xmark 0;=;OK
+-j MARK --set-xmark 4294967295;-j MARK --set-xmark 0xffffffff;OK
+-j MARK --set-xmark 4294967296;;FAIL
+-j MARK --set-xmark -1;;FAIL
+-j MARK;;FAIL
diff --git a/extensions/libxt_MARK.txlate b/extensions/libxt_MARK.txlate
new file mode 100644
index 0000000..d3250ab
--- /dev/null
+++ b/extensions/libxt_MARK.txlate
@@ -0,0 +1,26 @@
+iptables-translate -t mangle -A OUTPUT -j MARK --set-mark 0
+nft add rule ip mangle OUTPUT counter meta mark set 0x0
+
+iptables-translate -t mangle -A OUTPUT -j MARK --set-mark 64
+nft add rule ip mangle OUTPUT counter meta mark set 0x40
+
+iptables-translate -t mangle -A OUTPUT -j MARK --set-xmark 0x40/0x32
+nft add rule ip mangle OUTPUT counter meta mark set mark and 0xffffffcd xor 0x40
+
+iptables-translate -t mangle -A OUTPUT -j MARK --or-mark 64
+nft add rule ip mangle OUTPUT counter meta mark set mark or 0x40
+
+iptables-translate -t mangle -A OUTPUT -j MARK --and-mark 64
+nft add rule ip mangle OUTPUT counter meta mark set mark and 0x40
+
+iptables-translate -t mangle -A OUTPUT -j MARK --xor-mark 64
+nft add rule ip mangle OUTPUT counter meta mark set mark xor 0x40
+
+iptables-translate -t mangle -A PREROUTING -j MARK --set-mark 0x64
+nft add rule ip mangle PREROUTING counter meta mark set 0x64
+
+iptables-translate -t mangle -A PREROUTING -j MARK --and-mark 0x64
+nft add rule ip mangle PREROUTING counter meta mark set mark and 0x64
+
+iptables-translate -t mangle -A PREROUTING -j MARK --or-mark 0x64
+nft add rule ip mangle PREROUTING counter meta mark set mark or 0x64
diff --git a/extensions/libxt_MASQUERADE.man b/extensions/libxt_MASQUERADE.man
new file mode 100644
index 0000000..7746f47
--- /dev/null
+++ b/extensions/libxt_MASQUERADE.man
@@ -0,0 +1,35 @@
+This target is only valid in the
+.B nat
+table, in the
+.B POSTROUTING
+chain.  It should only be used with dynamically assigned IP (dialup)
+connections: if you have a static IP address, you should use the SNAT
+target.  Masquerading is equivalent to specifying a mapping to the IP
+address of the interface the packet is going out, but also has the
+effect that connections are
+.I forgotten
+when the interface goes down.  This is the correct behavior when the
+next dialup is unlikely to have the same interface address (and hence
+any established connections are lost anyway).
+.TP
+\fB\-\-to\-ports\fP \fIport\fP[\fB\-\fP\fIport\fP]
+This specifies a range of source ports to use, overriding the default
+.B SNAT
+source port-selection heuristics (see above).  This is only valid
+if the rule also specifies one of the following protocols:
+\fBtcp\fP, \fBudp\fP, \fBdccp\fP or \fBsctp\fP.
+.TP
+\fB\-\-random\fP
+Randomize source port mapping
+If option
+\fB\-\-random\fP
+is used then port mapping will be randomized (kernel >= 2.6.21).
+Since kernel 5.0, \fB\-\-random\fP is identical to \fB\-\-random-fully\fP.
+.TP
+\fB\-\-random-fully\fP
+Full randomize source port mapping
+If option
+\fB\-\-random-fully\fP
+is used then port mapping will be fully randomized (kernel >= 3.13).
+.TP
+IPv6 support available since Linux kernels >= 3.7.
diff --git a/extensions/libxt_NETMAP.man b/extensions/libxt_NETMAP.man
new file mode 100644
index 0000000..06507db
--- /dev/null
+++ b/extensions/libxt_NETMAP.man
@@ -0,0 +1,11 @@
+This target allows you to statically map a whole network of addresses onto
+another network of addresses.  It can only be used from rules in the
+.B nat
+table.
+.TP
+\fB\-\-to\fP \fIaddress\fP[\fB/\fP\fImask\fP]
+Network address to map to.  The resulting address will be constructed in the
+following way: All 'one' bits in the mask are filled in from the new `address'.
+All bits that are zero in the mask are filled in from the original address.
+.TP
+IPv6 support available since Linux kernels >= 3.7.
diff --git a/extensions/libxt_NFLOG.c b/extensions/libxt_NFLOG.c
index 448576a..02a1b4a 100644
--- a/extensions/libxt_NFLOG.c
+++ b/extensions/libxt_NFLOG.c
@@ -12,7 +12,10 @@
 	O_GROUP = 0,
 	O_PREFIX,
 	O_RANGE,
+	O_SIZE,
 	O_THRESHOLD,
+	F_RANGE = 1 << O_RANGE,
+	F_SIZE = 1 << O_SIZE,
 };
 
 #define s struct xt_nflog_info
@@ -22,7 +25,9 @@
 	{.name = "nflog-prefix", .id = O_PREFIX, .type = XTTYPE_STRING,
 	 .min = 1, .flags = XTOPT_PUT, XTOPT_POINTER(s, prefix)},
 	{.name = "nflog-range", .id = O_RANGE, .type = XTTYPE_UINT32,
-	 .flags = XTOPT_PUT, XTOPT_POINTER(s, len)},
+	 .excl = F_SIZE, .flags = XTOPT_PUT, XTOPT_POINTER(s, len)},
+	{.name = "nflog-size", .id = O_SIZE, .type = XTTYPE_UINT32,
+	 .excl = F_RANGE, .flags = XTOPT_PUT, XTOPT_POINTER(s, len)},
 	{.name = "nflog-threshold", .id = O_THRESHOLD, .type = XTTYPE_UINT16,
 	 .flags = XTOPT_PUT, XTOPT_POINTER(s, threshold)},
 	XTOPT_TABLEEND,
@@ -33,7 +38,8 @@
 {
 	printf("NFLOG target options:\n"
 	       " --nflog-group NUM		NETLINK group used for logging\n"
-	       " --nflog-range NUM		Number of byte to copy\n"
+	       " --nflog-range NUM		This option has no effect, use --nflog-size\n"
+	       " --nflog-size NUM		Number of bytes to copy\n"
 	       " --nflog-threshold NUM		Message threshold of in-kernel queue\n"
 	       " --nflog-prefix STRING		Prefix string for log messages\n");
 }
@@ -57,6 +63,18 @@
 	}
 }
 
+static void NFLOG_check(struct xt_fcheck_call *cb)
+{
+	struct xt_nflog_info *info = cb->data;
+
+	if (cb->xflags & F_RANGE)
+		fprintf(stderr, "warn: --nflog-range has never worked and is no"
+			" longer supported, please use --nflog-size insted\n");
+
+	if (cb->xflags & F_SIZE)
+		info->flags |= XT_NFLOG_F_COPY_LEN;
+}
+
 static void nflog_print(const struct xt_nflog_info *info, char *prefix)
 {
 	if (info->prefix[0] != '\0') {
@@ -65,14 +83,16 @@
 	}
 	if (info->group)
 		printf(" %snflog-group %u", prefix, info->group);
-	if (info->len)
+	if (info->flags & XT_NFLOG_F_COPY_LEN)
+		printf(" %snflog-size %u", prefix, info->len);
+	else if (info->len)
 		printf(" %snflog-range %u", prefix, info->len);
 	if (info->threshold != XT_NFLOG_DEFAULT_THRESHOLD)
 		printf(" %snflog-threshold %u", prefix, info->threshold);
 }
 
 static void NFLOG_print(const void *ip, const struct xt_entry_target *target,
-                        int numeric)
+			int numeric)
 {
 	const struct xt_nflog_info *info = (struct xt_nflog_info *)target->data;
 
@@ -86,6 +106,35 @@
 	nflog_print(info, "--");
 }
 
+static void nflog_print_xlate(const struct xt_nflog_info *info,
+			      struct xt_xlate *xl, bool escape_quotes)
+{
+	xt_xlate_add(xl, "log ");
+	if (info->prefix[0] != '\0') {
+		if (escape_quotes)
+			xt_xlate_add(xl, "prefix \\\"%s\\\" ", info->prefix);
+		else
+			xt_xlate_add(xl, "prefix \"%s\" ", info->prefix);
+
+	}
+	if (info->flags & XT_NFLOG_F_COPY_LEN)
+		xt_xlate_add(xl, "snaplen %u ", info->len);
+	if (info->threshold != XT_NFLOG_DEFAULT_THRESHOLD)
+		xt_xlate_add(xl, "queue-threshold %u ", info->threshold);
+	xt_xlate_add(xl, "group %u ", info->group);
+}
+
+static int NFLOG_xlate(struct xt_xlate *xl,
+		       const struct xt_xlate_tg_params *params)
+{
+	const struct xt_nflog_info *info =
+		(struct xt_nflog_info *)params->target->data;
+
+	nflog_print_xlate(info, xl, params->escape_quotes);
+
+	return 1;
+}
+
 static struct xtables_target nflog_target = {
 	.family		= NFPROTO_UNSPEC,
 	.name		= "NFLOG",
@@ -94,10 +143,12 @@
 	.userspacesize	= XT_ALIGN(sizeof(struct xt_nflog_info)),
 	.help		= NFLOG_help,
 	.init		= NFLOG_init,
+	.x6_fcheck	= NFLOG_check,
 	.x6_parse	= NFLOG_parse,
 	.print		= NFLOG_print,
 	.save		= NFLOG_save,
 	.x6_options	= NFLOG_opts,
+	.xlate		= NFLOG_xlate,
 };
 
 void _init(void)
diff --git a/extensions/libxt_NFLOG.man b/extensions/libxt_NFLOG.man
index 66f0b97..318e630 100644
--- a/extensions/libxt_NFLOG.man
+++ b/extensions/libxt_NFLOG.man
@@ -9,7 +9,7 @@
 non-terminating target, i.e. rule traversal continues at the next rule.
 .TP
 \fB\-\-nflog\-group\fP \fInlgroup\fP
-The netlink group (1 \- 2^32\-1) to which packets are (only applicable for
+The netlink group (0 - 2^16\-1) to which packets are (only applicable for
 nfnetlink_log). The default value is 0.
 .TP
 \fB\-\-nflog\-prefix\fP \fIprefix\fP
@@ -17,6 +17,9 @@
 long, useful for distinguishing messages in the logs.
 .TP
 \fB\-\-nflog\-range\fP \fIsize\fP
+This option has never worked, use --nflog-size instead
+.TP
+\fB\-\-nflog\-size\fP \fIsize\fP
 The number of bytes to be copied to userspace (only applicable for
 nfnetlink_log). nfnetlink_log instances may specify their own
 range, this option overrides it.
diff --git a/extensions/libxt_NFLOG.t b/extensions/libxt_NFLOG.t
new file mode 100644
index 0000000..933fa22
--- /dev/null
+++ b/extensions/libxt_NFLOG.t
@@ -0,0 +1,24 @@
+:INPUT,FORWARD,OUTPUT
+-j NFLOG --nflog-group 1;=;OK
+-j NFLOG --nflog-group 65535;=;OK
+-j NFLOG --nflog-group 65536;;FAIL
+-j NFLOG --nflog-group 0;-j NFLOG;OK
+-j NFLOG --nflog-range 1;=;OK
+-j NFLOG --nflog-range 4294967295;=;OK
+-j NFLOG --nflog-range 4294967296;;FAIL
+-j NFLOG --nflog-range -1;;FAIL
+-j NFLOG --nflog-size 0;=;OK
+-j NFLOG --nflog-size 1;=;OK
+-j NFLOG --nflog-size 4294967295;=;OK
+-j NFLOG --nflog-size 4294967296;;FAIL
+-j NFLOG --nflog-size -1;;FAIL
+# ERROR: cannot find: iptables -I INPUT -j NFLOG --nflog-prefix  xxxxxx [...]
+# -j NFLOG --nflog-prefix xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;=;OK
+# ERROR: should fail: iptables -A INPUT -j NFLOG --nflog-prefix  xxxxxxx [...]
+#  -j NFLOG --nflog-prefix xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;;FAIL
+-j NFLOG --nflog-threshold 1;=;OK
+# ERROR: line 13 (should fail: iptables -A INPUT -j NFLOG --nflog-threshold 0
+# -j NFLOG --nflog-threshold 0;;FAIL
+-j NFLOG --nflog-threshold 65535;=;OK
+-j NFLOG --nflog-threshold 65536;;FAIL
+-j NFLOG;=;OK
diff --git a/extensions/libxt_NFLOG.txlate b/extensions/libxt_NFLOG.txlate
new file mode 100644
index 0000000..a0872c9
--- /dev/null
+++ b/extensions/libxt_NFLOG.txlate
@@ -0,0 +1,14 @@
+iptables-translate -A FORWARD -j NFLOG --nflog-group 32 --nflog-prefix "Prefix 1.0"
+nft add rule ip filter FORWARD counter log prefix \"Prefix 1.0\" group 32
+
+iptables-translate -A OUTPUT -j NFLOG --nflog-group 30
+nft add rule ip filter OUTPUT counter log group 30
+
+iptables-translate -I INPUT -j NFLOG --nflog-threshold 2
+nft insert rule ip filter INPUT counter log queue-threshold 2 group 0
+
+iptables-translate -I INPUT -j NFLOG --nflog-size 256
+nft insert rule ip filter INPUT counter log snaplen 256 group 0
+
+iptables-translate -I INPUT -j NFLOG --nflog-threshold 25
+nft insert rule ip filter INPUT counter log queue-threshold 25 group 0
diff --git a/extensions/libxt_NFQUEUE.c b/extensions/libxt_NFQUEUE.c
index e47b586..fe51907 100644
--- a/extensions/libxt_NFQUEUE.c
+++ b/extensions/libxt_NFQUEUE.c
@@ -13,8 +13,10 @@
 	O_QUEUE_NUM = 0,
 	O_QUEUE_BALANCE,
 	O_QUEUE_BYPASS,
+	O_QUEUE_CPU_FANOUT,
 	F_QUEUE_NUM     = 1 << O_QUEUE_NUM,
 	F_QUEUE_BALANCE = 1 << O_QUEUE_BALANCE,
+	F_QUEUE_CPU_FANOUT = 1 << O_QUEUE_CPU_FANOUT,
 };
 
 static void NFQUEUE_help(void)
@@ -28,16 +30,33 @@
 
 static void NFQUEUE_help_v1(void)
 {
-	NFQUEUE_help();
 	printf(
+"NFQUEUE target options\n"
+"  --queue-num value            Send packet to QUEUE number <value>.\n"
+"                               Valid queue numbers are 0-65535\n"
 "  --queue-balance first:last	Balance flows between queues <value> to <value>.\n");
 }
 
 static void NFQUEUE_help_v2(void)
 {
-	NFQUEUE_help_v1();
 	printf(
-"  --queue-bypass		Bypass Queueing if no queue instance exists.\n");
+"NFQUEUE target options\n"
+"  --queue-num value            Send packet to QUEUE number <value>.\n"
+"                               Valid queue numbers are 0-65535\n"
+"  --queue-balance first:last   Balance flows between queues <value> to <value>.\n"
+"  --queue-bypass		Bypass Queueing if no queue instance exists.\n"
+"  --queue-cpu-fanout	Use current CPU (no hashing)\n");
+}
+
+static void NFQUEUE_help_v3(void)
+{
+	printf(
+"NFQUEUE target options\n"
+"  --queue-num value            Send packet to QUEUE number <value>.\n"
+"                               Valid queue numbers are 0-65535\n"
+"  --queue-balance first:last   Balance flows between queues <value> to <value>.\n"
+"  --queue-bypass               Bypass Queueing if no queue instance exists.\n"
+"  --queue-cpu-fanout	Use current CPU (no hashing)\n");
 }
 
 #define s struct xt_NFQ_info
@@ -48,6 +67,8 @@
 	{.name = "queue-balance", .id = O_QUEUE_BALANCE,
 	 .type = XTTYPE_UINT16RC, .excl = F_QUEUE_NUM},
 	{.name = "queue-bypass", .id = O_QUEUE_BYPASS, .type = XTTYPE_NONE},
+	{.name = "queue-cpu-fanout", .id = O_QUEUE_CPU_FANOUT,
+	 .type = XTTYPE_NONE, .also = F_QUEUE_BALANCE},
 	XTOPT_TABLEEND,
 };
 #undef s
@@ -83,11 +104,50 @@
 static void NFQUEUE_parse_v2(struct xt_option_call *cb)
 {
 	struct xt_NFQ_info_v2 *info = cb->data;
+	const uint16_t *r = cb->val.u16_range;
 
-	NFQUEUE_parse_v1(cb);
+	xtables_option_parse(cb);
 	switch (cb->entry->id) {
+	case O_QUEUE_BALANCE:
+		if (cb->nvals != 2)
+			xtables_error(PARAMETER_PROBLEM,
+				"Bad range \"%s\"", cb->arg);
+		if (r[0] >= r[1])
+			xtables_error(PARAMETER_PROBLEM,
+				      "%u should be less than %u",
+				      r[0], r[1]);
+		info->queuenum = r[0];
+		info->queues_total = r[1] - r[0] + 1;
+		break;
 	case O_QUEUE_BYPASS:
-		info->bypass = 1;
+		info->bypass |= NFQ_FLAG_BYPASS;
+		break;
+	}
+}
+
+static void NFQUEUE_parse_v3(struct xt_option_call *cb)
+{
+	struct xt_NFQ_info_v3 *info = cb->data;
+	const uint16_t *r = cb->val.u16_range;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_QUEUE_BALANCE:
+		if (cb->nvals != 2)
+			xtables_error(PARAMETER_PROBLEM,
+				"Bad range \"%s\"", cb->arg);
+		if (r[0] >= r[1])
+			xtables_error(PARAMETER_PROBLEM,
+				      "%u should be less than %u",
+				      r[0], r[1]);
+		info->queuenum = r[0];
+		info->queues_total = r[1] - r[0] + 1;
+		break;
+	case O_QUEUE_BYPASS:
+		info->flags |= NFQ_FLAG_BYPASS;
+		break;
+	case O_QUEUE_CPU_FANOUT:
+		info->flags |= NFQ_FLAG_CPU_FANOUT;
 		break;
 	}
 }
@@ -118,12 +178,37 @@
                              const struct xt_entry_target *target, int numeric)
 {
 	const struct xt_NFQ_info_v2 *info = (void *) target->data;
+	unsigned int last = info->queues_total;
 
-	NFQUEUE_print_v1(ip, target, numeric);
-	if (info->bypass)
+	if (last > 1) {
+		last += info->queuenum - 1;
+		printf(" NFQUEUE balance %u:%u", info->queuenum, last);
+	} else
+		printf(" NFQUEUE num %u", info->queuenum);
+
+	if (info->bypass & NFQ_FLAG_BYPASS)
 		printf(" bypass");
 }
 
+static void NFQUEUE_print_v3(const void *ip,
+                             const struct xt_entry_target *target, int numeric)
+{
+	const struct xt_NFQ_info_v3 *info = (void *)target->data;
+	unsigned int last = info->queues_total;
+
+	if (last > 1) {
+		last += info->queuenum - 1;
+		printf(" NFQUEUE balance %u:%u", info->queuenum, last);
+	} else
+		printf(" NFQUEUE num %u", info->queuenum);
+
+	if (info->flags & NFQ_FLAG_BYPASS)
+		printf(" bypass");
+
+	if (info->flags & NFQ_FLAG_CPU_FANOUT)
+		printf(" cpu-fanout");
+}
+
 static void NFQUEUE_save(const void *ip, const struct xt_entry_target *target)
 {
 	const struct xt_NFQ_info *tinfo =
@@ -148,11 +233,35 @@
 static void NFQUEUE_save_v2(const void *ip, const struct xt_entry_target *target)
 {
 	const struct xt_NFQ_info_v2 *info = (void *) target->data;
+	unsigned int last = info->queues_total;
 
-	NFQUEUE_save_v1(ip, target);
+	if (last > 1) {
+		last += info->queuenum - 1;
+		printf(" --queue-balance %u:%u", info->queuenum, last);
+	} else
+		printf(" --queue-num %u", info->queuenum);
 
-	if (info->bypass)
-		printf("--queue-bypass ");
+	if (info->bypass & NFQ_FLAG_BYPASS)
+		printf(" --queue-bypass");
+}
+
+static void NFQUEUE_save_v3(const void *ip,
+			    const struct xt_entry_target *target)
+{
+	const struct xt_NFQ_info_v3 *info = (void *)target->data;
+	unsigned int last = info->queues_total;
+
+	if (last > 1) {
+		last += info->queuenum - 1;
+		printf(" --queue-balance %u:%u", info->queuenum, last);
+	} else
+		printf(" --queue-num %u", info->queuenum);
+
+	if (info->flags & NFQ_FLAG_BYPASS)
+		printf(" --queue-bypass");
+
+	if (info->flags & NFQ_FLAG_CPU_FANOUT)
+		printf(" --queue-cpu-fanout");
 }
 
 static void NFQUEUE_init_v1(struct xt_entry_target *t)
@@ -161,6 +270,73 @@
 	tinfo->queues_total = 1;
 }
 
+static int NFQUEUE_xlate(struct xt_xlate *xl,
+			 const struct xt_xlate_tg_params *params)
+{
+	const struct xt_NFQ_info *tinfo =
+		(const struct xt_NFQ_info *)params->target->data;
+
+	xt_xlate_add(xl, "queue num %u ", tinfo->queuenum);
+
+	return 1;
+}
+
+static int NFQUEUE_xlate_v1(struct xt_xlate *xl,
+			    const struct xt_xlate_tg_params *params)
+{
+	const struct xt_NFQ_info_v1 *tinfo = (const void *)params->target->data;
+	unsigned int last = tinfo->queues_total;
+
+	if (last > 1) {
+		last += tinfo->queuenum - 1;
+		xt_xlate_add(xl, "queue num %u-%u ", tinfo->queuenum, last);
+	} else {
+		xt_xlate_add(xl, "queue num %u ", tinfo->queuenum);
+	}
+
+	return 1;
+}
+
+static int NFQUEUE_xlate_v2(struct xt_xlate *xl,
+			    const struct xt_xlate_tg_params *params)
+{
+	const struct xt_NFQ_info_v2 *info = (void *)params->target->data;
+	unsigned int last = info->queues_total;
+
+	if (last > 1) {
+		last += info->queuenum - 1;
+		xt_xlate_add(xl, "queue num %u-%u ", info->queuenum, last);
+	} else
+		xt_xlate_add(xl, "queue num %u ", info->queuenum);
+
+	if (info->bypass & NFQ_FLAG_BYPASS)
+		xt_xlate_add(xl, "bypass");
+
+	return 1;
+}
+
+static int NFQUEUE_xlate_v3(struct xt_xlate *xl,
+			    const struct xt_xlate_tg_params *params)
+{
+	const struct xt_NFQ_info_v3 *info = (void *)params->target->data;
+	unsigned int last = info->queues_total;
+
+	if (last > 1) {
+		last += info->queuenum - 1;
+		xt_xlate_add(xl, "queue num %u-%u ", info->queuenum, last);
+	} else
+		xt_xlate_add(xl, "queue num %u ", info->queuenum);
+
+	if (info->flags & NFQ_FLAG_BYPASS)
+		xt_xlate_add(xl, "bypass");
+
+	if (info->flags & NFQ_FLAG_CPU_FANOUT)
+		xt_xlate_add(xl, "%sfanout ",
+			     info->flags & NFQ_FLAG_BYPASS ? "," : "");
+
+	return 1;
+}
+
 static struct xtables_target nfqueue_targets[] = {
 {
 	.family		= NFPROTO_UNSPEC,
@@ -172,7 +348,8 @@
 	.print		= NFQUEUE_print,
 	.save		= NFQUEUE_save,
 	.x6_parse	= NFQUEUE_parse,
-	.x6_options	= NFQUEUE_opts
+	.x6_options	= NFQUEUE_opts,
+	.xlate		= NFQUEUE_xlate,
 },{
 	.family		= NFPROTO_UNSPEC,
 	.revision	= 1,
@@ -186,6 +363,7 @@
 	.save		= NFQUEUE_save_v1,
 	.x6_parse	= NFQUEUE_parse_v1,
 	.x6_options	= NFQUEUE_opts,
+	.xlate		= NFQUEUE_xlate_v1,
 },{
 	.family		= NFPROTO_UNSPEC,
 	.revision	= 2,
@@ -199,6 +377,21 @@
 	.save		= NFQUEUE_save_v2,
 	.x6_parse	= NFQUEUE_parse_v2,
 	.x6_options	= NFQUEUE_opts,
+	.xlate		= NFQUEUE_xlate_v2,
+},{
+	.family		= NFPROTO_UNSPEC,
+	.revision	= 3,
+	.name		= "NFQUEUE",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct xt_NFQ_info_v3)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_NFQ_info_v3)),
+	.help		= NFQUEUE_help_v3,
+	.init		= NFQUEUE_init_v1,
+	.print		= NFQUEUE_print_v3,
+	.save		= NFQUEUE_save_v3,
+	.x6_parse	= NFQUEUE_parse_v3,
+	.x6_options	= NFQUEUE_opts,
+	.xlate		= NFQUEUE_xlate_v3,
 }
 };
 
diff --git a/extensions/libxt_NFQUEUE.man b/extensions/libxt_NFQUEUE.man
index 910e386..1bfb7b8 100644
--- a/extensions/libxt_NFQUEUE.man
+++ b/extensions/libxt_NFQUEUE.man
@@ -1,11 +1,12 @@
-This target is an extension of the QUEUE target. As opposed to QUEUE, it allows
-you to put a packet into any specific queue, identified by its 16-bit queue
-number.
-It can only be used with Kernel versions 2.6.14 or later, since it requires
-the
+This target passes the packet to userspace using the
+\fBnfnetlink_queue\fP handler.  The packet is put into the queue
+identified by its 16-bit queue number.  Userspace can inspect
+and modify the packet if desired. Userspace must then drop or
+reinject the packet into the kernel.  Please see libnetfilter_queue
+for details.
 .B
 nfnetlink_queue
-kernel support. The \fBqueue-balance\fP option was added in Linux 2.6.31,
+was added in Linux 2.6.14. The \fBqueue-balance\fP option was added in Linux 2.6.31,
 \fBqueue-bypass\fP in 2.6.39.
 .TP
 \fB\-\-queue\-num\fP \fIvalue\fP
@@ -21,5 +22,12 @@
 .TP
 \fB\-\-queue\-bypass\fP
 By default, if no userspace program is listening on an NFQUEUE, then all packets that are to be queued
-are dropped.  When this option is used, the NFQUEUE rule is silently bypassed instead. The packet
-will move on to the next rule.
+are dropped.  When this option is used, the NFQUEUE rule behaves like ACCEPT instead, and the packet
+will move on to the next table.
+.PP
+.TP
+\fB\-\-queue\-cpu-fanout\fP
+Available starting Linux kernel 3.10. When used together with
+\fB--queue-balance\fP this will use the CPU ID as an index to map packets to
+the queues. The idea is that you can improve performance if there's a queue
+per CPU. This requires \fB--queue-balance\fP to be specified.
diff --git a/extensions/libxt_NFQUEUE.t b/extensions/libxt_NFQUEUE.t
new file mode 100644
index 0000000..b51b19f
--- /dev/null
+++ b/extensions/libxt_NFQUEUE.t
@@ -0,0 +1,16 @@
+:INPUT,FORWARD,OUTPUT
+-j NFQUEUE;=;OK
+-j NFQUEUE --queue-num 0;=;OK
+-j NFQUEUE --queue-num 65535;=;OK
+-j NFQUEUE --queue-num 65536;;FAIL
+-j NFQUEUE --queue-num -1;;FAIL
+# it says "NFQUEUE: number of total queues is 0", overflow in NFQUEUE_parse_v1?
+# ERROR: cannot load: iptables -A INPUT -j NFQUEUE --queue-balance 0:65535
+# -j NFQUEUE --queue-balance 0:65535;=;OK
+-j NFQUEUE --queue-balance 0:65536;;FAIL
+-j NFQUEUE --queue-balance -1:65535;;FAIL
+-j NFQUEUE --queue-num 10 --queue-bypass;=;OK
+-j NFQUEUE --queue-balance 0:6 --queue-cpu-fanout --queue-bypass;-j NFQUEUE --queue-balance 0:6 --queue-bypass --queue-cpu-fanout;OK
+-j NFQUEUE --queue-bypass --queue-balance 0:6 --queue-cpu-fanout;-j NFQUEUE --queue-balance 0:6 --queue-bypass --queue-cpu-fanout;OK
+-j NFQUEUE --queue-balance 0:6 --queue-bypass;=;OK
+-j NFQUEUE --queue-bypass;-j NFQUEUE --queue-num 0 --queue-bypass;OK
diff --git a/extensions/libxt_NFQUEUE.txlate b/extensions/libxt_NFQUEUE.txlate
new file mode 100644
index 0000000..3d188a7
--- /dev/null
+++ b/extensions/libxt_NFQUEUE.txlate
@@ -0,0 +1,8 @@
+iptables-translate -t nat -A PREROUTING -p tcp --dport 80 -j NFQUEUE --queue-num 30
+nft add rule ip nat PREROUTING tcp dport 80 counter queue num 30
+
+iptables-translate -A FORWARD -j NFQUEUE --queue-num 0 --queue-bypass -p TCP --sport 80
+nft add rule ip filter FORWARD tcp sport 80 counter queue num 0 bypass
+
+iptables-translate -A FORWARD -j NFQUEUE --queue-bypass -p TCP --sport 80 --queue-balance 0:3 --queue-cpu-fanout
+nft add rule ip filter FORWARD tcp sport 80 counter queue num 0-3 bypass,fanout
diff --git a/extensions/libxt_NOTRACK.c b/extensions/libxt_NOTRACK.c
deleted file mode 100644
index ca58700..0000000
--- a/extensions/libxt_NOTRACK.c
+++ /dev/null
@@ -1,15 +0,0 @@
-/* Shared library add-on to iptables to add NOTRACK target support. */
-#include <xtables.h>
-
-static struct xtables_target notrack_target = {
-	.family		= NFPROTO_UNSPEC,
-	.name		= "NOTRACK",
-	.version	= XTABLES_VERSION,
-	.size		= XT_ALIGN(0),
-	.userspacesize	= XT_ALIGN(0),
-};
-
-void _init(void)
-{
-	xtables_register_target(&notrack_target);
-}
diff --git a/extensions/libxt_NOTRACK.man b/extensions/libxt_NOTRACK.man
index c2cdf5a..4302b93 100644
--- a/extensions/libxt_NOTRACK.man
+++ b/extensions/libxt_NOTRACK.man
@@ -1,5 +1,3 @@
-This target disables connection tracking for all packets matching that rule.
-.PP
-It can only be used in the
-.B raw
-table.
+This extension disables connection tracking for all packets matching that rule.
+It is equivalent with \-j CT \-\-notrack. Like CT, NOTRACK can only be used in
+the \fBraw\fP table.
diff --git a/extensions/libxt_NOTRACK.t b/extensions/libxt_NOTRACK.t
new file mode 100644
index 0000000..27c4734
--- /dev/null
+++ b/extensions/libxt_NOTRACK.t
@@ -0,0 +1,3 @@
+:PREROUTING,OUTPUT
+*raw
+-j NOTRACK;=;OK
diff --git a/extensions/libxt_NOTRACK.txlate b/extensions/libxt_NOTRACK.txlate
new file mode 100644
index 0000000..9d35619
--- /dev/null
+++ b/extensions/libxt_NOTRACK.txlate
@@ -0,0 +1,2 @@
+iptables-translate -A PREROUTING -t raw -j NOTRACK
+nft add rule ip raw PREROUTING counter notrack
diff --git a/extensions/libxt_RATEEST.c b/extensions/libxt_RATEEST.c
index 6369e9e..449ceab 100644
--- a/extensions/libxt_RATEEST.c
+++ b/extensions/libxt_RATEEST.c
@@ -1,19 +1,20 @@
-#include <stdbool.h>
+/*
+ * Copyright (c) 2008-2013 Patrick McHardy <kaber@trash.net>
+ */
+
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
-#include <stddef.h>
-#include <getopt.h>
 #include <math.h>
 
 #include <xtables.h>
 #include <linux/netfilter/x_tables.h>
 #include <linux/netfilter/xt_RATEEST.h>
 
-/* hack to pass raw values to final_check */
-static struct xt_rateest_target_info *RATEEST_info;
-static unsigned int interval;
-static unsigned int ewma_log;
+struct rateest_tg_udata {
+	unsigned int interval;
+	unsigned int ewma_log;
+};
 
 static void
 RATEEST_help(void)
@@ -25,18 +26,23 @@
 "  --rateest-ewmalog value	Rate measurement averaging time constant\n");
 }
 
-enum RATEEST_options {
-	RATEEST_OPT_NAME,
-	RATEEST_OPT_INTERVAL,
-	RATEEST_OPT_EWMALOG,
+enum {
+	O_NAME = 0,
+	O_INTERVAL,
+	O_EWMALOG,
 };
 
-static const struct option RATEEST_opts[] = {
-	{.name = "rateest-name",     .has_arg = true, .val = RATEEST_OPT_NAME},
-	{.name = "rateest-interval", .has_arg = true, .val = RATEEST_OPT_INTERVAL},
-	{.name = "rateest-ewmalog",  .has_arg = true, .val = RATEEST_OPT_EWMALOG},
-	XT_GETOPT_TABLEEND,
+#define s struct xt_rateest_target_info
+static const struct xt_option_entry RATEEST_opts[] = {
+	{.name = "rateest-name", .id = O_NAME, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_PUT, XTOPT_POINTER(s, name)},
+	{.name = "rateest-interval", .id = O_INTERVAL, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND},
+	{.name = "rateest-ewmalog", .id = O_EWMALOG, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND},
+	XTOPT_TABLEEND,
 };
+#undef s
 
 /* Copied from iproute */
 #define TIME_UNITS_PER_SEC	1000000
@@ -82,66 +88,34 @@
 		printf(" %uus", time);
 }
 
-static int
-RATEEST_parse(int c, char **argv, int invert, unsigned int *flags,
-	      const void *entry, struct xt_entry_target **target)
+static void RATEEST_parse(struct xt_option_call *cb)
 {
-	struct xt_rateest_target_info *info = (void *)(*target)->data;
+	struct rateest_tg_udata *udata = cb->udata;
 
-	RATEEST_info = info;
-
-	switch (c) {
-	case RATEEST_OPT_NAME:
-		if (*flags & (1 << c))
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_INTERVAL:
+		if (RATEEST_get_time(&udata->interval, cb->arg) < 0)
 			xtables_error(PARAMETER_PROBLEM,
-				   "RATEEST: can't specify --rateest-name twice");
-		*flags |= 1 << c;
-
-		strncpy(info->name, optarg, sizeof(info->name) - 1);
+				   "RATEEST: bad interval value \"%s\"",
+				   cb->arg);
 		break;
-
-	case RATEEST_OPT_INTERVAL:
-		if (*flags & (1 << c))
+	case O_EWMALOG:
+		if (RATEEST_get_time(&udata->ewma_log, cb->arg) < 0)
 			xtables_error(PARAMETER_PROBLEM,
-				   "RATEEST: can't specify --rateest-interval twice");
-		*flags |= 1 << c;
-
-		if (RATEEST_get_time(&interval, optarg) < 0)
-			xtables_error(PARAMETER_PROBLEM,
-				   "RATEEST: bad interval value `%s'", optarg);
-
-		break;
-
-	case RATEEST_OPT_EWMALOG:
-		if (*flags & (1 << c))
-			xtables_error(PARAMETER_PROBLEM,
-				   "RATEEST: can't specify --rateest-ewmalog twice");
-		*flags |= 1 << c;
-
-		if (RATEEST_get_time(&ewma_log, optarg) < 0)
-			xtables_error(PARAMETER_PROBLEM,
-				   "RATEEST: bad ewmalog value `%s'", optarg);
-
+				   "RATEEST: bad ewmalog value \"%s\"",
+				   cb->arg);
 		break;
 	}
-
-	return 1;
 }
 
-static void
-RATEEST_final_check(unsigned int flags)
+static void RATEEST_final_check(struct xt_fcheck_call *cb)
 {
-	struct xt_rateest_target_info *info = RATEEST_info;
-
-	if (!(flags & (1 << RATEEST_OPT_NAME)))
-		xtables_error(PARAMETER_PROBLEM, "RATEEST: no name specified");
-	if (!(flags & (1 << RATEEST_OPT_INTERVAL)))
-		xtables_error(PARAMETER_PROBLEM, "RATEEST: no interval specified");
-	if (!(flags & (1 << RATEEST_OPT_EWMALOG)))
-		xtables_error(PARAMETER_PROBLEM, "RATEEST: no ewmalog specified");
+	struct xt_rateest_target_info *info = cb->data;
+	struct rateest_tg_udata *udata = cb->udata;
 
 	for (info->interval = 0; info->interval <= 5; info->interval++) {
-		if (interval <= (1 << info->interval) * (TIME_UNITS_PER_SEC / 4))
+		if (udata->interval <= (1 << info->interval) * (TIME_UNITS_PER_SEC / 4))
 			break;
 	}
 
@@ -152,7 +126,7 @@
 
 	for (info->ewma_log = 1; info->ewma_log < 32; info->ewma_log++) {
 		double w = 1.0 - 1.0 / (1 << info->ewma_log);
-		if (interval / (-log(w)) > ewma_log)
+		if (udata->interval / (-log(w)) > udata->ewma_log)
 			break;
 	}
 	info->ewma_log--;
@@ -197,13 +171,14 @@
 	.name		= "RATEEST",
 	.version	= XTABLES_VERSION,
 	.size		= XT_ALIGN(sizeof(struct xt_rateest_target_info)),
-	.userspacesize	= XT_ALIGN(sizeof(struct xt_rateest_target_info)),
+	.userspacesize	= offsetof(struct xt_rateest_target_info, est),
 	.help		= RATEEST_help,
-	.parse		= RATEEST_parse,
-	.final_check	= RATEEST_final_check,
+	.x6_parse	= RATEEST_parse,
+	.x6_fcheck	= RATEEST_final_check,
 	.print		= RATEEST_print,
 	.save		= RATEEST_save,
-	.extra_opts	= RATEEST_opts,
+	.x6_options	= RATEEST_opts,
+	.udata_size	= sizeof(struct rateest_tg_udata),
 };
 
 void _init(void)
diff --git a/extensions/libxt_RATEEST.t b/extensions/libxt_RATEEST.t
new file mode 100644
index 0000000..c2b6bb3
--- /dev/null
+++ b/extensions/libxt_RATEEST.t
@@ -0,0 +1,2 @@
+:INPUT,FORWARD,OUTPUT
+-j RATEEST --rateest-name RE1 --rateest-interval 250.0ms --rateest-ewmalog 500.0ms;=;OK
diff --git a/extensions/libxt_REDIRECT.man b/extensions/libxt_REDIRECT.man
new file mode 100644
index 0000000..28d4d10
--- /dev/null
+++ b/extensions/libxt_REDIRECT.man
@@ -0,0 +1,25 @@
+This target is only valid in the
+.B nat
+table, in the
+.B PREROUTING
+and
+.B OUTPUT
+chains, and user-defined chains which are only called from those
+chains.  It redirects the packet to the machine itself by changing the
+destination IP to the primary address of the incoming interface
+(locally-generated packets are mapped to the localhost address,
+127.0.0.1 for IPv4 and ::1 for IPv6, and packets arriving on
+interfaces that don't have an IP address configured are dropped).
+.TP
+\fB\-\-to\-ports\fP \fIport\fP[\fB\-\fP\fIport\fP]
+This specifies a destination port or range of ports to use: without
+this, the destination port is never altered.  This is only valid
+if the rule also specifies one of the following protocols:
+\fBtcp\fP, \fBudp\fP, \fBdccp\fP or \fBsctp\fP.
+.TP
+\fB\-\-random\fP
+If option
+\fB\-\-random\fP
+is used then port mapping will be randomized (kernel >= 2.6.22).
+.TP
+IPv6 support available starting Linux kernels >= 3.7.
diff --git a/extensions/libxt_SET.c b/extensions/libxt_SET.c
index 51c0cec..2a7640a 100644
--- a/extensions/libxt_SET.c
+++ b/extensions/libxt_SET.c
@@ -5,7 +5,7 @@
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.  
+ * published by the Free Software Foundation.
  */
 
 /* Shared library add-on to iptables to add IP set mangling target. */
@@ -67,10 +67,6 @@
 		xtables_error(PARAMETER_PROBLEM,
 			      "--%s can be specified only once", what);
 
-	if (xtables_check_inverse(optarg, &invert, NULL, 0, argv))
-		xtables_error(PARAMETER_PROBLEM,
-			      "Unexpected `!' after --%s", what);
-
 	if (!argv[optind]
 	    || argv[optind][0] == '-' || argv[optind][0] == '!')
 		xtables_error(PARAMETER_PROBLEM,
@@ -84,7 +80,7 @@
 	get_set_byname(optarg, (struct xt_set_info *)info);
 	parse_dirs_v0(argv[optind], info);
 	optind++;
-	
+
 	*flags = 1;
 }
 
@@ -120,7 +116,7 @@
 	printf(" %s %s", prefix, setname);
 	for (i = 0; i < IPSET_DIM_MAX; i++) {
 		if (!info->u.flags[i])
-			break;		
+			break;
 		printf("%s%s",
 		       i == 0 ? " " : ",",
 		       info->u.flags[i] & IPSET_SRC ? "src" : "dst");
@@ -129,7 +125,7 @@
 
 static void
 set_target_print_v0(const void *ip, const struct xt_entry_target *target,
-                    int numeric)
+		    int numeric)
 {
 	const struct xt_set_info_target_v0 *info = (const void *)target->data;
 
@@ -147,9 +143,6 @@
 }
 
 /* Revision 1 */
-
-#define set_target_help_v1	set_target_help_v0
-
 static void
 set_target_init_v1(struct xt_entry_target *target)
 {
@@ -165,6 +158,10 @@
 #define SET_TARGET_DEL		0x2
 #define SET_TARGET_EXIST	0x4
 #define SET_TARGET_TIMEOUT	0x8
+#define SET_TARGET_MAP		0x10
+#define SET_TARGET_MAP_MARK	0x20
+#define SET_TARGET_MAP_PRIO	0x40
+#define SET_TARGET_MAP_QUEUE	0x80
 
 static void
 parse_target(char **argv, int invert, struct xt_set_info *info,
@@ -173,11 +170,6 @@
 	if (info->dim)
 		xtables_error(PARAMETER_PROBLEM,
 			      "--%s can be specified only once", what);
-
-	if (xtables_check_inverse(optarg, &invert, NULL, 0, argv))
-		xtables_error(PARAMETER_PROBLEM,
-			      "Unexpected `!' after --%s", what);
-
 	if (!argv[optind]
 	    || argv[optind][0] == '-' || argv[optind][0] == '!')
 		xtables_error(PARAMETER_PROBLEM,
@@ -213,8 +205,6 @@
 	return 1;
 }
 
-#define set_target_check_v1	set_target_check_v0
-
 static void
 print_target(const char *prefix, const struct xt_set_info *info)
 {
@@ -251,8 +241,6 @@
 	print_target("--del-set", &info->del_set);
 }
 
-#define set_target_opts_v1	set_target_opts_v0
-
 /* Revision 2 */
 
 static void
@@ -330,7 +318,7 @@
 				      "or out of range 0-%u", UINT32_MAX - 1);
 		myinfo->timeout = timeout;
 		*flags |= SET_TARGET_TIMEOUT;
-		break;	
+		break;
 	}
 	return 1;
 }
@@ -362,6 +350,170 @@
 	print_target("--del-set", &info->del_set);
 }
 
+
+/* Revision 3 */
+
+static void
+set_target_help_v3(void)
+{
+	printf("SET target options:\n"
+	       " --add-set name flags [--exist] [--timeout n]\n"
+	       " --del-set name flags\n"
+	       " --map-set name flags"
+	       " [--map-mark] [--map-prio] [--map-queue]\n"
+	       "		add/del src/dst IP/port from/to named sets,\n"
+	       "		where flags are the comma separated list of\n"
+	       "		'src' and 'dst' specifications.\n");
+}
+
+static const struct option set_target_opts_v3[] = {
+	{.name = "add-set",	.has_arg = true,  .val = '1'},
+	{.name = "del-set",	.has_arg = true,  .val = '2'},
+	{.name = "exist",	.has_arg = false, .val = '3'},
+	{.name = "timeout",	.has_arg = true,  .val = '4'},
+	{.name = "map-set",	.has_arg = true,  .val = '5'},
+	{.name = "map-mark",	.has_arg = false, .val = '6'},
+	{.name = "map-prio",	.has_arg = false, .val = '7'},
+	{.name = "map-queue",	.has_arg = false, .val = '8'},
+	XT_GETOPT_TABLEEND,
+};
+
+static void
+set_target_check_v3(unsigned int flags)
+{
+	if (!(flags & (SET_TARGET_ADD|SET_TARGET_DEL|SET_TARGET_MAP)))
+		xtables_error(PARAMETER_PROBLEM,
+			      "You must specify either `--add-set' or "
+			      "`--del-set' or `--map-set'");
+	if (!(flags & SET_TARGET_ADD)) {
+		if (flags & SET_TARGET_EXIST)
+			xtables_error(PARAMETER_PROBLEM,
+				"Flag `--exist' can be used with `--add-set' only");
+		if (flags & SET_TARGET_TIMEOUT)
+			xtables_error(PARAMETER_PROBLEM,
+				"Option `--timeout' can be used with `--add-set' only");
+	}
+	if (!(flags & SET_TARGET_MAP)) {
+		if (flags & SET_TARGET_MAP_MARK)
+			xtables_error(PARAMETER_PROBLEM,
+				"Flag `--map-mark' can be used with `--map-set' only");
+		if (flags & SET_TARGET_MAP_PRIO)
+			xtables_error(PARAMETER_PROBLEM,
+				"Flag `--map-prio' can be used with `--map-set' only");
+		if (flags & SET_TARGET_MAP_QUEUE)
+			xtables_error(PARAMETER_PROBLEM,
+				"Flag `--map-queue' can be used with `--map-set' only");
+	}
+	if ((flags & SET_TARGET_MAP) && !(flags & (SET_TARGET_MAP_MARK |
+						   SET_TARGET_MAP_PRIO |
+						   SET_TARGET_MAP_QUEUE)))
+		xtables_error(PARAMETER_PROBLEM,
+			"You must specify flags `--map-mark' or "
+			"'--map-prio` or `--map-queue'");
+}
+
+static void
+set_target_init_v3(struct xt_entry_target *target)
+{
+	struct xt_set_info_target_v3 *info =
+		(struct xt_set_info_target_v3 *) target->data;
+
+	info->add_set.index =
+	info->del_set.index =
+	info->map_set.index = IPSET_INVALID_ID;
+	info->timeout = UINT32_MAX;
+}
+
+static int
+set_target_parse_v3(int c, char **argv, int invert, unsigned int *flags,
+		    const void *entry, struct xt_entry_target **target)
+{
+	struct xt_set_info_target_v3 *myinfo =
+		(struct xt_set_info_target_v3 *) (*target)->data;
+	unsigned int timeout;
+
+	switch (c) {
+	case '1':		/* --add-set <set> <flags> */
+		parse_target(argv, invert, &myinfo->add_set, "add-set");
+		*flags |= SET_TARGET_ADD;
+		break;
+	case '2':		/* --del-set <set>[:<flags>] <flags> */
+		parse_target(argv, invert, &myinfo->del_set, "del-set");
+		*flags |= SET_TARGET_DEL;
+		break;
+	case '3':
+		myinfo->flags |= IPSET_FLAG_EXIST;
+		*flags |= SET_TARGET_EXIST;
+		break;
+	case '4':
+		if (!xtables_strtoui(optarg, NULL, &timeout, 0, UINT32_MAX - 1))
+			xtables_error(PARAMETER_PROBLEM,
+				      "Invalid value for option --timeout "
+				      "or out of range 0-%u", UINT32_MAX - 1);
+		myinfo->timeout = timeout;
+		*flags |= SET_TARGET_TIMEOUT;
+		break;
+	case '5':		/* --map-set <set> <flags> */
+		parse_target(argv, invert, &myinfo->map_set, "map-set");
+		*flags |= SET_TARGET_MAP;
+		break;
+	case '6':
+		myinfo->flags |= IPSET_FLAG_MAP_SKBMARK;
+		*flags |= SET_TARGET_MAP_MARK;
+		break;
+	case '7':
+		myinfo->flags |= IPSET_FLAG_MAP_SKBPRIO;
+		*flags |= SET_TARGET_MAP_PRIO;
+		break;
+	case '8':
+		myinfo->flags |= IPSET_FLAG_MAP_SKBQUEUE;
+		*flags |= SET_TARGET_MAP_QUEUE;
+		break;
+	}
+	return 1;
+}
+
+static void
+set_target_print_v3(const void *ip, const struct xt_entry_target *target,
+		    int numeric)
+{
+	const struct xt_set_info_target_v3 *info = (const void *)target->data;
+
+	print_target("add-set", &info->add_set);
+	if (info->flags & IPSET_FLAG_EXIST)
+		printf(" exist");
+	if (info->timeout != UINT32_MAX)
+		printf(" timeout %u", info->timeout);
+	print_target("del-set", &info->del_set);
+	print_target("map-set", &info->map_set);
+	if (info->flags & IPSET_FLAG_MAP_SKBMARK)
+		printf(" map-mark");
+	if (info->flags & IPSET_FLAG_MAP_SKBPRIO)
+		printf(" map-prio");
+	if (info->flags & IPSET_FLAG_MAP_SKBQUEUE)
+		printf(" map-queue");
+}
+
+static void
+set_target_save_v3(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_set_info_target_v3 *info = (const void *)target->data;
+
+	print_target("--add-set", &info->add_set);
+	if (info->flags & IPSET_FLAG_EXIST)
+		printf(" --exist");
+	if (info->timeout != UINT32_MAX)
+		printf(" --timeout %u", info->timeout);
+	print_target("--del-set", &info->del_set);
+	print_target("--map-set", &info->map_set);
+	if (info->flags & IPSET_FLAG_MAP_SKBMARK)
+		printf(" --map-mark");
+	if (info->flags & IPSET_FLAG_MAP_SKBPRIO)
+		printf(" --map-prio");
+	if (info->flags & IPSET_FLAG_MAP_SKBQUEUE)
+		printf(" --map-queue");
+}
+
 static struct xtables_target set_tg_reg[] = {
 	{
 		.name		= "SET",
@@ -385,13 +537,13 @@
 		.family		= NFPROTO_UNSPEC,
 		.size		= XT_ALIGN(sizeof(struct xt_set_info_target_v1)),
 		.userspacesize	= XT_ALIGN(sizeof(struct xt_set_info_target_v1)),
-		.help		= set_target_help_v1,
+		.help		= set_target_help_v0,
 		.init		= set_target_init_v1,
 		.parse		= set_target_parse_v1,
-		.final_check	= set_target_check_v1,
+		.final_check	= set_target_check_v0,
 		.print		= set_target_print_v1,
 		.save		= set_target_save_v1,
-		.extra_opts	= set_target_opts_v1,
+		.extra_opts	= set_target_opts_v0,
 	},
 	{
 		.name		= "SET",
@@ -408,6 +560,21 @@
 		.save		= set_target_save_v2,
 		.extra_opts	= set_target_opts_v2,
 	},
+	{
+		.name		= "SET",
+		.revision	= 3,
+		.version	= XTABLES_VERSION,
+		.family		= NFPROTO_UNSPEC,
+		.size		= XT_ALIGN(sizeof(struct xt_set_info_target_v3)),
+		.userspacesize	= XT_ALIGN(sizeof(struct xt_set_info_target_v3)),
+		.help		= set_target_help_v3,
+		.init		= set_target_init_v3,
+		.parse		= set_target_parse_v3,
+		.final_check	= set_target_check_v3,
+		.print		= set_target_print_v3,
+		.save		= set_target_save_v3,
+		.extra_opts	= set_target_opts_v3,
+	},
 };
 
 void _init(void)
diff --git a/extensions/libxt_SET.man b/extensions/libxt_SET.man
index 739be41..c471337 100644
--- a/extensions/libxt_SET.man
+++ b/extensions/libxt_SET.man
@@ -1,26 +1,46 @@
-This modules adds and/or deletes entries from IP sets which can be defined 
+This module adds and/or deletes entries from IP sets which can be defined
 by ipset(8).
 .TP
 \fB\-\-add\-set\fP \fIsetname\fP \fIflag\fP[\fB,\fP\fIflag\fP...]
-add the address(es)/port(s) of the packet to the sets
+add the address(es)/port(s) of the packet to the set
 .TP
 \fB\-\-del\-set\fP \fIsetname\fP \fIflag\fP[\fB,\fP\fIflag\fP...]
-delete the address(es)/port(s) of the packet from the sets
+delete the address(es)/port(s) of the packet from the set
+.TP
+\fB\-\-map\-set\fP \fIsetname\fP \fIflag\fP[\fB,\fP\fIflag\fP...] 
+[\-\-map\-mark] [\-\-map\-prio] [\-\-map\-queue]
+map packet properties (firewall mark, tc priority, hardware queue)
 .IP
-where flags are
+where \fIflag\fP(s) are
 .BR "src"
 and/or
 .BR "dst"
 specifications and there can be no more than six of them.
 .TP
 \fB\-\-timeout\fP \fIvalue\fP
-when adding entry, the timeout value to use instead of the default
+when adding an entry, the timeout value to use instead of the default
 one from the set definition
 .TP
 \fB\-\-exist\fP
-when adding entry if it already exists, reset the timeout value
+when adding an entry if it already exists, reset the timeout value
 to the specified one or to the default from the set definition
+.TP
+\fB\-\-map\-set\fP \fIset\-name\fP
+the set-name should be created with --skbinfo option
+\fB\-\-map\-mark\fP
+map firewall mark to packet by lookup of value in the set
+\fB\-\-map\-prio\fP
+map traffic control priority to packet by lookup of value in the set
+\fB\-\-map\-queue\fP
+map hardware NIC queue to packet by lookup of value in the set
+.IP
+The
+\fB\-\-map\-set\fP
+option can be used from the mangle table only. The
+\fB\-\-map\-prio\fP
+and
+\fB\-\-map\-queue\fP
+flags can be used in the OUTPUT, FORWARD and POSTROUTING chains.
 .PP
-Use of -j SET requires that ipset kernel support is provided. As standard
-kernels do not ship this currently, the ipset or Xtables-addons package needs
-to be installed.
+Use of \-j SET requires that ipset kernel support is provided, which, for
+standard kernels, is the case since Linux 2.6.39.
diff --git a/extensions/libxt_SET.t b/extensions/libxt_SET.t
new file mode 100644
index 0000000..30c27ca
--- /dev/null
+++ b/extensions/libxt_SET.t
@@ -0,0 +1,3 @@
+:INPUT,FORWARD,OUTPUT
+# fails: foo does not exist
+-j SET --add-set foo src,dst;;FAIL
diff --git a/extensions/libxt_SNAT.man b/extensions/libxt_SNAT.man
new file mode 100644
index 0000000..8cd0b80
--- /dev/null
+++ b/extensions/libxt_SNAT.man
@@ -0,0 +1,50 @@
+This target is only valid in the
+.B nat
+table, in the
+.B POSTROUTING
+and
+.B INPUT
+chains, and user-defined chains which are only called from those
+chains.  It specifies that the source address of the packet should be
+modified (and all future packets in this connection will also be
+mangled), and rules should cease being examined.  It takes the
+following options:
+.TP
+\fB\-\-to\-source\fP [\fIipaddr\fP[\fB\-\fP\fIipaddr\fP]][\fB:\fP\fIport\fP[\fB\-\fP\fIport\fP]]
+which can specify a single new source IP address, an inclusive range
+of IP addresses. Optionally a port range,
+if the rule also specifies one of the following protocols:
+\fBtcp\fP, \fBudp\fP, \fBdccp\fP or \fBsctp\fP.
+If no port range is specified, then source ports below 512 will be
+mapped to other ports below 512: those between 512 and 1023 inclusive
+will be mapped to ports below 1024, and other ports will be mapped to
+1024 or above. Where possible, no port alteration will occur.
+In Kernels up to 2.6.10, you can add several \-\-to\-source options. For those
+kernels, if you specify more than one source address, either via an address
+range or multiple \-\-to\-source options, a simple round-robin (one after another
+in cycle) takes place between these addresses.
+Later Kernels (>= 2.6.11-rc1) don't have the ability to NAT to multiple ranges
+anymore.
+.TP
+\fB\-\-random\fP
+If option
+\fB\-\-random\fP
+is used then port mapping will be randomized through a hash-based algorithm (kernel >= 2.6.21).
+.TP
+\fB\-\-random-fully\fP
+If option
+\fB\-\-random-fully\fP
+is used then port mapping will be fully randomized through a PRNG (kernel >= 3.14).
+.TP
+\fB\-\-persistent\fP
+Gives a client the same source-/destination-address for each connection.
+This supersedes the SAME target. Support for persistent mappings is available
+from 2.6.29-rc2.
+.PP
+Kernels prior to 2.6.36-rc1 don't have the ability to
+.B SNAT
+in the
+.B INPUT
+chain.
+.TP
+IPv6 support available since Linux kernels >= 3.7.
diff --git a/extensions/libxt_SYNPROXY.c b/extensions/libxt_SYNPROXY.c
new file mode 100644
index 0000000..6a0b913
--- /dev/null
+++ b/extensions/libxt_SYNPROXY.c
@@ -0,0 +1,150 @@
+
+/*
+ * Copyright (c) 2013 Patrick McHardy <kaber@trash.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_SYNPROXY.h>
+
+enum {
+	O_SACK_PERM = 0,
+	O_TIMESTAMP,
+	O_WSCALE,
+	O_MSS,
+	O_ECN,
+};
+
+static void SYNPROXY_help(void)
+{
+	printf(
+"SYNPROXY target options:\n"
+"  --sack-perm                        Set SACK_PERM\n"
+"  --timestamp                        Set TIMESTAMP\n"
+"  --wscale value                     Set window scaling factor\n"
+"  --mss value                        Set MSS value\n"
+"  --ecn                              Set ECN\n");
+}
+
+static const struct xt_option_entry SYNPROXY_opts[] = {
+	{.name = "sack-perm", .id = O_SACK_PERM, .type = XTTYPE_NONE, },
+	{.name = "timestamp", .id = O_TIMESTAMP, .type = XTTYPE_NONE, },
+	{.name = "wscale",    .id = O_WSCALE,    .type = XTTYPE_UINT32, },
+	{.name = "mss",       .id = O_MSS,       .type = XTTYPE_UINT32, },
+	{.name = "ecn",       .id = O_ECN,	 .type = XTTYPE_NONE, },
+	XTOPT_TABLEEND,
+};
+
+static void SYNPROXY_parse(struct xt_option_call *cb)
+{
+	struct xt_synproxy_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SACK_PERM:
+		info->options |= XT_SYNPROXY_OPT_SACK_PERM;
+		break;
+	case O_TIMESTAMP:
+		info->options |= XT_SYNPROXY_OPT_TIMESTAMP;
+		break;
+	case O_WSCALE:
+		info->options |= XT_SYNPROXY_OPT_WSCALE;
+		info->wscale = cb->val.u32;
+		break;
+	case O_MSS:
+		info->options |= XT_SYNPROXY_OPT_MSS;
+		info->mss = cb->val.u32;
+		break;
+	case O_ECN:
+		info->options |= XT_SYNPROXY_OPT_ECN;
+		break;
+	}
+}
+
+static void SYNPROXY_check(struct xt_fcheck_call *cb)
+{
+}
+
+static void SYNPROXY_print(const void *ip, const struct xt_entry_target *target,
+                           int numeric)
+{
+	const struct xt_synproxy_info *info =
+		(const struct xt_synproxy_info *)target->data;
+
+	printf(" SYNPROXY ");
+	if (info->options & XT_SYNPROXY_OPT_SACK_PERM)
+		printf("sack-perm ");
+	if (info->options & XT_SYNPROXY_OPT_TIMESTAMP)
+		printf("timestamp ");
+	if (info->options & XT_SYNPROXY_OPT_WSCALE)
+		printf("wscale %u ", info->wscale);
+	if (info->options & XT_SYNPROXY_OPT_MSS)
+		printf("mss %u ", info->mss);
+	if (info->options & XT_SYNPROXY_OPT_ECN)
+		printf("ecn ");
+}
+
+static void SYNPROXY_save(const void *ip, const struct xt_entry_target *target)
+{
+	const struct xt_synproxy_info *info =
+		(const struct xt_synproxy_info *)target->data;
+
+	if (info->options & XT_SYNPROXY_OPT_SACK_PERM)
+		printf(" --sack-perm");
+	if (info->options & XT_SYNPROXY_OPT_TIMESTAMP)
+		printf(" --timestamp");
+	if (info->options & XT_SYNPROXY_OPT_WSCALE)
+		printf(" --wscale %u", info->wscale);
+	if (info->options & XT_SYNPROXY_OPT_MSS)
+		printf(" --mss %u", info->mss);
+	if (info->options & XT_SYNPROXY_OPT_ECN)
+		printf(" --ecn");
+}
+
+static int SYNPROXY_xlate(struct xt_xlate *xl,
+		          const struct xt_xlate_tg_params *params)
+{
+	const struct xt_synproxy_info *info =
+		(const struct xt_synproxy_info *)params->target->data;
+
+	xt_xlate_add(xl, "synproxy ");
+
+	if (info->options & XT_SYNPROXY_OPT_SACK_PERM)
+		xt_xlate_add(xl, "sack-perm ");
+	if (info->options & XT_SYNPROXY_OPT_TIMESTAMP)
+		xt_xlate_add(xl, "timestamp ");
+	if (info->options & XT_SYNPROXY_OPT_WSCALE)
+		xt_xlate_add(xl, "wscale %u ", info->wscale);
+	if (info->options & XT_SYNPROXY_OPT_MSS)
+		xt_xlate_add(xl, "mss %u ", info->mss);
+	if (info->options & XT_SYNPROXY_OPT_ECN)
+		xt_xlate_add(xl, "ecn ");
+
+	return 1;
+}
+
+static struct xtables_target synproxy_tg_reg = {
+	.family        = NFPROTO_UNSPEC,
+	.name          = "SYNPROXY",
+	.version       = XTABLES_VERSION,
+	.revision      = 0,
+	.size          = XT_ALIGN(sizeof(struct xt_synproxy_info)),
+	.userspacesize = XT_ALIGN(sizeof(struct xt_synproxy_info)),
+	.help          = SYNPROXY_help,
+	.print         = SYNPROXY_print,
+	.save          = SYNPROXY_save,
+	.x6_parse      = SYNPROXY_parse,
+	.x6_fcheck     = SYNPROXY_check,
+	.x6_options    = SYNPROXY_opts,
+	.xlate         = SYNPROXY_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_target(&synproxy_tg_reg);
+}
diff --git a/extensions/libxt_SYNPROXY.man b/extensions/libxt_SYNPROXY.man
new file mode 100644
index 0000000..30a71ed
--- /dev/null
+++ b/extensions/libxt_SYNPROXY.man
@@ -0,0 +1,66 @@
+This target will process TCP three-way-handshake parallel in netfilter
+context to protect either local or backend system. This target requires
+connection tracking because sequence numbers need to be translated.
+The kernels ability to absorb SYNFLOOD was greatly improved starting with
+Linux 4.4, so this target should not be needed anymore to protect Linux servers.
+.TP
+\fB\-\-mss\fP \fImaximum segment size\fP
+Maximum segment size announced to clients. This must match the backend.
+.TP
+\fB\-\-wscale\fP \fIwindow scale\fP
+Window scale announced to clients. This must match the backend.
+.TP
+\fB\-\-sack\-perm\fP
+Pass client selective acknowledgement option to backend (will be disabled
+if not present).
+.TP
+\fB\-\-timestamps\fP
+Pass client timestamp option to backend (will be disabled if not present,
+also needed for selective acknowledgement and window scaling).
+.PP
+Example:
+.PP
+Determine tcp options used by backend, from an external system
+.IP
+tcpdump -pni eth0 -c 1 'tcp[tcpflags] == (tcp-syn|tcp-ack)'
+.br
+    port 80 &
+.br
+telnet 192.0.2.42 80
+.br
+18:57:24.693307 IP 192.0.2.42.80 > 192.0.2.43.48757:
+.br
+    Flags [S.], seq 360414582, ack 788841994, win 14480,
+.br
+    options [mss 1460,sackOK,
+.br
+    TS val 1409056151 ecr 9690221,
+.br
+    nop,wscale 9],
+.br
+    length 0
+.PP
+Switch tcp_loose mode off, so conntrack will mark out\-of\-flow
+packets as state INVALID.
+.IP
+echo 0 > /proc/sys/net/netfilter/nf_conntrack_tcp_loose
+.PP
+Make SYN packets untracked
+.IP
+iptables \-t raw \-A PREROUTING \-i eth0 \-p tcp \-\-dport 80
+    \-\-syn \-j CT \-\-notrack
+.PP
+Catch UNTRACKED (SYN packets) and INVALID (3WHS ACK packets) states
+and send them to SYNPROXY. This rule will respond to SYN packets with
+SYN+ACK syncookies, create ESTABLISHED for valid client response (3WHS ACK
+packets) and drop incorrect cookies. Flags combinations not expected
+during 3WHS will not match and continue (e.g. SYN+FIN, SYN+ACK).
+.IP
+iptables \-A INPUT \-i eth0 \-p tcp \-\-dport 80
+    \-m state \-\-state UNTRACKED,INVALID \-j SYNPROXY
+    \-\-sack\-perm \-\-timestamp \-\-mss 1460 \-\-wscale 9
+.PP
+Drop invalid packets, this will be out\-of\-flow packets that were not
+matched by SYNPROXY.
+.IP
+iptables \-A INPUT \-i eth0 \-p tcp \-\-dport 80 \-m state \-\-state INVALID \-j DROP
diff --git a/extensions/libxt_SYNPROXY.t b/extensions/libxt_SYNPROXY.t
new file mode 100644
index 0000000..dd8b0e7
--- /dev/null
+++ b/extensions/libxt_SYNPROXY.t
@@ -0,0 +1,3 @@
+:INPUT,FORWARD
+-j SYNPROXY --sack-perm --timestamp --mss 1460 --wscale 9;;FAIL
+-p tcp -m tcp --dport 42 -m conntrack --ctstate INVALID,UNTRACKED -j SYNPROXY --sack-perm --timestamp --wscale 9 --mss 1460;=;OK
diff --git a/extensions/libxt_SYNPROXY.txlate b/extensions/libxt_SYNPROXY.txlate
new file mode 100644
index 0000000..b3de2b2
--- /dev/null
+++ b/extensions/libxt_SYNPROXY.txlate
@@ -0,0 +1,2 @@
+iptables-translate -t mangle -A INPUT -i iifname -p tcp -m tcp --dport 80 -m state --state INVALID,UNTRACKED -j SYNPROXY --sack-perm --timestamp --wscale 7 --mss 1460
+nft add rule ip mangle INPUT iifname "iifname" tcp dport 80 ct state invalid,untracked  counter synproxy sack-perm timestamp wscale 7 mss 1460
diff --git a/extensions/libxt_TCPMSS.c b/extensions/libxt_TCPMSS.c
index 2266326..0d9b200 100644
--- a/extensions/libxt_TCPMSS.c
+++ b/extensions/libxt_TCPMSS.c
@@ -2,10 +2,10 @@
  *
  * Copyright (c) 2000 Marc Boucher
 */
+#include "config.h"
 #include <stdio.h>
 #include <xtables.h>
 #include <netinet/ip.h>
-#include <netinet/ip6.h>
 #include <linux/netfilter/xt_TCPMSS.h>
 
 enum {
@@ -34,7 +34,7 @@
 
 static void TCPMSS_help6(void)
 {
-	__TCPMSS_help(sizeof(struct ip6_hdr));
+	__TCPMSS_help(SIZEOF_STRUCT_IP6_HDR);
 }
 
 static const struct xt_option_entry TCPMSS4_opts[] = {
@@ -47,7 +47,7 @@
 
 static const struct xt_option_entry TCPMSS6_opts[] = {
 	{.name = "set-mss", .id = O_SET_MSS, .type = XTTYPE_UINT16,
-	 .min = 0, .max = UINT16_MAX - sizeof(struct ip6_hdr),
+	 .min = 0, .max = UINT16_MAX - SIZEOF_STRUCT_IP6_HDR,
 	 .flags = XTOPT_PUT, XTOPT_POINTER(struct xt_tcpmss_info, mss)},
 	{.name = "clamp-mss-to-pmtu", .id = O_CLAMP_MSS, .type = XTTYPE_NONE},
 	XTOPT_TABLEEND,
@@ -91,36 +91,50 @@
 		printf(" --set-mss %u", mssinfo->mss);
 }
 
-static struct xtables_target tcpmss_target = {
-	.family		= NFPROTO_IPV4,
-	.name		= "TCPMSS",
-	.version	= XTABLES_VERSION,
-	.size		= XT_ALIGN(sizeof(struct xt_tcpmss_info)),
-	.userspacesize	= XT_ALIGN(sizeof(struct xt_tcpmss_info)),
-	.help		= TCPMSS_help,
-	.print		= TCPMSS_print,
-	.save		= TCPMSS_save,
-	.x6_parse	= TCPMSS_parse,
-	.x6_fcheck	= TCPMSS_check,
-	.x6_options	= TCPMSS4_opts,
-};
+static int TCPMSS_xlate(struct xt_xlate *xl,
+			const struct xt_xlate_tg_params *params)
+{
+	const struct xt_tcpmss_info *mssinfo =
+		(const struct xt_tcpmss_info *)params->target->data;
+	if (mssinfo->mss == XT_TCPMSS_CLAMP_PMTU)
+		xt_xlate_add(xl, "tcp option maxseg size set rt mtu");
+	else
+		xt_xlate_add(xl, "tcp option maxseg size set %d", mssinfo->mss);
 
-static struct xtables_target tcpmss_target6 = {
-	.family		= NFPROTO_IPV6,
-	.name		= "TCPMSS",
-	.version	= XTABLES_VERSION,
-	.size		= XT_ALIGN(sizeof(struct xt_tcpmss_info)),
-	.userspacesize	= XT_ALIGN(sizeof(struct xt_tcpmss_info)),
-	.help		= TCPMSS_help6,
-	.print		= TCPMSS_print,
-	.save		= TCPMSS_save,
-	.x6_parse	= TCPMSS_parse,
-	.x6_fcheck	= TCPMSS_check,
-	.x6_options	= TCPMSS6_opts,
+	return 1;
+}
+
+static struct xtables_target tcpmss_tg_reg[] = {
+	{
+		.family        = NFPROTO_IPV4,
+		.name          = "TCPMSS",
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_tcpmss_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_tcpmss_info)),
+		.help          = TCPMSS_help,
+		.print         = TCPMSS_print,
+		.save          = TCPMSS_save,
+		.x6_parse      = TCPMSS_parse,
+		.x6_fcheck     = TCPMSS_check,
+		.x6_options    = TCPMSS4_opts,
+		.xlate         = TCPMSS_xlate,
+	},
+	{
+		.family        = NFPROTO_IPV6,
+		.name          = "TCPMSS",
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_tcpmss_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_tcpmss_info)),
+		.help          = TCPMSS_help6,
+		.print         = TCPMSS_print,
+		.save          = TCPMSS_save,
+		.x6_parse      = TCPMSS_parse,
+		.x6_fcheck     = TCPMSS_check,
+		.x6_options    = TCPMSS6_opts,
+	},
 };
 
 void _init(void)
 {
-	xtables_register_target(&tcpmss_target);
-	xtables_register_target(&tcpmss_target6);
+	xtables_register_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
 }
diff --git a/extensions/libxt_TCPMSS.man b/extensions/libxt_TCPMSS.man
index 8da8e76..25b480d 100644
--- a/extensions/libxt_TCPMSS.man
+++ b/extensions/libxt_TCPMSS.man
@@ -1,4 +1,4 @@
-This target allows to alter the MSS value of TCP SYN packets, to control
+This target alters the MSS value of TCP SYN packets, to control
 the maximum size for that connection (usually limiting it to your
 outgoing interface's MTU minus 40 for IPv4 or 60 for IPv6, respectively).
 Of course, it can only be used
diff --git a/extensions/libxt_TCPMSS.t b/extensions/libxt_TCPMSS.t
new file mode 100644
index 0000000..553a345
--- /dev/null
+++ b/extensions/libxt_TCPMSS.t
@@ -0,0 +1,6 @@
+:FORWARD,OUTPUT,POSTROUTING
+*mangle
+-j TCPMSS;;FAIL
+-p tcp -j TCPMSS --set-mss 42;;FAIL
+-p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -j TCPMSS --set-mss 42;=;OK
+-p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -j TCPMSS --clamp-mss-to-pmtu;=;OK
diff --git a/extensions/libxt_TCPMSS.txlate b/extensions/libxt_TCPMSS.txlate
new file mode 100644
index 0000000..6a64d2c
--- /dev/null
+++ b/extensions/libxt_TCPMSS.txlate
@@ -0,0 +1,5 @@
+iptables-translate -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
+nft add rule ip filter FORWARD tcp flags & (syn|rst) == syn counter tcp option maxseg size set rt mtu
+
+iptables-translate -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 90
+nft add rule ip filter FORWARD tcp flags & (syn|rst) == syn counter tcp option maxseg size set 90
diff --git a/extensions/libxt_TCPOPTSTRIP.c b/extensions/libxt_TCPOPTSTRIP.c
index 6897857..6ea3489 100644
--- a/extensions/libxt_TCPOPTSTRIP.c
+++ b/extensions/libxt_TCPOPTSTRIP.c
@@ -12,6 +12,21 @@
 #ifndef TCPOPT_MD5SIG
 #	define TCPOPT_MD5SIG 19
 #endif
+#ifndef TCPOPT_MAXSEG
+#     define TCPOPT_MAXSEG 2
+#endif
+#ifndef TCPOPT_WINDOW
+#     define TCPOPT_WINDOW 3
+#endif
+#ifndef TCPOPT_SACK_PERMITTED
+#     define TCPOPT_SACK_PERMITTED 4
+#endif
+#ifndef TCPOPT_SACK
+#     define TCPOPT_SACK 5
+#endif
+#ifndef TCPOPT_TIMESTAMP
+#     define TCPOPT_TIMESTAMP 8
+#endif
 
 enum {
 	O_STRIP_OPTION = 0,
diff --git a/extensions/libxt_TCPOPTSTRIP.t b/extensions/libxt_TCPOPTSTRIP.t
new file mode 100644
index 0000000..b5c7a10
--- /dev/null
+++ b/extensions/libxt_TCPOPTSTRIP.t
@@ -0,0 +1,8 @@
+:PREROUTING,INPUT,FORWARD,OUTPUT,POSTROUTING
+*mangle
+-j TCPOPTSTRIP;;FAIL
+-p tcp -j TCPOPTSTRIP;=;OK
+-p tcp -j TCPOPTSTRIP --strip-options 2,3,4,5,6,7;=;OK
+-p tcp -j TCPOPTSTRIP --strip-options 0;;FAIL
+-p tcp -j TCPOPTSTRIP --strip-options 1;;FAIL
+-p tcp -j TCPOPTSTRIP --strip-options 1,2;;FAIL
diff --git a/extensions/libxt_TEE.c b/extensions/libxt_TEE.c
index c89e580..4676e33 100644
--- a/extensions/libxt_TEE.c
+++ b/extensions/libxt_TEE.c
@@ -92,36 +92,72 @@
 		printf(" --oif %s", info->oif);
 }
 
-static struct xtables_target tee_tg_reg = {
-	.name          = "TEE",
-	.version       = XTABLES_VERSION,
-	.revision      = 1,
-	.family        = NFPROTO_IPV4,
-	.size          = XT_ALIGN(sizeof(struct xt_tee_tginfo)),
-	.userspacesize = XT_ALIGN(sizeof(struct xt_tee_tginfo)),
-	.help          = tee_tg_help,
-	.print         = tee_tg_print,
-	.save          = tee_tg_save,
-	.x6_parse      = xtables_option_parse,
-	.x6_options    = tee_tg_opts,
-};
+static int tee_tg_xlate(struct xt_xlate *xl,
+			const struct xt_xlate_tg_params *params)
+{
+	const struct xt_tee_tginfo *info = (const void *)params->target->data;
 
-static struct xtables_target tee_tg6_reg = {
-	.name          = "TEE",
-	.version       = XTABLES_VERSION,
-	.revision      = 1,
-	.family        = NFPROTO_IPV6,
-	.size          = XT_ALIGN(sizeof(struct xt_tee_tginfo)),
-	.userspacesize = XT_ALIGN(sizeof(struct xt_tee_tginfo)),
-	.help          = tee_tg_help,
-	.print         = tee_tg6_print,
-	.save          = tee_tg6_save,
-	.x6_parse      = xtables_option_parse,
-	.x6_options    = tee_tg_opts,
+	if (params->numeric)
+		xt_xlate_add(xl, "dup to %s",
+			     xtables_ipaddr_to_numeric(&info->gw.in));
+	else
+		xt_xlate_add(xl, "dup to %s",
+			     xtables_ipaddr_to_anyname(&info->gw.in));
+	if (*info->oif != '\0')
+		xt_xlate_add(xl, " device %s", info->oif);
+
+	return 1;
+}
+
+static int tee_tg6_xlate(struct xt_xlate *xl,
+			 const struct xt_xlate_tg_params *params)
+{
+	const struct xt_tee_tginfo *info = (const void *)params->target->data;
+
+	if (params->numeric)
+		xt_xlate_add(xl, "dup to %s",
+			     xtables_ip6addr_to_numeric(&info->gw.in6));
+	else
+		xt_xlate_add(xl, "dup to %s",
+			     xtables_ip6addr_to_anyname(&info->gw.in6));
+	if (*info->oif != '\0')
+		xt_xlate_add(xl, " device %s", info->oif);
+
+	return 1;
+}
+
+static struct xtables_target tee_tg_reg[] = {
+	{
+		.name          = "TEE",
+		.version       = XTABLES_VERSION,
+		.revision      = 1,
+		.family        = NFPROTO_IPV4,
+		.size          = XT_ALIGN(sizeof(struct xt_tee_tginfo)),
+		.userspacesize = offsetof(struct xt_tee_tginfo, priv),
+		.help          = tee_tg_help,
+		.print         = tee_tg_print,
+		.save          = tee_tg_save,
+		.x6_parse      = xtables_option_parse,
+		.x6_options    = tee_tg_opts,
+		.xlate         = tee_tg_xlate,
+	},
+	{
+		.name          = "TEE",
+		.version       = XTABLES_VERSION,
+		.revision      = 1,
+		.family        = NFPROTO_IPV6,
+		.size          = XT_ALIGN(sizeof(struct xt_tee_tginfo)),
+		.userspacesize = offsetof(struct xt_tee_tginfo, priv),
+		.help          = tee_tg_help,
+		.print         = tee_tg6_print,
+		.save          = tee_tg6_save,
+		.x6_parse      = xtables_option_parse,
+		.x6_options    = tee_tg_opts,
+		.xlate         = tee_tg6_xlate,
+	},
 };
 
 void _init(void)
 {
-	xtables_register_target(&tee_tg_reg);
-	xtables_register_target(&tee_tg6_reg);
+	xtables_register_targets(tee_tg_reg, ARRAY_SIZE(tee_tg_reg));
 }
diff --git a/extensions/libxt_TEE.t b/extensions/libxt_TEE.t
new file mode 100644
index 0000000..ce8b103
--- /dev/null
+++ b/extensions/libxt_TEE.t
@@ -0,0 +1,4 @@
+:INPUT,FORWARD,OUTPUT
+-j TEE --gateway 1.1.1.1;=;OK
+-j TEE ! --gateway 1.1.1.1;;FAIL
+-j TEE;;FAIL
diff --git a/extensions/libxt_TEE.txlate b/extensions/libxt_TEE.txlate
new file mode 100644
index 0000000..9fcee25
--- /dev/null
+++ b/extensions/libxt_TEE.txlate
@@ -0,0 +1,11 @@
+# iptables-translate -t mangle -A PREROUTING -j TEE --gateway 192.168.0.2 --oif eth0
+# nft add rule ip mangle PREROUTING counter dup to 192.168.0.2 device eth0
+#
+# iptables-translate -t mangle -A PREROUTING -j TEE --gateway 192.168.0.2
+# nft add rule ip mangle PREROUTING counter dup to 192.168.0.2
+
+ip6tables-translate -t mangle -A PREROUTING -j TEE --gateway ab12:00a1:1112:acba::
+nft add rule ip6 mangle PREROUTING counter dup to ab12:a1:1112:acba::
+
+ip6tables-translate -t mangle -A PREROUTING -j TEE --gateway ab12:00a1:1112:acba:: --oif eth0
+nft add rule ip6 mangle PREROUTING counter dup to ab12:a1:1112:acba:: device eth0
diff --git a/extensions/libxt_TOS.c b/extensions/libxt_TOS.c
index cef5876..b66fa32 100644
--- a/extensions/libxt_TOS.c
+++ b/extensions/libxt_TOS.c
@@ -183,6 +183,30 @@
 	printf(" --set-tos 0x%02x/0x%02x", info->tos_value, info->tos_mask);
 }
 
+static int tos_xlate(struct xt_xlate *xl,
+		     const struct xt_xlate_tg_params *params)
+{
+	const struct ipt_tos_target_info *info =
+			(struct ipt_tos_target_info *) params->target->data;
+	uint8_t dscp = info->tos >> 2;
+
+	xt_xlate_add(xl, "ip dscp set 0x%02x", dscp);
+
+	return 1;
+}
+
+static int tos_xlate6(struct xt_xlate *xl,
+		     const struct xt_xlate_tg_params *params)
+{
+	const struct ipt_tos_target_info *info =
+			(struct ipt_tos_target_info *) params->target->data;
+	uint8_t dscp = info->tos >> 2;
+
+	xt_xlate_add(xl, "ip6 dscp set 0x%02x", dscp);
+
+	return 1;
+}
+
 static struct xtables_target tos_tg_reg[] = {
 	{
 		.version       = XTABLES_VERSION,
@@ -197,6 +221,7 @@
 		.x6_parse      = tos_tg_parse_v0,
 		.x6_fcheck     = tos_tg_check,
 		.x6_options    = tos_tg_opts_v0,
+		.xlate	       = tos_xlate,
 	},
 	{
 		.version       = XTABLES_VERSION,
@@ -211,6 +236,7 @@
 		.x6_parse      = tos_tg_parse,
 		.x6_fcheck     = tos_tg_check,
 		.x6_options    = tos_tg_opts,
+		.xlate	       = tos_xlate6,
 	},
 };
 
diff --git a/extensions/libxt_TOS.man b/extensions/libxt_TOS.man
index 46f6737..de2d22d 100644
--- a/extensions/libxt_TOS.man
+++ b/extensions/libxt_TOS.man
@@ -4,24 +4,33 @@
 \fBmangle\fP table.
 .TP
 \fB\-\-set\-tos\fP \fIvalue\fP[\fB/\fP\fImask\fP]
-Zeroes out the bits given by \fImask\fP and XORs \fIvalue\fP into the
-TOS/Priority field. If \fImask\fP is omitted, 0xFF is assumed.
+Zeroes out the bits given by \fImask\fP (see NOTE below) and XORs \fIvalue\fP
+into the TOS/Priority field. If \fImask\fP is omitted, 0xFF is assumed.
 .TP
 \fB\-\-set\-tos\fP \fIsymbol\fP
 You can specify a symbolic name when using the TOS target for IPv4. It implies
-a mask of 0xFF. The list of recognized TOS names can be obtained by calling
-iptables with \fB\-j TOS \-h\fP.
+a mask of 0xFF (see NOTE below). The list of recognized TOS names can be
+obtained by calling iptables with \fB\-j TOS \-h\fP.
 .PP
 The following mnemonics are available:
 .TP
 \fB\-\-and\-tos\fP \fIbits\fP
 Binary AND the TOS value with \fIbits\fP. (Mnemonic for \fB\-\-set\-tos
-0/\fP\fIinvbits\fP, where \fIinvbits\fP is the binary negation of \fIbits\fP.)
+0/\fP\fIinvbits\fP, where \fIinvbits\fP is the binary negation of \fIbits\fP.
+See NOTE below.)
 .TP
 \fB\-\-or\-tos\fP \fIbits\fP
 Binary OR the TOS value with \fIbits\fP. (Mnemonic for \fB\-\-set\-tos\fP
-\fIbits\fP\fB/\fP\fIbits\fP.)
+\fIbits\fP\fB/\fP\fIbits\fP. See NOTE below.)
 .TP
 \fB\-\-xor\-tos\fP \fIbits\fP
 Binary XOR the TOS value with \fIbits\fP. (Mnemonic for \fB\-\-set\-tos\fP
-\fIbits\fP\fB/0\fP.)
+\fIbits\fP\fB/0\fP. See NOTE below.)
+.PP
+NOTE: In Linux kernels up to and including 2.6.38, with the exception of
+longterm releases 2.6.32 (>=.42), 2.6.33 (>=.15), and 2.6.35 (>=.14), there is
+a bug whereby IPv6 TOS mangling does not behave as documented and differs from
+the IPv4 version. The TOS mask indicates the bits one wants to zero out, so it
+needs to be inverted before applying it to the original TOS field. However, the
+aformentioned kernels forgo the inversion which breaks \-\-set\-tos and its
+mnemonics.
diff --git a/extensions/libxt_TOS.t b/extensions/libxt_TOS.t
new file mode 100644
index 0000000..ae8531c
--- /dev/null
+++ b/extensions/libxt_TOS.t
@@ -0,0 +1,16 @@
+:PREROUTING,INPUT,FORWARD,OUTPUT,POSTROUTING
+*mangle
+-j TOS --set-tos 0x1f;=;OK
+-j TOS --set-tos 0x1f/0x1f;=;OK
+# maximum TOS is 0x1f (5 bits)
+# ERROR: should fail: iptables -A PREROUTING -t mangle -j TOS --set-tos 0xff
+# -j TOS --set-tos 0xff;;FAIL
+-j TOS --set-tos Minimize-Delay;-j TOS --set-tos 0x10;OK
+-j TOS --set-tos Maximize-Throughput;-j TOS --set-tos 0x08;OK
+-j TOS --set-tos Maximize-Reliability;-j TOS --set-tos 0x04;OK
+-j TOS --set-tos Minimize-Cost;-j TOS --set-tos 0x02;OK
+-j TOS --set-tos Normal-Service;-j TOS --set-tos 0x00;OK
+-j TOS --and-tos 0x12;-j TOS --set-tos 0x00/0xed;OK
+-j TOS --or-tos 0x12;-j TOS --set-tos 0x12/0x12;OK
+-j TOS --xor-tos 0x12;-j TOS --set-tos 0x12/0x00;OK
+-j TOS;;FAIL
diff --git a/extensions/libxt_TOS.txlate b/extensions/libxt_TOS.txlate
new file mode 100644
index 0000000..0952310
--- /dev/null
+++ b/extensions/libxt_TOS.txlate
@@ -0,0 +1,23 @@
+ip6tables-translate -A INPUT -j TOS --set-tos 0x1f
+nft add rule ip6 filter INPUT counter ip6 dscp set 0x07
+
+ip6tables-translate -A INPUT -j TOS --set-tos 0xff
+nft add rule ip6 filter INPUT counter ip6 dscp set 0x3f
+
+ip6tables-translate -A INPUT -j TOS --set-tos Minimize-Delay
+nft add rule ip6 filter INPUT counter ip6 dscp set 0x04
+
+ip6tables-translate -A INPUT -j TOS --set-tos Minimize-Cost
+nft add rule ip6 filter INPUT counter ip6 dscp set 0x00
+
+ip6tables-translate -A INPUT -j TOS --set-tos Normal-Service
+nft add rule ip6 filter INPUT counter ip6 dscp set 0x00
+
+ip6tables-translate -A INPUT -j TOS --and-tos 0x12
+nft add rule ip6 filter INPUT counter ip6 dscp set 0x00
+
+ip6tables-translate -A INPUT -j TOS --or-tos 0x12
+nft add rule ip6 filter INPUT counter ip6 dscp set 0x04
+
+ip6tables-translate -A INPUT -j TOS --xor-tos 0x12
+nft add rule ip6 filter INPUT counter ip6 dscp set 0x04
diff --git a/extensions/libxt_TPROXY.t b/extensions/libxt_TPROXY.t
new file mode 100644
index 0000000..12f82b1
--- /dev/null
+++ b/extensions/libxt_TPROXY.t
@@ -0,0 +1,5 @@
+:PREROUTING
+*mangle
+-j TPROXY --on-port 12345 --on-ip 10.0.0.1 --tproxy-mark 0x23/0xff;;FAIL
+-p udp -j TPROXY --on-port 12345 --on-ip 10.0.0.1 --tproxy-mark 0x23/0xff;=;OK
+-p tcp -m tcp --dport 2342 -j TPROXY --on-port 12345 --on-ip 10.0.0.1 --tproxy-mark 0x23/0xff;=;OK
diff --git a/extensions/libxt_TRACE.c b/extensions/libxt_TRACE.c
index 0282e6f..ac4f6fa 100644
--- a/extensions/libxt_TRACE.c
+++ b/extensions/libxt_TRACE.c
@@ -7,12 +7,20 @@
 #include <xtables.h>
 #include <linux/netfilter/x_tables.h>
 
+static int trace_xlate(struct xt_xlate *xl,
+		       const struct xt_xlate_tg_params *params)
+{
+	xt_xlate_add(xl, "nftrace set 1");
+	return 1;
+}
+
 static struct xtables_target trace_target = {
 	.family		= NFPROTO_UNSPEC,
 	.name		= "TRACE",
 	.version	= XTABLES_VERSION,
 	.size		= XT_ALIGN(0),
 	.userspacesize	= XT_ALIGN(0),
+	.xlate		= trace_xlate,
 };
 
 void _init(void)
diff --git a/extensions/libxt_TRACE.man b/extensions/libxt_TRACE.man
index ea0ce0f..5187a8d 100644
--- a/extensions/libxt_TRACE.man
+++ b/extensions/libxt_TRACE.man
@@ -1,13 +1,20 @@
-This target marks packes so that the kernel will log every rule which match 
-the packets as those traverse the tables, chains, rules.
+This target marks packets so that the kernel will log every rule which match 
+the packets as those traverse the tables, chains, rules. It can only be used in
+the
+.BR raw
+table.
 .PP
-A logging backend, such as ip(6)t_LOG or nfnetlink_log, must be loaded for this
-to be visible.
+With iptables-legacy, a logging backend, such as ip(6)t_LOG or nfnetlink_log,
+must be loaded for this to be visible.
 The packets are logged with the string prefix:
 "TRACE: tablename:chainname:type:rulenum " where type can be "rule" for 
 plain rule, "return" for implicit rule at the end of a user defined chain 
 and "policy" for the policy of the built in chains. 
-.br
-It can only be used in the
-.BR raw
-table.
+.PP
+With iptables-nft, the target is translated into nftables'
+.B "meta nftrace"
+expression. Hence the kernel sends trace events via netlink to userspace where
+they may be displayed using
+.B "xtables-monitor --trace"
+command. For details, refer to
+.BR xtables-monitor (8).
diff --git a/extensions/libxt_TRACE.t b/extensions/libxt_TRACE.t
new file mode 100644
index 0000000..cadb733
--- /dev/null
+++ b/extensions/libxt_TRACE.t
@@ -0,0 +1,3 @@
+:PREROUTING,OUTPUT
+*raw
+-j TRACE;=;OK
diff --git a/extensions/libxt_TRACE.txlate b/extensions/libxt_TRACE.txlate
new file mode 100644
index 0000000..8e3d2a7
--- /dev/null
+++ b/extensions/libxt_TRACE.txlate
@@ -0,0 +1,2 @@
+iptables-translate -t raw -A PREROUTING -j TRACE
+nft add rule ip raw PREROUTING counter nftrace set 1
diff --git a/extensions/libxt_addrtype.c b/extensions/libxt_addrtype.c
new file mode 100644
index 0000000..5cafa21
--- /dev/null
+++ b/extensions/libxt_addrtype.c
@@ -0,0 +1,372 @@
+/* Shared library add-on to iptables to add addrtype matching support 
+ *
+ * Copyright (c) 2003-2013 Patrick McHardy <kaber@trash.net>
+ * 
+ * This program is released under the terms of GNU GPL */
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_addrtype.h>
+
+enum {
+	O_SRC_TYPE = 0,
+	O_DST_TYPE,
+	O_LIMIT_IFACE_IN,
+	O_LIMIT_IFACE_OUT,
+	F_SRC_TYPE        = 1 << O_SRC_TYPE,
+	F_DST_TYPE        = 1 << O_DST_TYPE,
+	F_LIMIT_IFACE_IN  = 1 << O_LIMIT_IFACE_IN,
+	F_LIMIT_IFACE_OUT = 1 << O_LIMIT_IFACE_OUT,
+};
+
+/* from linux/rtnetlink.h, must match order of enumeration */
+static const char *const rtn_names[] = {
+	"UNSPEC",
+	"UNICAST",
+	"LOCAL",
+	"BROADCAST",
+	"ANYCAST",
+	"MULTICAST",
+	"BLACKHOLE",
+	"UNREACHABLE",
+	"PROHIBIT",
+	"THROW",
+	"NAT",
+	"XRESOLVE",
+	NULL
+};
+
+static void addrtype_help_types(void)
+{
+	int i;
+
+	for (i = 0; rtn_names[i]; i++)
+		printf("                                %s\n", rtn_names[i]);
+}
+
+static void addrtype_help_v0(void)
+{
+	printf(
+"Address type match options:\n"
+" [!] --src-type type[,...]      Match source address type\n"
+" [!] --dst-type type[,...]      Match destination address type\n"
+"\n"
+"Valid types:           \n");
+	addrtype_help_types();
+}
+
+static void addrtype_help_v1(void)
+{
+	printf(
+"Address type match options:\n"
+" [!] --src-type type[,...]      Match source address type\n"
+" [!] --dst-type type[,...]      Match destination address type\n"
+"     --limit-iface-in           Match only on the packet's incoming device\n"
+"     --limit-iface-out          Match only on the packet's outgoing device\n"
+"\n"
+"Valid types:           \n");
+	addrtype_help_types();
+}
+
+static int
+parse_type(const char *name, size_t len, uint16_t *mask)
+{
+	int i;
+
+	for (i = 0; rtn_names[i]; i++)
+		if (strncasecmp(name, rtn_names[i], len) == 0) {
+			/* build up bitmask for kernel module */
+			*mask |= (1 << i);
+			return 1;
+		}
+
+	return 0;
+}
+
+static void parse_types(const char *arg, uint16_t *mask)
+{
+	const char *comma;
+
+	while ((comma = strchr(arg, ',')) != NULL) {
+		if (comma == arg || !parse_type(arg, comma-arg, mask))
+			xtables_error(PARAMETER_PROBLEM,
+			           "addrtype: bad type `%s'", arg);
+		arg = comma + 1;
+	}
+
+	if (strlen(arg) == 0 || !parse_type(arg, strlen(arg), mask))
+		xtables_error(PARAMETER_PROBLEM, "addrtype: bad type \"%s\"", arg);
+}
+	
+static void addrtype_parse_v0(struct xt_option_call *cb)
+{
+	struct xt_addrtype_info *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SRC_TYPE:
+		parse_types(cb->arg, &info->source);
+		if (cb->invert)
+			info->invert_source = 1;
+		break;
+	case O_DST_TYPE:
+		parse_types(cb->arg, &info->dest);
+		if (cb->invert)
+			info->invert_dest = 1;
+		break;
+	}
+}
+
+static void addrtype_parse_v1(struct xt_option_call *cb)
+{
+	struct xt_addrtype_info_v1 *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_SRC_TYPE:
+		parse_types(cb->arg, &info->source);
+		if (cb->invert)
+			info->flags |= XT_ADDRTYPE_INVERT_SOURCE;
+		break;
+	case O_DST_TYPE:
+		parse_types(cb->arg, &info->dest);
+		if (cb->invert)
+			info->flags |= XT_ADDRTYPE_INVERT_DEST;
+		break;
+	case O_LIMIT_IFACE_IN:
+		info->flags |= XT_ADDRTYPE_LIMIT_IFACE_IN;
+		break;
+	case O_LIMIT_IFACE_OUT:
+		info->flags |= XT_ADDRTYPE_LIMIT_IFACE_OUT;
+		break;
+	}
+}
+
+static void addrtype_check(struct xt_fcheck_call *cb)
+{
+	if (!(cb->xflags & (F_SRC_TYPE | F_DST_TYPE)))
+		xtables_error(PARAMETER_PROBLEM,
+			   "addrtype: you must specify --src-type or --dst-type");
+}
+
+static void print_types(uint16_t mask)
+{
+	const char *sep = "";
+	int i;
+
+	for (i = 0; rtn_names[i]; i++)
+		if (mask & (1 << i)) {
+			printf("%s%s", sep, rtn_names[i]);
+			sep = ",";
+		}
+}
+
+static void addrtype_print_v0(const void *ip, const struct xt_entry_match *match,
+                              int numeric)
+{
+	const struct xt_addrtype_info *info = (const void *)match->data;
+
+	printf(" ADDRTYPE match");
+	if (info->source) {
+		printf(" src-type ");
+		if (info->invert_source)
+			printf("!");
+		print_types(info->source);
+	}
+	if (info->dest) {
+		printf(" dst-type");
+		if (info->invert_dest)
+			printf("!");
+		print_types(info->dest);
+	}
+}
+
+static void addrtype_print_v1(const void *ip, const struct xt_entry_match *match,
+                              int numeric)
+{
+	const struct xt_addrtype_info_v1 *info = (const void *)match->data;
+
+	printf(" ADDRTYPE match");
+	if (info->source) {
+		printf(" src-type ");
+		if (info->flags & XT_ADDRTYPE_INVERT_SOURCE)
+			printf("!");
+		print_types(info->source);
+	}
+	if (info->dest) {
+		printf(" dst-type ");
+		if (info->flags & XT_ADDRTYPE_INVERT_DEST)
+			printf("!");
+		print_types(info->dest);
+	}
+	if (info->flags & XT_ADDRTYPE_LIMIT_IFACE_IN)
+		printf(" limit-in");
+	if (info->flags & XT_ADDRTYPE_LIMIT_IFACE_OUT)
+		printf(" limit-out");
+}
+
+static void addrtype_save_v0(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_addrtype_info *info = (const void *)match->data;
+
+	if (info->source) {
+		if (info->invert_source)
+			printf(" !");
+		printf(" --src-type ");
+		print_types(info->source);
+	}
+	if (info->dest) {
+		if (info->invert_dest)
+			printf(" !");
+		printf(" --dst-type ");
+		print_types(info->dest);
+	}
+}
+
+static void addrtype_save_v1(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_addrtype_info_v1 *info = (const void *)match->data;
+
+	if (info->source) {
+		if (info->flags & XT_ADDRTYPE_INVERT_SOURCE)
+			printf(" !");
+		printf(" --src-type ");
+		print_types(info->source);
+	}
+	if (info->dest) {
+		if (info->flags & XT_ADDRTYPE_INVERT_DEST)
+			printf(" !");
+		printf(" --dst-type ");
+		print_types(info->dest);
+	}
+	if (info->flags & XT_ADDRTYPE_LIMIT_IFACE_IN)
+		printf(" --limit-iface-in");
+	if (info->flags & XT_ADDRTYPE_LIMIT_IFACE_OUT)
+		printf(" --limit-iface-out");
+}
+
+static const char *const rtn_lnames[] = {
+	"unspec",
+	"unicast",
+	"local",
+	"broadcast",
+	"anycast",
+	"multicast",
+	"blackhole",
+	"unreachable",
+	"prohibit",
+	NULL,
+};
+
+static bool multiple_bits_set(uint16_t val)
+{
+	int first = ffs(val);
+
+	return first && (val >> first) > 0;
+}
+
+static int addrtype_xlate(struct xt_xlate *xl,
+			  const struct xt_xlate_mt_params *params)
+{
+	const struct xt_addrtype_info_v1 *info =
+		(const void *)params->match->data;
+	const char *sep = "";
+	bool need_braces;
+	uint16_t val;
+	int i;
+
+	xt_xlate_add(xl, "fib ");
+
+	if (info->source) {
+		xt_xlate_add(xl, "saddr ");
+		val = info->source;
+	} else {
+		xt_xlate_add(xl, "daddr ");
+		val = info->dest;
+	}
+
+	if (info->flags & XT_ADDRTYPE_LIMIT_IFACE_IN)
+		xt_xlate_add(xl, ". iif ");
+	else if (info->flags & XT_ADDRTYPE_LIMIT_IFACE_OUT)
+		xt_xlate_add(xl, ". oif ");
+
+	xt_xlate_add(xl, "type ");
+
+	if (info->flags & (XT_ADDRTYPE_INVERT_SOURCE | XT_ADDRTYPE_INVERT_DEST))
+			xt_xlate_add(xl, "!= ");
+
+	need_braces = multiple_bits_set(val);
+
+	if (need_braces)
+		xt_xlate_add(xl, "{ ");
+
+	for (i = 0; rtn_lnames[i]; i++) {
+		if (val & (1 << i)) {
+			xt_xlate_add(xl, "%s%s", sep, rtn_lnames[i]);
+			sep = ", ";
+		}
+	}
+
+	if (need_braces)
+		xt_xlate_add(xl, " }");
+
+	return 1;
+}
+
+static const struct xt_option_entry addrtype_opts_v0[] = {
+	{.name = "src-type", .id = O_SRC_TYPE, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	{.name = "dst-type", .id = O_DST_TYPE, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	XTOPT_TABLEEND,
+};
+
+static const struct xt_option_entry addrtype_opts_v1[] = {
+	{.name = "src-type", .id = O_SRC_TYPE, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	{.name = "dst-type", .id = O_DST_TYPE, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	{.name = "limit-iface-in", .id = O_LIMIT_IFACE_IN,
+	 .type = XTTYPE_NONE, .excl = F_LIMIT_IFACE_OUT},
+	{.name = "limit-iface-out", .id = O_LIMIT_IFACE_OUT,
+	 .type = XTTYPE_NONE, .excl = F_LIMIT_IFACE_IN},
+	XTOPT_TABLEEND,
+};
+
+static struct xtables_match addrtype_mt_reg[] = {
+	{
+		.name          = "addrtype",
+		.version       = XTABLES_VERSION,
+		.family        = NFPROTO_IPV4,
+		.size          = XT_ALIGN(sizeof(struct xt_addrtype_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_addrtype_info)),
+		.help          = addrtype_help_v0,
+		.print         = addrtype_print_v0,
+		.save          = addrtype_save_v0,
+		.x6_parse      = addrtype_parse_v0,
+		.x6_fcheck     = addrtype_check,
+		.x6_options    = addrtype_opts_v0,
+	},
+	{
+		.name          = "addrtype",
+		.revision      = 1,
+		.version       = XTABLES_VERSION,
+		.family        = NFPROTO_UNSPEC,
+		.size          = XT_ALIGN(sizeof(struct xt_addrtype_info_v1)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_addrtype_info_v1)),
+		.help          = addrtype_help_v1,
+		.print         = addrtype_print_v1,
+		.save          = addrtype_save_v1,
+		.x6_parse      = addrtype_parse_v1,
+		.x6_fcheck     = addrtype_check,
+		.x6_options    = addrtype_opts_v1,
+		.xlate         = addrtype_xlate,
+	},
+};
+
+
+void _init(void) 
+{
+	xtables_register_matches(addrtype_mt_reg, ARRAY_SIZE(addrtype_mt_reg));
+}
diff --git a/extensions/libipt_addrtype.man b/extensions/libxt_addrtype.man
similarity index 100%
rename from extensions/libipt_addrtype.man
rename to extensions/libxt_addrtype.man
diff --git a/extensions/libxt_addrtype.t b/extensions/libxt_addrtype.t
new file mode 100644
index 0000000..390a63f
--- /dev/null
+++ b/extensions/libxt_addrtype.t
@@ -0,0 +1,17 @@
+:INPUT,FORWARD,OUTPUT
+-m addrtype;;FAIL
+-m addrtype --src-type wrong;;FAIL
+-m addrtype --src-type UNSPEC;=;OK
+-m addrtype --dst-type UNSPEC;=;OK
+-m addrtype --src-type LOCAL --dst-type LOCAL;=;OK
+-m addrtype --dst-type UNSPEC;=;OK
+-m addrtype --limit-iface-in;;FAIL
+-m addrtype --limit-iface-out;;FAIL
+-m addrtype --limit-iface-in --limit-iface-out;;FAIL
+-m addrtype --src-type LOCAL --limit-iface-in --limit-iface-out;;FAIL
+:INPUT
+-m addrtype --src-type LOCAL --limit-iface-in;=;OK
+-m addrtype --dst-type LOCAL --limit-iface-in;=;OK
+:OUTPUT
+-m addrtype --src-type LOCAL --limit-iface-out;=;OK
+-m addrtype --dst-type LOCAL --limit-iface-out;=;OK
diff --git a/extensions/libxt_addrtype.txlate b/extensions/libxt_addrtype.txlate
new file mode 100644
index 0000000..a719b2c
--- /dev/null
+++ b/extensions/libxt_addrtype.txlate
@@ -0,0 +1,11 @@
+iptables-translate -A INPUT -m addrtype --src-type LOCAL
+nft add rule ip filter INPUT fib saddr type local counter
+
+iptables-translate -A INPUT -m addrtype --dst-type LOCAL
+nft add rule ip filter INPUT fib daddr type local counter
+
+iptables-translate -A INPUT -m addrtype ! --dst-type ANYCAST,LOCAL
+nft add rule ip filter INPUT fib daddr type != { local, anycast } counter
+
+iptables-translate -A INPUT -m addrtype --limit-iface-in --dst-type ANYCAST,LOCAL
+nft add rule ip filter INPUT fib daddr . iif type { local, anycast } counter
diff --git a/extensions/libxt_bpf.c b/extensions/libxt_bpf.c
new file mode 100644
index 0000000..eeae86e
--- /dev/null
+++ b/extensions/libxt_bpf.c
@@ -0,0 +1,297 @@
+/*
+ * Xtables BPF extension
+ *
+ * Written by Willem de Bruijn (willemb@google.com)
+ * Copyright Google, Inc. 2013
+ * Licensed under the GNU General Public License version 2 (GPLv2)
+*/
+
+#include <linux/netfilter/xt_bpf.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <xtables.h>
+#include "config.h"
+
+#ifdef HAVE_LINUX_BPF_H
+#include <linux/bpf.h>
+#endif
+
+#include <linux/magic.h>
+#include <linux/unistd.h>
+
+#define BCODE_FILE_MAX_LEN_B	1024
+
+enum {
+	O_BCODE_STDIN = 0,
+	O_OBJ_PINNED = 1,
+};
+
+static void bpf_help(void)
+{
+	printf(
+"bpf match options:\n"
+"--bytecode <program>	: a bpf program as generated by\n"
+"                         $(nfbpf_compile RAW '<filter>')\n");
+}
+
+static void bpf_help_v1(void)
+{
+	printf(
+"bpf match options:\n"
+"--bytecode <program>	        : a bpf program as generated by\n"
+"                                 $(nfbpf_compile RAW '<filter>')\n"
+"--object-pinned <bpf object>	: a path to a pinned BPF object in bpf fs\n");
+}
+
+static const struct xt_option_entry bpf_opts[] = {
+	{.name = "bytecode", .id = O_BCODE_STDIN, .type = XTTYPE_STRING},
+	XTOPT_TABLEEND,
+};
+
+static const struct xt_option_entry bpf_opts_v1[] = {
+	{.name = "bytecode", .id = O_BCODE_STDIN, .type = XTTYPE_STRING},
+	{.name = "object-pinned" , .id = O_OBJ_PINNED, .type = XTTYPE_STRING,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(struct xt_bpf_info_v1, path)},
+	XTOPT_TABLEEND,
+};
+
+static int bpf_obj_get_readonly(const char *filepath)
+{
+#if defined HAVE_LINUX_BPF_H && defined __NR_bpf && defined BPF_FS_MAGIC
+	/* union bpf_attr includes this in an anonymous struct, but the
+	 * file_flags field and the BPF_F_RDONLY constant are only present
+	 * in Linux 4.15+ kernel headers (include/uapi/linux/bpf.h)
+	 */
+	struct {   // this part of union bpf_attr is for BPF_OBJ_* commands
+		__aligned_u64	pathname;
+		__u32		bpf_fd;
+		__u32		file_flags;
+	} attr = {
+		.pathname = (__u64)filepath,
+		.file_flags = (1U << 3),   // BPF_F_RDONLY
+	};
+	int fd = syscall(__NR_bpf, BPF_OBJ_GET, &attr, sizeof(attr));
+	if (fd >= 0) return fd;
+
+	/* on any error fallback to default R/W access for pre-4.15-rc1 kernels */
+	attr.file_flags = 0;
+	return syscall(__NR_bpf, BPF_OBJ_GET, &attr, sizeof(attr));
+#else
+	xtables_error(OTHER_PROBLEM,
+		      "No bpf header, kernel headers too old?\n");
+	return -EINVAL;
+#endif
+}
+
+static void bpf_parse_string(struct sock_filter *pc, __u16 *lenp, __u16 len_max,
+			     const char *bpf_program)
+{
+	const char separator = ',';
+	const char *token;
+	char sp;
+	int i;
+	__u16 len;
+
+	/* parse head: length. */
+	if (sscanf(bpf_program, "%hu%c", &len, &sp) != 2 ||
+		   sp != separator)
+		xtables_error(PARAMETER_PROBLEM,
+			      "bpf: error parsing program length");
+	if (!len)
+		xtables_error(PARAMETER_PROBLEM,
+			      "bpf: illegal zero length program");
+	if (len > len_max)
+		xtables_error(PARAMETER_PROBLEM,
+			      "bpf: number of instructions exceeds maximum");
+
+	/* parse instructions. */
+	i = 0;
+	token = bpf_program;
+	while ((token = strchr(token, separator)) && (++token)[0]) {
+		if (i >= len)
+			xtables_error(PARAMETER_PROBLEM,
+				      "bpf: real program length exceeds"
+				      " the encoded length parameter");
+		if (sscanf(token, "%hu %hhu %hhu %u,",
+			   &pc->code, &pc->jt, &pc->jf, &pc->k) != 4)
+			xtables_error(PARAMETER_PROBLEM,
+				      "bpf: error at instr %d", i);
+		i++;
+		pc++;
+	}
+
+	if (i != len)
+		xtables_error(PARAMETER_PROBLEM,
+			      "bpf: parsed program length is less than the"
+			      " encoded length parameter");
+
+	*lenp = len;
+}
+
+static void bpf_parse_obj_pinned(struct xt_bpf_info_v1 *bi,
+				 const char *filepath)
+{
+	bi->fd = bpf_obj_get_readonly(filepath);
+	if (bi->fd < 0)
+		xtables_error(PARAMETER_PROBLEM,
+			      "bpf: failed to get bpf object");
+
+	/* Cannot close bi->fd explicitly. Rely on exit */
+	if (fcntl(bi->fd, F_SETFD, FD_CLOEXEC) == -1) {
+		xtables_error(OTHER_PROBLEM,
+			      "Could not set close on exec: %s\n",
+			      strerror(errno));
+	}
+}
+
+static void bpf_parse(struct xt_option_call *cb)
+{
+	struct xt_bpf_info *bi = (void *) cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_BCODE_STDIN:
+		bpf_parse_string(bi->bpf_program, &bi->bpf_program_num_elem,
+				 ARRAY_SIZE(bi->bpf_program), cb->arg);
+		break;
+	default:
+		xtables_error(PARAMETER_PROBLEM, "bpf: unknown option");
+	}
+}
+
+static void bpf_parse_v1(struct xt_option_call *cb)
+{
+	struct xt_bpf_info_v1 *bi = (void *) cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_BCODE_STDIN:
+		bpf_parse_string(bi->bpf_program, &bi->bpf_program_num_elem,
+				 ARRAY_SIZE(bi->bpf_program), cb->arg);
+		bi->mode = XT_BPF_MODE_BYTECODE;
+		break;
+	case O_OBJ_PINNED:
+		bpf_parse_obj_pinned(bi, cb->arg);
+		bi->mode = XT_BPF_MODE_FD_PINNED;
+		break;
+	default:
+		xtables_error(PARAMETER_PROBLEM, "bpf: unknown option");
+	}
+}
+
+static void bpf_print_code(const struct sock_filter *pc, __u16 len, char tail)
+{
+	for (; len; len--, pc++)
+		printf("%hu %hhu %hhu %u%c",
+		       pc->code, pc->jt, pc->jf, pc->k,
+		       len > 1 ? ',' : tail);
+}
+
+static void bpf_save_code(const struct sock_filter *pc, __u16 len)
+{
+	printf(" --bytecode \"%hu,", len);
+	bpf_print_code(pc, len, '\"');
+}
+
+static void bpf_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_bpf_info *info = (void *) match->data;
+
+	bpf_save_code(info->bpf_program, info->bpf_program_num_elem);
+}
+
+static void bpf_save_v1(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_bpf_info_v1 *info = (void *) match->data;
+
+	if (info->mode == XT_BPF_MODE_BYTECODE)
+		bpf_save_code(info->bpf_program, info->bpf_program_num_elem);
+	else if (info->mode == XT_BPF_MODE_FD_PINNED)
+		printf(" --object-pinned %s", info->path);
+	else
+		xtables_error(OTHER_PROBLEM, "unknown bpf mode");
+}
+
+static void bpf_fcheck(struct xt_fcheck_call *cb)
+{
+	if (!(cb->xflags & (1 << O_BCODE_STDIN)))
+		xtables_error(PARAMETER_PROBLEM,
+			      "bpf: missing --bytecode parameter");
+}
+
+static void bpf_fcheck_v1(struct xt_fcheck_call *cb)
+{
+	const unsigned int bit_bcode = 1 << O_BCODE_STDIN;
+	const unsigned int bit_pinned = 1 << O_OBJ_PINNED;
+	unsigned int flags;
+
+	flags = cb->xflags & (bit_bcode | bit_pinned);
+	if (flags != bit_bcode && flags != bit_pinned)
+		xtables_error(PARAMETER_PROBLEM,
+			      "bpf: one of --bytecode or --pinned is required");
+}
+
+static void bpf_print(const void *ip, const struct xt_entry_match *match,
+		      int numeric)
+{
+	const struct xt_bpf_info *info = (void *) match->data;
+
+	printf("match bpf ");
+	bpf_print_code(info->bpf_program, info->bpf_program_num_elem, '\0');
+}
+
+static void bpf_print_v1(const void *ip, const struct xt_entry_match *match,
+			 int numeric)
+{
+	const struct xt_bpf_info_v1 *info = (void *) match->data;
+
+	printf("match bpf ");
+	if (info->mode == XT_BPF_MODE_BYTECODE)
+		bpf_print_code(info->bpf_program, info->bpf_program_num_elem, '\0');
+	else if (info->mode == XT_BPF_MODE_FD_PINNED)
+		printf("pinned %s", info->path);
+	else
+		printf("unknown");
+}
+
+static struct xtables_match bpf_matches[] = {
+	{
+		.family		= NFPROTO_UNSPEC,
+		.name		= "bpf",
+		.version	= XTABLES_VERSION,
+		.revision	= 0,
+		.size		= XT_ALIGN(sizeof(struct xt_bpf_info)),
+		.userspacesize	= XT_ALIGN(offsetof(struct xt_bpf_info, filter)),
+		.help		= bpf_help,
+		.print		= bpf_print,
+		.save		= bpf_save,
+		.x6_parse	= bpf_parse,
+		.x6_fcheck	= bpf_fcheck,
+		.x6_options	= bpf_opts,
+	},
+	{
+		.family		= NFPROTO_UNSPEC,
+		.name		= "bpf",
+		.version	= XTABLES_VERSION,
+		.revision	= 1,
+		.size		= XT_ALIGN(sizeof(struct xt_bpf_info_v1)),
+		.userspacesize	= XT_ALIGN(offsetof(struct xt_bpf_info_v1, filter)),
+		.help		= bpf_help_v1,
+		.print		= bpf_print_v1,
+		.save		= bpf_save_v1,
+		.x6_parse	= bpf_parse_v1,
+		.x6_fcheck	= bpf_fcheck_v1,
+		.x6_options	= bpf_opts_v1,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_matches(bpf_matches, ARRAY_SIZE(bpf_matches));
+}
diff --git a/extensions/libxt_bpf.man b/extensions/libxt_bpf.man
new file mode 100644
index 0000000..d6da204
--- /dev/null
+++ b/extensions/libxt_bpf.man
@@ -0,0 +1,60 @@
+Match using Linux Socket Filter. Expects a path to an eBPF object or a cBPF
+program in decimal format.
+.TP
+\fB\-\-object\-pinned\fP \fIpath\fP
+Pass a path to a pinned eBPF object.
+.PP
+Applications load eBPF programs into the kernel with the bpf() system call and
+BPF_PROG_LOAD command and can pin them in a virtual filesystem with BPF_OBJ_PIN.
+To use a pinned object in iptables, mount the bpf filesystem using
+.IP
+mount \-t bpf bpf ${BPF_MOUNT}
+.PP
+then insert the filter in iptables by path:
+.IP
+iptables \-A OUTPUT \-m bpf \-\-object\-pinned ${BPF_MOUNT}/{PINNED_PATH} \-j ACCEPT
+.TP
+\fB\-\-bytecode\fP \fIcode\fP
+Pass the BPF byte code format as generated by the \fBnfbpf_compile\fP utility.
+.PP
+The code format is similar to the output of the tcpdump \-ddd command: one line
+that stores the number of instructions, followed by one line for each
+instruction. Instruction lines follow the pattern 'u16 u8 u8 u32' in decimal
+notation. Fields encode the operation, jump offset if true, jump offset if
+false and generic multiuse field 'K'. Comments are not supported.
+.PP
+For example, to read only packets matching 'ip proto 6', insert the following,
+without the comments or trailing whitespace:
+.IP
+4               # number of instructions
+.br
+48 0 0 9        # load byte  ip->proto
+.br
+21 0 1 6        # jump equal IPPROTO_TCP
+.br
+6 0 0 1         # return     pass (non-zero)
+.br
+6 0 0 0         # return     fail (zero)
+.PP
+You can pass this filter to the bpf match with the following command:
+.IP
+iptables \-A OUTPUT \-m bpf \-\-bytecode '4,48 0 0 9,21 0 1 6,6 0 0 1,6 0 0 0' \-j ACCEPT
+.PP
+Or instead, you can invoke the nfbpf_compile utility.
+.IP
+iptables \-A OUTPUT \-m bpf \-\-bytecode "`nfbpf_compile RAW 'ip proto 6'`" \-j ACCEPT
+.PP
+Or use tcpdump -ddd. In that case, generate BPF targeting a device with the
+same data link type as the xtables match. Iptables passes packets from the
+network layer up, without mac layer. Select a device with data link type RAW,
+such as a tun device:
+.IP
+ip tuntap add tun0 mode tun
+.br
+ip link set tun0 up
+.br
+tcpdump -ddd -i tun0 ip proto 6
+.PP
+See tcpdump -L -i $dev for a list of known data link types for a given device.
+.PP
+You may want to learn more about BPF from FreeBSD's bpf(4) manpage.
diff --git a/extensions/libxt_bpf.t b/extensions/libxt_bpf.t
new file mode 100644
index 0000000..80361ad
--- /dev/null
+++ b/extensions/libxt_bpf.t
@@ -0,0 +1,2 @@
+:INPUT,FORWARD,OUTPUT
+-m bpf --bytecode "4,48 0 0 9,21 0 1 6,6 0 0 1,6 0 0 0";=;OK
diff --git a/extensions/libxt_cgroup.c b/extensions/libxt_cgroup.c
new file mode 100644
index 0000000..327032e
--- /dev/null
+++ b/extensions/libxt_cgroup.c
@@ -0,0 +1,278 @@
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_cgroup.h>
+
+enum {
+	O_CLASSID = 0,
+	O_PATH = 1,
+};
+
+static void cgroup_help_v0(void)
+{
+	printf(
+"cgroup match options:\n"
+"[!] --cgroup classid            Match cgroup classid\n");
+}
+
+static void cgroup_help_v1(void)
+{
+	printf(
+"cgroup match options:\n"
+"[!] --path path                 Recursively match path relative to cgroup2 root\n"
+"[!] --cgroup classid            Match cgroup classid, can't be used with --path\n");
+}
+
+static const struct xt_option_entry cgroup_opts_v0[] = {
+	{
+		.name = "cgroup",
+		.id = O_CLASSID,
+		.type = XTTYPE_UINT32,
+		.flags = XTOPT_INVERT | XTOPT_MAND | XTOPT_PUT,
+		XTOPT_POINTER(struct xt_cgroup_info_v0, id)
+	},
+	XTOPT_TABLEEND,
+};
+
+static const struct xt_option_entry cgroup_opts_v1[] = {
+	{
+		.name = "path",
+		.id = O_PATH,
+		.type = XTTYPE_STRING,
+		.flags = XTOPT_INVERT | XTOPT_PUT,
+		XTOPT_POINTER(struct xt_cgroup_info_v1, path)
+	},
+	{
+		.name = "cgroup",
+		.id = O_CLASSID,
+		.type = XTTYPE_UINT32,
+		.flags = XTOPT_INVERT | XTOPT_PUT,
+		XTOPT_POINTER(struct xt_cgroup_info_v1, classid)
+	},
+	XTOPT_TABLEEND,
+};
+
+static const struct xt_option_entry cgroup_opts_v2[] = {
+	{
+		.name = "path",
+		.id = O_PATH,
+		.type = XTTYPE_STRING,
+		.flags = XTOPT_INVERT | XTOPT_PUT,
+		XTOPT_POINTER(struct xt_cgroup_info_v2, path)
+	},
+	{
+		.name = "cgroup",
+		.id = O_CLASSID,
+		.type = XTTYPE_UINT32,
+		.flags = XTOPT_INVERT | XTOPT_PUT,
+		XTOPT_POINTER(struct xt_cgroup_info_v2, classid)
+	},
+	XTOPT_TABLEEND,
+};
+
+static void cgroup_parse_v0(struct xt_option_call *cb)
+{
+	struct xt_cgroup_info_v0 *cgroupinfo = cb->data;
+
+	xtables_option_parse(cb);
+	if (cb->invert)
+		cgroupinfo->invert = true;
+}
+
+static void cgroup_parse_v1(struct xt_option_call *cb)
+{
+	struct xt_cgroup_info_v1 *info = cb->data;
+
+	xtables_option_parse(cb);
+
+	switch (cb->entry->id) {
+	case O_PATH:
+		info->has_path = true;
+		if (cb->invert)
+			info->invert_path = true;
+		break;
+	case O_CLASSID:
+		info->has_classid = true;
+		if (cb->invert)
+			info->invert_classid = true;
+		break;
+	}
+}
+
+static void cgroup_parse_v2(struct xt_option_call *cb)
+{
+	struct xt_cgroup_info_v2 *info = cb->data;
+
+	xtables_option_parse(cb);
+
+	switch (cb->entry->id) {
+	case O_PATH:
+		info->has_path = true;
+		if (cb->invert)
+			info->invert_path = true;
+		break;
+	case O_CLASSID:
+		info->has_classid = true;
+		if (cb->invert)
+			info->invert_classid = true;
+		break;
+	}
+}
+
+static void
+cgroup_print_v0(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_cgroup_info_v0 *info = (void *) match->data;
+
+	printf(" cgroup %s%u", info->invert ? "! ":"", info->id);
+}
+
+static void cgroup_save_v0(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_cgroup_info_v0 *info = (void *) match->data;
+
+	printf("%s --cgroup %u", info->invert ? " !" : "", info->id);
+}
+
+static void
+cgroup_print_v1(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_cgroup_info_v1 *info = (void *)match->data;
+
+	printf(" cgroup");
+	if (info->has_path)
+		printf(" %s%s", info->invert_path ? "! ":"", info->path);
+	if (info->has_classid)
+		printf(" %s%u", info->invert_classid ? "! ":"", info->classid);
+}
+
+static void cgroup_save_v1(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_cgroup_info_v1 *info = (void *)match->data;
+
+	if (info->has_path) {
+		printf("%s --path", info->invert_path ? " !" : "");
+		xtables_save_string(info->path);
+	}
+
+	if (info->has_classid)
+		printf("%s --cgroup %u", info->invert_classid ? " !" : "",
+		       info->classid);
+}
+
+static void
+cgroup_print_v2(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_cgroup_info_v2 *info = (void *)match->data;
+
+	printf(" cgroup");
+	if (info->has_path)
+		printf(" %s%s", info->invert_path ? "! ":"", info->path);
+	if (info->has_classid)
+		printf(" %s%u", info->invert_classid ? "! ":"", info->classid);
+}
+
+static void cgroup_save_v2(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_cgroup_info_v2 *info = (void *)match->data;
+
+	if (info->has_path) {
+		printf("%s --path", info->invert_path ? " !" : "");
+		xtables_save_string(info->path);
+	}
+
+	if (info->has_classid)
+		printf("%s --cgroup %u", info->invert_classid ? " !" : "",
+		       info->classid);
+}
+
+static int cgroup_xlate_v0(struct xt_xlate *xl,
+			   const struct xt_xlate_mt_params *params)
+{
+	const struct xt_cgroup_info_v0 *info = (void *)params->match->data;
+
+	xt_xlate_add(xl, "meta cgroup %s%u", info->invert ? "!= " : "",
+		     info->id);
+	return 1;
+}
+
+static int cgroup_xlate_v1(struct xt_xlate *xl,
+			   const struct xt_xlate_mt_params *params)
+{
+	const struct xt_cgroup_info_v1 *info = (void *)params->match->data;
+
+	if (info->has_path)
+		return 0;
+
+	if (info->has_classid)
+		xt_xlate_add(xl, "meta cgroup %s%u",
+			     info->invert_classid ? "!= " : "",
+			     info->classid);
+
+	return 1;
+}
+
+static int cgroup_xlate_v2(struct xt_xlate *xl,
+			   const struct xt_xlate_mt_params *params)
+{
+	const struct xt_cgroup_info_v2 *info = (void *)params->match->data;
+
+	if (info->has_path)
+		return 0;
+
+	if (info->has_classid)
+		xt_xlate_add(xl, "meta cgroup %s%u",
+			     info->invert_classid ? "!= " : "",
+			     info->classid);
+
+	return 1;
+}
+
+static struct xtables_match cgroup_match[] = {
+	{
+		.family		= NFPROTO_UNSPEC,
+		.revision	= 0,
+		.name		= "cgroup",
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_cgroup_info_v0)),
+		.userspacesize	= XT_ALIGN(sizeof(struct xt_cgroup_info_v0)),
+		.help		= cgroup_help_v0,
+		.print		= cgroup_print_v0,
+		.save		= cgroup_save_v0,
+		.x6_parse	= cgroup_parse_v0,
+		.x6_options	= cgroup_opts_v0,
+		.xlate		= cgroup_xlate_v0,
+	},
+	{
+		.family		= NFPROTO_UNSPEC,
+		.revision	= 1,
+		.name		= "cgroup",
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_cgroup_info_v1)),
+		.userspacesize	= offsetof(struct xt_cgroup_info_v1, priv),
+		.help		= cgroup_help_v1,
+		.print		= cgroup_print_v1,
+		.save		= cgroup_save_v1,
+		.x6_parse	= cgroup_parse_v1,
+		.x6_options	= cgroup_opts_v1,
+		.xlate		= cgroup_xlate_v1,
+	},
+	{
+		.family		= NFPROTO_UNSPEC,
+		.revision	= 2,
+		.name		= "cgroup",
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_cgroup_info_v2)),
+		.userspacesize	= offsetof(struct xt_cgroup_info_v2, priv),
+		.help		= cgroup_help_v1,
+		.print		= cgroup_print_v2,
+		.save		= cgroup_save_v2,
+		.x6_parse	= cgroup_parse_v2,
+		.x6_options	= cgroup_opts_v2,
+		.xlate		= cgroup_xlate_v2,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_matches(cgroup_match, ARRAY_SIZE(cgroup_match));
+}
diff --git a/extensions/libxt_cgroup.man b/extensions/libxt_cgroup.man
new file mode 100644
index 0000000..4d5d1d8
--- /dev/null
+++ b/extensions/libxt_cgroup.man
@@ -0,0 +1,30 @@
+.TP
+[\fB!\fP] \fB\-\-path\fP \fIpath\fP
+Match cgroup2 membership.
+
+Each socket is associated with the v2 cgroup of the creating process.
+This matches packets coming from or going to all sockets in the
+sub-hierarchy of the specified path.  The path should be relative to
+the root of the cgroup2 hierarchy.
+.TP
+[\fB!\fP] \fB\-\-cgroup\fP \fIclassid\fP
+Match cgroup net_cls classid.
+
+classid is the marker set through the cgroup net_cls controller.  This
+option and \-\-path can't be used together.
+.PP
+Example:
+.IP
+iptables \-A OUTPUT \-p tcp \-\-sport 80 \-m cgroup ! \-\-path service/http-server \-j DROP
+.IP
+iptables \-A OUTPUT \-p tcp \-\-sport 80 \-m cgroup ! \-\-cgroup 1
+\-j DROP
+.PP
+\fBIMPORTANT\fP: when being used in the INPUT chain, the cgroup
+matcher is currently only of limited functionality, meaning it
+will only match on packets that are processed for local sockets
+through early socket demuxing. Therefore, general usage on the
+INPUT chain is not advised unless the implications are well
+understood.
+.PP
+Available since Linux 3.14.
diff --git a/extensions/libxt_cgroup.t b/extensions/libxt_cgroup.t
new file mode 100644
index 0000000..72c8e37
--- /dev/null
+++ b/extensions/libxt_cgroup.t
@@ -0,0 +1,8 @@
+:INPUT,OUTPUT,POSTROUTING
+*mangle
+-m cgroup --cgroup 1;=;OK
+-m cgroup ! --cgroup 1;=;OK
+-m cgroup --path "/";=;OK
+-m cgroup ! --path "/";=;OK
+-m cgroup --cgroup 1 --path "/";;FAIL
+-m cgroup ;;FAIL
diff --git a/extensions/libxt_cgroup.txlate b/extensions/libxt_cgroup.txlate
new file mode 100644
index 0000000..75f2e6a
--- /dev/null
+++ b/extensions/libxt_cgroup.txlate
@@ -0,0 +1,5 @@
+iptables-translate -t filter -A INPUT -m cgroup --cgroup 0 -j ACCEPT
+nft add rule ip filter INPUT meta cgroup 0 counter accept
+
+iptables-translate -t filter -A INPUT -m cgroup ! --cgroup 0 -j ACCEPT
+nft add rule ip filter INPUT meta cgroup != 0 counter accept
diff --git a/extensions/libxt_cluster.c b/extensions/libxt_cluster.c
index 3adff12..d164bf6 100644
--- a/extensions/libxt_cluster.c
+++ b/extensions/libxt_cluster.c
@@ -126,6 +126,56 @@
 		info->total_nodes, info->hash_seed);
 }
 
+static int cluster_xlate(struct xt_xlate *xl,
+			 const struct xt_xlate_mt_params *params)
+{
+	int node, shift_value = 1, comma_needed = 0;
+	uint32_t temp_node_mask, node_id = 0, needs_set = 0;
+	const struct xt_cluster_match_info *info = (void *)params->match->data;
+	const char *jhash_st = "jhash ct original saddr mod";
+	const char *pkttype_st = "meta pkttype set host";
+
+	if (!(info->node_mask & (info->node_mask - 1))) {
+		if (info->node_mask <= 2)
+			xt_xlate_add(xl, "%s %u seed 0x%08x eq %u %s", jhash_st,
+					info->total_nodes, info->hash_seed,
+					info->node_mask, pkttype_st);
+		else {
+			temp_node_mask = info->node_mask;
+			while (1) {
+				temp_node_mask = temp_node_mask >> shift_value;
+				node_id++;
+				if (temp_node_mask == 0)
+					break;
+			}
+			xt_xlate_add(xl, "%s %u seed 0x%08x eq %u %s", jhash_st,
+					info->total_nodes, info->hash_seed,
+					node_id, pkttype_st);
+		}
+	} else {
+		xt_xlate_add(xl, "%s %u seed 0x%08x ", jhash_st,
+				info->total_nodes, info->hash_seed);
+		for (node = 0; node < 32; node++) {
+			if (info->node_mask & (1u << node)) {
+				if (needs_set == 0) {
+					xt_xlate_add(xl, "{ ");
+					needs_set = 1;
+				}
+
+				if (comma_needed)
+					xt_xlate_add(xl, ", ");
+				xt_xlate_add(xl, "%u", node);
+				comma_needed++;
+			}
+		}
+		if (needs_set)
+			xt_xlate_add(xl, " }");
+		xt_xlate_add(xl, " %s", pkttype_st);
+	}
+
+	return 1;
+}
+
 static struct xtables_match cluster_mt_reg = {
 	.family		= NFPROTO_UNSPEC,
 	.name		= "cluster",
@@ -138,6 +188,7 @@
 	.x6_parse	= cluster_parse,
 	.x6_fcheck	= cluster_check,
 	.x6_options	= cluster_opts,
+	.xlate		= cluster_xlate,
 };
 
 void _init(void)
diff --git a/extensions/libxt_cluster.man b/extensions/libxt_cluster.man
index 62ad71c..23448e2 100644
--- a/extensions/libxt_cluster.man
+++ b/extensions/libxt_cluster.man
@@ -27,7 +27,7 @@
 iptables \-A PREROUTING \-t mangle \-i eth2 \-m cluster
 \-\-cluster\-total\-nodes 2 \-\-cluster\-local\-node 1
 \-\-cluster\-hash\-seed 0xdeadbeef
-\-j MARK -\-set\-mark 0xffff
+\-j MARK \-\-set\-mark 0xffff
 .IP
 iptables \-A PREROUTING \-t mangle \-i eth1
 \-m mark ! \-\-mark 0xffff \-j DROP
@@ -55,6 +55,11 @@
 \-\-destination\-mac 01:00:5e:00:01:02
 \-j mangle \-\-mangle\-mac\-d 00:zz:yy:xx:5a:27
 .PP
+\fBNOTE\fP: the arptables commands above use mainstream syntax. If you
+are using arptables-jf included in some RedHat, CentOS and Fedora
+versions, you will hit syntax errors. Therefore, you'll have to adapt
+these to the arptables-jf syntax to get them working.
+.PP
 In the case of TCP connections, pickup facility has to be disabled
 to avoid marking TCP ACK packets coming in the reply direction as
 valid.
diff --git a/extensions/libxt_cluster.t b/extensions/libxt_cluster.t
new file mode 100644
index 0000000..ac60824
--- /dev/null
+++ b/extensions/libxt_cluster.t
@@ -0,0 +1,10 @@
+:PREROUTING,FORWARD,POSTROUTING
+*mangle
+-m cluster;;FAIL
+-m cluster --cluster-total-nodes 3;;FAIL
+-m cluster --cluster-total-nodes 2 --cluster-local-node 2;;FAIL
+-m cluster --cluster-total-nodes 2 --cluster-local-node 3 --cluster-hash-seed;;FAIL
+#
+# outputs --cluster-local-nodemask instead of --cluster-local-node
+#
+-m cluster --cluster-total-nodes 2 --cluster-local-node 2 --cluster-hash-seed 0xfeedcafe;-m cluster --cluster-local-nodemask 0x00000002 --cluster-total-nodes 2 --cluster-hash-seed 0xfeedcafe;OK
diff --git a/extensions/libxt_cluster.txlate b/extensions/libxt_cluster.txlate
new file mode 100644
index 0000000..9dcf570
--- /dev/null
+++ b/extensions/libxt_cluster.txlate
@@ -0,0 +1,26 @@
+iptables-translate -A PREROUTING -t mangle -i eth1 -m cluster --cluster-total-nodes 2 --cluster-local-node 1 --cluster-hash-seed 0xdeadbeef -j MARK --set-mark 0xffff
+nft add rule ip mangle PREROUTING iifname "eth1" jhash ct original saddr mod 2 seed 0xdeadbeef eq 1 meta pkttype set host counter meta mark set 0xffff
+
+iptables-translate -A PREROUTING -t mangle -i eth1 -m cluster --cluster-total-nodes 1 --cluster-local-node 1 --cluster-hash-seed 0xdeadbeef -j MARK --set-mark 0xffff
+nft add rule ip mangle PREROUTING iifname "eth1" jhash ct original saddr mod 1 seed 0xdeadbeef eq 1 meta pkttype set host counter meta mark set 0xffff
+
+iptables-translate -A PREROUTING -t mangle -i eth1 -m cluster --cluster-total-nodes 2 --cluster-local-nodemask 1 --cluster-hash-seed 0xdeadbeef -j MARK --set-mark 0xffff
+nft add rule ip mangle PREROUTING iifname "eth1" jhash ct original saddr mod 2 seed 0xdeadbeef eq 1 meta pkttype set host counter meta mark set 0xffff
+
+iptables-translate -A PREROUTING -t mangle -i eth1 -m cluster --cluster-total-nodes 1 --cluster-local-nodemask 1 --cluster-hash-seed 0xdeadbeef -j MARK --set-mark 0xffff
+nft add rule ip mangle PREROUTING iifname "eth1" jhash ct original saddr mod 1 seed 0xdeadbeef eq 1 meta pkttype set host counter meta mark set 0xffff
+
+iptables-translate -A PREROUTING -t mangle -i eth1 -m cluster --cluster-total-nodes 32 --cluster-local-node 32 --cluster-hash-seed 0xdeadbeef -j MARK --set-mark 0xffff
+nft add rule ip mangle PREROUTING iifname "eth1" jhash ct original saddr mod 32 seed 0xdeadbeef eq 32 meta pkttype set host counter meta mark set 0xffff
+
+iptables-translate -A PREROUTING -t mangle -i eth1 -m cluster --cluster-total-nodes 32 --cluster-local-nodemask 32 --cluster-hash-seed 0xdeadbeef -j MARK --set-mark 0xffff
+nft add rule ip mangle PREROUTING iifname "eth1" jhash ct original saddr mod 32 seed 0xdeadbeef eq 6 meta pkttype set host counter meta mark set 0xffff
+
+iptables-translate -A PREROUTING -t mangle -i eth1 -m cluster --cluster-total-nodes 32 --cluster-local-nodemask 5 --cluster-hash-seed 0xdeadbeef -j MARK --set-mark 0xffff
+nft add rule ip mangle PREROUTING iifname "eth1" jhash ct original saddr mod 32 seed 0xdeadbeef { 0, 2 } meta pkttype set host counter meta mark set 0xffff
+
+iptables-translate -A PREROUTING -t mangle -i eth1 -m cluster --cluster-total-nodes 7 --cluster-local-nodemask 9 --cluster-hash-seed 0xdeadbeef -j MARK --set-mark 0xffff
+nft add rule ip mangle PREROUTING iifname "eth1" jhash ct original saddr mod 7 seed 0xdeadbeef { 0, 3 } meta pkttype set host counter meta mark set 0xffff
+
+iptables-translate -A PREROUTING -t mangle -i eth1 -m cluster --cluster-total-nodes 7 --cluster-local-node 5 --cluster-hash-seed 0xdeadbeef -j MARK --set-mark 0xffff
+nft add rule ip mangle PREROUTING iifname "eth1" jhash ct original saddr mod 7 seed 0xdeadbeef eq 5 meta pkttype set host counter meta mark set 0xffff
diff --git a/extensions/libxt_comment.c b/extensions/libxt_comment.c
index 6ed2ff9..69795b6 100644
--- a/extensions/libxt_comment.c
+++ b/extensions/libxt_comment.c
@@ -48,6 +48,25 @@
 	xtables_save_string(commentinfo->comment);
 }
 
+static int comment_xlate(struct xt_xlate *xl,
+			 const struct xt_xlate_mt_params *params)
+{
+	struct xt_comment_info *commentinfo = (void *)params->match->data;
+	char comment[XT_MAX_COMMENT_LEN + sizeof("\\\"\\\"")];
+
+	commentinfo->comment[XT_MAX_COMMENT_LEN - 1] = '\0';
+	if (params->escape_quotes)
+		snprintf(comment, sizeof(comment), "\\\"%s\\\"",
+			 commentinfo->comment);
+	else
+		snprintf(comment, sizeof(comment), "\"%s\"",
+			 commentinfo->comment);
+
+	xt_xlate_add_comment(xl, comment);
+
+	return 1;
+}
+
 static struct xtables_match comment_match = {
 	.family		= NFPROTO_UNSPEC,
 	.name		= "comment",
@@ -59,6 +78,7 @@
 	.save 		= comment_save,
 	.x6_parse	= xtables_option_parse,
 	.x6_options	= comment_opts,
+	.xlate		= comment_xlate,
 };
 
 void _init(void)
diff --git a/extensions/libxt_comment.t b/extensions/libxt_comment.t
new file mode 100644
index 0000000..f0c8fb9
--- /dev/null
+++ b/extensions/libxt_comment.t
@@ -0,0 +1,14 @@
+:INPUT,FORWARD,OUTPUT
+-m comment;;FAIL
+-m comment --comment;;FAIL
+-p tcp -m tcp --dport 22 -m comment --comment foo;=;OK
+-p tcp -m comment --comment foo -m tcp --dport 22;=;OK
+#
+# it fails with 256 characters
+#
+# should fail: iptables -A INPUT -m comment --comment xxxxxxxxxxxxxxxxx [....]
+# -m comment --comment xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;;FAIL
+#
+# success with 255 characters
+#
+-m comment --comment xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;=;OK
diff --git a/extensions/libxt_comment.txlate b/extensions/libxt_comment.txlate
new file mode 100644
index 0000000..c610b0e
--- /dev/null
+++ b/extensions/libxt_comment.txlate
@@ -0,0 +1,8 @@
+iptables-translate -A INPUT -s 192.168.0.0 -m comment --comment "A privatized IP block"
+nft add rule ip filter INPUT ip saddr 192.168.0.0 counter comment \"A privatized IP block\"
+
+iptables-translate -A INPUT -p tcp -m tcp --sport http -s  192.168.0.0/16 -d 192.168.0.0/16 -j LONGNACCEPT -m comment --comment "foobar"
+nft add rule ip filter INPUT ip saddr 192.168.0.0/16 ip daddr 192.168.0.0/16 tcp sport 80 counter jump LONGNACCEPT comment \"foobar\"
+
+iptables-translate -A FORWARD -p tcp -m tcp --sport http -s 192.168.0.0/16 -d 192.168.0.0/16 -j DROP -m comment --comment singlecomment
+nft add rule ip filter FORWARD ip saddr 192.168.0.0/16 ip daddr 192.168.0.0/16 tcp sport 80 counter drop comment \"singlecomment\"
diff --git a/extensions/libxt_connbytes.c b/extensions/libxt_connbytes.c
index 46a7e4b..b57f0fc 100644
--- a/extensions/libxt_connbytes.c
+++ b/extensions/libxt_connbytes.c
@@ -37,9 +37,14 @@
 	switch (cb->entry->id) {
 	case O_CONNBYTES:
 		sinfo->count.from = cb->val.u64_range[0];
-		sinfo->count.to   = cb->val.u64_range[0];
+		sinfo->count.to   = UINT64_MAX;
 		if (cb->nvals == 2)
 			sinfo->count.to = cb->val.u64_range[1];
+
+		if (sinfo->count.to < sinfo->count.from)
+			xtables_error(PARAMETER_PROBLEM, "%llu should be less than %llu",
+					(unsigned long long)sinfo->count.from,
+					(unsigned long long)sinfo->count.to);
 		if (cb->invert) {
 			i = sinfo->count.from;
 			sinfo->count.from = sinfo->count.to;
@@ -107,19 +112,29 @@
 	}
 }
 
+static void print_from_to(const struct xt_connbytes_info *sinfo, const char *prefix)
+{
+	unsigned long long from, to;
+
+	if (sinfo->count.from > sinfo->count.to) {
+		fputs(" !", stdout);
+		from = sinfo->count.to;
+		to = sinfo->count.from;
+	} else {
+		to = sinfo->count.to;
+		from = sinfo->count.from;
+	}
+	printf(" %sconnbytes %llu", prefix, from);
+	if (to && to < UINT64_MAX)
+		printf(":%llu", to);
+}
+
 static void
 connbytes_print(const void *ip, const struct xt_entry_match *match, int numeric)
 {
 	const struct xt_connbytes_info *sinfo = (const void *)match->data;
 
-	if (sinfo->count.from > sinfo->count.to) 
-		printf(" connbytes ! %llu:%llu",
-			(unsigned long long)sinfo->count.to,
-			(unsigned long long)sinfo->count.from);
-	else
-		printf(" connbytes %llu:%llu",
-			(unsigned long long)sinfo->count.from,
-			(unsigned long long)sinfo->count.to);
+	print_from_to(sinfo, "");
 
 	fputs(" connbytes mode", stdout);
 	print_mode(sinfo);
@@ -132,14 +147,7 @@
 {
 	const struct xt_connbytes_info *sinfo = (const void *)match->data;
 
-	if (sinfo->count.from > sinfo->count.to) 
-		printf(" ! --connbytes %llu:%llu",
-			(unsigned long long)sinfo->count.to,
-			(unsigned long long)sinfo->count.from);
-	else
-		printf(" --connbytes %llu:%llu",
-			(unsigned long long)sinfo->count.from,
-			(unsigned long long)sinfo->count.to);
+	print_from_to(sinfo, "--");
 
 	fputs(" --connbytes-mode", stdout);
 	print_mode(sinfo);
@@ -148,6 +156,61 @@
 	print_direction(sinfo);
 }
 
+
+static int connbytes_xlate(struct xt_xlate *xl,
+			   const struct xt_xlate_mt_params *params)
+{
+	const struct xt_connbytes_info *info = (void *)params->match->data;
+	unsigned long long from, to;
+	bool invert = false;
+
+	xt_xlate_add(xl, "ct ");
+
+	switch (info->direction) {
+	case XT_CONNBYTES_DIR_ORIGINAL:
+		xt_xlate_add(xl, "original ");
+		break;
+	case XT_CONNBYTES_DIR_REPLY:
+		xt_xlate_add(xl, "reply ");
+		break;
+	case XT_CONNBYTES_DIR_BOTH:
+		break;
+	default:
+		return 0;
+	}
+
+	switch (info->what) {
+	case XT_CONNBYTES_PKTS:
+		xt_xlate_add(xl, "packets ");
+		break;
+	case XT_CONNBYTES_BYTES:
+		xt_xlate_add(xl, "bytes ");
+		break;
+	case XT_CONNBYTES_AVGPKT:
+		xt_xlate_add(xl, "avgpkt ");
+		break;
+	default:
+		return 0;
+	}
+
+	if (info->count.from > info->count.to) {
+		invert = true;
+		from = info->count.to;
+		to = info->count.from;
+	} else {
+		to = info->count.to;
+		from = info->count.from;
+	}
+
+	if (from == to)
+		xt_xlate_add(xl, "%llu", from);
+	else if (to == UINT64_MAX)
+		xt_xlate_add(xl, "%s %llu", invert ? "lt" : "ge", from);
+	else
+		xt_xlate_add(xl, "%s%llu-%llu", invert ? "!= " : "", from, to);
+	return 1;
+}
+
 static struct xtables_match connbytes_match = {
 	.family		= NFPROTO_UNSPEC,
 	.name 		= "connbytes",
@@ -159,6 +222,7 @@
 	.save 		= connbytes_save,
 	.x6_parse	= connbytes_parse,
 	.x6_options	= connbytes_opts,
+	.xlate		= connbytes_xlate,
 };
 
 void _init(void)
diff --git a/extensions/libxt_connbytes.t b/extensions/libxt_connbytes.t
new file mode 100644
index 0000000..6b24e26
--- /dev/null
+++ b/extensions/libxt_connbytes.t
@@ -0,0 +1,21 @@
+:INPUT,FORWARD,OUTPUT
+-m connbytes --connbytes 0:1000 --connbytes-mode packets --connbytes-dir original;=;OK
+-m connbytes --connbytes 0:1000 --connbytes-mode packets --connbytes-dir reply;=;OK
+-m connbytes --connbytes 0:1000 --connbytes-mode packets --connbytes-dir both;=;OK
+-m connbytes --connbytes 0:1000 --connbytes-mode bytes --connbytes-dir original;=;OK
+-m connbytes --connbytes 0:1000 --connbytes-mode bytes --connbytes-dir reply;=;OK
+-m connbytes --connbytes 0:1000 --connbytes-mode bytes --connbytes-dir both;=;OK
+-m connbytes --connbytes 0:1000 --connbytes-mode avgpkt --connbytes-dir original;=;OK
+-m connbytes --connbytes 0:1000 --connbytes-mode avgpkt --connbytes-dir reply;=;OK
+-m connbytes --connbytes 0:1000 --connbytes-mode avgpkt --connbytes-dir both;=;OK
+-m connbytes --connbytes -1:0 --connbytes-mode packets --connbytes-dir original;;FAIL
+-m connbytes --connbytes 0:-1 --connbytes-mode packets --connbytes-dir original;;FAIL
+# ERROR: cannot find: iptables -I INPUT -m connbytes --connbytes 0:18446744073709551615 --connbytes-mode avgpkt --connbytes-dir both
+# -m connbytes --connbytes 0:18446744073709551615 --connbytes-mode avgpkt --connbytes-dir both;=;OK
+-m connbytes --connbytes 0:18446744073709551616 --connbytes-mode avgpkt --connbytes-dir both;;FAIL
+-m connbytes --connbytes 0:1000 --connbytes-mode wrong --connbytes-dir both;;FAIL
+-m connbytes --connbytes 0:1000 --connbytes-dir original;;FAIL
+-m connbytes --connbytes 0:1000 --connbytes-mode packets;;FAIL
+-m connbytes --connbytes-dir original;;FAIL
+-m connbytes --connbytes 0:1000;;FAIL
+-m connbytes;;FAIL
diff --git a/extensions/libxt_connbytes.txlate b/extensions/libxt_connbytes.txlate
new file mode 100644
index 0000000..f78958d
--- /dev/null
+++ b/extensions/libxt_connbytes.txlate
@@ -0,0 +1,14 @@
+iptables-translate -A OUTPUT -m connbytes --connbytes 200 --connbytes-dir original --connbytes-mode packets
+nft add rule ip filter OUTPUT ct original packets ge 200 counter
+
+iptables-translate -A OUTPUT -m connbytes ! --connbytes 200 --connbytes-dir reply --connbytes-mode packets
+nft add rule ip filter OUTPUT ct reply packets lt 200 counter
+
+iptables-translate -A OUTPUT -m connbytes --connbytes 200:600 --connbytes-dir both --connbytes-mode bytes
+nft add rule ip filter OUTPUT ct bytes 200-600 counter
+
+iptables-translate -A OUTPUT -m connbytes ! --connbytes 200:600 --connbytes-dir both --connbytes-mode bytes
+nft add rule ip filter OUTPUT ct bytes != 200-600 counter
+
+iptables-translate -A OUTPUT -m connbytes --connbytes 200:200 --connbytes-dir both --connbytes-mode avgpkt
+nft add rule ip filter OUTPUT ct avgpkt 200 counter
diff --git a/extensions/libxt_connlabel.c b/extensions/libxt_connlabel.c
new file mode 100644
index 0000000..565b8c7
--- /dev/null
+++ b/extensions/libxt_connlabel.c
@@ -0,0 +1,193 @@
+#define _GNU_SOURCE
+#include <errno.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_connlabel.h>
+#include <libnetfilter_conntrack/libnetfilter_conntrack.h>
+
+enum {
+	O_LABEL = 0,
+	O_SET = 1,
+};
+
+static struct nfct_labelmap *map;
+
+static void connlabel_mt_help(void)
+{
+	puts(
+"connlabel match options:\n"
+"[!] --label name     Match if label has been set on connection\n"
+"    --set            Set label on connection");
+}
+
+static const struct xt_option_entry connlabel_mt_opts[] = {
+	{.name = "label", .id = O_LABEL, .type = XTTYPE_STRING,
+	 .min = 1, .flags = XTOPT_MAND|XTOPT_INVERT},
+	{.name = "set", .id = O_SET, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+
+/* cannot do this via _init, else static builds might spew error message
+ * for every iptables invocation.
+ */
+static int connlabel_open(void)
+{
+	const char *fname;
+
+	if (map)
+		return 0;
+
+	map = nfct_labelmap_new(NULL);
+	if (map != NULL)
+		return 0;
+
+	fname = nfct_labels_get_path();
+	if (errno) {
+		fprintf(stderr, "Warning: cannot open %s: %s\n",
+			fname, strerror(errno));
+	} else {
+		xtables_error(RESOURCE_PROBLEM,
+			"cannot parse %s: no labels found", fname);
+	}
+	return 1;
+}
+
+static int connlabel_value_parse(const char *in)
+{
+	char *end;
+	unsigned long value = strtoul(in, &end, 0);
+
+	if (in[0] == '\0' || *end != '\0')
+		return -1;
+
+	return value;
+}
+
+static void connlabel_mt_parse(struct xt_option_call *cb)
+{
+	struct xt_connlabel_mtinfo *info = cb->data;
+	int tmp;
+
+	xtables_option_parse(cb);
+
+	switch (cb->entry->id) {
+	case O_LABEL:
+		tmp = connlabel_value_parse(cb->arg);
+		if (tmp < 0 && !connlabel_open())
+			tmp = nfct_labelmap_get_bit(map, cb->arg);
+		if (tmp < 0)
+			xtables_error(PARAMETER_PROBLEM,
+				      "label '%s' not found or invalid value",
+				      cb->arg);
+
+		info->bit = tmp;
+		if (cb->invert)
+			info->options |= XT_CONNLABEL_OP_INVERT;
+		break;
+	case O_SET:
+		info->options |= XT_CONNLABEL_OP_SET;
+		break;
+	}
+
+}
+
+static const char *connlabel_get_name(int b)
+{
+	const char *name;
+
+	if (connlabel_open())
+		return NULL;
+
+	name = nfct_labelmap_get_name(map, b);
+	if (name && strcmp(name, ""))
+		return name;
+	return NULL;
+}
+
+static void
+connlabel_mt_print_op(const struct xt_connlabel_mtinfo *info, const char *prefix)
+{
+	if (info->options & XT_CONNLABEL_OP_SET)
+		printf(" %sset", prefix);
+}
+
+static void
+connlabel_mt_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_connlabel_mtinfo *info = (const void *)match->data;
+	const char *name = connlabel_get_name(info->bit);
+
+	printf(" connlabel");
+	if (info->options & XT_CONNLABEL_OP_INVERT)
+		printf(" !");
+	if (numeric || name == NULL) {
+		printf(" %u", info->bit);
+	} else {
+		printf(" '%s'", name);
+	}
+	connlabel_mt_print_op(info, "");
+}
+
+static void
+connlabel_mt_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_connlabel_mtinfo *info = (const void *)match->data;
+	const char *name = connlabel_get_name(info->bit);
+
+	if (info->options & XT_CONNLABEL_OP_INVERT)
+		printf(" !");
+	if (name)
+		printf(" --label \"%s\"", name);
+	else
+		printf(" --label \"%u\"", info->bit);
+	connlabel_mt_print_op(info, "--");
+}
+
+static int connlabel_mt_xlate(struct xt_xlate *xl,
+			      const struct xt_xlate_mt_params *params)
+{
+	const struct xt_connlabel_mtinfo *info =
+		(const void *)params->match->data;
+	const char *name = connlabel_get_name(info->bit);
+	char *valbuf = NULL;
+
+	if (name == NULL) {
+		if (asprintf(&valbuf, "%u", info->bit) < 0)
+			return 0;
+		name = valbuf;
+	}
+
+	if (info->options & XT_CONNLABEL_OP_SET)
+		xt_xlate_add(xl, "ct label set %s ", name);
+
+	xt_xlate_add(xl, "ct label ");
+	if (info->options & XT_CONNLABEL_OP_INVERT)
+		xt_xlate_add(xl, "and %s != ", name);
+	xt_xlate_add(xl, "%s", name);
+
+	free(valbuf);
+	return 1;
+}
+
+static struct xtables_match connlabel_mt_reg = {
+	.family        = NFPROTO_UNSPEC,
+	.name          = "connlabel",
+	.version       = XTABLES_VERSION,
+	.size          = XT_ALIGN(sizeof(struct xt_connlabel_mtinfo)),
+	.userspacesize = offsetof(struct xt_connlabel_mtinfo, bit),
+	.help          = connlabel_mt_help,
+	.print         = connlabel_mt_print,
+	.save          = connlabel_mt_save,
+	.x6_parse      = connlabel_mt_parse,
+	.x6_options    = connlabel_mt_opts,
+	.xlate	       = connlabel_mt_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_match(&connlabel_mt_reg);
+}
diff --git a/extensions/libxt_connlabel.man b/extensions/libxt_connlabel.man
new file mode 100644
index 0000000..bdaa51e
--- /dev/null
+++ b/extensions/libxt_connlabel.man
@@ -0,0 +1,33 @@
+Module matches or adds connlabels to a connection.
+connlabels are similar to connmarks, except labels are bit-based; i.e.
+all labels may be attached to a flow at the same time.
+Up to 128 unique labels are currently supported.
+.TP
+[\fB!\fP] \fB\-\-label\fP \fBname\fP
+matches if label \fBname\fP has been set on a connection.
+Instead of a name (which will be translated to a number, see EXAMPLE below),
+a number may be used instead.  Using a number always overrides connlabel.conf.
+.TP
+\fB\-\-set\fP
+if the label has not been set on the connection, set it.
+Note that setting a label can fail.  This is because the kernel allocates the
+conntrack label storage area when the connection is created, and it only
+reserves the amount of memory required by the ruleset that exists at
+the time the connection is created.
+In this case, the match will fail (or succeed, in case \fB\-\-label\fP
+option was negated).
+.PP
+This match depends on libnetfilter_conntrack 1.0.4 or later.
+Label translation is done via the \fB/etc/xtables/connlabel.conf\fP configuration file.
+.PP
+Example:
+.IP
+.nf
+0	eth0-in
+1	eth0-out
+2	ppp-in
+3	ppp-out
+4	bulk-traffic
+5	interactive
+.fi
+.PP
diff --git a/extensions/libxt_connlabel.t b/extensions/libxt_connlabel.t
new file mode 100644
index 0000000..7265bd4
--- /dev/null
+++ b/extensions/libxt_connlabel.t
@@ -0,0 +1,7 @@
+:INPUT,FORWARD,OUTPUT
+-m connlabel --label "40";=;OK
+-m connlabel ! --label "40";=;OK
+-m connlabel --label "41" --set;=;OK
+-m connlabel ! --label "41" --set;=;OK
+-m connlabel --label "2048";;FAIL
+-m connlabel --label "foobar_not_there";;FAIL
diff --git a/extensions/libxt_connlabel.txlate b/extensions/libxt_connlabel.txlate
new file mode 100644
index 0000000..12e4ac0
--- /dev/null
+++ b/extensions/libxt_connlabel.txlate
@@ -0,0 +1,5 @@
+iptables-translate -A INPUT -m connlabel --label 40
+nft add rule ip filter INPUT ct label 40 counter
+
+iptables-translate -A INPUT -m connlabel ! --label 40 --set
+nft add rule ip filter INPUT ct label set 40 ct label and 40 != 40 counter
diff --git a/extensions/libxt_connlimit.man b/extensions/libxt_connlimit.man
index bd369a6..ad9f40f 100644
--- a/extensions/libxt_connlimit.man
+++ b/extensions/libxt_connlimit.man
@@ -13,7 +13,8 @@
 maximum prefix length for the applicable protocol is used.
 .TP
 \fB\-\-connlimit\-saddr\fP
-Apply the limit onto the source group.
+Apply the limit onto the source group. This is the default if
+\-\-connlimit\-daddr is not specified.
 .TP
 \fB\-\-connlimit\-daddr\fP
 Apply the limit onto the destination group.
diff --git a/extensions/libxt_connlimit.t b/extensions/libxt_connlimit.t
new file mode 100644
index 0000000..c7ea61e
--- /dev/null
+++ b/extensions/libxt_connlimit.t
@@ -0,0 +1,16 @@
+:INPUT,FORWARD,OUTPUT
+-m connlimit --connlimit-upto 0;=;OK
+-m connlimit --connlimit-upto 4294967295;=;OK
+-m connlimit --connlimit-upto 4294967296;;FAIL
+-m connlimit --connlimit-upto -1;;FAIL
+-m connlimit --connlimit-above 0;=;OK
+-m connlimit --connlimit-above 4294967295;=;OK
+-m connlimit --connlimit-above 4294967296;;FAIL
+-m connlimit --connlimit-above -1;;FAIL
+-m connlimit --connlimit-upto 1 --conlimit-above 1;;FAIL
+-m connlimit --connlimit-above 10 --connlimit-saddr;-m connlimit --connlimit-above 10 --connlimit-mask 32 --connlimit-saddr;OK
+-m connlimit --connlimit-above 10 --connlimit-daddr;-m connlimit --connlimit-above 10 --connlimit-mask 32 --connlimit-daddr;OK
+-m connlimit --connlimit-above 10 --connlimit-saddr --connlimit-daddr;;FAIL
+-m connlimit --connlimit-above 10 --connlimit-mask 32 --connlimit-saddr;=;OK
+-m connlimit --connlimit-above 10 --connlimit-mask 32 --connlimit-daddr;=;OK
+-m connlimit;;FAIL
diff --git a/extensions/libxt_connmark.c b/extensions/libxt_connmark.c
index 6f1d532..cb4264e 100644
--- a/extensions/libxt_connmark.c
+++ b/extensions/libxt_connmark.c
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 #include <stdbool.h>
 #include <stdint.h>
@@ -69,14 +69,6 @@
 		markinfo->invert = 1;
 }
 
-static void print_mark(unsigned int mark, unsigned int mask)
-{
-	if (mask != 0xffffffffU)
-		printf(" 0x%x/0x%x", mark, mask);
-	else
-		printf(" 0x%x", mark);
-}
-
 static void
 connmark_print(const void *ip, const struct xt_entry_match *match, int numeric)
 {
@@ -85,18 +77,21 @@
 	printf(" CONNMARK match ");
 	if (info->invert)
 		printf("!");
-	print_mark(info->mark, info->mask);
+
+	xtables_print_mark_mask(info->mark, info->mask);
 }
 
 static void
-connmark_mt_print(const void *ip, const struct xt_entry_match *match, int numeric)
+connmark_mt_print(const void *ip, const struct xt_entry_match *match,
+		  int numeric)
 {
 	const struct xt_connmark_mtinfo1 *info = (const void *)match->data;
 
 	printf(" connmark match ");
 	if (info->invert)
 		printf("!");
-	print_mark(info->mark, info->mask);
+
+	xtables_print_mark_mask(info->mark, info->mask);
 }
 
 static void connmark_save(const void *ip, const struct xt_entry_match *match)
@@ -107,7 +102,7 @@
 		printf(" !");
 
 	printf(" --mark");
-	print_mark(info->mark, info->mask);
+	xtables_print_mark_mask(info->mark, info->mask);
 }
 
 static void
@@ -119,7 +114,50 @@
 		printf(" !");
 
 	printf(" --mark");
-	print_mark(info->mark, info->mask);
+	xtables_print_mark_mask(info->mark, info->mask);
+}
+
+static void print_mark_xlate(unsigned int mark, unsigned int mask,
+			     struct xt_xlate *xl, uint32_t op)
+{
+	if (mask != 0xffffffffU)
+		xt_xlate_add(xl, " and 0x%x %s 0x%x", mask,
+			   op == XT_OP_EQ ? "==" : "!=", mark);
+	else
+		xt_xlate_add(xl, " %s0x%x",
+			   op == XT_OP_EQ ? "" : "!= ", mark);
+}
+
+static int connmark_xlate(struct xt_xlate *xl,
+			  const struct xt_xlate_mt_params *params)
+{
+	const struct xt_connmark_info *info = (const void *)params->match->data;
+	enum xt_op op = XT_OP_EQ;
+
+	if (info->invert)
+		op = XT_OP_NEQ;
+
+	xt_xlate_add(xl, "ct mark");
+	print_mark_xlate(info->mark, info->mask, xl, op);
+
+	return 1;
+}
+
+static int
+connmark_mt_xlate(struct xt_xlate *xl,
+		  const struct xt_xlate_mt_params *params)
+{
+	const struct xt_connmark_mtinfo1 *info =
+		(const void *)params->match->data;
+	enum xt_op op = XT_OP_EQ;
+
+	if (info->invert)
+		op = XT_OP_NEQ;
+
+	xt_xlate_add(xl, "ct mark");
+	print_mark_xlate(info->mark, info->mask, xl, op);
+
+	return 1;
 }
 
 static struct xtables_match connmark_mt_reg[] = {
@@ -135,6 +173,7 @@
 		.save          = connmark_save,
 		.x6_parse      = connmark_parse,
 		.x6_options    = connmark_mt_opts,
+		.xlate	       = connmark_xlate,
 	},
 	{
 		.version       = XTABLES_VERSION,
@@ -148,6 +187,7 @@
 		.save          = connmark_mt_save,
 		.x6_parse      = connmark_mt_parse,
 		.x6_options    = connmark_mt_opts,
+		.xlate	       = connmark_mt_xlate,
 	},
 };
 
diff --git a/extensions/libxt_connmark.t b/extensions/libxt_connmark.t
new file mode 100644
index 0000000..4dd7d9a
--- /dev/null
+++ b/extensions/libxt_connmark.t
@@ -0,0 +1,9 @@
+:PREROUTING,FORWARD,OUTPUT,POSTROUTING
+*mangle
+-m connmark --mark 0xffffffff;=;OK
+-m connmark --mark 0xffffffff/0xffffffff;-m connmark --mark 0xffffffff;OK
+-m connmark --mark 0xffffffff/0;=;OK
+-m connmark --mark 0/0xffffffff;-m connmark --mark 0;OK
+-m connmark --mark -1;;FAIL
+-m connmark --mark 0xfffffffff;;FAIL
+-m connmark;;FAIL
diff --git a/extensions/libxt_connmark.txlate b/extensions/libxt_connmark.txlate
new file mode 100644
index 0000000..8942325
--- /dev/null
+++ b/extensions/libxt_connmark.txlate
@@ -0,0 +1,14 @@
+iptables-translate -A INPUT -m connmark --mark 2 -j ACCEPT
+nft add rule ip filter INPUT ct mark 0x2 counter accept
+
+iptables-translate -A INPUT -m connmark ! --mark 2 -j ACCEPT
+nft add rule ip filter INPUT ct mark != 0x2 counter accept
+
+iptables-translate -A INPUT -m connmark --mark 10/10 -j ACCEPT
+nft add rule ip filter INPUT ct mark and 0xa == 0xa counter accept
+
+iptables-translate -A INPUT -m connmark ! --mark 10/10 -j ACCEPT
+nft add rule ip filter INPUT ct mark and 0xa != 0xa counter accept
+
+iptables-translate -t mangle -A PREROUTING -p tcp --dport 40 -m connmark --mark 0x40
+nft add rule ip mangle PREROUTING tcp dport 40 ct mark 0x40 counter
diff --git a/extensions/libxt_conntrack.c b/extensions/libxt_conntrack.c
index e1d8575..7734509 100644
--- a/extensions/libxt_conntrack.c
+++ b/extensions/libxt_conntrack.c
@@ -13,7 +13,11 @@
 #include <string.h>
 #include <xtables.h>
 #include <linux/netfilter/xt_conntrack.h>
+#include <linux/netfilter/xt_state.h>
 #include <linux/netfilter/nf_conntrack_common.h>
+#ifndef XT_STATE_UNTRACKED
+#define XT_STATE_UNTRACKED (1 << (IP_CT_NUMBER + 1))
+#endif
 
 struct ip_conntrack_old_tuple {
 	struct {
@@ -110,9 +114,48 @@
 };
 #undef s
 
-#define s struct xt_conntrack_mtinfo3 /* for v1-v3 */
-/* We exploit the fact that v1-v3 share the same layout */
-static const struct xt_option_entry conntrack_mt_opts[] = {
+#define s struct xt_conntrack_mtinfo2
+/* We exploit the fact that v1-v2 share the same xt_o_e layout */
+static const struct xt_option_entry conntrack2_mt_opts[] = {
+	{.name = "ctstate", .id = O_CTSTATE, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctproto", .id = O_CTPROTO, .type = XTTYPE_PROTOCOL,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctorigsrc", .id = O_CTORIGSRC, .type = XTTYPE_HOSTMASK,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctorigdst", .id = O_CTORIGDST, .type = XTTYPE_HOSTMASK,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctreplsrc", .id = O_CTREPLSRC, .type = XTTYPE_HOSTMASK,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctrepldst", .id = O_CTREPLDST, .type = XTTYPE_HOSTMASK,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctstatus", .id = O_CTSTATUS, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
+	{.name = "ctexpire", .id = O_CTEXPIRE, .type = XTTYPE_UINT32RC,
+	 .flags = XTOPT_INVERT},
+	/*
+	 * Rev 1 and 2 only store one port, and we would normally use
+	 * %XTTYPE_PORT (rather than %XTTYPE_PORTRC) for that. The resulting
+	 * error message - in case a user passed a range nevertheless -
+	 * "port 22:23 resolved to nothing" is not quite as useful as using
+	 * %XTTYPE_PORTC and libxt_conntrack's own range test.
+	 */
+	{.name = "ctorigsrcport", .id = O_CTORIGSRCPORT, .type = XTTYPE_PORTRC,
+	 .flags = XTOPT_INVERT | XTOPT_NBO},
+	{.name = "ctorigdstport", .id = O_CTORIGDSTPORT, .type = XTTYPE_PORTRC,
+	 .flags = XTOPT_INVERT | XTOPT_NBO},
+	{.name = "ctreplsrcport", .id = O_CTREPLSRCPORT, .type = XTTYPE_PORTRC,
+	 .flags = XTOPT_INVERT | XTOPT_NBO},
+	{.name = "ctrepldstport", .id = O_CTREPLDSTPORT, .type = XTTYPE_PORTRC,
+	 .flags = XTOPT_INVERT | XTOPT_NBO},
+	{.name = "ctdir", .id = O_CTDIR, .type = XTTYPE_STRING},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+#define s struct xt_conntrack_mtinfo3
+/* Difference from v2 is the non-NBO form. */
+static const struct xt_option_entry conntrack3_mt_opts[] = {
 	{.name = "ctstate", .id = O_CTSTATE, .type = XTTYPE_STRING,
 	 .flags = XTOPT_INVERT},
 	{.name = "ctproto", .id = O_CTPROTO, .type = XTTYPE_PROTOCOL,
@@ -303,10 +346,9 @@
 			sinfo->invflags |= XT_CONNTRACK_STATE;
 		break;
 	case O_CTPROTO:
+		sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum = cb->val.protocol;
 		if (cb->invert)
 			sinfo->invflags |= XT_CONNTRACK_PROTO;
-		sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum = cb->val.protocol;
-
 		if (sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum == 0
 		    && (sinfo->invflags & XT_INV_PROTO))
 			xtables_error(PARAMETER_PROBLEM,
@@ -631,20 +673,20 @@
 print_addr(const struct in_addr *addr, const struct in_addr *mask,
            int inv, int numeric)
 {
-	char buf[BUFSIZ];
-
 	if (inv)
 		printf(" !");
 
 	if (mask->s_addr == 0L && !numeric)
-		printf(" %s", "anywhere");
+		printf(" anywhere");
 	else {
 		if (numeric)
-			strcpy(buf, xtables_ipaddr_to_numeric(addr));
+			printf(" %s%s",
+			       xtables_ipaddr_to_numeric(addr),
+			       xtables_ipmask_to_numeric(mask));
 		else
-			strcpy(buf, xtables_ipaddr_to_anyname(addr));
-		strcat(buf, xtables_ipmask_to_numeric(mask));
-		printf(" %s", buf);
+			printf(" %s%s",
+			       xtables_ipaddr_to_anyname(addr),
+			       xtables_ipmask_to_numeric(mask));
 	}
 }
 
@@ -732,14 +774,6 @@
         	else
 			printf("%lu:%lu", sinfo->expires_min, sinfo->expires_max);
 	}
-
-	if (sinfo->flags & XT_CONNTRACK_DIRECTION) {
-		if (sinfo->invflags & XT_CONNTRACK_DIRECTION)
-			printf(" %sctdir REPLY", optpfx);
-		else
-			printf(" %sctdir ORIGINAL", optpfx);
-	}
-
 }
 
 static void
@@ -759,7 +793,9 @@
 	if (info->match_flags & XT_CONNTRACK_STATE) {
 		if (info->invert_flags & XT_CONNTRACK_STATE)
 			printf(" !");
-		printf(" %sctstate", prefix);
+		printf(" %s%s", prefix,
+			info->match_flags & XT_CONNTRACK_STATE_ALIAS
+				? "state" : "ctstate");
 		print_state(info->state_mask);
 	}
 
@@ -860,6 +896,15 @@
 	}
 }
 
+static const char *
+conntrack_print_name_alias(const struct xt_entry_match *match)
+{
+	struct xt_conntrack_mtinfo1 *info = (void *)match->data;
+
+	return info->match_flags & XT_CONNTRACK_STATE_ALIAS
+		? "state" : "conntrack";
+}
+
 static void conntrack_print(const void *ip, const struct xt_entry_match *match,
                             int numeric)
 {
@@ -965,6 +1010,390 @@
 	conntrack_dump(&up, "--", NFPROTO_IPV6, true, false);
 }
 
+static void
+state_help(void)
+{
+	printf(
+"state match options:\n"
+" [!] --state [INVALID|ESTABLISHED|NEW|RELATED|UNTRACKED][,...]\n"
+"				State(s) to match\n");
+}
+
+static const struct xt_option_entry state_opts[] = {
+	{.name = "state", .id = O_CTSTATE, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_INVERT},
+	XTOPT_TABLEEND,
+};
+
+static unsigned int
+state_parse_state(const char *state, size_t len)
+{
+	if (strncasecmp(state, "INVALID", len) == 0)
+		return XT_CONNTRACK_STATE_INVALID;
+	else if (strncasecmp(state, "NEW", len) == 0)
+		return XT_CONNTRACK_STATE_BIT(IP_CT_NEW);
+	else if (strncasecmp(state, "ESTABLISHED", len) == 0)
+		return XT_CONNTRACK_STATE_BIT(IP_CT_ESTABLISHED);
+	else if (strncasecmp(state, "RELATED", len) == 0)
+		return XT_CONNTRACK_STATE_BIT(IP_CT_RELATED);
+	else if (strncasecmp(state, "UNTRACKED", len) == 0)
+		return XT_CONNTRACK_STATE_UNTRACKED;
+	return 0;
+}
+
+static unsigned int
+state_parse_states(const char *arg)
+{
+	const char *comma;
+	unsigned int mask = 0, flag;
+
+	while ((comma = strchr(arg, ',')) != NULL) {
+		if (comma == arg)
+			goto badstate;
+		flag = state_parse_state(arg, comma-arg);
+		if (flag == 0)
+			goto badstate;
+		mask |= flag;
+		arg = comma+1;
+	}
+	if (!*arg)
+		xtables_error(PARAMETER_PROBLEM, "\"--state\" requires a list of "
+					      "states with no spaces, e.g. "
+					      "ESTABLISHED,RELATED");
+	if (strlen(arg) == 0)
+		goto badstate;
+	flag = state_parse_state(arg, strlen(arg));
+	if (flag == 0)
+		goto badstate;
+	mask |= flag;
+	return mask;
+ badstate:
+	xtables_error(PARAMETER_PROBLEM, "Bad state \"%s\"", arg);
+}
+
+static void state_parse(struct xt_option_call *cb)
+{
+	struct xt_state_info *sinfo = cb->data;
+
+	xtables_option_parse(cb);
+	sinfo->statemask = state_parse_states(cb->arg);
+	if (cb->invert)
+		sinfo->statemask = ~sinfo->statemask;
+}
+
+static void state_ct1_parse(struct xt_option_call *cb)
+{
+	struct xt_conntrack_mtinfo1 *sinfo = cb->data;
+
+	xtables_option_parse(cb);
+	sinfo->match_flags = XT_CONNTRACK_STATE | XT_CONNTRACK_STATE_ALIAS;
+	sinfo->state_mask = state_parse_states(cb->arg);
+	if (cb->invert)
+		sinfo->invert_flags |= XT_CONNTRACK_STATE;
+}
+
+static void state_ct23_parse(struct xt_option_call *cb)
+{
+	struct xt_conntrack_mtinfo3 *sinfo = cb->data;
+
+	xtables_option_parse(cb);
+	sinfo->match_flags = XT_CONNTRACK_STATE | XT_CONNTRACK_STATE_ALIAS;
+	sinfo->state_mask = state_parse_states(cb->arg);
+	if (cb->invert)
+		sinfo->invert_flags |= XT_CONNTRACK_STATE;
+}
+
+static void state_print_state(unsigned int statemask)
+{
+	const char *sep = "";
+
+	if (statemask & XT_CONNTRACK_STATE_INVALID) {
+		printf("%sINVALID", sep);
+		sep = ",";
+	}
+	if (statemask & XT_CONNTRACK_STATE_BIT(IP_CT_NEW)) {
+		printf("%sNEW", sep);
+		sep = ",";
+	}
+	if (statemask & XT_CONNTRACK_STATE_BIT(IP_CT_RELATED)) {
+		printf("%sRELATED", sep);
+		sep = ",";
+	}
+	if (statemask & XT_CONNTRACK_STATE_BIT(IP_CT_ESTABLISHED)) {
+		printf("%sESTABLISHED", sep);
+		sep = ",";
+	}
+	if (statemask & XT_CONNTRACK_STATE_UNTRACKED) {
+		printf("%sUNTRACKED", sep);
+		sep = ",";
+	}
+}
+
+static void
+state_print(const void *ip,
+      const struct xt_entry_match *match,
+      int numeric)
+{
+	const struct xt_state_info *sinfo = (const void *)match->data;
+
+	printf(" state ");
+	state_print_state(sinfo->statemask);
+}
+
+static void state_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_state_info *sinfo = (const void *)match->data;
+
+	printf(" --state ");
+	state_print_state(sinfo->statemask);
+}
+
+static void state_xlate_print(struct xt_xlate *xl, unsigned int statemask)
+{
+	const char *sep = "";
+
+	if (statemask & XT_CONNTRACK_STATE_INVALID) {
+		xt_xlate_add(xl, "%s%s", sep, "invalid");
+		sep = ",";
+	}
+	if (statemask & XT_CONNTRACK_STATE_BIT(IP_CT_NEW)) {
+		xt_xlate_add(xl, "%s%s", sep, "new");
+		sep = ",";
+	}
+	if (statemask & XT_CONNTRACK_STATE_BIT(IP_CT_RELATED)) {
+		xt_xlate_add(xl, "%s%s", sep, "related");
+		sep = ",";
+	}
+	if (statemask & XT_CONNTRACK_STATE_BIT(IP_CT_ESTABLISHED)) {
+		xt_xlate_add(xl, "%s%s", sep, "established");
+		sep = ",";
+	}
+	if (statemask & XT_CONNTRACK_STATE_UNTRACKED) {
+		xt_xlate_add(xl, "%s%s", sep, "untracked");
+		sep = ",";
+	}
+}
+
+static int state_xlate(struct xt_xlate *xl,
+		       const struct xt_xlate_mt_params *params)
+{
+	const struct xt_conntrack_mtinfo3 *sinfo =
+		(const void *)params->match->data;
+
+	xt_xlate_add(xl, "ct state %s", sinfo->invert_flags & XT_CONNTRACK_STATE ?
+					"!= " : "");
+	state_xlate_print(xl, sinfo->state_mask);
+	xt_xlate_add(xl, " ");
+	return 1;
+}
+
+static void status_xlate_print(struct xt_xlate *xl, unsigned int statusmask)
+{
+	const char *sep = "";
+
+	if (statusmask & IPS_EXPECTED) {
+		xt_xlate_add(xl, "%s%s", sep, "expected");
+		sep = ",";
+	}
+	if (statusmask & IPS_SEEN_REPLY) {
+		xt_xlate_add(xl, "%s%s", sep, "seen-reply");
+		sep = ",";
+	}
+	if (statusmask & IPS_ASSURED) {
+		xt_xlate_add(xl, "%s%s", sep, "assured");
+		sep = ",";
+	}
+	if (statusmask & IPS_CONFIRMED) {
+		xt_xlate_add(xl, "%s%s", sep, "confirmed");
+		sep = ",";
+	}
+}
+
+static void addr_xlate_print(struct xt_xlate *xl,
+			     const union nf_inet_addr *addr,
+			     const union nf_inet_addr *mask,
+			     unsigned int family)
+{
+	if (family == NFPROTO_IPV4) {
+		xt_xlate_add(xl, "%s%s", xtables_ipaddr_to_numeric(&addr->in),
+		     xtables_ipmask_to_numeric(&mask->in));
+	} else if (family == NFPROTO_IPV6) {
+		xt_xlate_add(xl, "%s%s", xtables_ip6addr_to_numeric(&addr->in6),
+		     xtables_ip6mask_to_numeric(&mask->in6));
+	}
+}
+
+static int _conntrack3_mt_xlate(struct xt_xlate *xl,
+				const struct xt_xlate_mt_params *params,
+				int family)
+{
+	const struct xt_conntrack_mtinfo3 *sinfo =
+		(const void *)params->match->data;
+	char *space = "";
+
+	if (sinfo->match_flags & XT_CONNTRACK_DIRECTION) {
+		xt_xlate_add(xl, "ct direction %s",
+			     sinfo->invert_flags & XT_CONNTRACK_DIRECTION ?
+			     "reply" : "original");
+		space = " ";
+	}
+
+	if (sinfo->match_flags & XT_CONNTRACK_PROTO) {
+		xt_xlate_add(xl, "%sct %s protocol %s%u", space,
+			     sinfo->invert_flags & XT_CONNTRACK_DIRECTION ?
+			     "reply" : "original",
+			     sinfo->invert_flags & XT_CONNTRACK_PROTO ?
+			     "!= " : "",
+			     sinfo->l4proto);
+		space = " ";
+	}
+
+	if (sinfo->match_flags & XT_CONNTRACK_STATE) {
+		if ((sinfo->state_mask & XT_CONNTRACK_STATE_SNAT) ||
+		    (sinfo->state_mask & XT_CONNTRACK_STATE_DNAT)) {
+			xt_xlate_add(xl, "%sct status %s%s", space,
+				     sinfo->invert_flags & XT_CONNTRACK_STATUS ? "!=" : "",
+				     sinfo->state_mask & XT_CONNTRACK_STATE_SNAT ? "snat" : "dnat");
+			space = " ";
+		} else {
+			xt_xlate_add(xl, "%sct state %s", space,
+				     sinfo->invert_flags & XT_CONNTRACK_STATE ?
+				     "!= " : "");
+			state_xlate_print(xl, sinfo->state_mask);
+			space = " ";
+		}
+	}
+
+	if (sinfo->match_flags & XT_CONNTRACK_STATUS) {
+		xt_xlate_add(xl, "%sct status %s", space,
+			     sinfo->invert_flags & XT_CONNTRACK_STATUS ?
+			     "!= " : "");
+		status_xlate_print(xl, sinfo->status_mask);
+		space = " ";
+	}
+
+	if (sinfo->match_flags & XT_CONNTRACK_EXPIRES) {
+		xt_xlate_add(xl, "%sct expiration %s", space,
+			     sinfo->invert_flags & XT_CONNTRACK_EXPIRES ?
+			     "!= " : "");
+		if (sinfo->expires_max == sinfo->expires_min)
+			xt_xlate_add(xl, "%u", sinfo->expires_min);
+		else
+			xt_xlate_add(xl, "%u-%u", sinfo->expires_min,
+				     sinfo->expires_max);
+		space = " ";
+	}
+
+	if (sinfo->match_flags & XT_CONNTRACK_ORIGSRC) {
+		if (&sinfo->origsrc_addr == 0L)
+			return 0;
+
+		xt_xlate_add(xl, "%sct original saddr %s", space,
+			     sinfo->invert_flags & XT_CONNTRACK_ORIGSRC ?
+			     "!= " : "");
+		addr_xlate_print(xl, &sinfo->origsrc_addr,
+				 &sinfo->origsrc_mask, family);
+		space = " ";
+	}
+
+	if (sinfo->match_flags & XT_CONNTRACK_ORIGDST) {
+		if (&sinfo->origdst_addr == 0L)
+			return 0;
+
+		xt_xlate_add(xl, "%sct original daddr %s", space,
+			     sinfo->invert_flags & XT_CONNTRACK_ORIGDST ?
+			     "!= " : "");
+		addr_xlate_print(xl, &sinfo->origdst_addr,
+				 &sinfo->origdst_mask, family);
+		space = " ";
+	}
+
+	if (sinfo->match_flags & XT_CONNTRACK_REPLSRC) {
+		if (&sinfo->replsrc_addr == 0L)
+			return 0;
+
+		xt_xlate_add(xl, "%sct reply saddr %s", space,
+			     sinfo->invert_flags & XT_CONNTRACK_REPLSRC ?
+			     "!= " : "");
+		addr_xlate_print(xl, &sinfo->replsrc_addr,
+				 &sinfo->replsrc_mask, family);
+		space = " ";
+	}
+
+	if (sinfo->match_flags & XT_CONNTRACK_REPLDST) {
+		if (&sinfo->repldst_addr == 0L)
+			return 0;
+
+		xt_xlate_add(xl, "%sct reply daddr %s", space,
+			     sinfo->invert_flags & XT_CONNTRACK_REPLDST ?
+			     "!= " : "");
+		addr_xlate_print(xl, &sinfo->repldst_addr,
+				 &sinfo->repldst_mask, family);
+		space = " ";
+	}
+
+	if (sinfo->match_flags & XT_CONNTRACK_ORIGSRC_PORT) {
+		xt_xlate_add(xl, "%sct original proto-src %s", space,
+			     sinfo->invert_flags & XT_CONNTRACK_ORIGSRC_PORT ?
+			     "!= " : "");
+		if (sinfo->origsrc_port == sinfo->origsrc_port_high)
+			xt_xlate_add(xl, "%u", sinfo->origsrc_port);
+		else
+			xt_xlate_add(xl, "%u-%u", sinfo->origsrc_port,
+				     sinfo->origsrc_port_high);
+		space = " ";
+	}
+
+	if (sinfo->match_flags & XT_CONNTRACK_ORIGDST_PORT) {
+		xt_xlate_add(xl, "%sct original proto-dst %s", space,
+			     sinfo->invert_flags & XT_CONNTRACK_ORIGDST_PORT ?
+			     "!= " : "");
+		if (sinfo->origdst_port == sinfo->origdst_port_high)
+			xt_xlate_add(xl, "%u", sinfo->origdst_port);
+		else
+			xt_xlate_add(xl, "%u-%u", sinfo->origdst_port,
+				     sinfo->origdst_port_high);
+		space = " ";
+	}
+
+	if (sinfo->match_flags & XT_CONNTRACK_REPLSRC_PORT) {
+		xt_xlate_add(xl, "%sct reply proto-src %s", space,
+			     sinfo->invert_flags & XT_CONNTRACK_REPLSRC_PORT ?
+			     "!= " : "");
+		if (sinfo->replsrc_port == sinfo->replsrc_port_high)
+			xt_xlate_add(xl, "%u", sinfo->replsrc_port);
+		else
+			xt_xlate_add(xl, "%u-%u", sinfo->replsrc_port,
+				     sinfo->replsrc_port_high);
+		space = " ";
+	}
+
+	if (sinfo->match_flags & XT_CONNTRACK_REPLDST_PORT) {
+		xt_xlate_add(xl, "%sct reply proto-dst %s", space,
+			     sinfo->invert_flags & XT_CONNTRACK_REPLDST_PORT ?
+			     "!= " : "");
+		if (sinfo->repldst_port == sinfo->repldst_port_high)
+			xt_xlate_add(xl, "%u", sinfo->repldst_port);
+		else
+			xt_xlate_add(xl, "%u-%u", sinfo->repldst_port,
+				     sinfo->repldst_port_high);
+	}
+
+	return 1;
+}
+
+static int conntrack3_mt4_xlate(struct xt_xlate *xl,
+				const struct xt_xlate_mt_params *params)
+{
+	return _conntrack3_mt_xlate(xl, params, NFPROTO_IPV4);
+}
+
+static int conntrack3_mt6_xlate(struct xt_xlate *xl,
+				const struct xt_xlate_mt_params *params)
+{
+	return _conntrack3_mt_xlate(xl, params, NFPROTO_IPV6);
+}
+
 static struct xtables_match conntrack_mt_reg[] = {
 	{
 		.version       = XTABLES_VERSION,
@@ -978,6 +1407,7 @@
 		.x6_fcheck     = conntrack_mt_check,
 		.print         = conntrack_print,
 		.save          = conntrack_save,
+		.alias	       = conntrack_print_name_alias,
 		.x6_options    = conntrack_mt_opts_v0,
 	},
 	{
@@ -992,7 +1422,8 @@
 		.x6_fcheck     = conntrack_mt_check,
 		.print         = conntrack1_mt4_print,
 		.save          = conntrack1_mt4_save,
-		.x6_options    = conntrack_mt_opts,
+		.alias	       = conntrack_print_name_alias,
+		.x6_options    = conntrack2_mt_opts,
 	},
 	{
 		.version       = XTABLES_VERSION,
@@ -1006,7 +1437,8 @@
 		.x6_fcheck     = conntrack_mt_check,
 		.print         = conntrack1_mt6_print,
 		.save          = conntrack1_mt6_save,
-		.x6_options    = conntrack_mt_opts,
+		.alias	       = conntrack_print_name_alias,
+		.x6_options    = conntrack2_mt_opts,
 	},
 	{
 		.version       = XTABLES_VERSION,
@@ -1020,7 +1452,8 @@
 		.x6_fcheck     = conntrack_mt_check,
 		.print         = conntrack2_mt_print,
 		.save          = conntrack2_mt_save,
-		.x6_options    = conntrack_mt_opts,
+		.alias	       = conntrack_print_name_alias,
+		.x6_options    = conntrack2_mt_opts,
 	},
 	{
 		.version       = XTABLES_VERSION,
@@ -1034,7 +1467,8 @@
 		.x6_fcheck     = conntrack_mt_check,
 		.print         = conntrack2_mt6_print,
 		.save          = conntrack2_mt6_save,
-		.x6_options    = conntrack_mt_opts,
+		.alias	       = conntrack_print_name_alias,
+		.x6_options    = conntrack2_mt_opts,
 	},
 	{
 		.version       = XTABLES_VERSION,
@@ -1048,7 +1482,9 @@
 		.x6_fcheck     = conntrack_mt_check,
 		.print         = conntrack3_mt_print,
 		.save          = conntrack3_mt_save,
-		.x6_options    = conntrack_mt_opts,
+		.alias	       = conntrack_print_name_alias,
+		.x6_options    = conntrack3_mt_opts,
+		.xlate	       = conntrack3_mt4_xlate,
 	},
 	{
 		.version       = XTABLES_VERSION,
@@ -1062,7 +1498,68 @@
 		.x6_fcheck     = conntrack_mt_check,
 		.print         = conntrack3_mt6_print,
 		.save          = conntrack3_mt6_save,
-		.x6_options    = conntrack_mt_opts,
+		.alias	       = conntrack_print_name_alias,
+		.x6_options    = conntrack3_mt_opts,
+		.xlate	       = conntrack3_mt6_xlate,
+	},
+	{
+		.family        = NFPROTO_UNSPEC,
+		.name          = "state",
+		.real_name     = "conntrack",
+		.revision      = 1,
+		.ext_flags     = XTABLES_EXT_ALIAS,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_conntrack_mtinfo1)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_conntrack_mtinfo1)),
+		.help          = state_help,
+		.print         = state_print,
+		.save          = state_save,
+		.x6_parse      = state_ct1_parse,
+		.x6_options    = state_opts,
+	},
+	{
+		.family        = NFPROTO_UNSPEC,
+		.name          = "state",
+		.real_name     = "conntrack",
+		.revision      = 2,
+		.ext_flags     = XTABLES_EXT_ALIAS,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_conntrack_mtinfo2)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_conntrack_mtinfo2)),
+		.help          = state_help,
+		.print         = state_print,
+		.save          = state_save,
+		.x6_parse      = state_ct23_parse,
+		.x6_options    = state_opts,
+	},
+	{
+		.family        = NFPROTO_UNSPEC,
+		.name          = "state",
+		.real_name     = "conntrack",
+		.revision      = 3,
+		.ext_flags     = XTABLES_EXT_ALIAS,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_conntrack_mtinfo3)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_conntrack_mtinfo3)),
+		.help          = state_help,
+		.print         = state_print,
+		.save          = state_save,
+		.x6_parse      = state_ct23_parse,
+		.x6_options    = state_opts,
+		.xlate         = state_xlate,
+	},
+	{
+		.family        = NFPROTO_UNSPEC,
+		.name          = "state",
+		.revision      = 0,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_state_info)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_state_info)),
+		.help          = state_help,
+		.print         = state_print,
+		.save          = state_save,
+		.x6_parse      = state_parse,
+		.x6_options    = state_opts,
 	},
 };
 
diff --git a/extensions/libxt_conntrack.man b/extensions/libxt_conntrack.man
index c397f74..4b13f0f 100644
--- a/extensions/libxt_conntrack.man
+++ b/extensions/libxt_conntrack.man
@@ -42,23 +42,23 @@
 States for \fB\-\-ctstate\fP:
 .TP
 \fBINVALID\fP
-meaning that the packet is associated with no known connection
+The packet is associated with no known connection.
 .TP
 \fBNEW\fP
-meaning that the packet has started a new connection, or otherwise associated
-with a connection which has not seen packets in both directions, and
+The packet has started a new connection or otherwise associated
+with a connection which has not seen packets in both directions.
 .TP
 \fBESTABLISHED\fP
-meaning that the packet is associated with a connection which has seen packets
-in both directions,
+The packet is associated with a connection which has seen packets
+in both directions.
 .TP
 \fBRELATED\fP
-meaning that the packet is starting a new connection, but is associated with an
-existing connection, such as an FTP data transfer, or an ICMP error.
+The packet is starting a new connection, but is associated with an
+existing connection, such as an FTP data transfer or an ICMP error.
 .TP
 \fBUNTRACKED\fP
-meaning that the packet is not tracked at all, which happens if you use
-the NOTRACK target in raw table.
+The packet is not tracked at all, which happens if you explicitly untrack it
+by using \-j CT \-\-notrack in the raw table.
 .TP
 \fBSNAT\fP
 A virtual state, matching if the original source address differs from the reply
@@ -74,7 +74,7 @@
 None of the below.
 .TP
 \fBEXPECTED\fP
-This is an expected connection (i.e. a conntrack helper set it up)
+This is an expected connection (i.e. a conntrack helper set it up).
 .TP
 \fBSEEN_REPLY\fP
 Conntrack has seen packets in both directions.
diff --git a/extensions/libxt_conntrack.t b/extensions/libxt_conntrack.t
new file mode 100644
index 0000000..db53147
--- /dev/null
+++ b/extensions/libxt_conntrack.t
@@ -0,0 +1,27 @@
+:INPUT,FORWARD,OUTPUT
+-m conntrack --ctstate NEW;=;OK
+-m conntrack --ctstate NEW,ESTABLISHED;=;OK
+-m conntrack --ctstate NEW,RELATED,ESTABLISHED;=;OK
+-m conntrack --ctstate INVALID;=;OK
+-m conntrack --ctstate UNTRACKED;=;OK
+-m conntrack --ctstate SNAT,DNAT;=;OK
+-m conntrack --ctstate wrong;;FAIL
+# should we convert this to output "tcp" instead of 6?
+-m conntrack --ctproto tcp;-m conntrack --ctproto 6;OK
+-m conntrack --ctorigsrc 1.1.1.1;=;OK
+-m conntrack --ctorigdst 1.1.1.1;=;OK
+-m conntrack --ctreplsrc 1.1.1.1;=;OK
+-m conntrack --ctrepldst 1.1.1.1;=;OK
+-m conntrack --ctexpire 0;=;OK
+-m conntrack --ctexpire 4294967295;=;OK
+-m conntrack --ctexpire 0:4294967295;=;OK
+-m conntrack --ctexpire 42949672956;;FAIL
+-m conntrack --ctexpire -1;;FAIL
+-m conntrack --ctdir ORIGINAL;=;OK
+-m conntrack --ctdir REPLY;=;OK
+-m conntrack --ctstatus NONE;=;OK
+-m conntrack --ctstatus CONFIRMED;=;OK
+-m conntrack --ctstatus ASSURED;=;OK
+-m conntrack --ctstatus EXPECTED;=;OK
+-m conntrack --ctstatus SEEN_REPLY;=;OK
+-m conntrack;;FAIL
diff --git a/extensions/libxt_conntrack.txlate b/extensions/libxt_conntrack.txlate
new file mode 100644
index 0000000..d374f8a
--- /dev/null
+++ b/extensions/libxt_conntrack.txlate
@@ -0,0 +1,51 @@
+iptables-translate -t filter -A INPUT -m conntrack --ctstate NEW,RELATED -j ACCEPT
+nft add rule ip filter INPUT ct state new,related counter accept
+
+ip6tables-translate -t filter -A INPUT -m conntrack ! --ctstate NEW,RELATED -j ACCEPT
+nft add rule ip6 filter INPUT ct state != new,related counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack --ctproto UDP -j ACCEPT
+nft add rule ip filter INPUT ct original protocol 17 counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack ! --ctproto UDP -j ACCEPT
+nft add rule ip filter INPUT ct original protocol != 17 counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack --ctorigsrc 10.100.2.131 -j ACCEPT
+nft add rule ip filter INPUT ct original saddr 10.100.2.131 counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack --ctorigsrc 10.100.0.0/255.255.0.0 -j ACCEPT
+nft add rule ip filter INPUT ct original saddr 10.100.0.0/16 counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack --ctorigdst 10.100.2.131 -j ACCEPT
+nft add rule ip filter INPUT ct original daddr 10.100.2.131 counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack --ctreplsrc 10.100.2.131 -j ACCEPT
+nft add rule ip filter INPUT ct reply saddr 10.100.2.131 counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack --ctrepldst 10.100.2.131 -j ACCEPT
+nft add rule ip filter INPUT ct reply daddr 10.100.2.131 counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack --ctproto tcp --ctorigsrcport 443:444 -j ACCEPT
+nft add rule ip filter INPUT ct original protocol 6 ct original proto-src 443-444 counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack --ctstatus EXPECTED -j ACCEPT
+nft add rule ip filter INPUT ct status expected counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack ! --ctstatus CONFIRMED -j ACCEPT
+nft add rule ip filter INPUT ct status != confirmed counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack --ctexpire 3 -j ACCEPT
+nft add rule ip filter INPUT ct expiration 3 counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack --ctdir ORIGINAL -j ACCEPT
+nft add rule ip filter INPUT ct direction original counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack --ctstate NEW --ctproto tcp --ctorigsrc 192.168.0.1 --ctorigdst 192.168.0.1 --ctreplsrc 192.168.0.1 --ctrepldst 192.168.0.1 --ctorigsrcport 12 --ctorigdstport 14 --ctreplsrcport 16 --ctrepldstport 18 --ctexpire 10 --ctstatus SEEN_REPLY --ctdir ORIGINAL -j ACCEPT
+nft add rule ip filter INPUT ct direction original ct original protocol 6 ct state new ct status seen-reply ct expiration 10 ct original saddr 192.168.0.1 ct original daddr 192.168.0.1 ct reply saddr 192.168.0.1 ct reply daddr 192.168.0.1 ct original proto-src 12 ct original proto-dst 14 ct reply proto-src 16 ct reply proto-dst 18 counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack --ctstate SNAT -j ACCEPT
+nft add rule ip filter INPUT ct status snat counter accept
+
+iptables-translate -t filter -A INPUT -m conntrack --ctstate DNAT -j ACCEPT
+nft add rule ip filter INPUT ct status dnat counter accept
+
diff --git a/extensions/libxt_cpu.c b/extensions/libxt_cpu.c
index 404a6a6..41c13c3 100644
--- a/extensions/libxt_cpu.c
+++ b/extensions/libxt_cpu.c
@@ -44,9 +44,19 @@
 	printf("%s --cpu %u", info->invert ? " !" : "", info->cpu);
 }
 
+static int cpu_xlate(struct xt_xlate *xl,
+		     const struct xt_xlate_mt_params *params)
+{
+	const struct xt_cpu_info *info = (void *)params->match->data;
+
+	xt_xlate_add(xl, "cpu%s %u", info->invert ? " !=" : "", info->cpu);
+
+	return 1;
+}
+
 static struct xtables_match cpu_match = {
 	.family		= NFPROTO_UNSPEC,
- 	.name		= "cpu",
+	.name		= "cpu",
 	.version	= XTABLES_VERSION,
 	.size		= XT_ALIGN(sizeof(struct xt_cpu_info)),
 	.userspacesize	= XT_ALIGN(sizeof(struct xt_cpu_info)),
@@ -55,6 +65,7 @@
 	.save		= cpu_save,
 	.x6_parse	= cpu_parse,
 	.x6_options	= cpu_opts,
+	.xlate		= cpu_xlate,
 };
 
 void _init(void)
diff --git a/extensions/libxt_cpu.t b/extensions/libxt_cpu.t
new file mode 100644
index 0000000..f5adb45
--- /dev/null
+++ b/extensions/libxt_cpu.t
@@ -0,0 +1,6 @@
+:INPUT,FORWARD,OUTPUT
+-m cpu --cpu 0;=;OK
+-m cpu ! --cpu 0;=;OK
+-m cpu --cpu 4294967295;=;OK
+-m cpu --cpu 4294967296;;FAIL
+-m cpu;;FAIL
diff --git a/extensions/libxt_cpu.txlate b/extensions/libxt_cpu.txlate
new file mode 100644
index 0000000..c59b0e0
--- /dev/null
+++ b/extensions/libxt_cpu.txlate
@@ -0,0 +1,5 @@
+iptables-translate -A INPUT -p tcp --dport 80 -m cpu --cpu 0 -j ACCEPT
+nft add rule ip filter INPUT tcp dport 80 cpu 0 counter accept
+
+iptables-translate -A INPUT -p tcp --dport 80 -m cpu ! --cpu 1 -j ACCEPT
+nft add rule ip filter INPUT tcp dport 80 cpu != 1 counter accept
diff --git a/extensions/libxt_dccp.c b/extensions/libxt_dccp.c
index 28c59b9..aea3e20 100644
--- a/extensions/libxt_dccp.c
+++ b/extensions/libxt_dccp.c
@@ -37,7 +37,10 @@
 "[!] --source-port port[:port]                          match source port(s)\n"
 " --sport ...\n"
 "[!] --destination-port port[:port]                     match destination port(s)\n"
-" --dport ...\n");
+" --dport ...\n"
+"[!] --dccp-types type[,...]                            match when packet is one of the given types\n"
+"[!] --dccp-option option                               match if option (by number!) is set\n"
+);
 }
 
 #define s struct xt_dccp_info
@@ -50,9 +53,10 @@
 	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, dpts)},
 	{.name = "dport", .id = O_DEST_PORT, .type = XTTYPE_PORTRC,
 	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, dpts)},
-	{.name = "dccp-types", .id = O_DCCP_TYPES, .type = XTTYPE_STRING},
+	{.name = "dccp-types", .id = O_DCCP_TYPES, .type = XTTYPE_STRING,
+	 .flags = XTOPT_INVERT},
 	{.name = "dccp-option", .id = O_DCCP_OPTION, .type = XTTYPE_UINT8,
-	 .min = 1, .max = UINT8_MAX, .flags = XTOPT_PUT,
+	 .min = 1, .max = UINT8_MAX, .flags = XTOPT_INVERT | XTOPT_PUT,
 	 XTOPT_POINTER(s, option)},
 	XTOPT_TABLEEND,
 };
@@ -72,6 +76,9 @@
 	[DCCP_PKT_INVALID]	= "INVALID",
 };
 
+/* Bits for type values 11-15 */
+#define INVALID_OTHER_TYPE_MASK		0xf800
+
 static uint16_t
 parse_dccp_types(const char *typestring)
 {
@@ -91,6 +98,9 @@
 			xtables_error(PARAMETER_PROBLEM,
 				   "Unknown DCCP type `%s'", ptr);
 	}
+	if (typemask & (1 << DCCP_PKT_INVALID))
+		typemask |= INVALID_OTHER_TYPE_MASK;
+
 
 	free(buffer);
 	return typemask;
@@ -189,9 +199,13 @@
 
 		if (numeric)
 			printf("%u", i);
-		else
+		else {
 			printf("%s", dccp_pkt_types[i]);
 
+			if (i == DCCP_PKT_INVALID)
+				break;
+		}
+
 		types &= ~(1 << i);
 	}
 }
@@ -261,17 +275,113 @@
 	}
 
 	if (einfo->flags & XT_DCCP_TYPE) {
-		printf(" --dccp-type");
-		print_types(einfo->typemask, einfo->invflags & XT_DCCP_TYPE,0);
+		printf("%s --dccp-types",
+		       einfo->invflags & XT_DCCP_TYPE ? " !" : "");
+		print_types(einfo->typemask, false, 0);
 	}
 
 	if (einfo->flags & XT_DCCP_OPTION) {
-		printf(" --dccp-option %s%u",
-			einfo->typemask & XT_DCCP_OPTION ? "! " : "",
+		printf("%s --dccp-option %u",
+			einfo->invflags & XT_DCCP_OPTION ? " !" : "",
 			einfo->option);
 	}
 }
 
+static const char *const dccp_pkt_types_xlate[] = {
+	[DCCP_PKT_REQUEST]      = "request",
+	[DCCP_PKT_RESPONSE]     = "response",
+	[DCCP_PKT_DATA]         = "data",
+	[DCCP_PKT_ACK]          = "ack",
+	[DCCP_PKT_DATAACK]      = "dataack",
+	[DCCP_PKT_CLOSEREQ]     = "closereq",
+	[DCCP_PKT_CLOSE]        = "close",
+	[DCCP_PKT_RESET]        = "reset",
+	[DCCP_PKT_SYNC]         = "sync",
+	[DCCP_PKT_SYNCACK]      = "syncack",
+	[DCCP_PKT_INVALID]	= "10-15",
+};
+
+static int dccp_type_xlate(const struct xt_dccp_info *einfo,
+			   struct xt_xlate *xl)
+{
+	bool have_type = false, set_need = false;
+	uint16_t types = einfo->typemask;
+
+	if (types & INVALID_OTHER_TYPE_MASK) {
+		types &= ~INVALID_OTHER_TYPE_MASK;
+		types |= 1 << DCCP_PKT_INVALID;
+	}
+
+	if ((types != 0) && !(types == (types & -types))) {
+		xt_xlate_add(xl, "{");
+		set_need = true;
+	}
+
+	while (types) {
+		unsigned int i;
+
+		for (i = 0; !(types & (1 << i)); i++);
+
+		if (have_type)
+			xt_xlate_add(xl, ", ");
+		else
+			have_type = true;
+
+		xt_xlate_add(xl, "%s", dccp_pkt_types_xlate[i]);
+
+		types &= ~(1 << i);
+	}
+
+	if (set_need)
+		xt_xlate_add(xl, "}");
+
+	return 1;
+}
+
+static int dccp_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_mt_params *params)
+{
+	const struct xt_dccp_info *einfo =
+		(const struct xt_dccp_info *)params->match->data;
+	char *space = "";
+	int ret = 1;
+
+	if (einfo->flags & XT_DCCP_SRC_PORTS) {
+		xt_xlate_add(xl, "dccp sport%s %u",
+			     einfo->invflags & XT_DCCP_SRC_PORTS ? " !=" : "",
+			     einfo->spts[0]);
+
+		if (einfo->spts[0] != einfo->spts[1])
+			xt_xlate_add(xl, "-%u", einfo->spts[1]);
+
+		space = " ";
+	}
+
+	if (einfo->flags & XT_DCCP_DEST_PORTS) {
+		xt_xlate_add(xl, "%sdccp dport%s %u", space,
+			     einfo->invflags & XT_DCCP_DEST_PORTS ? " !=" : "",
+			     einfo->dpts[0]);
+
+		if (einfo->dpts[0] != einfo->dpts[1])
+			xt_xlate_add(xl, "-%u", einfo->dpts[1]);
+
+		space = " ";
+	}
+
+	if (einfo->flags & XT_DCCP_TYPE && einfo->typemask) {
+		xt_xlate_add(xl, "%sdccp type%s ", space,
+			     einfo->invflags & XT_DCCP_TYPE ? " !=" : "");
+		ret = dccp_type_xlate(einfo, xl);
+
+		space = " ";
+	}
+
+	/* FIXME: no dccp option support in nftables yet */
+	if (einfo->flags & XT_DCCP_OPTION)
+		ret = 0;
+
+	return ret;
+}
 static struct xtables_match dccp_match = {
 	.name		= "dccp",
 	.family		= NFPROTO_UNSPEC,
@@ -283,6 +393,7 @@
 	.save		= dccp_save,
 	.x6_parse	= dccp_parse,
 	.x6_options	= dccp_opts,
+	.xlate		= dccp_xlate,
 };
 
 void _init(void)
diff --git a/extensions/libxt_dccp.man b/extensions/libxt_dccp.man
index 82c3f70..71beb4b 100644
--- a/extensions/libxt_dccp.man
+++ b/extensions/libxt_dccp.man
@@ -9,4 +9,4 @@
 .BR "REQUEST RESPONSE DATA ACK DATAACK CLOSEREQ CLOSE RESET SYNC SYNCACK INVALID" .
 .TP
 [\fB!\fP] \fB\-\-dccp\-option\fP \fInumber\fP
-Match if DCP option set.
+Match if DCCP option set.
diff --git a/extensions/libxt_dccp.t b/extensions/libxt_dccp.t
new file mode 100644
index 0000000..f60b480
--- /dev/null
+++ b/extensions/libxt_dccp.t
@@ -0,0 +1,30 @@
+:INPUT,FORWARD,OUTPUT
+-p dccp -m dccp --sport 1;=;OK
+-p dccp -m dccp --sport 65535;=;OK
+-p dccp -m dccp --dport 1;=;OK
+-p dccp -m dccp --dport 65535;=;OK
+-p dccp -m dccp --sport 1:1023;=;OK
+-p dccp -m dccp --sport 1024:65535;=;OK
+-p dccp -m dccp --sport 1024:;-p dccp -m dccp --sport 1024:65535;OK
+-p dccp -m dccp ! --sport 1;=;OK
+-p dccp -m dccp ! --sport 65535;=;OK
+-p dccp -m dccp ! --dport 1;=;OK
+-p dccp -m dccp ! --dport 65535;=;OK
+-p dccp -m dccp --sport 1 --dport 65535;=;OK
+-p dccp -m dccp --sport 65535 --dport 1;=;OK
+-p dccp -m dccp ! --sport 1 --dport 65535;=;OK
+-p dccp -m dccp ! --sport 65535 --dport 1;=;OK
+# ERROR: should fail: iptables -A INPUT -p dccp -m dccp --sport 65536
+# -p dccp -m dccp --sport 65536;;FAIL
+-p dccp -m dccp --sport -1;;FAIL
+-p dccp -m dccp --dport -1;;FAIL
+-p dccp -m dccp --dccp-types REQUEST,RESPONSE,DATA,ACK,DATAACK,CLOSEREQ,CLOSE,RESET,SYNC,SYNCACK,INVALID;=;OK
+-p dccp -m dccp ! --dccp-types REQUEST,RESPONSE,DATA,ACK,DATAACK,CLOSEREQ,CLOSE,RESET,SYNC,SYNCACK,INVALID;=;OK
+# DCCP option 0 is valid, see http://tools.ietf.org/html/rfc4340#page-29
+# ERROR: cannot load: iptables -A INPUT -p dccp -m dccp --dccp-option 0
+#-p dccp -m dccp --dccp-option 0;=;OK
+-p dccp -m dccp --dccp-option 255;=;OK
+-p dccp -m dccp --dccp-option 256;;FAIL
+-p dccp -m dccp --dccp-option -1;;FAIL
+# should we accept this below?
+-p dccp -m dccp;=;OK
diff --git a/extensions/libxt_dccp.txlate b/extensions/libxt_dccp.txlate
new file mode 100644
index 0000000..ea853f6
--- /dev/null
+++ b/extensions/libxt_dccp.txlate
@@ -0,0 +1,20 @@
+iptables-translate -A INPUT -p dccp -m dccp --sport 100
+nft add rule ip filter INPUT dccp sport 100 counter
+
+iptables-translate -A INPUT -p dccp -m dccp --dport 100:200
+nft add rule ip filter INPUT dccp dport 100-200 counter
+
+iptables-translate -A INPUT -p dccp -m dccp ! --dport 100
+nft add rule ip filter INPUT dccp dport != 100 counter
+
+iptables-translate -A INPUT -p dccp -m dccp --dccp-types CLOSE
+nft add rule ip filter INPUT dccp type close counter
+
+iptables-translate -A INPUT -p dccp -m dccp --dccp-types INVALID
+nft add rule ip filter INPUT dccp type 10-15 counter
+
+iptables-translate -A INPUT -p dccp -m dccp --dport 100 --dccp-types REQUEST,RESPONSE,DATA,ACK,DATAACK,CLOSEREQ,CLOSE,SYNC,SYNCACK,INVALID
+nft add rule ip filter INPUT dccp dport 100 dccp type {request, response, data, ack, dataack, closereq, close, sync, syncack, 10-15} counter
+
+iptables-translate -A INPUT -p dccp -m dccp --sport 200 --dport 100
+nft add rule ip filter INPUT dccp sport 200 dccp dport 100 counter
diff --git a/extensions/libxt_devgroup.c b/extensions/libxt_devgroup.c
index 4487c83..a88211c 100644
--- a/extensions/libxt_devgroup.c
+++ b/extensions/libxt_devgroup.c
@@ -31,69 +31,29 @@
 	XTOPT_TABLEEND,
 };
 
-/* array of devgroups from /etc/iproute2/group_map */
+static const char f_devgroups[] = "/etc/iproute2/group";
+/* array of devgroups from f_devgroups[] */
 static struct xtables_lmap *devgroups;
 
-static void devgroup_init(struct xt_entry_match *match)
-{
-	const char file[] = "/etc/iproute2/group_map";
-	devgroups = xtables_lmap_init(file);
-	if (devgroups == NULL && errno != ENOENT)
-		fprintf(stderr, "Warning: %s: %s\n", file, strerror(errno));
-}
-
 static void devgroup_parse(struct xt_option_call *cb)
 {
 	struct xt_devgroup_info *info = cb->data;
-	unsigned int id;
-	char *end;
+	unsigned int group, mask;
 
 	xtables_option_parse(cb);
+	xtables_parse_val_mask(cb, &group, &mask, devgroups);
+
 	switch (cb->entry->id) {
 	case O_SRC_GROUP:
-		info->src_group = strtoul(cb->arg, &end, 0);
-		if (end != cb->arg && (*end == '/' || *end == '\0')) {
-			if (*end == '/')
-				info->src_mask = strtoul(end+1, &end, 0);
-			else
-				info->src_mask = 0xffffffff;
-			if (*end != '\0' || end == cb->arg)
-				xtables_error(PARAMETER_PROBLEM,
-					      "Bad src-group value `%s'",
-					      cb->arg);
-		} else {
-			id = xtables_lmap_name2id(devgroups, cb->arg);
-			if (id == -1)
-				xtables_error(PARAMETER_PROBLEM,
-					      "Device group `%s' not found",
-					      cb->arg);
-			info->src_group = id;
-			info->src_mask  = 0xffffffff;
-		}
+		info->src_group = group;
+		info->src_mask  = mask;
 		info->flags |= XT_DEVGROUP_MATCH_SRC;
 		if (cb->invert)
 			info->flags |= XT_DEVGROUP_INVERT_SRC;
 		break;
 	case O_DST_GROUP:
-		info->dst_group = strtoul(cb->arg, &end, 0);
-		if (end != cb->arg && (*end == '/' || *end == '\0')) {
-			if (*end == '/')
-				info->dst_mask = strtoul(end+1, &end, 0);
-			else
-				info->dst_mask = 0xffffffff;
-			if (*end != '\0' || end == cb->arg)
-				xtables_error(PARAMETER_PROBLEM,
-					      "Bad dst-group value `%s'",
-					      cb->arg);
-		} else {
-			id = xtables_lmap_name2id(devgroups, cb->arg);
-			if (id == -1)
-				xtables_error(PARAMETER_PROBLEM,
-					      "Device group `%s' not found",
-					      cb->arg);
-			info->dst_group = id;
-			info->dst_mask  = 0xffffffff;
-		}
+		info->dst_group = group;
+		info->dst_mask  = mask;
 		info->flags |= XT_DEVGROUP_MATCH_DST;
 		if (cb->invert)
 			info->flags |= XT_DEVGROUP_INVERT_DST;
@@ -101,38 +61,23 @@
 	}
 }
 
-static void
-print_devgroup(unsigned int id, unsigned int mask, int numeric)
-{
-	const char *name = NULL;
-
-	if (mask != 0xffffffff)
-		printf("0x%x/0x%x", id, mask);
-	else {
-		if (numeric == 0)
-			name = xtables_lmap_id2name(devgroups, id);
-		if (name)
-			printf("%s", name);
-		else
-			printf("0x%x", id);
-	}
-}
-
 static void devgroup_show(const char *pfx, const struct xt_devgroup_info *info,
 			  int numeric)
 {
 	if (info->flags & XT_DEVGROUP_MATCH_SRC) {
 		if (info->flags & XT_DEVGROUP_INVERT_SRC)
 			printf(" !");
-		printf(" %ssrc-group ", pfx);
-		print_devgroup(info->src_group, info->src_mask, numeric);
+		printf(" %ssrc-group", pfx);
+		xtables_print_val_mask(info->src_group, info->src_mask,
+				       numeric ? NULL : devgroups);
 	}
 
 	if (info->flags & XT_DEVGROUP_MATCH_DST) {
 		if (info->flags & XT_DEVGROUP_INVERT_DST)
 			printf(" !");
-		printf(" %sdst-group ", pfx);
-		print_devgroup(info->src_group, info->src_mask, numeric);
+		printf(" %sdst-group", pfx);
+		xtables_print_val_mask(info->dst_group, info->dst_mask,
+				       numeric ? NULL : devgroups);
 	}
 }
 
@@ -159,22 +104,82 @@
 			      "'--src-group' or '--dst-group'");
 }
 
+static void
+print_devgroup_xlate(unsigned int id, uint32_t op,  unsigned int mask,
+		     struct xt_xlate *xl, int numeric)
+{
+	const char *name = NULL;
+
+	if (mask != 0xffffffff)
+		xt_xlate_add(xl, "and 0x%x %s 0x%x", mask,
+			   op == XT_OP_EQ ? "==" : "!=", id);
+	else {
+		if (numeric == 0)
+			name = xtables_lmap_id2name(devgroups, id);
+
+		xt_xlate_add(xl, "%s", op == XT_OP_EQ ? "" : "!= ");
+		if (name)
+			xt_xlate_add(xl, "%s", name);
+		else
+			xt_xlate_add(xl, "0x%x", id);
+	}
+}
+
+static void devgroup_show_xlate(const struct xt_devgroup_info *info,
+				struct xt_xlate *xl, int numeric)
+{
+	enum xt_op op = XT_OP_EQ;
+	char *space = "";
+
+	if (info->flags & XT_DEVGROUP_MATCH_SRC) {
+		if (info->flags & XT_DEVGROUP_INVERT_SRC)
+			op = XT_OP_NEQ;
+		xt_xlate_add(xl, "iifgroup ");
+		print_devgroup_xlate(info->src_group, op,
+				     info->src_mask, xl, numeric);
+		space = " ";
+	}
+
+	if (info->flags & XT_DEVGROUP_MATCH_DST) {
+		if (info->flags & XT_DEVGROUP_INVERT_DST)
+			op = XT_OP_NEQ;
+		xt_xlate_add(xl, "%soifgroup ", space);
+		print_devgroup_xlate(info->dst_group, op,
+				     info->dst_mask, xl, numeric);
+	}
+}
+
+static int devgroup_xlate(struct xt_xlate *xl,
+			  const struct xt_xlate_mt_params *params)
+{
+	const struct xt_devgroup_info *info = (const void *)params->match->data;
+
+	devgroup_show_xlate(info, xl, 0);
+
+	return 1;
+}
+
 static struct xtables_match devgroup_mt_reg = {
 	.name		= "devgroup",
 	.version	= XTABLES_VERSION,
 	.family		= NFPROTO_UNSPEC,
 	.size		= XT_ALIGN(sizeof(struct xt_devgroup_info)),
 	.userspacesize	= XT_ALIGN(sizeof(struct xt_devgroup_info)),
-	.init		= devgroup_init,
 	.help		= devgroup_help,
 	.print		= devgroup_print,
 	.save		= devgroup_save,
 	.x6_parse	= devgroup_parse,
 	.x6_fcheck	= devgroup_check,
 	.x6_options	= devgroup_opts,
+	.xlate		= devgroup_xlate,
 };
 
 void _init(void)
 {
+	devgroups = xtables_lmap_init(f_devgroups);
+	if (devgroups == NULL && errno != ENOENT)
+		fprintf(stderr, "Warning: %s: %s\n", f_devgroups,
+			strerror(errno));
+
 	xtables_register_match(&devgroup_mt_reg);
 }
diff --git a/extensions/libxt_devgroup.man b/extensions/libxt_devgroup.man
new file mode 100644
index 0000000..4a66c9f
--- /dev/null
+++ b/extensions/libxt_devgroup.man
@@ -0,0 +1,7 @@
+Match device group of a packets incoming/outgoing interface.
+.TP
+[\fB!\fP] \fB\-\-src\-group\fP \fIname\fP
+Match device group of incoming device
+.TP
+[\fB!\fP] \fB\-\-dst\-group\fP \fIname\fP
+Match device group of outgoing device
diff --git a/extensions/libxt_devgroup.txlate b/extensions/libxt_devgroup.txlate
new file mode 100644
index 0000000..aeb597b
--- /dev/null
+++ b/extensions/libxt_devgroup.txlate
@@ -0,0 +1,17 @@
+iptables-translate -A FORWARD -m devgroup --src-group 0x2 -j ACCEPT
+nft add rule ip filter FORWARD iifgroup 0x2 counter accept
+
+iptables-translate -A FORWARD -m devgroup --dst-group 0xc/0xc -j ACCEPT
+nft add rule ip filter FORWARD oifgroup and 0xc == 0xc counter accept
+
+iptables-translate -t mangle -A PREROUTING -p tcp --dport 46000 -m devgroup --src-group 23 -j ACCEPT
+nft add rule ip mangle PREROUTING tcp dport 46000 iifgroup 0x17 counter accept
+
+iptables-translate -A FORWARD -m devgroup ! --dst-group 0xc/0xc -j ACCEPT
+nft add rule ip filter FORWARD oifgroup and 0xc != 0xc counter accept
+
+iptables-translate -A FORWARD -m devgroup ! --src-group 0x2 -j ACCEPT
+nft add rule ip filter FORWARD iifgroup != 0x2 counter accept
+
+iptables-translate -A FORWARD -m devgroup ! --src-group 0x2 --dst-group 0xc/0xc -j ACCEPT
+nft add rule ip filter FORWARD iifgroup != 0x2 oifgroup and 0xc != 0xc counter accept
diff --git a/extensions/libxt_dscp.c b/extensions/libxt_dscp.c
index 69533d6..d5c7323 100644
--- a/extensions/libxt_dscp.c
+++ b/extensions/libxt_dscp.c
@@ -43,9 +43,10 @@
 static const struct xt_option_entry dscp_opts[] = {
 	{.name = "dscp", .id = O_DSCP, .excl = F_DSCP_CLASS,
 	 .type = XTTYPE_UINT8, .min = 0, .max = XT_DSCP_MAX,
-	 .flags = XTOPT_PUT, XTOPT_POINTER(struct xt_dscp_info, dscp)},
+	 .flags = XTOPT_INVERT | XTOPT_PUT,
+	 XTOPT_POINTER(struct xt_dscp_info, dscp)},
 	{.name = "dscp-class", .id = O_DSCP_CLASS, .excl = F_DSCP,
-	 .type = XTTYPE_STRING},
+	 .type = XTTYPE_STRING, .flags = XTOPT_INVERT},
 	XTOPT_TABLEEND,
 };
 
@@ -90,21 +91,66 @@
 	printf("%s --dscp 0x%02x", dinfo->invert ? " !" : "", dinfo->dscp);
 }
 
-static struct xtables_match dscp_match = {
-	.family		= NFPROTO_UNSPEC,
-	.name 		= "dscp",
-	.version 	= XTABLES_VERSION,
-	.size 		= XT_ALIGN(sizeof(struct xt_dscp_info)),
-	.userspacesize	= XT_ALIGN(sizeof(struct xt_dscp_info)),
-	.help		= dscp_help,
-	.print		= dscp_print,
-	.save		= dscp_save,
-	.x6_parse	= dscp_parse,
-	.x6_fcheck	= dscp_check,
-	.x6_options	= dscp_opts,
+static int __dscp_xlate(struct xt_xlate *xl,
+			const struct xt_xlate_mt_params *params)
+{
+	const struct xt_dscp_info *dinfo =
+		(const struct xt_dscp_info *)params->match->data;
+
+	xt_xlate_add(xl, "dscp %s0x%02x", dinfo->invert ? "!= " : "",
+		     dinfo->dscp);
+
+	return 1;
+}
+
+static int dscp_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_mt_params *params)
+{
+	xt_xlate_add(xl, "ip ");
+
+	return __dscp_xlate(xl, params);
+}
+
+static int dscp_xlate6(struct xt_xlate *xl,
+		       const struct xt_xlate_mt_params *params)
+{
+	xt_xlate_add(xl, "ip6 ");
+
+	return __dscp_xlate(xl, params);
+}
+
+static struct xtables_match dscp_mt_reg[] = {
+	{
+		.family		= NFPROTO_IPV4,
+		.name           = "dscp",
+		.version        = XTABLES_VERSION,
+		.size           = XT_ALIGN(sizeof(struct xt_dscp_info)),
+		.userspacesize	= XT_ALIGN(sizeof(struct xt_dscp_info)),
+		.help		= dscp_help,
+		.print		= dscp_print,
+		.save		= dscp_save,
+		.x6_parse	= dscp_parse,
+		.x6_fcheck	= dscp_check,
+		.x6_options	= dscp_opts,
+		.xlate		= dscp_xlate,
+	},
+	{
+		.family		= NFPROTO_IPV6,
+		.name           = "dscp",
+		.version        = XTABLES_VERSION,
+		.size           = XT_ALIGN(sizeof(struct xt_dscp_info)),
+		.userspacesize	= XT_ALIGN(sizeof(struct xt_dscp_info)),
+		.help		= dscp_help,
+		.print		= dscp_print,
+		.save		= dscp_save,
+		.x6_parse	= dscp_parse,
+		.x6_fcheck	= dscp_check,
+		.x6_options	= dscp_opts,
+		.xlate		= dscp_xlate6,
+	},
 };
 
 void _init(void)
 {
-	xtables_register_match(&dscp_match);
+	xtables_register_matches(dscp_mt_reg, ARRAY_SIZE(dscp_mt_reg));
 }
diff --git a/extensions/libxt_dscp.t b/extensions/libxt_dscp.t
new file mode 100644
index 0000000..38d7f04
--- /dev/null
+++ b/extensions/libxt_dscp.t
@@ -0,0 +1,10 @@
+:INPUT,FORWARD,OUTPUT
+-m dscp --dscp 0;=;OK
+-m dscp --dscp 0x3f;=;OK
+-m dscp --dscp -1;;FAIL
+-m dscp --dscp 0x40;;FAIL
+-m dscp --dscp 0x3f --dscp-class CS0;;FAIL
+-m dscp --dscp-class CS0;-m dscp --dscp 0x00;OK
+-m dscp --dscp-class BE;-m dscp --dscp 0x00;OK
+-m dscp --dscp-class EF;-m dscp --dscp 0x2e;OK
+-m dscp;;FAIL
diff --git a/extensions/libxt_dscp.txlate b/extensions/libxt_dscp.txlate
new file mode 100644
index 0000000..2cccc3b
--- /dev/null
+++ b/extensions/libxt_dscp.txlate
@@ -0,0 +1,5 @@
+iptables-translate -t filter -A INPUT -m dscp --dscp 0x32 -j ACCEPT
+nft add rule ip filter INPUT ip dscp 0x32 counter accept
+
+ip6tables-translate -t filter -A INPUT -m dscp ! --dscp 0x32 -j ACCEPT
+nft add rule ip6 filter INPUT ip6 dscp != 0x32 counter accept
diff --git a/extensions/libxt_ecn.c b/extensions/libxt_ecn.c
new file mode 100644
index 0000000..ad3c7a0
--- /dev/null
+++ b/extensions/libxt_ecn.c
@@ -0,0 +1,182 @@
+/* Shared library add-on to iptables for ECN matching
+ *
+ * (C) 2002 by Harald Welte <laforge@netfilter.org>
+ * (C) 2011 by Patrick McHardy <kaber@trash.net>
+ *
+ * This program is distributed under the terms of GNU GPL v2, 1991
+ *
+ * libipt_ecn.c borrowed heavily from libipt_dscp.c
+ *
+ */
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_ecn.h>
+
+enum {
+	O_ECN_TCP_CWR = 0,
+	O_ECN_TCP_ECE,
+	O_ECN_IP_ECT,
+};
+
+static void ecn_help(void)
+{
+	printf(
+"ECN match options\n"
+"[!] --ecn-tcp-cwr 		Match CWR bit of TCP header\n"
+"[!] --ecn-tcp-ece		Match ECE bit of TCP header\n"
+"[!] --ecn-ip-ect [0..3]	Match ECN codepoint in IPv4/IPv6 header\n");
+}
+
+static const struct xt_option_entry ecn_opts[] = {
+	{.name = "ecn-tcp-cwr", .id = O_ECN_TCP_CWR, .type = XTTYPE_NONE,
+	 .flags = XTOPT_INVERT},
+	{.name = "ecn-tcp-ece", .id = O_ECN_TCP_ECE, .type = XTTYPE_NONE,
+	 .flags = XTOPT_INVERT},
+	{.name = "ecn-ip-ect", .id = O_ECN_IP_ECT, .type = XTTYPE_UINT8,
+	 .min = 0, .max = 3, .flags = XTOPT_INVERT},
+	XTOPT_TABLEEND,
+};
+
+static void ecn_parse(struct xt_option_call *cb)
+{
+	struct xt_ecn_info *einfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_ECN_TCP_CWR:
+		einfo->operation |= XT_ECN_OP_MATCH_CWR;
+		if (cb->invert)
+			einfo->invert |= XT_ECN_OP_MATCH_CWR;
+		break;
+	case O_ECN_TCP_ECE:
+		einfo->operation |= XT_ECN_OP_MATCH_ECE;
+		if (cb->invert)
+			einfo->invert |= XT_ECN_OP_MATCH_ECE;
+		break;
+	case O_ECN_IP_ECT:
+		if (cb->invert)
+			einfo->invert |= XT_ECN_OP_MATCH_IP;
+		einfo->operation |= XT_ECN_OP_MATCH_IP;
+		einfo->ip_ect = cb->val.u8;
+		break;
+	}
+}
+
+static void ecn_check(struct xt_fcheck_call *cb)
+{
+	if (cb->xflags == 0)
+		xtables_error(PARAMETER_PROBLEM,
+		           "ECN match: some option required");
+}
+
+static void ecn_print(const void *ip, const struct xt_entry_match *match,
+                      int numeric)
+{
+	const struct xt_ecn_info *einfo =
+		(const struct xt_ecn_info *)match->data;
+
+	printf(" ECN match");
+
+	if (einfo->operation & XT_ECN_OP_MATCH_ECE) {
+		printf(" %sECE",
+		       (einfo->invert & XT_ECN_OP_MATCH_ECE) ? "!" : "");
+	}
+
+	if (einfo->operation & XT_ECN_OP_MATCH_CWR) {
+		printf(" %sCWR",
+		       (einfo->invert & XT_ECN_OP_MATCH_CWR) ? "!" : "");
+	}
+
+	if (einfo->operation & XT_ECN_OP_MATCH_IP) {
+		printf(" %sECT=%d",
+		       (einfo->invert & XT_ECN_OP_MATCH_IP) ? "!" : "",
+		       einfo->ip_ect);
+	}
+}
+
+static void ecn_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_ecn_info *einfo =
+		(const struct xt_ecn_info *)match->data;
+
+	if (einfo->operation & XT_ECN_OP_MATCH_ECE) {
+		if (einfo->invert & XT_ECN_OP_MATCH_ECE)
+			printf(" !");
+		printf(" --ecn-tcp-ece");
+	}
+
+	if (einfo->operation & XT_ECN_OP_MATCH_CWR) {
+		if (einfo->invert & XT_ECN_OP_MATCH_CWR)
+			printf(" !");
+		printf(" --ecn-tcp-cwr");
+	}
+
+	if (einfo->operation & XT_ECN_OP_MATCH_IP) {
+		if (einfo->invert & XT_ECN_OP_MATCH_IP)
+			printf(" !");
+		printf(" --ecn-ip-ect %d", einfo->ip_ect);
+	}
+}
+
+static int ecn_xlate(struct xt_xlate *xl,
+		     const struct xt_xlate_mt_params *params)
+{
+	const struct xt_ecn_info *einfo =
+		(const struct xt_ecn_info *)params->match->data;
+
+	if (einfo->operation & XT_ECN_OP_MATCH_ECE) {
+		xt_xlate_add(xl, "tcp flags ");
+		if (einfo->invert)
+			xt_xlate_add(xl,"!= ");
+		xt_xlate_add(xl, "ecn");
+	}
+
+	if (einfo->operation & XT_ECN_OP_MATCH_CWR) {
+		xt_xlate_add(xl, "tcp flags ");
+		if (einfo->invert)
+			xt_xlate_add(xl,"!= ");
+		xt_xlate_add(xl, "cwr");
+	}
+
+	if (einfo->operation & XT_ECN_OP_MATCH_IP) {
+		xt_xlate_add(xl, "ip ecn ");
+		if (einfo->invert)
+			xt_xlate_add(xl,"!= ");
+
+		switch (einfo->ip_ect) {
+		case 0:
+			xt_xlate_add(xl, "not-ect");
+			break;
+		case 1:
+			xt_xlate_add(xl, "ect1");
+			break;
+		case 2:
+			xt_xlate_add(xl, "ect0");
+			break;
+		case 3:
+			xt_xlate_add(xl, "ce");
+			break;
+		}
+	}
+	return 1;
+}
+
+static struct xtables_match ecn_mt_reg = {
+	.name          = "ecn",
+	.version       = XTABLES_VERSION,
+	.family        = NFPROTO_UNSPEC,
+	.size          = XT_ALIGN(sizeof(struct xt_ecn_info)),
+	.userspacesize = XT_ALIGN(sizeof(struct xt_ecn_info)),
+	.help          = ecn_help,
+	.print         = ecn_print,
+	.save          = ecn_save,
+	.x6_parse      = ecn_parse,
+	.x6_fcheck     = ecn_check,
+	.x6_options    = ecn_opts,
+	.xlate	       = ecn_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_match(&ecn_mt_reg);
+}
diff --git a/extensions/libxt_ecn.man b/extensions/libxt_ecn.man
new file mode 100644
index 0000000..31c0a3e
--- /dev/null
+++ b/extensions/libxt_ecn.man
@@ -0,0 +1,11 @@
+This allows you to match the ECN bits of the IPv4/IPv6 and TCP header.  ECN is the Explicit Congestion Notification mechanism as specified in RFC3168
+.TP
+[\fB!\fP] \fB\-\-ecn\-tcp\-cwr\fP
+This matches if the TCP ECN CWR (Congestion Window Received) bit is set.
+.TP
+[\fB!\fP] \fB\-\-ecn\-tcp\-ece\fP
+This matches if the TCP ECN ECE (ECN Echo) bit is set.
+.TP
+[\fB!\fP] \fB\-\-ecn\-ip\-ect\fP \fInum\fP
+This matches a particular IPv4/IPv6 ECT (ECN-Capable Transport). You have to specify
+a number between `0' and `3'.
diff --git a/extensions/libxt_ecn.t b/extensions/libxt_ecn.t
new file mode 100644
index 0000000..b32aea3
--- /dev/null
+++ b/extensions/libxt_ecn.t
@@ -0,0 +1,5 @@
+:INPUT,FORWARD,OUTPUT
+-m ecn --ecn-tcp-cwr;;FAIL
+-p tcp -m ecn --ecn-tcp-cwr;=;OK
+-p tcp -m ecn --ecn-tcp-ece --ecn-tcp-cwr --ecn-ip-ect 2;=;OK
+-p tcp -m ecn ! --ecn-tcp-ece ! --ecn-tcp-cwr ! --ecn-ip-ect 2;=;OK
diff --git a/extensions/libxt_ecn.txlate b/extensions/libxt_ecn.txlate
new file mode 100644
index 0000000..f012f12
--- /dev/null
+++ b/extensions/libxt_ecn.txlate
@@ -0,0 +1,29 @@
+iptables-translate -A INPUT -m ecn --ecn-ip-ect 0
+nft add rule ip filter INPUT ip ecn not-ect counter
+
+iptables-translate -A INPUT -m ecn --ecn-ip-ect 1
+nft add rule ip filter INPUT ip ecn ect1 counter
+
+iptables-translate -A INPUT -m ecn --ecn-ip-ect 2
+nft add rule ip filter INPUT ip ecn ect0 counter
+
+iptables-translate -A INPUT -m ecn --ecn-ip-ect 3
+nft add rule ip filter INPUT ip ecn ce counter
+
+iptables-translate -A INPUT -m ecn ! --ecn-ip-ect 0
+nft add rule ip filter INPUT ip ecn != not-ect counter
+
+iptables-translate -A INPUT -m ecn ! --ecn-ip-ect 1
+nft add rule ip filter INPUT ip ecn != ect1 counter
+
+iptables-translate -A INPUT -m ecn ! --ecn-ip-ect 2
+nft add rule ip filter INPUT ip ecn != ect0 counter
+
+iptables-translate -A INPUT -m ecn ! --ecn-ip-ect 3
+nft add rule ip filter INPUT ip ecn != ce counter
+
+iptables-translate -A INPUT -m ecn ! --ecn-tcp-ece
+nft add rule ip filter INPUT tcp flags != ecn counter
+
+iptables-translate -A INPUT -m ecn --ecn-tcp-cwr
+nft add rule ip filter INPUT tcp flags cwr counter
diff --git a/extensions/libxt_esp.c b/extensions/libxt_esp.c
index 294338b..2c7ff94 100644
--- a/extensions/libxt_esp.c
+++ b/extensions/libxt_esp.c
@@ -21,6 +21,13 @@
 	XTOPT_TABLEEND,
 };
 
+static void esp_init(struct xt_entry_match *m)
+{
+	struct xt_esp *espinfo = (void *)m->data;
+
+	espinfo->spis[1] = ~0U;
+}
+
 static void esp_parse(struct xt_option_call *cb)
 {
 	struct xt_esp *espinfo = cb->data;
@@ -79,17 +86,37 @@
 
 }
 
+static int esp_xlate(struct xt_xlate *xl,
+		     const struct xt_xlate_mt_params *params)
+{
+	const struct xt_esp *espinfo = (struct xt_esp *)params->match->data;
+
+	if (!(espinfo->spis[0] == 0 && espinfo->spis[1] == 0xFFFFFFFF)) {
+		xt_xlate_add(xl, "esp spi%s",
+			   (espinfo->invflags & XT_ESP_INV_SPI) ? " !=" : "");
+		if (espinfo->spis[0] != espinfo->spis[1])
+			xt_xlate_add(xl, " %u-%u", espinfo->spis[0],
+				   espinfo->spis[1]);
+		else
+			xt_xlate_add(xl, " %u", espinfo->spis[0]);
+	}
+
+	return 1;
+}
+
 static struct xtables_match esp_match = {
 	.family		= NFPROTO_UNSPEC,
-	.name 		= "esp",
-	.version 	= XTABLES_VERSION,
+	.name		= "esp",
+	.version	= XTABLES_VERSION,
 	.size		= XT_ALIGN(sizeof(struct xt_esp)),
 	.userspacesize	= XT_ALIGN(sizeof(struct xt_esp)),
 	.help		= esp_help,
+	.init		= esp_init,
 	.print		= esp_print,
 	.save		= esp_save,
 	.x6_parse	= esp_parse,
 	.x6_options	= esp_opts,
+	.xlate		= esp_xlate,
 };
 
 void
diff --git a/extensions/libxt_esp.t b/extensions/libxt_esp.t
new file mode 100644
index 0000000..92c5779
--- /dev/null
+++ b/extensions/libxt_esp.t
@@ -0,0 +1,8 @@
+:INPUT,FORWARD,OUTPUT
+-p esp -m esp --espspi 0;=;OK
+-p esp -m esp --espspi :32;-p esp -m esp --espspi 0:32;OK
+-p esp -m esp --espspi 0:4294967295;-p esp -m esp;OK
+-p esp -m esp ! --espspi 0:4294967294;=;OK
+-p esp -m esp --espspi -1;;FAIL
+-p esp -m esp;=;OK
+-m esp;;FAIL
diff --git a/extensions/libxt_esp.txlate b/extensions/libxt_esp.txlate
new file mode 100644
index 0000000..5e2f18f
--- /dev/null
+++ b/extensions/libxt_esp.txlate
@@ -0,0 +1,11 @@
+iptables-translate -A FORWARD -p esp -j ACCEPT
+nft add rule ip filter FORWARD ip protocol esp counter accept
+
+iptables-translate -A INPUT  --in-interface  wan --protocol esp -j ACCEPT
+nft add rule ip filter INPUT iifname "wan" ip protocol esp counter accept
+
+iptables-translate -A INPUT -p 50 -m esp --espspi 500 -j DROP
+nft add rule ip filter INPUT esp spi 500 counter drop
+
+iptables-translate -A INPUT -p 50 -m esp --espspi 500:600 -j DROP
+nft add rule ip filter INPUT esp spi 500-600 counter drop
diff --git a/extensions/libxt_hashlimit.c b/extensions/libxt_hashlimit.c
index e683f9a..7f1d2a4 100644
--- a/extensions/libxt_hashlimit.c
+++ b/extensions/libxt_hashlimit.c
@@ -7,23 +7,36 @@
  * Based on ipt_limit.c by
  * Jérôme de Vivie   <devivie@info.enserb.u-bordeaux.fr>
  * Hervé Eychenne    <rv@wallfire.org>
- * 
+ *
  * Error corections by nmalykh@bilim.com (22.01.2005)
  */
+#define _BSD_SOURCE 1
+#define _DEFAULT_SOURCE 1
+#define _ISOC99_SOURCE 1
+#include <inttypes.h>
+#include <math.h>
 #include <stdbool.h>
-#include <stdint.h>
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
+#include <errno.h>
 #include <xtables.h>
 #include <linux/netfilter/x_tables.h>
 #include <linux/netfilter/xt_hashlimit.h>
 
 #define XT_HASHLIMIT_BURST	5
+#define XT_HASHLIMIT_BURST_MAX_v1	10000
+#define XT_HASHLIMIT_BURST_MAX		1000000
+
+#define XT_HASHLIMIT_BYTE_EXPIRE	15
+#define XT_HASHLIMIT_BYTE_EXPIRE_BURST	60
 
 /* miliseconds */
 #define XT_HASHLIMIT_GCINTERVAL	1000
-#define XT_HASHLIMIT_EXPIRE	10000
+
+struct hashlimit_mt_udata {
+	uint32_t mult;
+};
 
 static void hashlimit_help(void)
 {
@@ -56,8 +69,13 @@
 	O_HTABLE_MAX,
 	O_HTABLE_GCINT,
 	O_HTABLE_EXPIRE,
-	F_UPTO  = 1 << O_UPTO,
-	F_ABOVE = 1 << O_ABOVE,
+	O_RATEMATCH,
+	O_INTERVAL,
+	F_BURST         = 1 << O_BURST,
+	F_UPTO          = 1 << O_UPTO,
+	F_ABOVE         = 1 << O_ABOVE,
+	F_HTABLE_EXPIRE = 1 << O_HTABLE_EXPIRE,
+	F_RATEMATCH	= 1 << O_RATEMATCH,
 };
 
 static void hashlimit_mt_help(void)
@@ -81,12 +99,35 @@
 "\n", XT_HASHLIMIT_BURST);
 }
 
+static void hashlimit_mt_help_v3(void)
+{
+	printf(
+"hashlimit match options:\n"
+"  --hashlimit-upto <avg>           max average match rate\n"
+"                                   [Packets per second unless followed by \n"
+"                                   /sec /minute /hour /day postfixes]\n"
+"  --hashlimit-above <avg>          min average match rate\n"
+"  --hashlimit-mode <mode>          mode is a comma-separated list of\n"
+"                                   dstip,srcip,dstport,srcport (or none)\n"
+"  --hashlimit-srcmask <length>     source address grouping prefix length\n"
+"  --hashlimit-dstmask <length>     destination address grouping prefix length\n"
+"  --hashlimit-name <name>          name for /proc/net/ipt_hashlimit\n"
+"  --hashlimit-burst <num>	    number to match in a burst, default %u\n"
+"  --hashlimit-htable-size <num>    number of hashtable buckets\n"
+"  --hashlimit-htable-max <num>     number of hashtable entries\n"
+"  --hashlimit-htable-gcinterval    interval between garbage collection runs\n"
+"  --hashlimit-htable-expire        after which time are idle entries expired?\n"
+"  --hashlimit-rate-match           rate match the flow without rate-limiting it\n"
+"  --hashlimit-rate-interval        interval in seconds for hashlimit-rate-match\n"
+"\n", XT_HASHLIMIT_BURST);
+}
+
 #define s struct xt_hashlimit_info
 static const struct xt_option_entry hashlimit_opts[] = {
 	{.name = "hashlimit", .id = O_UPTO, .excl = F_ABOVE,
-	 .type = XTTYPE_STRING, .flags = XTOPT_INVERT},
+	 .type = XTTYPE_STRING},
 	{.name = "hashlimit-burst", .id = O_BURST, .type = XTTYPE_UINT32,
-	 .min = 1, .max = 10000, .flags = XTOPT_PUT,
+	 .min = 1, .max = XT_HASHLIMIT_BURST_MAX_v1, .flags = XTOPT_PUT,
 	 XTOPT_POINTER(s, cfg.burst)},
 	{.name = "hashlimit-htable-size", .id = O_HTABLE_SIZE,
 	 .type = XTTYPE_UINT32, .flags = XTOPT_PUT,
@@ -109,7 +150,7 @@
 #undef s
 
 #define s struct xt_hashlimit_mtinfo1
-static const struct xt_option_entry hashlimit_mt_opts[] = {
+static const struct xt_option_entry hashlimit_mt_opts_v1[] = {
 	{.name = "hashlimit-upto", .id = O_UPTO, .excl = F_ABOVE,
 	 .type = XTTYPE_STRING, .flags = XTOPT_INVERT},
 	{.name = "hashlimit-above", .id = O_ABOVE, .excl = F_UPTO,
@@ -118,9 +159,7 @@
 	 .type = XTTYPE_STRING, .flags = XTOPT_INVERT}, /* old name */
 	{.name = "hashlimit-srcmask", .id = O_SRCMASK, .type = XTTYPE_PLEN},
 	{.name = "hashlimit-dstmask", .id = O_DSTMASK, .type = XTTYPE_PLEN},
-	{.name = "hashlimit-burst", .id = O_BURST, .type = XTTYPE_UINT32,
-	 .min = 1, .max = 10000, .flags = XTOPT_PUT,
-	 XTOPT_POINTER(s, cfg.burst)},
+	{.name = "hashlimit-burst", .id = O_BURST, .type = XTTYPE_STRING},
 	{.name = "hashlimit-htable-size", .id = O_HTABLE_SIZE,
 	 .type = XTTYPE_UINT32, .flags = XTOPT_PUT,
 	 XTOPT_POINTER(s, cfg.size)},
@@ -140,39 +179,251 @@
 };
 #undef s
 
+#define s struct xt_hashlimit_mtinfo2
+static const struct xt_option_entry hashlimit_mt_opts_v2[] = {
+	{.name = "hashlimit-upto", .id = O_UPTO, .excl = F_ABOVE,
+	 .type = XTTYPE_STRING, .flags = XTOPT_INVERT},
+	{.name = "hashlimit-above", .id = O_ABOVE, .excl = F_UPTO,
+	 .type = XTTYPE_STRING, .flags = XTOPT_INVERT},
+	{.name = "hashlimit", .id = O_UPTO, .excl = F_ABOVE,
+	 .type = XTTYPE_STRING, .flags = XTOPT_INVERT}, /* old name */
+	{.name = "hashlimit-srcmask", .id = O_SRCMASK, .type = XTTYPE_PLEN},
+	{.name = "hashlimit-dstmask", .id = O_DSTMASK, .type = XTTYPE_PLEN},
+	{.name = "hashlimit-burst", .id = O_BURST, .type = XTTYPE_STRING},
+	{.name = "hashlimit-htable-size", .id = O_HTABLE_SIZE,
+	 .type = XTTYPE_UINT32, .flags = XTOPT_PUT,
+	 XTOPT_POINTER(s, cfg.size)},
+	{.name = "hashlimit-htable-max", .id = O_HTABLE_MAX,
+	 .type = XTTYPE_UINT32, .flags = XTOPT_PUT,
+	 XTOPT_POINTER(s, cfg.max)},
+	{.name = "hashlimit-htable-gcinterval", .id = O_HTABLE_GCINT,
+	 .type = XTTYPE_UINT32, .flags = XTOPT_PUT,
+	 XTOPT_POINTER(s, cfg.gc_interval)},
+	{.name = "hashlimit-htable-expire", .id = O_HTABLE_EXPIRE,
+	 .type = XTTYPE_UINT32, .flags = XTOPT_PUT,
+	 XTOPT_POINTER(s, cfg.expire)},
+	{.name = "hashlimit-mode", .id = O_MODE, .type = XTTYPE_STRING},
+	{.name = "hashlimit-name", .id = O_NAME, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_PUT, XTOPT_POINTER(s, name), .min = 1},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+#define s struct xt_hashlimit_mtinfo3
+static const struct xt_option_entry hashlimit_mt_opts[] = {
+	{.name = "hashlimit-upto", .id = O_UPTO, .excl = F_ABOVE,
+	 .type = XTTYPE_STRING, .flags = XTOPT_INVERT},
+	{.name = "hashlimit-above", .id = O_ABOVE, .excl = F_UPTO,
+	 .type = XTTYPE_STRING, .flags = XTOPT_INVERT},
+	{.name = "hashlimit", .id = O_UPTO, .excl = F_ABOVE,
+	 .type = XTTYPE_STRING, .flags = XTOPT_INVERT}, /* old name */
+	{.name = "hashlimit-srcmask", .id = O_SRCMASK, .type = XTTYPE_PLEN},
+	{.name = "hashlimit-dstmask", .id = O_DSTMASK, .type = XTTYPE_PLEN},
+	{.name = "hashlimit-burst", .id = O_BURST, .type = XTTYPE_STRING},
+	{.name = "hashlimit-htable-size", .id = O_HTABLE_SIZE,
+	 .type = XTTYPE_UINT32, .flags = XTOPT_PUT,
+	 XTOPT_POINTER(s, cfg.size)},
+	{.name = "hashlimit-htable-max", .id = O_HTABLE_MAX,
+	 .type = XTTYPE_UINT32, .flags = XTOPT_PUT,
+	 XTOPT_POINTER(s, cfg.max)},
+	{.name = "hashlimit-htable-gcinterval", .id = O_HTABLE_GCINT,
+	 .type = XTTYPE_UINT32, .flags = XTOPT_PUT,
+	 XTOPT_POINTER(s, cfg.gc_interval)},
+	{.name = "hashlimit-htable-expire", .id = O_HTABLE_EXPIRE,
+	 .type = XTTYPE_UINT32, .flags = XTOPT_PUT,
+	 XTOPT_POINTER(s, cfg.expire)},
+	{.name = "hashlimit-mode", .id = O_MODE, .type = XTTYPE_STRING},
+	{.name = "hashlimit-name", .id = O_NAME, .type = XTTYPE_STRING,
+	 .flags = XTOPT_MAND | XTOPT_PUT, XTOPT_POINTER(s, name), .min = 1},
+	{.name = "hashlimit-rate-match", .id = O_RATEMATCH, .type = XTTYPE_NONE},
+	{.name = "hashlimit-rate-interval", .id = O_INTERVAL, .type = XTTYPE_STRING},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static int
+cfg_copy(struct hashlimit_cfg3 *to, const void *from, int revision)
+{
+	if (revision == 1) {
+		struct hashlimit_cfg1 *cfg = (struct hashlimit_cfg1 *)from;
+
+		to->mode = cfg->mode;
+		to->avg = cfg->avg;
+		to->burst = cfg->burst;
+		to->size = cfg->size;
+		to->max = cfg->max;
+		to->gc_interval = cfg->gc_interval;
+		to->expire = cfg->expire;
+		to->srcmask = cfg->srcmask;
+		to->dstmask = cfg->dstmask;
+	} else if (revision == 2) {
+		struct hashlimit_cfg2 *cfg = (struct hashlimit_cfg2 *)from;
+
+		to->mode = cfg->mode;
+		to->avg = cfg->avg;
+		to->burst = cfg->burst;
+		to->size = cfg->size;
+		to->max = cfg->max;
+		to->gc_interval = cfg->gc_interval;
+		to->expire = cfg->expire;
+		to->srcmask = cfg->srcmask;
+		to->dstmask = cfg->dstmask;
+	} else if (revision == 3) {
+		memcpy(to, from, sizeof(struct hashlimit_cfg3));
+	} else {
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static uint64_t cost_to_bytes(uint64_t cost)
+{
+	uint64_t r;
+
+	r = cost ? UINT32_MAX / cost : UINT32_MAX;
+	r = (r - 1) << XT_HASHLIMIT_BYTE_SHIFT;
+	return r;
+}
+
+static uint64_t bytes_to_cost(uint64_t bytes)
+{
+	uint32_t r = bytes >> XT_HASHLIMIT_BYTE_SHIFT;
+	return UINT32_MAX / (r+1);
+}
+
+static uint32_t get_factor(int chr)
+{
+	switch (chr) {
+	case 'm': return 1024 * 1024;
+	case 'k': return 1024;
+	}
+	return 1;
+}
+
+static void burst_error_v1(void)
+{
+	xtables_error(PARAMETER_PROBLEM, "bad value for option "
+			"\"--hashlimit-burst\", or out of range (1-%u).", XT_HASHLIMIT_BURST_MAX_v1);
+}
+
+static void burst_error(void)
+{
+	xtables_error(PARAMETER_PROBLEM, "bad value for option "
+			"\"--hashlimit-burst\", or out of range (1-%u).", XT_HASHLIMIT_BURST_MAX);
+}
+
+static uint64_t parse_burst(const char *burst, int revision)
+{
+	uintmax_t v;
+	char *end;
+	uint64_t max = (revision == 1) ? UINT32_MAX : UINT64_MAX;
+	uint64_t burst_max = (revision == 1) ?
+			      XT_HASHLIMIT_BURST_MAX_v1 : XT_HASHLIMIT_BURST_MAX;
+
+	if (!xtables_strtoul(burst, &end, &v, 1, max) ||
+		(*end == 0 && v > burst_max)) {
+		if (revision == 1)
+			burst_error_v1();
+		else
+			burst_error();
+	}
+
+	v *= get_factor(*end);
+	if (v > max)
+		xtables_error(PARAMETER_PROBLEM, "bad value for option "
+			"\"--hashlimit-burst\", value \"%s\" too large "
+				"(max %"PRIu64"mb).", burst, max/1024/1024);
+	return v;
+}
+
+static bool parse_bytes(const char *rate, void *val, struct hashlimit_mt_udata *ud, int revision)
+{
+	unsigned int factor = 1;
+	uint64_t tmp, r;
+	const char *mode = strstr(rate, "b/s");
+	uint64_t max = (revision == 1) ? UINT32_MAX : UINT64_MAX;
+
+	if (!mode || mode == rate)
+		return false;
+
+	mode--;
+	r = atoll(rate);
+	if (r == 0)
+		return false;
+
+	factor = get_factor(*mode);
+	tmp = (uint64_t) r * factor;
+	if (tmp > max)
+		xtables_error(PARAMETER_PROBLEM,
+			"Rate value too large \"%"PRIu64"\" (max %"PRIu64")\n",
+					tmp, max);
+
+	tmp = bytes_to_cost(tmp);
+	if (tmp == 0)
+		xtables_error(PARAMETER_PROBLEM, "Rate too high \"%s\"\n", rate);
+
+	ud->mult = XT_HASHLIMIT_BYTE_EXPIRE;
+
+	if(revision == 1)
+		*((uint32_t*)val) = tmp;
+	else
+		*((uint64_t*)val) = tmp;
+
+	return true;
+}
+
 static
-int parse_rate(const char *rate, uint32_t *val)
+int parse_rate(const char *rate, void *val, struct hashlimit_mt_udata *ud, int revision)
 {
 	const char *delim;
-	uint32_t r;
-	uint32_t mult = 1;  /* Seconds by default. */
+	uint64_t tmp, r;
+	uint64_t scale = (revision == 1) ? XT_HASHLIMIT_SCALE : XT_HASHLIMIT_SCALE_v2;
 
+	ud->mult = 1;  /* Seconds by default. */
 	delim = strchr(rate, '/');
 	if (delim) {
 		if (strlen(delim+1) == 0)
 			return 0;
 
 		if (strncasecmp(delim+1, "second", strlen(delim+1)) == 0)
-			mult = 1;
+			ud->mult = 1;
 		else if (strncasecmp(delim+1, "minute", strlen(delim+1)) == 0)
-			mult = 60;
+			ud->mult = 60;
 		else if (strncasecmp(delim+1, "hour", strlen(delim+1)) == 0)
-			mult = 60*60;
+			ud->mult = 60*60;
 		else if (strncasecmp(delim+1, "day", strlen(delim+1)) == 0)
-			mult = 24*60*60;
+			ud->mult = 24*60*60;
 		else
 			return 0;
 	}
-	r = atoi(rate);
+	r = atoll(rate);
 	if (!r)
 		return 0;
 
-	/* This would get mapped to infinite (1/day is minimum they
-           can specify, so we're ok at that end). */
-	if (r / mult > XT_HASHLIMIT_SCALE)
+	tmp = scale * ud->mult / r;
+	if (tmp == 0)
+		/*
+		 * The rate maps to infinity. (1/day is the minimum they can
+		 * specify, so we are ok at that end).
+		 */
 		xtables_error(PARAMETER_PROBLEM, "Rate too fast \"%s\"\n", rate);
 
-	*val = XT_HASHLIMIT_SCALE * mult / r;
+	if(revision == 1)
+		*((uint32_t*)val) = tmp;
+	else
+		*((uint64_t*)val) = tmp;
+
+	return 1;
+}
+
+static int parse_interval(const char *rate, uint32_t *val)
+{
+	int r = atoi(rate);
+	if (r <= 0)
+		return 0;
+
+	*val = r;
 	return 1;
 }
 
@@ -182,34 +433,77 @@
 
 	r->cfg.burst = XT_HASHLIMIT_BURST;
 	r->cfg.gc_interval = XT_HASHLIMIT_GCINTERVAL;
-	r->cfg.expire = XT_HASHLIMIT_EXPIRE;
 
 }
 
-static void hashlimit_mt4_init(struct xt_entry_match *match)
+static void hashlimit_mt4_init_v1(struct xt_entry_match *match)
 {
 	struct xt_hashlimit_mtinfo1 *info = (void *)match->data;
 
 	info->cfg.mode        = 0;
 	info->cfg.burst       = XT_HASHLIMIT_BURST;
 	info->cfg.gc_interval = XT_HASHLIMIT_GCINTERVAL;
-	info->cfg.expire      = XT_HASHLIMIT_EXPIRE;
 	info->cfg.srcmask     = 32;
 	info->cfg.dstmask     = 32;
 }
 
-static void hashlimit_mt6_init(struct xt_entry_match *match)
+static void hashlimit_mt6_init_v1(struct xt_entry_match *match)
 {
 	struct xt_hashlimit_mtinfo1 *info = (void *)match->data;
 
 	info->cfg.mode        = 0;
 	info->cfg.burst       = XT_HASHLIMIT_BURST;
 	info->cfg.gc_interval = XT_HASHLIMIT_GCINTERVAL;
-	info->cfg.expire      = XT_HASHLIMIT_EXPIRE;
 	info->cfg.srcmask     = 128;
 	info->cfg.dstmask     = 128;
 }
 
+static void hashlimit_mt4_init_v2(struct xt_entry_match *match)
+{
+	struct xt_hashlimit_mtinfo2 *info = (void *)match->data;
+
+	info->cfg.mode        = 0;
+	info->cfg.burst       = XT_HASHLIMIT_BURST;
+	info->cfg.gc_interval = XT_HASHLIMIT_GCINTERVAL;
+	info->cfg.srcmask     = 32;
+	info->cfg.dstmask     = 32;
+}
+
+static void hashlimit_mt6_init_v2(struct xt_entry_match *match)
+{
+	struct xt_hashlimit_mtinfo2 *info = (void *)match->data;
+
+	info->cfg.mode        = 0;
+	info->cfg.burst       = XT_HASHLIMIT_BURST;
+	info->cfg.gc_interval = XT_HASHLIMIT_GCINTERVAL;
+	info->cfg.srcmask     = 128;
+	info->cfg.dstmask     = 128;
+}
+
+static void hashlimit_mt4_init(struct xt_entry_match *match)
+{
+	struct xt_hashlimit_mtinfo3 *info = (void *)match->data;
+
+	info->cfg.mode        = 0;
+	info->cfg.burst       = XT_HASHLIMIT_BURST;
+	info->cfg.gc_interval = XT_HASHLIMIT_GCINTERVAL;
+	info->cfg.srcmask     = 32;
+	info->cfg.dstmask     = 32;
+	info->cfg.interval    = 0;
+}
+
+static void hashlimit_mt6_init(struct xt_entry_match *match)
+{
+	struct xt_hashlimit_mtinfo3 *info = (void *)match->data;
+
+	info->cfg.mode        = 0;
+	info->cfg.burst       = XT_HASHLIMIT_BURST;
+	info->cfg.gc_interval = XT_HASHLIMIT_GCINTERVAL;
+	info->cfg.srcmask     = 128;
+	info->cfg.dstmask     = 128;
+	info->cfg.interval    = 0;
+}
+
 /* Parse a 'mode' parameter into the required bitmask */
 static int parse_mode(uint32_t *mode, const char *option_arg)
 {
@@ -246,19 +540,10 @@
 	xtables_option_parse(cb);
 	switch (cb->entry->id) {
 	case O_UPTO:
-		if (cb->invert)
-			info->cfg.mode |= XT_HASHLIMIT_INVERT;
-		if (!parse_rate(cb->arg, &info->cfg.avg))
+		if (!parse_rate(cb->arg, &info->cfg.avg, cb->udata, 1))
 			xtables_param_act(XTF_BAD_VALUE, "hashlimit",
 			          "--hashlimit-upto", cb->arg);
 		break;
-	case O_ABOVE:
-		if (!cb->invert)
-			info->cfg.mode |= XT_HASHLIMIT_INVERT;
-		if (!parse_rate(cb->arg, &info->cfg.avg))
-			xtables_param_act(XTF_BAD_VALUE, "hashlimit",
-			          "--hashlimit-above", cb->arg);
-		break;
 	case O_MODE:
 		if (parse_mode(&info->cfg.mode, cb->arg) < 0)
 			xtables_param_act(XTF_BAD_VALUE, "hashlimit",
@@ -267,23 +552,30 @@
 	}
 }
 
-static void hashlimit_mt_parse(struct xt_option_call *cb)
+static void hashlimit_mt_parse_v1(struct xt_option_call *cb)
 {
 	struct xt_hashlimit_mtinfo1 *info = cb->data;
 
 	xtables_option_parse(cb);
 	switch (cb->entry->id) {
+	case O_BURST:
+		info->cfg.burst = parse_burst(cb->arg, 1);
+		break;
 	case O_UPTO:
 		if (cb->invert)
 			info->cfg.mode |= XT_HASHLIMIT_INVERT;
-		if (!parse_rate(cb->arg, &info->cfg.avg))
+		if (parse_bytes(cb->arg, &info->cfg.avg, cb->udata, 1))
+			info->cfg.mode |= XT_HASHLIMIT_BYTES;
+		else if (!parse_rate(cb->arg, &info->cfg.avg, cb->udata, 1))
 			xtables_param_act(XTF_BAD_VALUE, "hashlimit",
 			          "--hashlimit-upto", cb->arg);
 		break;
 	case O_ABOVE:
 		if (!cb->invert)
 			info->cfg.mode |= XT_HASHLIMIT_INVERT;
-		if (!parse_rate(cb->arg, &info->cfg.avg))
+		if (parse_bytes(cb->arg, &info->cfg.avg, cb->udata, 1))
+			info->cfg.mode |= XT_HASHLIMIT_BYTES;
+		else if (!parse_rate(cb->arg, &info->cfg.avg, cb->udata, 1))
 			xtables_param_act(XTF_BAD_VALUE, "hashlimit",
 			          "--hashlimit-above", cb->arg);
 		break;
@@ -301,32 +593,279 @@
 	}
 }
 
+static void hashlimit_mt_parse_v2(struct xt_option_call *cb)
+{
+	struct xt_hashlimit_mtinfo2 *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_BURST:
+		info->cfg.burst = parse_burst(cb->arg, 2);
+		break;
+	case O_UPTO:
+		if (cb->invert)
+			info->cfg.mode |= XT_HASHLIMIT_INVERT;
+		if (parse_bytes(cb->arg, &info->cfg.avg, cb->udata, 2))
+			info->cfg.mode |= XT_HASHLIMIT_BYTES;
+		else if (!parse_rate(cb->arg, &info->cfg.avg, cb->udata, 2))
+			xtables_param_act(XTF_BAD_VALUE, "hashlimit",
+			          "--hashlimit-upto", cb->arg);
+		break;
+	case O_ABOVE:
+		if (!cb->invert)
+			info->cfg.mode |= XT_HASHLIMIT_INVERT;
+		if (parse_bytes(cb->arg, &info->cfg.avg, cb->udata, 2))
+			info->cfg.mode |= XT_HASHLIMIT_BYTES;
+		else if (!parse_rate(cb->arg, &info->cfg.avg, cb->udata, 2))
+			xtables_param_act(XTF_BAD_VALUE, "hashlimit",
+			          "--hashlimit-above", cb->arg);
+		break;
+	case O_MODE:
+		if (parse_mode(&info->cfg.mode, cb->arg) < 0)
+			xtables_param_act(XTF_BAD_VALUE, "hashlimit",
+			          "--hashlimit-mode", cb->arg);
+		break;
+	case O_SRCMASK:
+		info->cfg.srcmask = cb->val.hlen;
+		break;
+	case O_DSTMASK:
+		info->cfg.dstmask = cb->val.hlen;
+		break;
+	}
+}
+
+static void hashlimit_mt_parse(struct xt_option_call *cb)
+{
+	struct xt_hashlimit_mtinfo3 *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_BURST:
+		info->cfg.burst = parse_burst(cb->arg, 2);
+		break;
+	case O_UPTO:
+		if (cb->invert)
+			info->cfg.mode |= XT_HASHLIMIT_INVERT;
+		if (parse_bytes(cb->arg, &info->cfg.avg, cb->udata, 2))
+			info->cfg.mode |= XT_HASHLIMIT_BYTES;
+		else if (!parse_rate(cb->arg, &info->cfg.avg, cb->udata, 2))
+			xtables_param_act(XTF_BAD_VALUE, "hashlimit",
+			          "--hashlimit-upto", cb->arg);
+		break;
+	case O_ABOVE:
+		if (!cb->invert)
+			info->cfg.mode |= XT_HASHLIMIT_INVERT;
+		if (parse_bytes(cb->arg, &info->cfg.avg, cb->udata, 2))
+			info->cfg.mode |= XT_HASHLIMIT_BYTES;
+		else if (!parse_rate(cb->arg, &info->cfg.avg, cb->udata, 2))
+			xtables_param_act(XTF_BAD_VALUE, "hashlimit",
+			          "--hashlimit-above", cb->arg);
+		break;
+	case O_MODE:
+		if (parse_mode(&info->cfg.mode, cb->arg) < 0)
+			xtables_param_act(XTF_BAD_VALUE, "hashlimit",
+			          "--hashlimit-mode", cb->arg);
+		break;
+	case O_SRCMASK:
+		info->cfg.srcmask = cb->val.hlen;
+		break;
+	case O_DSTMASK:
+		info->cfg.dstmask = cb->val.hlen;
+		break;
+	case O_RATEMATCH:
+		info->cfg.mode |= XT_HASHLIMIT_RATE_MATCH;
+		break;
+	case O_INTERVAL:
+		if (!parse_interval(cb->arg, &info->cfg.interval))
+			xtables_param_act(XTF_BAD_VALUE, "hashlimit",
+				"--hashlimit-rate-interval", cb->arg);
+	}
+}
+
 static void hashlimit_check(struct xt_fcheck_call *cb)
 {
+	const struct hashlimit_mt_udata *udata = cb->udata;
+	struct xt_hashlimit_info *info = cb->data;
+
 	if (!(cb->xflags & (F_UPTO | F_ABOVE)))
 		xtables_error(PARAMETER_PROBLEM,
 				"You have to specify --hashlimit");
+	if (!(cb->xflags & F_HTABLE_EXPIRE))
+		info->cfg.expire = udata->mult * 1000; /* from s to msec */
 }
 
-static const struct rates
+static void hashlimit_mt_check_v1(struct xt_fcheck_call *cb)
 {
-	const char *name;
-	uint32_t mult;
-} rates[] = { { "day", XT_HASHLIMIT_SCALE*24*60*60 },
-	      { "hour", XT_HASHLIMIT_SCALE*60*60 },
-	      { "min", XT_HASHLIMIT_SCALE*60 },
-	      { "sec", XT_HASHLIMIT_SCALE } };
+	const struct hashlimit_mt_udata *udata = cb->udata;
+	struct xt_hashlimit_mtinfo1 *info = cb->data;
 
-static void print_rate(uint32_t period)
+	if (!(cb->xflags & (F_UPTO | F_ABOVE)))
+		xtables_error(PARAMETER_PROBLEM,
+				"You have to specify --hashlimit");
+	if (!(cb->xflags & F_HTABLE_EXPIRE))
+		info->cfg.expire = udata->mult * 1000; /* from s to msec */
+
+	if (info->cfg.mode & XT_HASHLIMIT_BYTES) {
+		uint32_t burst = 0;
+		if (cb->xflags & F_BURST) {
+			if (info->cfg.burst < cost_to_bytes(info->cfg.avg))
+				xtables_error(PARAMETER_PROBLEM,
+					"burst cannot be smaller than %"PRIu64"b",
+					cost_to_bytes(info->cfg.avg));
+
+			burst = info->cfg.burst;
+			burst /= cost_to_bytes(info->cfg.avg);
+			if (info->cfg.burst % cost_to_bytes(info->cfg.avg))
+				burst++;
+			if (!(cb->xflags & F_HTABLE_EXPIRE))
+				info->cfg.expire = XT_HASHLIMIT_BYTE_EXPIRE_BURST * 1000;
+		}
+		info->cfg.burst = burst;
+	} else if (info->cfg.burst > XT_HASHLIMIT_BURST_MAX_v1)
+		burst_error_v1();
+}
+
+static void hashlimit_mt_check_v2(struct xt_fcheck_call *cb)
+{
+	const struct hashlimit_mt_udata *udata = cb->udata;
+	struct xt_hashlimit_mtinfo2 *info = cb->data;
+
+	if (!(cb->xflags & (F_UPTO | F_ABOVE)))
+		xtables_error(PARAMETER_PROBLEM,
+				"You have to specify --hashlimit");
+	if (!(cb->xflags & F_HTABLE_EXPIRE))
+		info->cfg.expire = udata->mult * 1000; /* from s to msec */
+
+	if (info->cfg.mode & XT_HASHLIMIT_BYTES) {
+		uint32_t burst = 0;
+		if (cb->xflags & F_BURST) {
+			if (info->cfg.burst < cost_to_bytes(info->cfg.avg))
+				xtables_error(PARAMETER_PROBLEM,
+					"burst cannot be smaller than %"PRIu64"b",
+					cost_to_bytes(info->cfg.avg));
+
+			burst = info->cfg.burst;
+			burst /= cost_to_bytes(info->cfg.avg);
+			if (info->cfg.burst % cost_to_bytes(info->cfg.avg))
+				burst++;
+			if (!(cb->xflags & F_HTABLE_EXPIRE))
+				info->cfg.expire = XT_HASHLIMIT_BYTE_EXPIRE_BURST * 1000;
+		}
+		info->cfg.burst = burst;
+	} else if (info->cfg.burst > XT_HASHLIMIT_BURST_MAX)
+		burst_error();
+}
+
+static void hashlimit_mt_check(struct xt_fcheck_call *cb)
+{
+	const struct hashlimit_mt_udata *udata = cb->udata;
+	struct xt_hashlimit_mtinfo3 *info = cb->data;
+
+	if (!(cb->xflags & (F_UPTO | F_ABOVE)))
+		xtables_error(PARAMETER_PROBLEM,
+				"You have to specify --hashlimit");
+	if (!(cb->xflags & F_HTABLE_EXPIRE))
+		info->cfg.expire = udata->mult * 1000; /* from s to msec */
+
+	if (info->cfg.mode & XT_HASHLIMIT_BYTES) {
+		uint32_t burst = 0;
+		if (cb->xflags & F_BURST) {
+			if (info->cfg.burst < cost_to_bytes(info->cfg.avg))
+				xtables_error(PARAMETER_PROBLEM,
+					"burst cannot be smaller than %"PRIu64"b", cost_to_bytes(info->cfg.avg));
+
+			burst = info->cfg.burst;
+			burst /= cost_to_bytes(info->cfg.avg);
+			if (info->cfg.burst % cost_to_bytes(info->cfg.avg))
+				burst++;
+			if (!(cb->xflags & F_HTABLE_EXPIRE))
+				info->cfg.expire = XT_HASHLIMIT_BYTE_EXPIRE_BURST * 1000;
+		}
+		info->cfg.burst = burst;
+	} else if (info->cfg.burst > XT_HASHLIMIT_BURST_MAX)
+		burst_error();
+
+	if (cb->xflags & F_RATEMATCH) {
+		if (!(info->cfg.mode & XT_HASHLIMIT_BYTES))
+			info->cfg.avg /= udata->mult;
+
+		if (info->cfg.interval == 0) {
+			if (info->cfg.mode & XT_HASHLIMIT_BYTES)
+				info->cfg.interval = 1;
+			else
+				info->cfg.interval = udata->mult;
+		}
+	}
+}
+
+struct rates {
+	const char *name;
+	uint64_t mult;
+} rates_v1[] = { { "day", XT_HASHLIMIT_SCALE*24*60*60 },
+		 { "hour", XT_HASHLIMIT_SCALE*60*60 },
+		 { "min", XT_HASHLIMIT_SCALE*60 },
+		 { "sec", XT_HASHLIMIT_SCALE } };
+
+static const struct rates rates[] = {
+	{ "day", XT_HASHLIMIT_SCALE_v2*24*60*60 },
+	{ "hour", XT_HASHLIMIT_SCALE_v2*60*60 },
+	{ "min", XT_HASHLIMIT_SCALE_v2*60 },
+	{ "sec", XT_HASHLIMIT_SCALE_v2 } };
+
+static uint32_t print_rate(uint64_t period, int revision)
 {
 	unsigned int i;
+	const struct rates *_rates = (revision == 1) ? rates_v1 : rates;
+	uint64_t scale = (revision == 1) ? XT_HASHLIMIT_SCALE : XT_HASHLIMIT_SCALE_v2;
+
+	if (period == 0) {
+		printf(" %f", INFINITY);
+		return 0;
+	}
 
 	for (i = 1; i < ARRAY_SIZE(rates); ++i)
-		if (period > rates[i].mult
-            || rates[i].mult/period < rates[i].mult%period)
+		if (period > _rates[i].mult
+            || _rates[i].mult/period < _rates[i].mult%period)
 			break;
 
-	printf(" %u/%s", rates[i-1].mult / period, rates[i-1].name);
+	printf(" %"PRIu64"/%s", _rates[i-1].mult / period, _rates[i-1].name);
+	/* return in msec */
+	return _rates[i-1].mult / scale * 1000;
+}
+
+static const struct {
+	const char *name;
+	uint32_t thresh;
+} units[] = {
+	{ "m", 1024 * 1024 },
+	{ "k", 1024 },
+	{ "", 1 },
+};
+
+static uint32_t print_bytes(uint64_t avg, uint64_t burst, const char *prefix)
+{
+	unsigned int i;
+	unsigned long long r;
+
+	r = cost_to_bytes(avg);
+
+	for (i = 0; i < ARRAY_SIZE(units) -1; ++i)
+		if (r >= units[i].thresh &&
+		    bytes_to_cost(r & ~(units[i].thresh - 1)) == avg)
+			break;
+	printf(" %llu%sb/s", r/units[i].thresh, units[i].name);
+
+	if (burst == 0)
+		return XT_HASHLIMIT_BYTE_EXPIRE * 1000;
+
+	r *= burst;
+	printf(" %s", prefix);
+	for (i = 0; i < ARRAY_SIZE(units) -1; ++i)
+		if (r >= units[i].thresh)
+			break;
+
+	printf("burst %llu%sb", r / units[i].thresh, units[i].name);
+	return XT_HASHLIMIT_BYTE_EXPIRE_BURST * 1000;
 }
 
 static void print_mode(unsigned int mode, char separator)
@@ -361,7 +900,10 @@
                             const struct xt_entry_match *match, int numeric)
 {
 	const struct xt_hashlimit_info *r = (const void *)match->data;
-	fputs(" limit: avg", stdout); print_rate(r->cfg.avg);
+	uint32_t quantum;
+
+	fputs(" limit: avg", stdout);
+	quantum = print_rate(r->cfg.avg, 1);
 	printf(" burst %u", r->cfg.burst);
 	fputs(" mode", stdout);
 	print_mode(r->cfg.mode, '-');
@@ -371,67 +913,155 @@
 		printf(" htable-max %u", r->cfg.max);
 	if (r->cfg.gc_interval != XT_HASHLIMIT_GCINTERVAL)
 		printf(" htable-gcinterval %u", r->cfg.gc_interval);
-	if (r->cfg.expire != XT_HASHLIMIT_EXPIRE)
+	if (r->cfg.expire != quantum)
 		printf(" htable-expire %u", r->cfg.expire);
 }
 
 static void
-hashlimit_mt_print(const struct xt_hashlimit_mtinfo1 *info, unsigned int dmask)
+hashlimit_mt_print(const struct hashlimit_cfg3 *cfg, unsigned int dmask, int revision)
 {
-	if (info->cfg.mode & XT_HASHLIMIT_INVERT)
+	uint64_t quantum;
+	uint64_t period;
+
+	if (cfg->mode & XT_HASHLIMIT_INVERT)
 		fputs(" limit: above", stdout);
 	else
 		fputs(" limit: up to", stdout);
-	print_rate(info->cfg.avg);
-	printf(" burst %u", info->cfg.burst);
-	if (info->cfg.mode & (XT_HASHLIMIT_HASH_SIP | XT_HASHLIMIT_HASH_SPT |
+
+	if (cfg->mode & XT_HASHLIMIT_BYTES) {
+		quantum = print_bytes(cfg->avg, cfg->burst, "");
+	} else {
+		if (revision == 3) {
+			period = cfg->avg;
+			if (cfg->interval != 0)
+				period *= cfg->interval;
+
+			quantum = print_rate(period, revision);
+		} else {
+			quantum = print_rate(cfg->avg, revision);
+		}
+		printf(" burst %llu", cfg->burst);
+	}
+	if (cfg->mode & (XT_HASHLIMIT_HASH_SIP | XT_HASHLIMIT_HASH_SPT |
 	    XT_HASHLIMIT_HASH_DIP | XT_HASHLIMIT_HASH_DPT)) {
 		fputs(" mode", stdout);
-		print_mode(info->cfg.mode, '-');
+		print_mode(cfg->mode, '-');
 	}
-	if (info->cfg.size != 0)
-		printf(" htable-size %u", info->cfg.size);
-	if (info->cfg.max != 0)
-		printf(" htable-max %u", info->cfg.max);
-	if (info->cfg.gc_interval != XT_HASHLIMIT_GCINTERVAL)
-		printf(" htable-gcinterval %u", info->cfg.gc_interval);
-	if (info->cfg.expire != XT_HASHLIMIT_EXPIRE)
-		printf(" htable-expire %u", info->cfg.expire);
+	if (cfg->size != 0)
+		printf(" htable-size %u", cfg->size);
+	if (cfg->max != 0)
+		printf(" htable-max %u", cfg->max);
+	if (cfg->gc_interval != XT_HASHLIMIT_GCINTERVAL)
+		printf(" htable-gcinterval %u", cfg->gc_interval);
+	if (cfg->expire != quantum)
+		printf(" htable-expire %u", cfg->expire);
 
-	if (info->cfg.srcmask != dmask)
-		printf(" srcmask %u", info->cfg.srcmask);
-	if (info->cfg.dstmask != dmask)
-		printf(" dstmask %u", info->cfg.dstmask);
+	if (cfg->srcmask != dmask)
+		printf(" srcmask %u", cfg->srcmask);
+	if (cfg->dstmask != dmask)
+		printf(" dstmask %u", cfg->dstmask);
+
+	if ((revision == 3) && (cfg->mode & XT_HASHLIMIT_RATE_MATCH))
+		printf(" rate-match");
+
+	if ((revision == 3) && (cfg->mode & XT_HASHLIMIT_RATE_MATCH))
+		if (cfg->interval != 1)
+			printf(" rate-interval %u", cfg->interval);
 }
 
 static void
+hashlimit_mt4_print_v1(const void *ip, const struct xt_entry_match *match,
+                   int numeric)
+{
+	const struct xt_hashlimit_mtinfo1 *info = (const void *)match->data;
+	struct hashlimit_cfg3 cfg;
+	int ret;
+
+	ret = cfg_copy(&cfg, (const void *)&info->cfg, 1);
+
+	if (ret)
+		xtables_error(OTHER_PROBLEM, "unknown revision");
+
+	hashlimit_mt_print(&cfg, 32, 1);
+}
+
+static void
+hashlimit_mt6_print_v1(const void *ip, const struct xt_entry_match *match,
+                   int numeric)
+{
+	const struct xt_hashlimit_mtinfo1 *info = (const void *)match->data;
+	struct hashlimit_cfg3 cfg;
+	int ret;
+
+	ret = cfg_copy(&cfg, (const void *)&info->cfg, 1);
+
+	if (ret)
+		xtables_error(OTHER_PROBLEM, "unknown revision");
+
+	hashlimit_mt_print(&cfg, 128, 1);
+}
+
+static void
+hashlimit_mt4_print_v2(const void *ip, const struct xt_entry_match *match,
+                   int numeric)
+{
+	const struct xt_hashlimit_mtinfo2 *info = (const void *)match->data;
+	struct hashlimit_cfg3 cfg;
+	int ret;
+
+	ret = cfg_copy(&cfg, (const void *)&info->cfg, 2);
+
+	if (ret)
+		xtables_error(OTHER_PROBLEM, "unknown revision");
+
+	hashlimit_mt_print(&cfg, 32, 2);
+}
+
+static void
+hashlimit_mt6_print_v2(const void *ip, const struct xt_entry_match *match,
+                   int numeric)
+{
+	const struct xt_hashlimit_mtinfo2 *info = (const void *)match->data;
+	struct hashlimit_cfg3 cfg;
+	int ret;
+
+	ret = cfg_copy(&cfg, (const void *)&info->cfg, 2);
+
+	if (ret)
+		xtables_error(OTHER_PROBLEM, "unknown revision");
+
+	hashlimit_mt_print(&cfg, 128, 2);
+}
+static void
 hashlimit_mt4_print(const void *ip, const struct xt_entry_match *match,
                    int numeric)
 {
-	const struct xt_hashlimit_mtinfo1 *info = (const void *)match->data;
+	const struct xt_hashlimit_mtinfo3 *info = (const void *)match->data;
 
-	hashlimit_mt_print(info, 32);
+	hashlimit_mt_print(&info->cfg, 32, 3);
 }
 
 static void
 hashlimit_mt6_print(const void *ip, const struct xt_entry_match *match,
                    int numeric)
 {
-	const struct xt_hashlimit_mtinfo1 *info = (const void *)match->data;
+	const struct xt_hashlimit_mtinfo3 *info = (const void *)match->data;
 
-	hashlimit_mt_print(info, 128);
+	hashlimit_mt_print(&info->cfg, 128, 3);
 }
 
 static void hashlimit_save(const void *ip, const struct xt_entry_match *match)
 {
 	const struct xt_hashlimit_info *r = (const void *)match->data;
+	uint32_t quantum;
 
-	fputs(" --hashlimit", stdout); print_rate(r->cfg.avg);
+	fputs(" --hashlimit", stdout);
+	quantum = print_rate(r->cfg.avg, 1);
 	printf(" --hashlimit-burst %u", r->cfg.burst);
 
 	fputs(" --hashlimit-mode", stdout);
 	print_mode(r->cfg.mode, ',');
-	
+
 	printf(" --hashlimit-name %s", r->name);
 
 	if (r->cfg.size)
@@ -440,57 +1070,382 @@
 		printf(" --hashlimit-htable-max %u", r->cfg.max);
 	if (r->cfg.gc_interval != XT_HASHLIMIT_GCINTERVAL)
 		printf(" --hashlimit-htable-gcinterval %u", r->cfg.gc_interval);
-	if (r->cfg.expire != XT_HASHLIMIT_EXPIRE)
+	if (r->cfg.expire != quantum)
 		printf(" --hashlimit-htable-expire %u", r->cfg.expire);
 }
 
 static void
-hashlimit_mt_save(const struct xt_hashlimit_mtinfo1 *info, unsigned int dmask)
+hashlimit_mt_save(const struct hashlimit_cfg3 *cfg, const char* name, unsigned int dmask, int revision)
 {
-	if (info->cfg.mode & XT_HASHLIMIT_INVERT)
+	uint32_t quantum;
+
+	if (cfg->mode & XT_HASHLIMIT_INVERT)
 		fputs(" --hashlimit-above", stdout);
 	else
 		fputs(" --hashlimit-upto", stdout);
-	print_rate(info->cfg.avg);
-	printf(" --hashlimit-burst %u", info->cfg.burst);
 
-	if (info->cfg.mode & (XT_HASHLIMIT_HASH_SIP | XT_HASHLIMIT_HASH_SPT |
-	    XT_HASHLIMIT_HASH_DIP | XT_HASHLIMIT_HASH_DPT)) {
-		fputs(" --hashlimit-mode", stdout);
-		print_mode(info->cfg.mode, ',');
+	if (cfg->mode & XT_HASHLIMIT_BYTES) {
+		quantum = print_bytes(cfg->avg, cfg->burst, "--hashlimit-");
+	} else {
+		quantum = print_rate(cfg->avg, revision);
+		printf(" --hashlimit-burst %llu", cfg->burst);
 	}
 
-	printf(" --hashlimit-name %s", info->name);
+	if (cfg->mode & (XT_HASHLIMIT_HASH_SIP | XT_HASHLIMIT_HASH_SPT |
+	    XT_HASHLIMIT_HASH_DIP | XT_HASHLIMIT_HASH_DPT)) {
+		fputs(" --hashlimit-mode", stdout);
+		print_mode(cfg->mode, ',');
+	}
 
-	if (info->cfg.size != 0)
-		printf(" --hashlimit-htable-size %u", info->cfg.size);
-	if (info->cfg.max != 0)
-		printf(" --hashlimit-htable-max %u", info->cfg.max);
-	if (info->cfg.gc_interval != XT_HASHLIMIT_GCINTERVAL)
-		printf(" --hashlimit-htable-gcinterval %u", info->cfg.gc_interval);
-	if (info->cfg.expire != XT_HASHLIMIT_EXPIRE)
-		printf(" --hashlimit-htable-expire %u", info->cfg.expire);
+	printf(" --hashlimit-name %s", name);
 
-	if (info->cfg.srcmask != dmask)
-		printf(" --hashlimit-srcmask %u", info->cfg.srcmask);
-	if (info->cfg.dstmask != dmask)
-		printf(" --hashlimit-dstmask %u", info->cfg.dstmask);
+	if (cfg->size != 0)
+		printf(" --hashlimit-htable-size %u", cfg->size);
+	if (cfg->max != 0)
+		printf(" --hashlimit-htable-max %u", cfg->max);
+	if (cfg->gc_interval != XT_HASHLIMIT_GCINTERVAL)
+		printf(" --hashlimit-htable-gcinterval %u", cfg->gc_interval);
+	if (cfg->expire != quantum)
+		printf(" --hashlimit-htable-expire %u", cfg->expire);
+
+	if (cfg->srcmask != dmask)
+		printf(" --hashlimit-srcmask %u", cfg->srcmask);
+	if (cfg->dstmask != dmask)
+		printf(" --hashlimit-dstmask %u", cfg->dstmask);
+
+	if ((revision == 3) && (cfg->mode & XT_HASHLIMIT_RATE_MATCH))
+		printf(" --hashlimit-rate-match");
+
+	if ((revision == 3) && (cfg->mode & XT_HASHLIMIT_RATE_MATCH))
+		if (cfg->interval != 1)
+			printf(" --hashlimit-rate-interval %u", cfg->interval);
+}
+
+static void
+hashlimit_mt4_save_v1(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_hashlimit_mtinfo1 *info = (const void *)match->data;
+	struct hashlimit_cfg3 cfg;
+	int ret;
+
+	ret = cfg_copy(&cfg, (const void *)&info->cfg, 1);
+
+	if (ret)
+		xtables_error(OTHER_PROBLEM, "unknown revision");
+
+	hashlimit_mt_save(&cfg, info->name, 32, 1);
+}
+
+static void
+hashlimit_mt6_save_v1(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_hashlimit_mtinfo1 *info = (const void *)match->data;
+	struct hashlimit_cfg3 cfg;
+	int ret;
+
+	ret = cfg_copy(&cfg, (const void *)&info->cfg, 1);
+
+	if (ret)
+		xtables_error(OTHER_PROBLEM, "unknown revision");
+
+	hashlimit_mt_save(&cfg, info->name, 128, 1);
+}
+
+static void
+hashlimit_mt4_save_v2(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_hashlimit_mtinfo2 *info = (const void *)match->data;
+	struct hashlimit_cfg3 cfg;
+	int ret;
+
+	ret = cfg_copy(&cfg, (const void *)&info->cfg, 2);
+
+	if (ret)
+		xtables_error(OTHER_PROBLEM, "unknown revision");
+
+	hashlimit_mt_save(&cfg, info->name, 32, 2);
+}
+
+static void
+hashlimit_mt6_save_v2(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_hashlimit_mtinfo2 *info = (const void *)match->data;
+	struct hashlimit_cfg3 cfg;
+	int ret;
+
+	ret = cfg_copy(&cfg, (const void *)&info->cfg, 2);
+
+	if (ret)
+		xtables_error(OTHER_PROBLEM, "unknown revision");
+
+	hashlimit_mt_save(&cfg, info->name, 128, 2);
 }
 
 static void
 hashlimit_mt4_save(const void *ip, const struct xt_entry_match *match)
 {
-	const struct xt_hashlimit_mtinfo1 *info = (const void *)match->data;
+	const struct xt_hashlimit_mtinfo3 *info = (const void *)match->data;
 
-	hashlimit_mt_save(info, 32);
+	hashlimit_mt_save(&info->cfg, info->name, 32, 3);
 }
 
 static void
 hashlimit_mt6_save(const void *ip, const struct xt_entry_match *match)
 {
-	const struct xt_hashlimit_mtinfo1 *info = (const void *)match->data;
+	const struct xt_hashlimit_mtinfo3 *info = (const void *)match->data;
 
-	hashlimit_mt_save(info, 128);
+	hashlimit_mt_save(&info->cfg, info->name, 128, 3);
+}
+
+static const struct rates rates_v1_xlate[] = {
+	{ "day", XT_HASHLIMIT_SCALE * 24 * 60 * 60 },
+	{ "hour", XT_HASHLIMIT_SCALE * 60 * 60 },
+	{ "minute", XT_HASHLIMIT_SCALE * 60 },
+	{ "second", XT_HASHLIMIT_SCALE } };
+
+static const struct rates rates_xlate[] = {
+	{ "day", XT_HASHLIMIT_SCALE_v2 * 24 * 60 * 60 },
+	{ "hour", XT_HASHLIMIT_SCALE_v2 * 60 * 60 },
+	{ "minute", XT_HASHLIMIT_SCALE_v2 * 60 },
+	{ "second", XT_HASHLIMIT_SCALE_v2 } };
+
+static void print_packets_rate_xlate(struct xt_xlate *xl, uint64_t avg,
+				     int revision)
+{
+	unsigned int i;
+	const struct rates *_rates = (revision == 1) ?
+		rates_v1_xlate : rates_xlate;
+
+	for (i = 1; i < ARRAY_SIZE(rates); ++i)
+		if (avg > _rates[i].mult ||
+		    _rates[i].mult / avg < _rates[i].mult % avg)
+			break;
+
+	xt_xlate_add(xl, " %" PRIu64 "/%s ",
+		     _rates[i-1].mult / avg, _rates[i-1].name);
+}
+
+static void print_bytes_rate_xlate(struct xt_xlate *xl,
+				   const struct hashlimit_cfg3 *cfg)
+{
+	unsigned int i;
+	unsigned long long r;
+
+	r = cost_to_bytes(cfg->avg);
+
+	for (i = 0; i < ARRAY_SIZE(units) -1; ++i)
+		if (r >= units[i].thresh &&
+		    bytes_to_cost(r & ~(units[i].thresh - 1)) == cfg->avg)
+			break;
+
+	xt_xlate_add(xl, " %llu %sbytes/second", r / units[i].thresh,
+		     units[i].name);
+
+	r *= cfg->burst;
+	for (i = 0; i < ARRAY_SIZE(units) -1; ++i)
+		if (r >= units[i].thresh)
+			break;
+
+	if (cfg->burst > 0)
+		xt_xlate_add(xl, " burst %llu %sbytes", r / units[i].thresh,
+			     units[i].name);
+}
+
+static void hashlimit_print_subnet_xlate(struct xt_xlate *xl,
+					 uint32_t nsub, int family)
+{
+	char sep = (family == NFPROTO_IPV4) ? '.' : ':';
+	char *fmt = (family == NFPROTO_IPV4) ? "%u" : "%04x";
+	unsigned int nblocks = (family == NFPROTO_IPV4) ? 4 : 8;
+	unsigned int nbits = (family == NFPROTO_IPV4) ? 8 : 16;
+	unsigned int acm, i;
+
+	xt_xlate_add(xl, " and ");
+	while (nblocks--) {
+		acm = 0;
+
+		for (i = 0; i < nbits; i++) {
+			acm <<= 1;
+
+			if (nsub > 0) {
+				acm++;
+				nsub--;
+			}
+		}
+
+		xt_xlate_add(xl, fmt, acm);
+		if (nblocks > 0)
+			xt_xlate_add(xl, "%c", sep);
+	}
+}
+
+static const char *const hashlimit_modes4_xlate[] = {
+	[XT_HASHLIMIT_HASH_DIP]	= "ip daddr",
+	[XT_HASHLIMIT_HASH_DPT]	= "tcp dport",
+	[XT_HASHLIMIT_HASH_SIP]	= "ip saddr",
+	[XT_HASHLIMIT_HASH_SPT]	= "tcp sport",
+};
+
+static const char *const hashlimit_modes6_xlate[] = {
+	[XT_HASHLIMIT_HASH_DIP]	= "ip6 daddr",
+	[XT_HASHLIMIT_HASH_DPT]	= "tcp dport",
+	[XT_HASHLIMIT_HASH_SIP]	= "ip6 saddr",
+	[XT_HASHLIMIT_HASH_SPT]	= "tcp sport",
+};
+
+static int hashlimit_mode_xlate(struct xt_xlate *xl,
+				uint32_t mode, int family,
+				unsigned int nsrc, unsigned int ndst)
+{
+	const char * const *_modes = (family == NFPROTO_IPV4) ?
+		hashlimit_modes4_xlate : hashlimit_modes6_xlate;
+	bool prevopt = false;
+	unsigned int mask;
+
+	mode &= ~XT_HASHLIMIT_INVERT & ~XT_HASHLIMIT_BYTES;
+
+	for (mask = 1; mode > 0; mask <<= 1) {
+		if (!(mode & mask))
+			continue;
+
+		if (!prevopt) {
+			xt_xlate_add(xl, " ");
+			prevopt = true;
+		}
+		else {
+			xt_xlate_add(xl, " . ");
+		}
+
+		xt_xlate_add(xl, "%s", _modes[mask]);
+
+		if (mask == XT_HASHLIMIT_HASH_DIP &&
+		    ((family == NFPROTO_IPV4 && ndst != 32) ||
+		     (family == NFPROTO_IPV6 && ndst != 128)))
+			hashlimit_print_subnet_xlate(xl, ndst, family);
+		else if (mask == XT_HASHLIMIT_HASH_SIP &&
+			 ((family == NFPROTO_IPV4 && nsrc != 32) ||
+			  (family == NFPROTO_IPV6 && nsrc != 128)))
+			hashlimit_print_subnet_xlate(xl, nsrc, family);
+
+		mode &= ~mask;
+	}
+
+	return prevopt;
+}
+
+static int hashlimit_mt_xlate(struct xt_xlate *xl, const char *name,
+			      const struct hashlimit_cfg3 *cfg,
+			      int revision, int family)
+{
+	int ret = 1;
+
+	xt_xlate_add(xl, "meter %s {", name);
+	ret = hashlimit_mode_xlate(xl, cfg->mode, family,
+				   cfg->srcmask, cfg->dstmask);
+	if (cfg->expire != 1000)
+		xt_xlate_add(xl, " timeout %us", cfg->expire / 1000);
+	xt_xlate_add(xl, " limit rate");
+
+	if (cfg->mode & XT_HASHLIMIT_INVERT)
+		xt_xlate_add(xl, " over");
+
+	if (cfg->mode & XT_HASHLIMIT_BYTES)
+		print_bytes_rate_xlate(xl, cfg);
+	else {
+		print_packets_rate_xlate(xl, cfg->avg, revision);
+		if (cfg->burst != XT_HASHLIMIT_BURST)
+			xt_xlate_add(xl, "burst %" PRIu64 " packets", (uint64_t)cfg->burst);
+
+	}
+	xt_xlate_add(xl, "}");
+
+	return ret;
+}
+
+static int hashlimit_xlate(struct xt_xlate *xl,
+			   const struct xt_xlate_mt_params *params)
+{
+	const struct xt_hashlimit_info *info = (const void *)params->match->data;
+	int ret = 1;
+
+	xt_xlate_add(xl, "meter %s {", info->name);
+	ret = hashlimit_mode_xlate(xl, info->cfg.mode, NFPROTO_IPV4, 32, 32);
+	xt_xlate_add(xl, " timeout %us limit rate", info->cfg.expire / 1000);
+	print_packets_rate_xlate(xl, info->cfg.avg, 1);
+	xt_xlate_add(xl, " burst %u packets", info->cfg.burst);
+	xt_xlate_add(xl, "}");
+
+	return ret;
+}
+
+static int hashlimit_mt4_xlate_v1(struct xt_xlate *xl,
+				  const struct xt_xlate_mt_params *params)
+{
+	const struct xt_hashlimit_mtinfo1 *info =
+		(const void *)params->match->data;
+	struct hashlimit_cfg3 cfg;
+
+	if (cfg_copy(&cfg, (const void *)&info->cfg, 1))
+		xtables_error(OTHER_PROBLEM, "unknown revision");
+
+	return hashlimit_mt_xlate(xl, info->name, &cfg, 1, NFPROTO_IPV4);
+}
+
+static int hashlimit_mt6_xlate_v1(struct xt_xlate *xl,
+				  const struct xt_xlate_mt_params *params)
+{
+	const struct xt_hashlimit_mtinfo1 *info =
+		(const void *)params->match->data;
+	struct hashlimit_cfg3 cfg;
+
+	if (cfg_copy(&cfg, (const void *)&info->cfg, 1))
+		xtables_error(OTHER_PROBLEM, "unknown revision");
+
+	return hashlimit_mt_xlate(xl, info->name, &cfg, 1, NFPROTO_IPV6);
+}
+
+static int hashlimit_mt4_xlate_v2(struct xt_xlate *xl,
+				  const struct xt_xlate_mt_params *params)
+{
+	const struct xt_hashlimit_mtinfo2 *info =
+		(const void *)params->match->data;
+	struct hashlimit_cfg3 cfg;
+
+	if (cfg_copy(&cfg, (const void *)&info->cfg, 2))
+		xtables_error(OTHER_PROBLEM, "unknown revision");
+
+	return hashlimit_mt_xlate(xl, info->name, &cfg, 2, NFPROTO_IPV4);
+}
+
+static int hashlimit_mt6_xlate_v2(struct xt_xlate *xl,
+				  const struct xt_xlate_mt_params *params)
+{
+	const struct xt_hashlimit_mtinfo2 *info =
+		(const void *)params->match->data;
+	struct hashlimit_cfg3 cfg;
+
+	if (cfg_copy(&cfg, (const void *)&info->cfg, 2))
+		xtables_error(OTHER_PROBLEM, "unknown revision");
+
+	return hashlimit_mt_xlate(xl, info->name, &cfg, 2, NFPROTO_IPV6);
+}
+
+static int hashlimit_mt4_xlate(struct xt_xlate *xl,
+			       const struct xt_xlate_mt_params *params)
+{
+	const struct xt_hashlimit_mtinfo3 *info =
+		(const void *)params->match->data;
+
+	return hashlimit_mt_xlate(xl, info->name, &info->cfg, 3, NFPROTO_IPV4);
+}
+
+static int hashlimit_mt6_xlate(struct xt_xlate *xl,
+			       const struct xt_xlate_mt_params *params)
+{
+	const struct xt_hashlimit_mtinfo3 *info =
+		(const void *)params->match->data;
+
+	return hashlimit_mt_xlate(xl, info->name, &info->cfg, 3, NFPROTO_IPV6);
 }
 
 static struct xtables_match hashlimit_mt_reg[] = {
@@ -507,7 +1462,9 @@
 		.x6_fcheck     = hashlimit_check,
 		.print         = hashlimit_print,
 		.save          = hashlimit_save,
-		.x6_options    = hashlimit_mt_opts,
+		.x6_options    = hashlimit_opts,
+		.udata_size    = sizeof(struct hashlimit_mt_udata),
+		.xlate         = hashlimit_xlate,
 	},
 	{
 		.version       = XTABLES_VERSION,
@@ -517,12 +1474,14 @@
 		.size          = XT_ALIGN(sizeof(struct xt_hashlimit_mtinfo1)),
 		.userspacesize = offsetof(struct xt_hashlimit_mtinfo1, hinfo),
 		.help          = hashlimit_mt_help,
-		.init          = hashlimit_mt4_init,
-		.x6_parse      = hashlimit_mt_parse,
-		.x6_fcheck     = hashlimit_check,
-		.print         = hashlimit_mt4_print,
-		.save          = hashlimit_mt4_save,
-		.x6_options    = hashlimit_mt_opts,
+		.init          = hashlimit_mt4_init_v1,
+		.x6_parse      = hashlimit_mt_parse_v1,
+		.x6_fcheck     = hashlimit_mt_check_v1,
+		.print         = hashlimit_mt4_print_v1,
+		.save          = hashlimit_mt4_save_v1,
+		.x6_options    = hashlimit_mt_opts_v1,
+		.udata_size    = sizeof(struct hashlimit_mt_udata),
+		.xlate         = hashlimit_mt4_xlate_v1,
 	},
 	{
 		.version       = XTABLES_VERSION,
@@ -532,12 +1491,82 @@
 		.size          = XT_ALIGN(sizeof(struct xt_hashlimit_mtinfo1)),
 		.userspacesize = offsetof(struct xt_hashlimit_mtinfo1, hinfo),
 		.help          = hashlimit_mt_help,
+		.init          = hashlimit_mt6_init_v1,
+		.x6_parse      = hashlimit_mt_parse_v1,
+		.x6_fcheck     = hashlimit_mt_check_v1,
+		.print         = hashlimit_mt6_print_v1,
+		.save          = hashlimit_mt6_save_v1,
+		.x6_options    = hashlimit_mt_opts_v1,
+		.udata_size    = sizeof(struct hashlimit_mt_udata),
+		.xlate         = hashlimit_mt6_xlate_v1,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "hashlimit",
+		.revision      = 2,
+		.family        = NFPROTO_IPV4,
+		.size          = XT_ALIGN(sizeof(struct xt_hashlimit_mtinfo2)),
+		.userspacesize = offsetof(struct xt_hashlimit_mtinfo2, hinfo),
+		.help          = hashlimit_mt_help,
+		.init          = hashlimit_mt4_init_v2,
+		.x6_parse      = hashlimit_mt_parse_v2,
+		.x6_fcheck     = hashlimit_mt_check_v2,
+		.print         = hashlimit_mt4_print_v2,
+		.save          = hashlimit_mt4_save_v2,
+		.x6_options    = hashlimit_mt_opts_v2,
+		.udata_size    = sizeof(struct hashlimit_mt_udata),
+		.xlate         = hashlimit_mt4_xlate_v2,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "hashlimit",
+		.revision      = 2,
+		.family        = NFPROTO_IPV6,
+		.size          = XT_ALIGN(sizeof(struct xt_hashlimit_mtinfo2)),
+		.userspacesize = offsetof(struct xt_hashlimit_mtinfo2, hinfo),
+		.help          = hashlimit_mt_help,
+		.init          = hashlimit_mt6_init_v2,
+		.x6_parse      = hashlimit_mt_parse_v2,
+		.x6_fcheck     = hashlimit_mt_check_v2,
+		.print         = hashlimit_mt6_print_v2,
+		.save          = hashlimit_mt6_save_v2,
+		.x6_options    = hashlimit_mt_opts_v2,
+		.udata_size    = sizeof(struct hashlimit_mt_udata),
+		.xlate         = hashlimit_mt6_xlate_v2,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "hashlimit",
+		.revision      = 3,
+		.family        = NFPROTO_IPV4,
+		.size          = XT_ALIGN(sizeof(struct xt_hashlimit_mtinfo3)),
+		.userspacesize = offsetof(struct xt_hashlimit_mtinfo3, hinfo),
+		.help          = hashlimit_mt_help_v3,
+		.init          = hashlimit_mt4_init,
+		.x6_parse      = hashlimit_mt_parse,
+		.x6_fcheck     = hashlimit_mt_check,
+		.print         = hashlimit_mt4_print,
+		.save          = hashlimit_mt4_save,
+		.x6_options    = hashlimit_mt_opts,
+		.udata_size    = sizeof(struct hashlimit_mt_udata),
+		.xlate         = hashlimit_mt4_xlate,
+	},
+	{
+		.version       = XTABLES_VERSION,
+		.name          = "hashlimit",
+		.revision      = 3,
+		.family        = NFPROTO_IPV6,
+		.size          = XT_ALIGN(sizeof(struct xt_hashlimit_mtinfo3)),
+		.userspacesize = offsetof(struct xt_hashlimit_mtinfo3, hinfo),
+		.help          = hashlimit_mt_help_v3,
 		.init          = hashlimit_mt6_init,
 		.x6_parse      = hashlimit_mt_parse,
-		.x6_fcheck     = hashlimit_check,
+		.x6_fcheck     = hashlimit_mt_check,
 		.print         = hashlimit_mt6_print,
 		.save          = hashlimit_mt6_save,
 		.x6_options    = hashlimit_mt_opts,
+		.udata_size    = sizeof(struct hashlimit_mt_udata),
+		.xlate         = hashlimit_mt6_xlate,
 	},
 };
 
diff --git a/extensions/libxt_hashlimit.man b/extensions/libxt_hashlimit.man
index f90577e..8a35d56 100644
--- a/extensions/libxt_hashlimit.man
+++ b/extensions/libxt_hashlimit.man
@@ -2,14 +2,15 @@
 \fBlimit\fP match) for a group of connections using a \fBsingle\fP iptables
 rule. Grouping can be done per-hostgroup (source and/or destination address)
 and/or per-port. It gives you the ability to express "\fIN\fP packets per time
-quantum per group" (see below for some examples).
+quantum per group" or "\fIN\fP bytes per seconds" (see below for some examples).
 .PP
 A hash limit option (\fB\-\-hashlimit\-upto\fP, \fB\-\-hashlimit\-above\fP) and
 \fB\-\-hashlimit\-name\fP are required.
 .TP
 \fB\-\-hashlimit\-upto\fP \fIamount\fP[\fB/second\fP|\fB/minute\fP|\fB/hour\fP|\fB/day\fP]
-Match if the rate is below or equal to \fIamount\fP/quantum. It is specified as
-a number, with an optional time quantum suffix; the default is 3/hour.
+Match if the rate is below or equal to \fIamount\fP/quantum. It is specified either as
+a number, with an optional time quantum suffix (the default is 3/hour), or as
+\fIamount\fPb/second (number of bytes per second).
 .TP
 \fB\-\-hashlimit\-above\fP \fIamount\fP[\fB/second\fP|\fB/minute\fP|\fB/hour\fP|\fB/day\fP]
 Match if the rate is above \fIamount\fP/quantum.
@@ -17,7 +18,9 @@
 \fB\-\-hashlimit\-burst\fP \fIamount\fP
 Maximum initial number of packets to match: this number gets recharged by one
 every time the limit specified above is not reached, up to this number; the
-default is 5.
+default is 5.  When byte-based rate matching is requested, this option specifies
+the amount of bytes that can exceed the given rate.  This option should be used
+with caution -- if the entry expires, the burst value is reset too.
 .TP
 \fB\-\-hashlimit\-mode\fP {\fBsrcip\fP|\fBsrcport\fP|\fBdstip\fP|\fBdstport\fP}\fB,\fP...
 A comma-separated list of objects to take into consideration. If no
@@ -48,6 +51,14 @@
 .TP
 \fB\-\-hashlimit\-htable\-gcinterval\fP \fImsec\fP
 How many milliseconds between garbage collection intervals.
+.TP
+\fB\-\-hashlimit\-rate\-match\fP
+Classify the flow instead of rate-limiting it. This acts like a
+true/false match on whether the rate is above/below a certain number
+.TP
+\fB\-\-hashlimit\-rate\-interval\fP \fIsec\fP
+Can be used with \-\-hashlimit\-rate\-match to specify the interval
+at which the rate should be sampled
 .PP
 Examples:
 .TP
@@ -62,4 +73,12 @@
 matching on subnet
 "10000 packets per minute for every /28 subnet (groups of 8 addresses)
 in 10.0.0.0/8" =>
-\-s 10.0.0.8 \-\-hashlimit\-mask 28 \-\-hashlimit\-upto 10000/min
+\-s 10.0.0.0/8 \-\-hashlimit\-mask 28 \-\-hashlimit\-upto 10000/min
+.TP
+matching bytes per second
+"flows exceeding 512kbyte/s" =>
+\-\-hashlimit-mode srcip,dstip,srcport,dstport \-\-hashlimit\-above 512kb/s
+.TP
+matching bytes per second
+"hosts that exceed 512kbyte/s, but permit up to 1Megabytes without matching"
+\-\-hashlimit-mode dstip \-\-hashlimit\-above 512kb/s \-\-hashlimit-burst 1mb
diff --git a/extensions/libxt_hashlimit.t b/extensions/libxt_hashlimit.t
new file mode 100644
index 0000000..ccd0d1e
--- /dev/null
+++ b/extensions/libxt_hashlimit.t
@@ -0,0 +1,33 @@
+:INPUT,FORWARD,OUTPUT
+-m hashlimit --hashlimit-above 1/sec --hashlimit-burst 5 --hashlimit-name mini1;=;OK
+-m hashlimit --hashlimit-above 1000000/sec --hashlimit-burst 5 --hashlimit-name mini1;=;OK
+-m hashlimit --hashlimit-above 1/min --hashlimit-burst 5 --hashlimit-name mini1;=;OK
+-m hashlimit --hashlimit-above 1/hour --hashlimit-burst 5 --hashlimit-name mini1;=;OK
+# kernel says "xt_hashlimit: overflow, try lower: 864000000/5"
+-m hashlimit --hashlimit-above 1/day --hashlimit-burst 5 --hashlimit-name mini1;;FAIL
+-m hashlimit --hashlimit-upto 1/sec --hashlimit-burst 5 --hashlimit-name mini1;=;OK
+-m hashlimit --hashlimit-upto 1000000/sec --hashlimit-burst 5 --hashlimit-name mini1;=;OK
+-m hashlimit --hashlimit-upto 1/min --hashlimit-burst 5 --hashlimit-name mini1;=;OK
+-m hashlimit --hashlimit-upto 1/hour --hashlimit-burst 5 --hashlimit-name mini1;=;OK
+# kernel says "xt_hashlimit: overflow, try lower: 864000000/5"
+-m hashlimit --hashlimit-upto 1/day --hashlimit-burst 5 --hashlimit-name mini1;;FAIL
+-m hashlimit --hashlimit-upto 1/sec --hashlimit-burst 1 --hashlimit-name mini1 --hashlimit-htable-expire 2000;=;OK
+-m hashlimit --hashlimit-upto 1/sec --hashlimit-burst 1 --hashlimit-mode srcip --hashlimit-name mini1 --hashlimit-htable-expire 2000;=;OK
+-m hashlimit --hashlimit-upto 1/sec --hashlimit-burst 1 --hashlimit-mode dstip --hashlimit-name mini1 --hashlimit-htable-expire 2000;=;OK
+-m hashlimit --hashlimit-upto 1/sec --hashlimit-burst 1 --hashlimit-mode dstip --hashlimit-name mini1 --hashlimit-htable-max 2000 --hashlimit-htable-expire 2000;=;OK
+-m hashlimit --hashlimit-upto 1/sec --hashlimit-burst 1 --hashlimit-mode dstip --hashlimit-name mini1 --hashlimit-htable-max 2000 --hashlimit-htable-gcinterval 60000 --hashlimit-htable-expire 2000;=;OK
+-m hashlimit --hashlimit-upto 1/sec --hashlimit-name mini1;-m hashlimit --hashlimit-upto 1/sec --hashlimit-burst 5 --hashlimit-name mini1;OK
+-m hashlimit --hashlimit-upto 4kb/s --hashlimit-burst 400kb --hashlimit-name mini5;=;OK
+-m hashlimit --hashlimit-upto 10mb/s --hashlimit-name mini6;=;OK
+-m hashlimit --hashlimit-upto 123456b/s --hashlimit-burst 1mb --hashlimit-name mini7;=;OK
+# should work, it says "iptables v1.4.15: burst cannot be smaller than 96b"
+# ERROR: cannot load: iptables -A INPUT -m hashlimit --hashlimit-upto 96b/s --hashlimit-burst 5 --hashlimit-name mini1
+# -m hashlimit --hashlimit-upto 96b/s --hashlimit-burst 5 --hashlimit-name mini1;=;OK
+-m hashlimit --hashlimit-name mini1;;FAIL
+-m hashlimit --hashlimit-upto 1/sec;;FAIL
+-m hashlimit;;FAIL
+-m hashlimit --hashlimit-upto 40/sec --hashlimit-burst 20 --hashlimit-mode srcip --hashlimit-name syn-flood;=;OK
+-m hashlimit --hashlimit-upto 40/sec --hashlimit-burst 20 --hashlimit-mode srcip --hashlimit-name rate1 --hashlimit-rate-match;=;OK
+-m hashlimit --hashlimit-upto 40mb/s --hashlimit-mode srcip --hashlimit-name rate2 --hashlimit-rate-match;=;OK
+-m hashlimit --hashlimit-upto 40/sec --hashlimit-burst 20 --hashlimit-mode srcip --hashlimit-name rate3 --hashlimit-rate-match --hashlimit-rate-interval 10;=;OK
+-m hashlimit --hashlimit-upto 40mb/s --hashlimit-mode srcip --hashlimit-name rate4 --hashlimit-rate-match --hashlimit-rate-interval 10;=;OK
diff --git a/extensions/libxt_hashlimit.txlate b/extensions/libxt_hashlimit.txlate
new file mode 100644
index 0000000..6c8d07f
--- /dev/null
+++ b/extensions/libxt_hashlimit.txlate
@@ -0,0 +1,5 @@
+iptables-translate -A OUTPUT -m tcp -p tcp --dport 443 -m hashlimit --hashlimit-above 20kb/s --hashlimit-burst 1mb --hashlimit-mode dstip --hashlimit-name https --hashlimit-dstmask 24 -m state --state NEW -j DROP
+nft add rule ip filter OUTPUT tcp dport 443 meter https { ip daddr and 255.255.255.0 timeout 60s limit rate over 20 kbytes/second burst 1 mbytes} ct state new  counter drop
+
+iptables-translate -A OUTPUT -m tcp -p tcp --dport 443 -m hashlimit --hashlimit-upto 300 --hashlimit-burst 15 --hashlimit-mode srcip,dstip --hashlimit-name https --hashlimit-htable-expire 300000 -m state --state NEW -j DROP
+nft add rule ip filter OUTPUT tcp dport 443 meter https { ip daddr . ip saddr timeout 300s limit rate 300/second burst 15 packets} ct state new  counter drop
diff --git a/extensions/libxt_helper.c b/extensions/libxt_helper.c
index c9f9435..2afbf99 100644
--- a/extensions/libxt_helper.c
+++ b/extensions/libxt_helper.c
@@ -45,6 +45,21 @@
 	xtables_save_string(info->name);
 }
 
+static int helper_xlate(struct xt_xlate *xl,
+			const struct xt_xlate_mt_params *params)
+{
+	const struct xt_helper_info *info = (const void *)params->match->data;
+
+	if (params->escape_quotes)
+		xt_xlate_add(xl, "ct helper%s \\\"%s\\\"",
+			   info->invert ? " !=" : "", info->name);
+	else
+		xt_xlate_add(xl, "ct helper%s \"%s\"",
+			   info->invert ? " !=" : "", info->name);
+
+	return 1;
+}
+
 static struct xtables_match helper_match = {
 	.family		= NFPROTO_UNSPEC,
 	.name		= "helper",
@@ -55,6 +70,7 @@
 	.save		= helper_save,
 	.x6_parse	= helper_parse,
 	.x6_options	= helper_opts,
+	.xlate		= helper_xlate,
 };
 
 void _init(void)
diff --git a/extensions/libxt_helper.t b/extensions/libxt_helper.t
new file mode 100644
index 0000000..8c8420a
--- /dev/null
+++ b/extensions/libxt_helper.t
@@ -0,0 +1,6 @@
+:INPUT,FORWARD,OUTPUT
+-m helper --helper ftp;=;OK
+# should be OK?
+# ERROR: should fail: iptables -A INPUT -m helper --helper wrong
+# -m helper --helper wrong;;FAIL
+-m helper;;FAIL
diff --git a/extensions/libxt_helper.txlate b/extensions/libxt_helper.txlate
new file mode 100644
index 0000000..8259aba
--- /dev/null
+++ b/extensions/libxt_helper.txlate
@@ -0,0 +1,5 @@
+iptables-translate -A FORWARD -m helper --helper sip
+nft add rule ip filter FORWARD ct helper \"sip\" counter
+
+iptables-translate -A FORWARD -m helper ! --helper ftp
+nft add rule ip filter FORWARD ct helper != \"ftp\" counter
diff --git a/extensions/libxt_icmp.h b/extensions/libxt_icmp.h
new file mode 100644
index 0000000..5820206
--- /dev/null
+++ b/extensions/libxt_icmp.h
@@ -0,0 +1,25 @@
+struct xt_icmp_names {
+	const char *name;
+	uint8_t type;
+	uint8_t code_min, code_max;
+};
+
+static void xt_print_icmp_types(const struct xt_icmp_names *icmp_codes,
+				unsigned int n_codes)
+{
+	unsigned int i;
+
+	for (i = 0; i < n_codes; ++i) {
+		if (i && icmp_codes[i].type == icmp_codes[i-1].type) {
+			if (icmp_codes[i].code_min == icmp_codes[i-1].code_min
+			    && (icmp_codes[i].code_max
+				== icmp_codes[i-1].code_max))
+				printf(" (%s)", icmp_codes[i].name);
+			else
+				printf("\n   %s", icmp_codes[i].name);
+		}
+		else
+			printf("\n%s", icmp_codes[i].name);
+	}
+	printf("\n");
+}
diff --git a/extensions/libxt_ipcomp.c b/extensions/libxt_ipcomp.c
new file mode 100644
index 0000000..b5c4312
--- /dev/null
+++ b/extensions/libxt_ipcomp.c
@@ -0,0 +1,134 @@
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_ipcomp.h>
+
+enum {
+	O_compSPI = 0,
+	O_compRES,
+};
+
+static void comp_help(void)
+{
+	printf(
+"comp match options:\n"
+"[!] --ipcompspi spi[:spi]\n"
+"				match spi (range)\n");
+}
+
+static const struct xt_option_entry comp_opts[] = {
+	{.name = "ipcompspi", .id = O_compSPI, .type = XTTYPE_UINT32RC,
+	 .flags = XTOPT_INVERT | XTOPT_PUT,
+	 XTOPT_POINTER(struct xt_ipcomp, spis)},
+	{.name = "compres", .id = O_compRES, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static void comp_parse(struct xt_option_call *cb)
+{
+	struct xt_ipcomp *compinfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_compSPI:
+		if (cb->nvals == 1)
+			compinfo->spis[1] = compinfo->spis[0];
+		if (cb->invert)
+			compinfo->invflags |= XT_IPCOMP_INV_SPI;
+		break;
+	case O_compRES:
+		compinfo->hdrres = 1;
+		break;
+	}
+}
+
+static void
+print_spis(const char *name, uint32_t min, uint32_t max,
+	    int invert)
+{
+	const char *inv = invert ? "!" : "";
+
+	if (min != 0 || max != 0xFFFFFFFF || invert) {
+		if (min == max)
+			printf("%s:%s%u", name, inv, min);
+		else
+			printf("%ss:%s%u:%u", name, inv, min, max);
+	}
+}
+
+static void comp_print(const void *ip, const struct xt_entry_match *match,
+                     int numeric)
+{
+	const struct xt_ipcomp *comp = (struct xt_ipcomp *)match->data;
+
+	printf(" comp ");
+	print_spis("spi", comp->spis[0], comp->spis[1],
+		    comp->invflags & XT_IPCOMP_INV_SPI);
+
+	if (comp->hdrres)
+		printf(" reserved");
+
+	if (comp->invflags & ~XT_IPCOMP_INV_MASK)
+		printf(" Unknown invflags: 0x%X",
+		       comp->invflags & ~XT_IPCOMP_INV_MASK);
+}
+
+static void comp_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_ipcomp *compinfo = (struct xt_ipcomp *)match->data;
+
+	if (!(compinfo->spis[0] == 0
+	    && compinfo->spis[1] == 0xFFFFFFFF)) {
+		printf("%s --ipcompspi ",
+			(compinfo->invflags & XT_IPCOMP_INV_SPI) ? " !" : "");
+		if (compinfo->spis[0]
+		    != compinfo->spis[1])
+			printf("%u:%u",
+			       compinfo->spis[0],
+			       compinfo->spis[1]);
+		else
+			printf("%u",
+			       compinfo->spis[0]);
+	}
+
+	if (compinfo->hdrres != 0 )
+		printf(" --compres");
+}
+
+static int comp_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_mt_params *params)
+{
+	const struct xt_ipcomp *compinfo =
+		(struct xt_ipcomp *)params->match->data;
+
+	xt_xlate_add(xl, "comp cpi %s",
+		     compinfo->invflags & XT_IPCOMP_INV_SPI ? "!= " : "");
+	if (compinfo->spis[0] != compinfo->spis[1])
+		xt_xlate_add(xl, "%u-%u", compinfo->spis[0],
+			     compinfo->spis[1]);
+	else
+		xt_xlate_add(xl, "%u", compinfo->spis[0]);
+
+	return 1;
+}
+
+static struct xtables_match comp_mt_reg = {
+	.name          = "ipcomp",
+	.version       = XTABLES_VERSION,
+	.family        = NFPROTO_UNSPEC,
+	.size          = XT_ALIGN(sizeof(struct xt_ipcomp)),
+	.userspacesize = XT_ALIGN(sizeof(struct xt_ipcomp)),
+	.help          = comp_help,
+	.print         = comp_print,
+	.save          = comp_save,
+	.x6_parse      = comp_parse,
+	.x6_options    = comp_opts,
+	.xlate         = comp_xlate,
+};
+
+void
+_init(void)
+{
+	xtables_register_match(&comp_mt_reg);
+};
+
diff --git a/extensions/libxt_ipcomp.c.man b/extensions/libxt_ipcomp.c.man
new file mode 100644
index 0000000..f3b17d2
--- /dev/null
+++ b/extensions/libxt_ipcomp.c.man
@@ -0,0 +1,7 @@
+This module matches the parameters in IPcomp header of IPsec packets.
+.TP
+[\fB!\fP] \fB\-\-ipcompspi\fP \fIspi\fP[\fB:\fP\fIspi\fP]
+Matches IPcomp header CPI value.
+.TP
+\fB\-\-compres\fP
+Matches if the reserved field is filled with zero.
diff --git a/extensions/libxt_ipcomp.t b/extensions/libxt_ipcomp.t
new file mode 100644
index 0000000..8546ba9
--- /dev/null
+++ b/extensions/libxt_ipcomp.t
@@ -0,0 +1,3 @@
+:INPUT,OUTPUT
+-p ipcomp -m ipcomp --ipcompspi 18 -j DROP;=;OK
+-p ipcomp -m ipcomp ! --ipcompspi 18 -j ACCEPT;=;OK
diff --git a/extensions/libxt_ipcomp.txlate b/extensions/libxt_ipcomp.txlate
new file mode 100644
index 0000000..f9efe53
--- /dev/null
+++ b/extensions/libxt_ipcomp.txlate
@@ -0,0 +1,5 @@
+iptables-translate -t filter -A INPUT -m ipcomp --ipcompspi 0x12 -j ACCEPT
+nft add rule ip filter INPUT comp cpi 18 counter accept
+
+iptables-translate -t filter -A INPUT -m ipcomp ! --ipcompspi 0x12 -j ACCEPT
+nft add rule ip filter INPUT comp cpi != 18 counter accept
diff --git a/extensions/libxt_iprange.c b/extensions/libxt_iprange.c
index 2c9ea99..8be2481 100644
--- a/extensions/libxt_iprange.c
+++ b/extensions/libxt_iprange.c
@@ -104,7 +104,8 @@
 		info->flags |= IPRANGE_SRC;
 		if (cb->invert)
 			info->flags |= IPRANGE_SRC_INV;
-		iprange_parse_range(cb->arg, range, NFPROTO_IPV4, "--src-range");
+		iprange_parse_range(cb->arg, range,
+				    NFPROTO_IPV4, "--src-range");
 		info->src.min_ip = range[0].ip;
 		info->src.max_ip = range[1].ip;
 		break;
@@ -112,7 +113,8 @@
 		info->flags |= IPRANGE_DST;
 		if (cb->invert)
 			info->flags |= IPRANGE_DST_INV;
-		iprange_parse_range(cb->arg, range, NFPROTO_IPV4, "--dst-range");
+		iprange_parse_range(cb->arg, range,
+				    NFPROTO_IPV4, "--dst-range");
 		info->dst.min_ip = range[0].ip;
 		info->dst.max_ip = range[1].ip;
 		break;
@@ -172,7 +174,7 @@
 }
 
 static void iprange_print(const void *ip, const struct xt_entry_match *match,
-                          int numeric)
+			  int numeric)
 {
 	const struct ipt_iprange_info *info = (const void *)match->data;
 
@@ -192,7 +194,7 @@
 
 static void
 iprange_mt4_print(const void *ip, const struct xt_entry_match *match,
-                  int numeric)
+		  int numeric)
 {
 	const struct xt_iprange_mtinfo *info = (const void *)match->data;
 
@@ -218,7 +220,7 @@
 
 static void
 iprange_mt6_print(const void *ip, const struct xt_entry_match *match,
-                  int numeric)
+		  int numeric)
 {
 	const struct xt_iprange_mtinfo *info = (const void *)match->data;
 
@@ -267,13 +269,15 @@
 	if (info->flags & IPRANGE_SRC) {
 		if (info->flags & IPRANGE_SRC_INV)
 			printf(" !");
-		printf(" --src-range %s", xtables_ipaddr_to_numeric(&info->src_min.in));
+		printf(" --src-range %s",
+		       xtables_ipaddr_to_numeric(&info->src_min.in));
 		printf("-%s", xtables_ipaddr_to_numeric(&info->src_max.in));
 	}
 	if (info->flags & IPRANGE_DST) {
 		if (info->flags & IPRANGE_DST_INV)
 			printf(" !");
-		printf(" --dst-range %s", xtables_ipaddr_to_numeric(&info->dst_min.in));
+		printf(" --dst-range %s",
+		       xtables_ipaddr_to_numeric(&info->dst_min.in));
 		printf("-%s", xtables_ipaddr_to_numeric(&info->dst_max.in));
 	}
 }
@@ -285,17 +289,105 @@
 	if (info->flags & IPRANGE_SRC) {
 		if (info->flags & IPRANGE_SRC_INV)
 			printf(" !");
-		printf(" --src-range %s", xtables_ip6addr_to_numeric(&info->src_min.in6));
+		printf(" --src-range %s",
+		       xtables_ip6addr_to_numeric(&info->src_min.in6));
 		printf("-%s", xtables_ip6addr_to_numeric(&info->src_max.in6));
 	}
 	if (info->flags & IPRANGE_DST) {
 		if (info->flags & IPRANGE_DST_INV)
 			printf(" !");
-		printf(" --dst-range %s", xtables_ip6addr_to_numeric(&info->dst_min.in6));
+		printf(" --dst-range %s",
+		       xtables_ip6addr_to_numeric(&info->dst_min.in6));
 		printf("-%s", xtables_ip6addr_to_numeric(&info->dst_max.in6));
 	}
 }
 
+static void
+print_iprange_xlate(const struct ipt_iprange *range,
+		    struct xt_xlate *xl)
+{
+	const unsigned char *byte_min, *byte_max;
+
+	byte_min = (const unsigned char *)&range->min_ip;
+	byte_max = (const unsigned char *)&range->max_ip;
+	xt_xlate_add(xl, " %u.%u.%u.%u-%u.%u.%u.%u ",
+		   byte_min[0], byte_min[1], byte_min[2], byte_min[3],
+		   byte_max[0], byte_max[1], byte_max[2], byte_max[3]);
+}
+
+static int iprange_xlate(struct xt_xlate *xl,
+			 const struct xt_xlate_mt_params *params)
+{
+	const struct ipt_iprange_info *info = (const void *)params->match->data;
+	char *space = "";
+
+	if (info->flags & IPRANGE_SRC) {
+		xt_xlate_add(xl, "ip saddr%s",
+			     info->flags & IPRANGE_SRC_INV ? " !=" : "");
+		print_iprange_xlate(&info->src, xl);
+		space = " ";
+	}
+	if (info->flags & IPRANGE_DST) {
+		xt_xlate_add(xl, "%sip daddr%s", space,
+			     info->flags & IPRANGE_DST_INV ? " !=" : "");
+		print_iprange_xlate(&info->dst, xl);
+	}
+
+	return 1;
+}
+
+static int iprange_mt4_xlate(struct xt_xlate *xl,
+			     const struct xt_xlate_mt_params *params)
+{
+	const struct xt_iprange_mtinfo *info =
+		(const void *)params->match->data;
+	char *space = "";
+
+	if (info->flags & IPRANGE_SRC) {
+		xt_xlate_add(xl, "ip saddr%s %s",
+			     info->flags & IPRANGE_SRC_INV ? " !=" : "",
+			     xtables_ipaddr_to_numeric(&info->src_min.in));
+		xt_xlate_add(xl, "-%s",
+			     xtables_ipaddr_to_numeric(&info->src_max.in));
+		space = " ";
+	}
+	if (info->flags & IPRANGE_DST) {
+		xt_xlate_add(xl, "%sip daddr%s %s", space,
+			     info->flags & IPRANGE_DST_INV ? " !=" : "",
+			     xtables_ipaddr_to_numeric(&info->dst_min.in));
+		xt_xlate_add(xl, "-%s",
+			     xtables_ipaddr_to_numeric(&info->dst_max.in));
+	}
+
+	return 1;
+}
+
+static int iprange_mt6_xlate(struct xt_xlate *xl,
+			     const struct xt_xlate_mt_params *params)
+{
+	const struct xt_iprange_mtinfo *info =
+		(const void *)params->match->data;
+	char *space = "";
+
+	if (info->flags & IPRANGE_SRC) {
+		xt_xlate_add(xl, "ip6 saddr%s %s",
+			     info->flags & IPRANGE_SRC_INV ? " !=" : "",
+			     xtables_ip6addr_to_numeric(&info->src_min.in6));
+		xt_xlate_add(xl, "-%s",
+			     xtables_ip6addr_to_numeric(&info->src_max.in6));
+		space = " ";
+	}
+	if (info->flags & IPRANGE_DST) {
+		xt_xlate_add(xl, "%sip6 daddr%s %s", space,
+			     info->flags & IPRANGE_DST_INV ? " !=" : "",
+			     xtables_ip6addr_to_numeric(&info->dst_min.in6));
+		xt_xlate_add(xl, "-%s",
+			     xtables_ip6addr_to_numeric(&info->dst_max.in6));
+	}
+
+	return 1;
+}
+
 static struct xtables_match iprange_mt_reg[] = {
 	{
 		.version       = XTABLES_VERSION,
@@ -310,6 +402,7 @@
 		.print         = iprange_print,
 		.save          = iprange_save,
 		.x6_options    = iprange_mt_opts,
+		.xlate	       = iprange_xlate,
 	},
 	{
 		.version       = XTABLES_VERSION,
@@ -324,6 +417,7 @@
 		.print         = iprange_mt4_print,
 		.save          = iprange_mt4_save,
 		.x6_options    = iprange_mt_opts,
+		.xlate	       = iprange_mt4_xlate,
 	},
 	{
 		.version       = XTABLES_VERSION,
@@ -338,6 +432,7 @@
 		.print         = iprange_mt6_print,
 		.save          = iprange_mt6_save,
 		.x6_options    = iprange_mt_opts,
+		.xlate	       = iprange_mt6_xlate,
 	},
 };
 
diff --git a/extensions/libxt_iprange.t b/extensions/libxt_iprange.t
new file mode 100644
index 0000000..6fd98be
--- /dev/null
+++ b/extensions/libxt_iprange.t
@@ -0,0 +1,11 @@
+:INPUT,FORWARD,OUTPUT
+-m iprange --src-range 1.1.1.1-1.1.1.10;=;OK
+-m iprange ! --src-range 1.1.1.1-1.1.1.10;=;OK
+-m iprange --dst-range 1.1.1.1-1.1.1.10;=;OK
+-m iprange ! --dst-range 1.1.1.1-1.1.1.10;=;OK
+# it shows -A INPUT -m iprange --src-range 1.1.1.1-1.1.1.1, should we support this?
+# ERROR: should fail: iptables -A INPUT -m iprange --src-range 1.1.1.1
+# -m iprange --src-range 1.1.1.1;;FAIL
+# ERROR: should fail: iptables -A INPUT -m iprange --dst-range 1.1.1.1
+#-m iprange --dst-range 1.1.1.1;;FAIL
+-m iprange;;FAIL
diff --git a/extensions/libxt_iprange.txlate b/extensions/libxt_iprange.txlate
new file mode 100644
index 0000000..999f4b7
--- /dev/null
+++ b/extensions/libxt_iprange.txlate
@@ -0,0 +1,14 @@
+iptables-translate -A INPUT -m iprange --src-range 192.168.25.149-192.168.25.151 -j ACCEPT
+nft add rule ip filter INPUT ip saddr 192.168.25.149-192.168.25.151 counter accept
+
+iptables-translate -A INPUT -m iprange --dst-range 192.168.25.149-192.168.25.151 -j ACCEPT
+nft add rule ip filter INPUT ip daddr 192.168.25.149-192.168.25.151 counter accept
+
+iptables-translate -A INPUT -m iprange --dst-range 3.3.3.3-6.6.6.6 --src-range 4.4.4.4-7.7.7.7 -j ACCEPT
+nft add rule ip filter INPUT ip saddr 4.4.4.4-7.7.7.7 ip daddr 3.3.3.3-6.6.6.6 counter accept
+
+ip6tables-translate -A INPUT -m iprange ! --dst-range ::2d01-::2d03 -j ACCEPT
+nft add rule ip6 filter INPUT ip6 daddr != ::2d01-::2d03 counter accept
+
+ip6tables-translate -A INPUT -m iprange ! --dst-range ::2d01-::2d03 --src-range ::2d01-::2d03 -j ACCEPT
+nft add rule ip6 filter INPUT ip6 saddr ::2d01-::2d03 ip6 daddr != ::2d01-::2d03 counter accept
diff --git a/extensions/libxt_ipvs.c b/extensions/libxt_ipvs.c
index 4672766..51952be 100644
--- a/extensions/libxt_ipvs.c
+++ b/extensions/libxt_ipvs.c
@@ -27,7 +27,7 @@
 static const struct xt_option_entry ipvs_mt_opts[] = {
 	{.name = "ipvs", .id = O_IPVS, .type = XTTYPE_NONE,
 	 .flags = XTOPT_INVERT},
-	{.name = "vproto", .id = O_VPROTO, .type = XTTYPE_STRING,
+	{.name = "vproto", .id = O_VPROTO, .type = XTTYPE_PROTOCOL,
 	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, l4proto)},
 	{.name = "vaddr", .id = O_VADDR, .type = XTTYPE_HOSTMASK,
 	 .flags = XTOPT_INVERT},
@@ -69,9 +69,6 @@
 
 	xtables_option_parse(cb);
 	switch (cb->entry->id) {
-	case O_VPROTO:
-		data->l4proto = cb->val.protocol;
-		break;
 	case O_VADDR:
 		memcpy(&data->vaddr, &cb->val.haddr, sizeof(cb->val.haddr));
 		memcpy(&data->vmask, &cb->val.hmask, sizeof(cb->val.hmask));
@@ -126,19 +123,19 @@
 			      const union nf_inet_addr *mask,
 			      unsigned int family, bool numeric)
 {
-	char buf[BUFSIZ];
-
 	if (family == NFPROTO_IPV4) {
 		if (!numeric && addr->ip == 0) {
 			printf(" anywhere");
 			return;
 		}
 		if (numeric)
-			strcpy(buf, xtables_ipaddr_to_numeric(&addr->in));
+			printf(" %s%s",
+			       xtables_ipaddr_to_numeric(&addr->in),
+			       xtables_ipmask_to_numeric(&mask->in));
 		else
-			strcpy(buf, xtables_ipaddr_to_anyname(&addr->in));
-		strcat(buf, xtables_ipmask_to_numeric(&mask->in));
-		printf(" %s", buf);
+			printf(" %s%s",
+			       xtables_ipaddr_to_anyname(&addr->in),
+			       xtables_ipmask_to_numeric(&mask->in));
 	} else if (family == NFPROTO_IPV6) {
 		if (!numeric && addr->ip6[0] == 0 && addr->ip6[1] == 0 &&
 		    addr->ip6[2] == 0 && addr->ip6[3] == 0) {
@@ -146,11 +143,13 @@
 			return;
 		}
 		if (numeric)
-			strcpy(buf, xtables_ip6addr_to_numeric(&addr->in6));
+			printf(" %s%s",
+			       xtables_ip6addr_to_numeric(&addr->in6),
+			       xtables_ip6mask_to_numeric(&mask->in6));
 		else
-			strcpy(buf, xtables_ip6addr_to_anyname(&addr->in6));
-		strcat(buf, xtables_ip6mask_to_numeric(&mask->in6));
-		printf(" %s", buf);
+			printf(" %s%s",
+			       xtables_ip6addr_to_anyname(&addr->in6),
+			       xtables_ip6mask_to_numeric(&mask->in6));
 	}
 }
 
@@ -166,7 +165,7 @@
 	if (data->bitmask & XT_IPVS_PROTO) {
 		if (data->invert & XT_IPVS_PROTO)
 			printf(" !");
-		printf(" %sproto %u", prefix, data->l4proto);
+		printf(" %svproto %u", prefix, data->l4proto);
 	}
 
 	if (data->bitmask & XT_IPVS_VADDR) {
diff --git a/extensions/libxt_ipvs.t b/extensions/libxt_ipvs.t
new file mode 100644
index 0000000..c2acc66
--- /dev/null
+++ b/extensions/libxt_ipvs.t
@@ -0,0 +1,20 @@
+:INPUT,FORWARD,OUTPUT
+-m ipvs --ipvs;=;OK
+-m ipvs ! --ipvs;=;OK
+-m ipvs --vproto tcp;-m ipvs --vproto 6;OK
+-m ipvs ! --vproto TCP;-m ipvs ! --vproto 6;OK
+-m ipvs --vproto 23;=;OK
+-m ipvs --vaddr 1.2.3.4;=;OK
+-m ipvs ! --vaddr 1.2.3.4/255.255.255.0;-m ipvs ! --vaddr 1.2.3.4/24;OK
+-m ipvs --vport http;-m ipvs --vport 80;OK
+-m ipvs ! --vport ssh;-m ipvs ! --vport 22;OK
+-m ipvs --vport 22;=;OK
+-m ipvs ! --vport 443;=;OK
+-m ipvs --vdir ORIGINAL;=;OK
+-m ipvs --vdir REPLY;=;OK
+-m ipvs --vmethod GATE;=;OK
+-m ipvs ! --vmethod IPIP;=;OK
+-m ipvs --vmethod MASQ;=;OK
+-m ipvs --vportctl 21;=;OK
+-m ipvs ! --vportctl 21;=;OK
+-m ipvs --vproto 6 --vaddr 1.2.3.4/16 --vport 22 --vdir ORIGINAL --vmethod GATE;=;OK
diff --git a/extensions/libxt_length.c b/extensions/libxt_length.c
index 6ea7646..04eac4a 100644
--- a/extensions/libxt_length.c
+++ b/extensions/libxt_length.c
@@ -56,6 +56,21 @@
 		printf("%u:%u", info->min, info->max);
 }
 
+static int length_xlate(struct xt_xlate *xl,
+			const struct xt_xlate_mt_params *params)
+{
+	const struct xt_length_info *info = (void *)params->match->data;
+
+	xt_xlate_add(xl, "meta length %s", info->invert ? "!= " : "");
+	if (info->min == info->max)
+		xt_xlate_add(xl, "%u", info->min);
+	else
+		xt_xlate_add(xl, "%u-%u", info->min, info->max);
+
+	return 1;
+}
+
+
 static struct xtables_match length_match = {
 	.family		= NFPROTO_UNSPEC,
 	.name		= "length",
@@ -67,6 +82,7 @@
 	.save		= length_save,
 	.x6_parse	= length_parse,
 	.x6_options	= length_opts,
+	.xlate		= length_xlate,
 };
 
 void _init(void)
diff --git a/extensions/libxt_length.t b/extensions/libxt_length.t
new file mode 100644
index 0000000..0b6624e
--- /dev/null
+++ b/extensions/libxt_length.t
@@ -0,0 +1,10 @@
+:INPUT,FORWARD,OUTPUT
+-m length --length 1;=;OK
+-m length --length :2;-m length --length 0:2;OK
+-m length --length 0:3;=;OK
+-m length --length 4:;=;OK
+-m length --length 0:65535;=;OK
+-m length ! --length 0:65535;=;OK
+-m length --length 0:65536;;FAIL
+-m length --length -1:65535;;FAIL
+-m length;;FAIL
diff --git a/extensions/libxt_length.txlate b/extensions/libxt_length.txlate
new file mode 100644
index 0000000..e777c26
--- /dev/null
+++ b/extensions/libxt_length.txlate
@@ -0,0 +1,11 @@
+iptables-translate -A INPUT -p icmp -m length --length 86:0xffff -j DROP
+nft add rule ip filter INPUT ip protocol icmp meta length 86-65535 counter drop
+
+iptables-translate -A INPUT -p udp -m length --length :400
+nft add rule ip filter INPUT ip protocol udp meta length 0-400 counter
+
+iptables-translate -A INPUT -p udp -m length --length 40
+nft add rule ip filter INPUT ip protocol udp meta length 40 counter
+
+iptables-translate -A INPUT -p udp -m length ! --length 40
+nft add rule ip filter INPUT ip protocol udp meta length != 40 counter
diff --git a/extensions/libxt_limit.c b/extensions/libxt_limit.c
index b15b02f..1b32465 100644
--- a/extensions/libxt_limit.c
+++ b/extensions/libxt_limit.c
@@ -3,12 +3,19 @@
  * Jérôme de Vivie   <devivie@info.enserb.u-bordeaux.fr>
  * Hervé Eychenne    <rv@wallfire.org>
  */
+#define _BSD_SOURCE 1
+#define _DEFAULT_SOURCE 1
+#define _ISOC99_SOURCE 1
+#include <errno.h>
+#include <getopt.h>
+#include <math.h>
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
 #include <xtables.h>
 #include <linux/netfilter/x_tables.h>
 #include <linux/netfilter/xt_limit.h>
+#include "iptables/nft-bridge.h"
 
 #define XT_LIMIT_AVG	"3/hour"
 #define XT_LIMIT_BURST	5
@@ -64,12 +71,13 @@
 	if (!r)
 		return 0;
 
-	/* This would get mapped to infinite (1/day is minimum they
-           can specify, so we're ok at that end). */
-	if (r / mult > XT_LIMIT_SCALE)
-		xtables_error(PARAMETER_PROBLEM, "Rate too fast \"%s\"\n", rate);
-
 	*val = XT_LIMIT_SCALE * mult / r;
+	if (*val == 0)
+		/*
+		 * The rate maps to infinity. (1/day is the minimum they can
+		 * specify, so we are ok at that end).
+		 */
+		xtables_error(PARAMETER_PROBLEM, "Rate too fast \"%s\"\n", rate);
 	return 1;
 }
 
@@ -118,6 +126,11 @@
 {
 	unsigned int i;
 
+	if (period == 0) {
+		printf(" %f", INFINITY);
+		return;
+	}
+
 	for (i = 1; i < ARRAY_SIZE(rates); ++i)
 		if (period > rates[i].mult
             || rates[i].mult/period < rates[i].mult%period)
@@ -143,21 +156,138 @@
 		printf(" --limit-burst %u", r->burst);
 }
 
-static struct xtables_match limit_match = {
-	.family		= NFPROTO_UNSPEC,
-	.name		= "limit",
-	.version	= XTABLES_VERSION,
-	.size		= XT_ALIGN(sizeof(struct xt_rateinfo)),
-	.userspacesize	= offsetof(struct xt_rateinfo, prev),
-	.help		= limit_help,
-	.init		= limit_init,
-	.x6_parse	= limit_parse,
-	.print		= limit_print,
-	.save		= limit_save,
-	.x6_options	= limit_opts,
+static const struct rates rates_xlate[] = {
+	{ "day",	XT_LIMIT_SCALE * 24 * 60 * 60 },
+	{ "hour",	XT_LIMIT_SCALE * 60 * 60 },
+	{ "minute",	XT_LIMIT_SCALE * 60 },
+	{ "second",	XT_LIMIT_SCALE }
+};
+
+static void print_rate_xlate(uint32_t period, struct xt_xlate *xl)
+{
+	unsigned int i;
+
+	if (period == 0) {
+		xt_xlate_add(xl, " %f", INFINITY);
+		return;
+	}
+
+	for (i = 1; i < ARRAY_SIZE(rates); ++i)
+		if (period > rates_xlate[i].mult ||
+		    rates_xlate[i].mult / period < rates_xlate[i].mult % period)
+			break;
+
+	xt_xlate_add(xl, " %u/%s", rates_xlate[i - 1].mult / period,
+		   rates_xlate[i - 1].name);
+}
+
+static int limit_xlate(struct xt_xlate *xl,
+		       const struct xt_xlate_mt_params *params)
+{
+	const struct xt_rateinfo *r = (const void *)params->match->data;
+
+	xt_xlate_add(xl, "limit rate");
+	print_rate_xlate(r->avg, xl);
+	if (r->burst != 0)
+		xt_xlate_add(xl, " burst %u packets", r->burst);
+
+	return 1;
+}
+
+static int limit_xlate_eb(struct xt_xlate *xl,
+			  const struct xt_xlate_mt_params *params)
+{
+	limit_xlate(xl, params);
+	xt_xlate_add(xl, " ");
+	return 1;
+}
+
+#define FLAG_LIMIT		0x01
+#define FLAG_LIMIT_BURST	0x02
+#define ARG_LIMIT		'1'
+#define ARG_LIMIT_BURST		'2'
+
+static int brlimit_parse(int c, char **argv, int invert, unsigned int *flags,
+			 const void *entry, struct xt_entry_match **match)
+{
+	struct xt_rateinfo *r = (struct xt_rateinfo *)(*match)->data;
+	uintmax_t num;
+
+	switch (c) {
+	case ARG_LIMIT:
+		EBT_CHECK_OPTION(flags, FLAG_LIMIT);
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Unexpected `!' after --limit");
+		if (!parse_rate(optarg, &r->avg))
+			xtables_error(PARAMETER_PROBLEM,
+				      "bad rate `%s'", optarg);
+		break;
+	case ARG_LIMIT_BURST:
+		EBT_CHECK_OPTION(flags, FLAG_LIMIT_BURST);
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Unexpected `!' after --limit-burst");
+		if (!xtables_strtoul(optarg, NULL, &num, 0, 10000))
+			xtables_error(PARAMETER_PROBLEM,
+				      "bad --limit-burst `%s'", optarg);
+		r->burst = num;
+		break;
+	default:
+		return 0;
+	}
+
+	return 1;
+}
+
+static void brlimit_print(const void *ip, const struct xt_entry_match *match,
+			  int numeric)
+{
+	const struct xt_rateinfo *r = (struct xt_rateinfo *)match->data;
+
+	printf("--limit");
+	print_rate(r->avg);
+	printf(" --limit-burst %u ", r->burst);
+}
+
+static const struct option brlimit_opts[] =
+{
+	{ .name = "limit",	.has_arg = true,	.val = ARG_LIMIT },
+	{ .name = "limit-burst",.has_arg = true,	.val = ARG_LIMIT_BURST },
+	XT_GETOPT_TABLEEND,
+};
+
+static struct xtables_match limit_match[] = {
+	{
+		.family		= NFPROTO_UNSPEC,
+		.name		= "limit",
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_rateinfo)),
+		.userspacesize	= offsetof(struct xt_rateinfo, prev),
+		.help		= limit_help,
+		.init		= limit_init,
+		.x6_parse	= limit_parse,
+		.print		= limit_print,
+		.save		= limit_save,
+		.x6_options	= limit_opts,
+		.xlate		= limit_xlate,
+	},
+	{
+		.family		= NFPROTO_BRIDGE,
+		.name		= "limit",
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_rateinfo)),
+		.userspacesize	= offsetof(struct xt_rateinfo, prev),
+		.help		= limit_help,
+		.init		= limit_init,
+		.parse		= brlimit_parse,
+		.print		= brlimit_print,
+		.extra_opts	= brlimit_opts,
+		.xlate		= limit_xlate_eb,
+	},
 };
 
 void _init(void)
 {
-	xtables_register_match(&limit_match);
+	xtables_register_matches(limit_match, ARRAY_SIZE(limit_match));
 }
diff --git a/extensions/libxt_limit.t b/extensions/libxt_limit.t
new file mode 100644
index 0000000..b0af653
--- /dev/null
+++ b/extensions/libxt_limit.t
@@ -0,0 +1,6 @@
+:INPUT,FORWARD,OUTPUT
+-m limit --limit 1/sec;=;OK
+-m limit --limit 1/min;=;OK
+-m limit --limit 1000/hour;=;OK
+-m limit --limit 1000/day;=;OK
+-m limit --limit 1/sec --limit-burst 1;=;OK
diff --git a/extensions/libxt_limit.txlate b/extensions/libxt_limit.txlate
new file mode 100644
index 0000000..df9ed2d
--- /dev/null
+++ b/extensions/libxt_limit.txlate
@@ -0,0 +1,8 @@
+iptables-translate -A INPUT -m limit --limit 3/m --limit-burst 3
+nft add rule ip filter INPUT limit rate 3/minute burst 3 packets counter
+
+iptables-translate -A INPUT -m limit --limit 10/s --limit-burst 5
+nft add rule ip filter INPUT limit rate 10/second burst 5 packets counter
+
+iptables-translate -A INPUT -m limit --limit 10/s --limit-burst 0
+nft add rule ip filter INPUT limit rate 10/second counter
diff --git a/extensions/libxt_mac.c b/extensions/libxt_mac.c
index f171d15..b90eef2 100644
--- a/extensions/libxt_mac.c
+++ b/extensions/libxt_mac.c
@@ -37,25 +37,17 @@
 		macinfo->invert = 1;
 }
 
-static void print_mac(const unsigned char *macaddress)
-{
-	unsigned int i;
-
-	printf(" %02X", macaddress[0]);
-	for (i = 1; i < ETH_ALEN; ++i)
-		printf(":%02X", macaddress[i]);
-}
-
 static void
 mac_print(const void *ip, const struct xt_entry_match *match, int numeric)
 {
 	const struct xt_mac_info *info = (void *)match->data;
+
 	printf(" MAC");
 
 	if (info->invert)
 		printf(" !");
-	
-	print_mac(info->srcaddr);
+
+	xtables_print_mac(info->srcaddr);
 }
 
 static void mac_save(const void *ip, const struct xt_entry_match *match)
@@ -65,13 +57,34 @@
 	if (info->invert)
 		printf(" !");
 
-	printf(" --mac-source");
-	print_mac(info->srcaddr);
+	printf(" --mac-source ");
+	xtables_print_mac(info->srcaddr);
+}
+
+static void print_mac_xlate(const unsigned char *macaddress,
+			    struct xt_xlate *xl)
+{
+	unsigned int i;
+
+	xt_xlate_add(xl, "%02x", macaddress[0]);
+	for (i = 1; i < ETH_ALEN; ++i)
+		xt_xlate_add(xl, ":%02x", macaddress[i]);
+}
+
+static int mac_xlate(struct xt_xlate *xl,
+		     const struct xt_xlate_mt_params *params)
+{
+	const struct xt_mac_info *info = (void *)params->match->data;
+
+	xt_xlate_add(xl, "ether saddr%s ", info->invert ? " !=" : "");
+	print_mac_xlate(info->srcaddr, xl);
+
+	return 1;
 }
 
 static struct xtables_match mac_match = {
 	.family		= NFPROTO_UNSPEC,
- 	.name		= "mac",
+	.name		= "mac",
 	.version	= XTABLES_VERSION,
 	.size		= XT_ALIGN(sizeof(struct xt_mac_info)),
 	.userspacesize	= XT_ALIGN(sizeof(struct xt_mac_info)),
@@ -80,6 +93,7 @@
 	.print		= mac_print,
 	.save		= mac_save,
 	.x6_options	= mac_opts,
+	.xlate		= mac_xlate,
 };
 
 void _init(void)
diff --git a/extensions/libxt_mac.t b/extensions/libxt_mac.t
new file mode 100644
index 0000000..a5ec81d
--- /dev/null
+++ b/extensions/libxt_mac.t
@@ -0,0 +1,5 @@
+:INPUT,FORWARD
+-m mac --mac-source 42:01:02:03:04:05;=;OK
+-m mac --mac-source 42:01:02:03:04;=;FAIL
+-m mac --mac-source 42:01:02:03:04:05:06;=;FAIL
+-m mac;;FAIL
diff --git a/extensions/libxt_mac.txlate b/extensions/libxt_mac.txlate
new file mode 100644
index 0000000..08696f3
--- /dev/null
+++ b/extensions/libxt_mac.txlate
@@ -0,0 +1,5 @@
+iptables-translate -A INPUT -m mac --mac-source 0a:12:3e:4f:b2:c6 -j DROP
+nft add rule ip filter INPUT ether saddr 0a:12:3e:4f:b2:c6 counter drop
+
+iptables-translate -A INPUT -p tcp --dport 80 -m mac --mac-source 0a:12:3e:4f:b2:c6 -j ACCEPT
+nft add rule ip filter INPUT tcp dport 80 ether saddr 0a:12:3e:4f:b2:c6 counter accept
diff --git a/extensions/libxt_mark.c b/extensions/libxt_mark.c
index 7f8c995..134ad43 100644
--- a/extensions/libxt_mark.c
+++ b/extensions/libxt_mark.c
@@ -47,14 +47,6 @@
 	markinfo->mask = cb->val.mask;
 }
 
-static void print_mark(unsigned int mark, unsigned int mask)
-{
-	if (mask != 0xffffffffU)
-		printf(" 0x%x/0x%x", mark, mask);
-	else
-		printf(" 0x%x", mark);
-}
-
 static void
 mark_mt_print(const void *ip, const struct xt_entry_match *match, int numeric)
 {
@@ -63,7 +55,8 @@
 	printf(" mark match");
 	if (info->invert)
 		printf(" !");
-	print_mark(info->mark, info->mask);
+
+	xtables_print_mark_mask(info->mark, info->mask);
 }
 
 static void
@@ -75,8 +68,8 @@
 
 	if (info->invert)
 		printf(" !");
-	
-	print_mark(info->mark, info->mask);
+
+	xtables_print_mark_mask(info->mark, info->mask);
 }
 
 static void mark_mt_save(const void *ip, const struct xt_entry_match *match)
@@ -87,7 +80,7 @@
 		printf(" !");
 
 	printf(" --mark");
-	print_mark(info->mark, info->mask);
+	xtables_print_mark_mask(info->mark, info->mask);
 }
 
 static void
@@ -97,9 +90,51 @@
 
 	if (info->invert)
 		printf(" !");
-	
+
 	printf(" --mark");
-	print_mark(info->mark, info->mask);
+	xtables_print_mark_mask(info->mark, info->mask);
+}
+
+static void
+print_mark_xlate(struct xt_xlate *xl, unsigned int mark,
+		 unsigned int mask, uint32_t op)
+{
+	if (mask != 0xffffffffU)
+		xt_xlate_add(xl, " and 0x%x %s 0x%x", mask,
+			   op == XT_OP_EQ ? "==" : "!=", mark);
+	else
+		xt_xlate_add(xl, " %s0x%x",
+			   op == XT_OP_EQ ? "" : "!= ", mark);
+}
+
+static int mark_mt_xlate(struct xt_xlate *xl,
+			 const struct xt_xlate_mt_params *params)
+{
+	const struct xt_mark_mtinfo1 *info = (const void *)params->match->data;
+	enum xt_op op = XT_OP_EQ;
+
+	if (info->invert)
+		op = XT_OP_NEQ;
+
+	xt_xlate_add(xl, "mark");
+	print_mark_xlate(xl, info->mark, info->mask, op);
+
+	return 1;
+}
+
+static int mark_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_mt_params *params)
+{
+	const struct xt_mark_info *info = (const void *)params->match->data;
+	enum xt_op op = XT_OP_EQ;
+
+	if (info->invert)
+		op = XT_OP_NEQ;
+
+	xt_xlate_add(xl, "mark");
+	print_mark_xlate(xl, info->mark, info->mask, op);
+
+	return 1;
 }
 
 static struct xtables_match mark_mt_reg[] = {
@@ -115,6 +150,7 @@
 		.save          = mark_save,
 		.x6_parse      = mark_parse,
 		.x6_options    = mark_mt_opts,
+		.xlate	       = mark_xlate,
 	},
 	{
 		.version       = XTABLES_VERSION,
@@ -128,6 +164,7 @@
 		.save          = mark_mt_save,
 		.x6_parse      = mark_mt_parse,
 		.x6_options    = mark_mt_opts,
+		.xlate	       = mark_mt_xlate,
 	},
 };
 
diff --git a/extensions/libxt_mark.t b/extensions/libxt_mark.t
new file mode 100644
index 0000000..7c00537
--- /dev/null
+++ b/extensions/libxt_mark.t
@@ -0,0 +1,7 @@
+:INPUT,FORWARD,OUTPUT
+-m mark --mark 0xfeedcafe/0xfeedcafe;=;OK
+-m mark --mark 0;=;OK
+-m mark --mark 4294967295;-m mark --mark 0xffffffff;OK
+-m mark --mark 4294967296;;FAIL
+-m mark --mark -1;;FAIL
+-m mark;;FAIL
diff --git a/extensions/libxt_mark.txlate b/extensions/libxt_mark.txlate
new file mode 100644
index 0000000..6bfb524
--- /dev/null
+++ b/extensions/libxt_mark.txlate
@@ -0,0 +1,5 @@
+iptables-translate -I INPUT -p tcp -m mark ! --mark 0xa/0xa
+nft insert rule ip filter INPUT ip protocol tcp mark and 0xa != 0xa counter
+
+iptables-translate -I INPUT -p tcp -m mark ! --mark 0x1
+nft insert rule ip filter INPUT ip protocol tcp mark != 0x1 counter
diff --git a/extensions/libxt_multiport.c b/extensions/libxt_multiport.c
index 03af5a9..07ad4cf 100644
--- a/extensions/libxt_multiport.c
+++ b/extensions/libxt_multiport.c
@@ -108,7 +108,6 @@
 {
 	char *buffer, *cp, *next, *range;
 	unsigned int i;
-	uint16_t m;
 
 	buffer = strdup(portstring);
 	if (!buffer) xtables_error(OTHER_PROBLEM, "strdup failed");
@@ -133,7 +132,6 @@
 			if (multiinfo->ports[i-1] >= multiinfo->ports[i])
 				xtables_error(PARAMETER_PROBLEM,
 					   "invalid portrange specified");
-			m <<= 1;
 		}
  	}
 	multiinfo->count = i;
@@ -468,6 +466,110 @@
 	__multiport_save_v1(match, ip->proto);
 }
 
+static int __multiport_xlate(struct xt_xlate *xl,
+			     const struct xt_xlate_mt_params *params)
+{
+	const struct xt_multiport *multiinfo
+		= (const struct xt_multiport *)params->match->data;
+	unsigned int i;
+
+	switch (multiinfo->flags) {
+	case XT_MULTIPORT_SOURCE:
+		xt_xlate_add(xl, " sport ");
+		break;
+	case XT_MULTIPORT_DESTINATION:
+		xt_xlate_add(xl, " dport ");
+		break;
+	case XT_MULTIPORT_EITHER:
+		return 0;
+	}
+
+	if (multiinfo->count > 1)
+		xt_xlate_add(xl, "{ ");
+
+	for (i = 0; i < multiinfo->count; i++)
+		xt_xlate_add(xl, "%s%u", i ? "," : "", multiinfo->ports[i]);
+
+	if (multiinfo->count > 1)
+		xt_xlate_add(xl, "}");
+
+	return 1;
+}
+
+static int multiport_xlate(struct xt_xlate *xl,
+			   const struct xt_xlate_mt_params *params)
+{
+	uint8_t proto = ((const struct ipt_ip *)params->ip)->proto;
+
+	xt_xlate_add(xl, "%s", proto_to_name(proto));
+	return __multiport_xlate(xl, params);
+}
+
+static int multiport_xlate6(struct xt_xlate *xl,
+			    const struct xt_xlate_mt_params *params)
+{
+	uint8_t proto = ((const struct ip6t_ip6 *)params->ip)->proto;
+
+	xt_xlate_add(xl, "%s", proto_to_name(proto));
+	return __multiport_xlate(xl, params);
+}
+
+static int __multiport_xlate_v1(struct xt_xlate *xl,
+				const struct xt_xlate_mt_params *params)
+{
+	const struct xt_multiport_v1 *multiinfo =
+		(const struct xt_multiport_v1 *)params->match->data;
+	unsigned int i;
+
+	switch (multiinfo->flags) {
+	case XT_MULTIPORT_SOURCE:
+		xt_xlate_add(xl, " sport ");
+		break;
+	case XT_MULTIPORT_DESTINATION:
+		xt_xlate_add(xl, " dport ");
+		break;
+	case XT_MULTIPORT_EITHER:
+		return 0;
+	}
+
+	if (multiinfo->invert)
+		xt_xlate_add(xl, "!= ");
+
+	if (multiinfo->count > 2 ||
+	    (multiinfo->count > 1 && !multiinfo->pflags[0]))
+		xt_xlate_add(xl, "{ ");
+
+	for (i = 0; i < multiinfo->count; i++) {
+		xt_xlate_add(xl, "%s%u", i ? "," : "", multiinfo->ports[i]);
+		if (multiinfo->pflags[i])
+			xt_xlate_add(xl, "-%u", multiinfo->ports[++i]);
+	}
+
+	if (multiinfo->count > 2 ||
+	    (multiinfo->count > 1 && !multiinfo->pflags[0]))
+		xt_xlate_add(xl, "}");
+
+	return 1;
+}
+
+static int multiport_xlate_v1(struct xt_xlate *xl,
+			      const struct xt_xlate_mt_params *params)
+{
+	uint8_t proto = ((const struct ipt_ip *)params->ip)->proto;
+
+	xt_xlate_add(xl, "%s", proto_to_name(proto));
+	return __multiport_xlate_v1(xl, params);
+}
+
+static int multiport_xlate6_v1(struct xt_xlate *xl,
+			       const struct xt_xlate_mt_params *params)
+{
+	uint8_t proto = ((const struct ip6t_ip6 *)params->ip)->proto;
+
+	xt_xlate_add(xl, "%s", proto_to_name(proto));
+	return __multiport_xlate_v1(xl, params);
+}
+
 static struct xtables_match multiport_mt_reg[] = {
 	{
 		.family        = NFPROTO_IPV4,
@@ -482,6 +584,7 @@
 		.print         = multiport_print,
 		.save          = multiport_save,
 		.x6_options    = multiport_opts,
+		.xlate         = multiport_xlate,
 	},
 	{
 		.family        = NFPROTO_IPV6,
@@ -496,6 +599,7 @@
 		.print         = multiport_print6,
 		.save          = multiport_save6,
 		.x6_options    = multiport_opts,
+		.xlate         = multiport_xlate6,
 	},
 	{
 		.family        = NFPROTO_IPV4,
@@ -510,6 +614,7 @@
 		.print         = multiport_print_v1,
 		.save          = multiport_save_v1,
 		.x6_options    = multiport_opts,
+		.xlate         = multiport_xlate_v1,
 	},
 	{
 		.family        = NFPROTO_IPV6,
@@ -524,6 +629,7 @@
 		.print         = multiport_print6_v1,
 		.save          = multiport_save6_v1,
 		.x6_options    = multiport_opts,
+		.xlate         = multiport_xlate6_v1,
 	},
 };
 
diff --git a/extensions/libxt_multiport.man b/extensions/libxt_multiport.man
index caf5c56..7eb083e 100644
--- a/extensions/libxt_multiport.man
+++ b/extensions/libxt_multiport.man
@@ -1,9 +1,8 @@
 This module matches a set of source or destination ports.  Up to 15
 ports can be specified.  A port range (port:port) counts as two
-ports.  It can only be used in conjunction with
-\fB\-p tcp\fP
-or
-\fB\-p udp\fP.
+ports.  It can only be used in conjunction with one of the
+following protocols:
+\fBtcp\fP, \fBudp\fP, \fBudplite\fP, \fBdccp\fP and \fBsctp\fP.
 .TP
 [\fB!\fP] \fB\-\-source\-ports\fP,\fB\-\-sports\fP \fIport\fP[\fB,\fP\fIport\fP|\fB,\fP\fIport\fP\fB:\fP\fIport\fP]...
 Match if the source port is one of the given ports.  The flag
diff --git a/extensions/libxt_multiport.t b/extensions/libxt_multiport.t
new file mode 100644
index 0000000..e9b80a4
--- /dev/null
+++ b/extensions/libxt_multiport.t
@@ -0,0 +1,23 @@
+:INPUT,FORWARD,OUTPUT
+-p tcp -m multiport --sports 53,1024:65535;=;OK
+-p tcp -m multiport --dports 53,1024:65535;=;OK
+-p udp -m multiport --sports 53,1024:65535;=;OK
+-p udp -m multiport --dports 53,1024:65535;=;OK
+-p udp -m multiport --ports 53,1024:65535;=;OK
+-p udp -m multiport --ports 53,1024:65535;=;OK
+-p sctp -m multiport --sports 53,1024:65535;=;OK
+-p sctp -m multiport --dports 53,1024:65535;=;OK
+-p dccp -m multiport --sports 53,1024:65535;=;OK
+-p dccp -m multiport --dports 53,1024:65535;=;OK
+-p udplite -m multiport --sports 53,1024:65535;=;OK
+-p udplite -m multiport --dports 53,1024:65535;=;OK
+-p tcp -m multiport --sports 1024:65536;;FAIL
+-p udp -m multiport --sports 1024:65536;;FAIL
+-p tcp -m multiport --ports 1024:65536;;FAIL
+-p udp -m multiport --ports 1024:65536;;FAIL
+-p tcp -m multiport --ports 1,2,3,4,6,7,8,9,10,11,12,13,14,15;=;OK
+# fix manpage, it says "up to 15 ports supported"
+# ERROR: should fail: iptables -A INPUT -p tcp -m multiport --ports 1,2,3,4,6,7,8,9,10,11,12,13,14,15,16
+# -p tcp -m multiport --ports 1,2,3,4,6,7,8,9,10,11,12,13,14,15,16;;FAIL
+-p tcp --multiport;;FAIL
+-m multiport;;FAIL
diff --git a/extensions/libxt_multiport.txlate b/extensions/libxt_multiport.txlate
new file mode 100644
index 0000000..752e714
--- /dev/null
+++ b/extensions/libxt_multiport.txlate
@@ -0,0 +1,11 @@
+iptables-translate -t filter -A INPUT -p tcp -m multiport --dports 80,81 -j ACCEPT
+nft add rule ip filter INPUT ip protocol tcp tcp dport { 80,81} counter accept
+
+iptables-translate -t filter -A INPUT -p tcp -m multiport --dports 80:88 -j ACCEPT
+nft add rule ip filter INPUT ip protocol tcp tcp dport 80-88 counter accept
+
+iptables-translate -t filter -A INPUT -p tcp -m multiport ! --dports 80:88 -j ACCEPT
+nft add rule ip filter INPUT ip protocol tcp tcp dport != 80-88 counter accept
+
+iptables-translate -t filter -A INPUT -p tcp -m multiport --sports 50 -j ACCEPT
+nft add rule ip filter INPUT ip protocol tcp tcp sport 50 counter accept
diff --git a/extensions/libxt_nfacct.c b/extensions/libxt_nfacct.c
new file mode 100644
index 0000000..d9c0309
--- /dev/null
+++ b/extensions/libxt_nfacct.c
@@ -0,0 +1,105 @@
+/*
+ * (C) 2011 by Pablo Neira Ayuso <pablo@netfilter.org>
+ * (C) 2011 by Intra2Net AG <http://www.intra2net.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 (or
+ * any later at your option) as published by the Free Software Foundation.
+ */
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <getopt.h>
+#include <xtables.h>
+
+#include <linux/netfilter/x_tables.h>
+#include <linux/netfilter/xt_nfacct.h>
+
+enum {
+	O_NAME = 0,
+};
+
+#define s struct xt_nfacct_match_info
+static const struct xt_option_entry nfacct_opts[] = {
+	{.name = "nfacct-name", .id = O_NAME, .type = XTTYPE_STRING,
+	 .min = 1, .flags = XTOPT_MAND|XTOPT_PUT, XTOPT_POINTER(s, name)},
+	XTOPT_TABLEEND,
+};
+#undef s
+
+static void nfacct_help(void)
+{
+	printf("nfacct match options:\n"
+	       " --nfacct-name STRING		Name of accouting area\n");
+}
+
+static void nfacct_parse(struct xt_option_call *cb)
+{
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_NAME:
+		if (strchr(cb->arg, '\n') != NULL)
+			xtables_error(PARAMETER_PROBLEM,
+				   "Newlines not allowed in --nfacct-name");
+		break;
+	}
+}
+
+static void
+nfacct_print_name(const struct xt_nfacct_match_info *info, char *name)
+{
+	printf(" %snfacct-name ", name);
+	xtables_save_string(info->name);
+}
+
+static void nfacct_print(const void *ip, const struct xt_entry_match *match,
+                        int numeric)
+{
+	const struct xt_nfacct_match_info *info =
+		(struct xt_nfacct_match_info *)match->data;
+
+	nfacct_print_name(info, "");
+}
+
+static void nfacct_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_nfacct_match_info *info =
+		(struct xt_nfacct_match_info *)match->data;
+
+	nfacct_print_name(info, "--");
+}
+
+static struct xtables_match nfacct_matches[] = {
+	{
+		.family		= NFPROTO_UNSPEC,
+		.revision	= 0,
+		.name		= "nfacct",
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_nfacct_match_info)),
+		.userspacesize	= offsetof(struct xt_nfacct_match_info, nfacct),
+		.help		= nfacct_help,
+		.x6_parse	= nfacct_parse,
+		.print		= nfacct_print,
+		.save		= nfacct_save,
+		.x6_options	= nfacct_opts,
+	},
+	{
+		.family		= NFPROTO_UNSPEC,
+		.revision	= 1,
+		.name		= "nfacct",
+		.version	= XTABLES_VERSION,
+		.size		= XT_ALIGN(sizeof(struct xt_nfacct_match_info_v1)),
+		.userspacesize	= offsetof(struct xt_nfacct_match_info_v1, nfacct),
+		.help		= nfacct_help,
+		.x6_parse	= nfacct_parse,
+		.print		= nfacct_print,
+		.save		= nfacct_save,
+		.x6_options	= nfacct_opts,
+	},
+};
+
+void _init(void)
+{
+	xtables_register_matches(nfacct_matches, ARRAY_SIZE(nfacct_matches));
+}
diff --git a/extensions/libxt_nfacct.man b/extensions/libxt_nfacct.man
new file mode 100644
index 0000000..b755f97
--- /dev/null
+++ b/extensions/libxt_nfacct.man
@@ -0,0 +1,30 @@
+The nfacct match provides the extended accounting infrastructure for iptables.
+You have to use this match together with the standalone user-space utility
+.B nfacct(8)
+.PP
+The only option available for this match is the following:
+.TP
+\fB\-\-nfacct\-name\fP \fIname\fP
+This allows you to specify the existing object name that will be use for
+accounting the traffic that this rule-set is matching.
+.PP
+To use this extension, you have to create an accounting object:
+.IP
+nfacct add http\-traffic
+.PP
+Then, you have to attach it to the accounting object via iptables:
+.IP
+iptables \-I INPUT \-p tcp \-\-sport 80 \-m nfacct \-\-nfacct\-name http\-traffic
+.IP
+iptables \-I OUTPUT \-p tcp \-\-dport 80 \-m nfacct \-\-nfacct\-name http\-traffic
+.PP
+Then, you can check for the amount of traffic that the rules match:
+.IP
+nfacct get http\-traffic
+.IP
+{ pkts = 00000000000000000156, bytes = 00000000000000151786 } = http-traffic;
+.PP
+You can obtain
+.B nfacct(8)
+from http://www.netfilter.org or, alternatively, from the git.netfilter.org
+repository.
diff --git a/extensions/libxt_nfacct.t b/extensions/libxt_nfacct.t
new file mode 100644
index 0000000..3419b4c
--- /dev/null
+++ b/extensions/libxt_nfacct.t
@@ -0,0 +1,10 @@
+:INPUT,FORWARD,OUTPUT
+@nfacct add test
+#
+# extra space in iptables-save output, fix it
+#
+# ERROR: cannot load: iptables -A INPUT -m nfacct --nfacct-name test
+#-m nfacct --nfacct-name test;=;OK
+-m nfacct --nfacct-name wrong;;FAIL
+-m nfacct;;FAIL
+@nfacct del test
diff --git a/extensions/libxt_osf.c b/extensions/libxt_osf.c
index 88274a0..c567d9e 100644
--- a/extensions/libxt_osf.c
+++ b/extensions/libxt_osf.c
@@ -14,7 +14,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
 /*
@@ -40,7 +40,7 @@
 		"--ttl level            Use some TTL check extensions to determine OS:\n"
 		"       0                       true ip and fingerprint TTL comparison. Works for LAN.\n"
 		"       1                       check if ip TTL is less than fingerprint one. Works for global addresses.\n"
-		"       2                       do not compare TTL at all. Allows to detect NMAP, but can produce false results.\n"
+		"       2                       do not compare TTL at all. This allows NMAP detection, but can produce false results.\n"
 		"--log level            Log determined genres into dmesg even if they do not match desired one:\n"
 		"       0                       log all matched or unknown signatures.\n"
 		"       1                       log only first one.\n"
@@ -92,7 +92,14 @@
 {
 	const struct xt_osf_info *info = (const struct xt_osf_info*) match->data;
 
-	printf(" --genre %s%s", (info->flags & XT_OSF_INVERT) ? "! ": "", info->genre);
+	if (info->flags & XT_OSF_INVERT)
+		printf(" !");
+
+	printf(" --genre %s", info->genre);
+	if (info->flags & XT_OSF_TTL)
+		printf(" --ttl %u", info->ttl);
+	if (info->flags & XT_OSF_LOG)
+		printf(" --log %u", info->loglevel);
 }
 
 static struct xtables_match osf_match = {
diff --git a/extensions/libxt_osf.man b/extensions/libxt_osf.man
index f3a85fb..41103f2 100644
--- a/extensions/libxt_osf.man
+++ b/extensions/libxt_osf.man
@@ -1,4 +1,4 @@
-The osf module does passive operating system fingerprinting. This modules
+The osf module does passive operating system fingerprinting. This module
 compares some data (Window Size, MSS, options and their order, TTL, DF,
 and others) from packets with the SYN bit set. 
 .TP
@@ -35,11 +35,11 @@
 OS fingerprints are loadable using the \fBnfnl_osf\fP program. To load
 fingerprints from a file, use:
 .PP
-\fBnfnl_osf -f /usr/share/xtables/pf.os\fP
+\fBnfnl_osf \-f /usr/share/xtables/pf.os\fP
 .PP
 To remove them again,
 .PP
-\fBnfnl_osf -f /usr/share/xtables/pf.os -d\fP
+\fBnfnl_osf \-f /usr/share/xtables/pf.os \-d\fP
 .PP
-The fingerprint database can be downlaoded from
+The fingerprint database can be downloaded from
 http://www.openbsd.org/cgi-bin/cvsweb/src/etc/pf.os .
diff --git a/extensions/libxt_osf.t b/extensions/libxt_osf.t
new file mode 100644
index 0000000..ede6d32
--- /dev/null
+++ b/extensions/libxt_osf.t
@@ -0,0 +1,4 @@
+:INPUT,FORWARD
+-m osf --genre linux --ttl 0 --log 0;;FAIL
+-p tcp -m osf --genre linux --ttl 0 --log 0;=;OK
+-p tcp -m osf --genre linux --ttl 3 --log 0;;FAIL
diff --git a/extensions/libxt_owner.c b/extensions/libxt_owner.c
index d2fdfa9..1702b47 100644
--- a/extensions/libxt_owner.c
+++ b/extensions/libxt_owner.c
@@ -56,6 +56,7 @@
 	O_PROCESS,
 	O_SESSION,
 	O_COMM,
+	O_SUPPL_GROUPS,
 };
 
 static void owner_mt_help_v0(void)
@@ -87,7 +88,8 @@
 "owner match options:\n"
 "[!] --uid-owner userid[-userid]      Match local UID\n"
 "[!] --gid-owner groupid[-groupid]    Match local GID\n"
-"[!] --socket-exists                  Match if socket exists\n");
+"[!] --socket-exists                  Match if socket exists\n"
+"    --suppl-groups                   Also match supplementary groups set with --gid-owner\n");
 }
 
 #define s struct ipt_owner_info
@@ -129,7 +131,9 @@
 	 .flags = XTOPT_INVERT},
 	{.name = "gid-owner", .id = O_GROUP, .type = XTTYPE_STRING,
 	 .flags = XTOPT_INVERT},
-	{.name = "socket-exists", .id = O_SOCK_EXISTS, .type = XTTYPE_NONE},
+	{.name = "socket-exists", .id = O_SOCK_EXISTS, .type = XTTYPE_NONE,
+	 .flags = XTOPT_INVERT},
+	{.name = "suppl-groups", .id = O_SUPPL_GROUPS, .type = XTTYPE_NONE},
 	XTOPT_TABLEEND,
 };
 
@@ -274,6 +278,11 @@
 			info->invert |= XT_OWNER_SOCKET;
 		info->match |= XT_OWNER_SOCKET;
 		break;
+	case O_SUPPL_GROUPS:
+		if (!(info->match & XT_OWNER_GID))
+			xtables_param_act(XTF_BAD_VALUE, "owner", "--suppl-groups", "you need to use --gid-owner first");
+		info->match |= XT_OWNER_SUPPL_GROUPS;
+		break;
 	}
 }
 
@@ -454,9 +463,10 @@
 {
 	const struct xt_owner_match_info *info = (void *)match->data;
 
-	owner_mt_print_item(info, "owner socket exists", XT_OWNER_SOCKET, numeric);
-	owner_mt_print_item(info, "owner UID match",     XT_OWNER_UID,    numeric);
-	owner_mt_print_item(info, "owner GID match",     XT_OWNER_GID,    numeric);
+	owner_mt_print_item(info, "owner socket exists", XT_OWNER_SOCKET,       numeric);
+	owner_mt_print_item(info, "owner UID match",     XT_OWNER_UID,          numeric);
+	owner_mt_print_item(info, "owner GID match",     XT_OWNER_GID,          numeric);
+	owner_mt_print_item(info, "incl. suppl. groups", XT_OWNER_SUPPL_GROUPS, numeric);
 }
 
 static void
@@ -486,9 +496,60 @@
 {
 	const struct xt_owner_match_info *info = (void *)match->data;
 
-	owner_mt_print_item(info, "--socket-exists",  XT_OWNER_SOCKET, true);
-	owner_mt_print_item(info, "--uid-owner",      XT_OWNER_UID,    true);
-	owner_mt_print_item(info, "--gid-owner",      XT_OWNER_GID,    true);
+	owner_mt_print_item(info, "--socket-exists",  XT_OWNER_SOCKET,       true);
+	owner_mt_print_item(info, "--uid-owner",      XT_OWNER_UID,          true);
+	owner_mt_print_item(info, "--gid-owner",      XT_OWNER_GID,          true);
+	owner_mt_print_item(info, "--suppl-groups",   XT_OWNER_SUPPL_GROUPS, true);
+}
+
+static int
+owner_mt_print_uid_xlate(const struct xt_owner_match_info *info,
+			 struct xt_xlate *xl)
+{
+	xt_xlate_add(xl, "skuid%s ", info->invert ? " !=" : "");
+
+	if (info->uid_min != info->uid_max)
+		xt_xlate_add(xl, "%u-%u", (unsigned int)info->uid_min,
+			     (unsigned int)info->uid_max);
+	else
+		xt_xlate_add(xl, "%u", (unsigned int)info->uid_min);
+
+	return 1;
+}
+
+static int
+owner_mt_print_gid_xlate(const struct xt_owner_match_info *info,
+			 struct xt_xlate *xl)
+{
+	xt_xlate_add(xl, "skgid%s ", info->invert ? " !=" : "");
+
+	if (info->gid_min != info->gid_max)
+		xt_xlate_add(xl, "%u-%u", (unsigned int)info->gid_min,
+			     (unsigned int)info->gid_max);
+	else
+		xt_xlate_add(xl, "%u", (unsigned int)info->gid_min);
+
+	return 1;
+}
+
+static int owner_mt_xlate(struct xt_xlate *xl,
+			  const struct xt_xlate_mt_params *params)
+{
+	const struct xt_owner_match_info *info = (void *)params->match->data;
+	int ret;
+
+	switch (info->match) {
+	case XT_OWNER_UID:
+		ret = owner_mt_print_uid_xlate(info, xl);
+		break;
+	case XT_OWNER_GID:
+		ret = owner_mt_print_gid_xlate(info, xl);
+		break;
+	default:
+		ret = 0;
+	}
+
+	return ret;
 }
 
 static struct xtables_match owner_mt_reg[] = {
@@ -533,6 +594,7 @@
 		.print         = owner_mt_print,
 		.save          = owner_mt_save,
 		.x6_options    = owner_mt_opts,
+		.xlate	       = owner_mt_xlate,
 	},
 };
 
diff --git a/extensions/libxt_owner.man b/extensions/libxt_owner.man
index 49b58ce..e247986 100644
--- a/extensions/libxt_owner.man
+++ b/extensions/libxt_owner.man
@@ -15,5 +15,9 @@
 Matches if the packet socket's file structure is owned by the given group.
 You may also specify a numerical GID, or a GID range.
 .TP
+\fB\-\-suppl\-groups\fP
+Causes group(s) specified with \fB\-\-gid-owner\fP to be also checked in the
+supplementary groups of a process.
+.TP
 [\fB!\fP] \fB\-\-socket\-exists\fP
 Matches if the packet is associated with a socket.
diff --git a/extensions/libxt_owner.t b/extensions/libxt_owner.t
new file mode 100644
index 0000000..2779e5c
--- /dev/null
+++ b/extensions/libxt_owner.t
@@ -0,0 +1,16 @@
+:OUTPUT,POSTROUTING
+*mangle
+-m owner --uid-owner root;-m owner --uid-owner 0;OK
+-m owner --uid-owner 0-10;=;OK
+-m owner --gid-owner root;-m owner --gid-owner 0;OK
+-m owner --gid-owner 0-10;=;OK
+-m owner --uid-owner root --gid-owner root;-m owner --uid-owner 0 --gid-owner 0;OK
+-m owner --uid-owner 0-10 --gid-owner 0-10;=;OK
+-m owner ! --uid-owner root;-m owner ! --uid-owner 0;OK
+-m owner --socket-exists;=;OK
+-m owner --gid-owner 0-10 --suppl-groups;=;OK
+-m owner --suppl-groups --gid-owner 0-10;;FAIL
+-m owner --gid-owner 0-10 ! --suppl-groups;;FAIL
+-m owner --suppl-groups;;FAIL
+:INPUT
+-m owner --uid-owner root;;FAIL
diff --git a/extensions/libxt_owner.txlate b/extensions/libxt_owner.txlate
new file mode 100644
index 0000000..86fb058
--- /dev/null
+++ b/extensions/libxt_owner.txlate
@@ -0,0 +1,8 @@
+iptables-translate -t nat -A OUTPUT -p tcp --dport 80 -m owner --uid-owner root -j ACCEPT
+nft add rule ip nat OUTPUT tcp dport 80 skuid 0 counter accept
+
+iptables-translate -t nat -A OUTPUT -p tcp --dport 80 -m owner --gid-owner 0-10 -j ACCEPT
+nft add rule ip nat OUTPUT tcp dport 80 skgid 0-10 counter accept
+
+iptables-translate -t nat -A OUTPUT -p tcp --dport 80 -m owner ! --uid-owner 1000 -j ACCEPT
+nft add rule ip nat OUTPUT tcp dport 80 skuid != 1000 counter accept
diff --git a/extensions/libxt_physdev.c b/extensions/libxt_physdev.c
index 8f57fe9..a11faf4 100644
--- a/extensions/libxt_physdev.c
+++ b/extensions/libxt_physdev.c
@@ -27,11 +27,12 @@
 	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, physindev)},
 	{.name = "physdev-out", .id = O_PHYSDEV_OUT, .type = XTTYPE_STRING,
 	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, physoutdev)},
-	{.name = "physdev-is-in", .id = O_PHYSDEV_IS_IN, .type = XTTYPE_NONE},
+	{.name = "physdev-is-in", .id = O_PHYSDEV_IS_IN, .type = XTTYPE_NONE,
+	 .flags = XTOPT_INVERT},
 	{.name = "physdev-is-out", .id = O_PHYSDEV_IS_OUT,
-	 .type = XTTYPE_NONE},
+	 .type = XTTYPE_NONE, .flags = XTOPT_INVERT},
 	{.name = "physdev-is-bridged", .id = O_PHYSDEV_IS_BRIDGED,
-	 .type = XTTYPE_NONE},
+	 .type = XTTYPE_NONE, .flags = XTOPT_INVERT},
 	XTOPT_TABLEEND,
 };
 #undef s
diff --git a/extensions/libxt_physdev.man b/extensions/libxt_physdev.man
index 53beb2e..06b778a 100644
--- a/extensions/libxt_physdev.man
+++ b/extensions/libxt_physdev.man
@@ -15,21 +15,13 @@
 through a bridge device, this packet won't match this option, unless '!' is used.
 .TP
 [\fB!\fP] \fB\-\-physdev\-out\fP \fIname\fP
-Name of a bridge port via which a packet is going to be sent (for packets
+Name of a bridge port via which a packet is going to be sent (for bridged packets
 entering the
-.BR FORWARD ,
-.B OUTPUT
+.BR FORWARD
 and
 .B POSTROUTING
 chains).  If the interface name ends in a "+", then any
-interface which begins with this name will match. Note that in the
-.BR nat " and " mangle
-.B OUTPUT
-chains one cannot match on the bridge output port, however one can in the
-.B "filter OUTPUT"
-chain. If the packet won't leave by a bridge device or if it is yet unknown what
-the output device will be, then the packet won't match this option,
-unless '!' is used.
+interface which begins with this name will match.
 .TP
 [\fB!\fP] \fB\-\-physdev\-is\-in\fP
 Matches if the packet has entered through a bridge interface.
diff --git a/extensions/libxt_physdev.t b/extensions/libxt_physdev.t
new file mode 100644
index 0000000..1fab7e1
--- /dev/null
+++ b/extensions/libxt_physdev.t
@@ -0,0 +1,14 @@
+:INPUT,FORWARD
+-m physdev --physdev-in lo;=;OK
+-m physdev --physdev-is-in --physdev-in lo;=;OK
+:OUTPUT,FORWARD
+# xt_physdev: using --physdev-out in the OUTPUT, FORWARD and POSTROUTING chains for non-bridged traffic is not supported anymore.
+# ERROR: should fail: iptables -A FORWARD -m physdev --physdev-out lo
+#-m physdev --physdev-out lo;;FAIL
+# ERROR: cannot load: iptables -A OUTPUT -m physdev --physdev-is-out --physdev-out lo
+#-m physdev --physdev-is-out --physdev-out lo;=;OK
+:FORWARD
+-m physdev --physdev-in lo --physdev-is-bridged;=;OK
+:POSTROUTING
+*mangle
+-m physdev --physdev-out lo --physdev-is-bridged;=;OK
diff --git a/extensions/libxt_pkttype.c b/extensions/libxt_pkttype.c
index 1ed3b44..bf6f5b9 100644
--- a/extensions/libxt_pkttype.c
+++ b/extensions/libxt_pkttype.c
@@ -21,6 +21,11 @@
 	const char *help;
 };
 
+struct pkttypes_xlate {
+	const char *name;
+	unsigned char pkttype;
+};
+
 static const struct pkttypes supported_types[] = {
 	{"unicast", PACKET_HOST, 1, "to us"},
 	{"broadcast", PACKET_BROADCAST, 1, "to all"},
@@ -115,6 +120,37 @@
 	print_pkttype(info);
 }
 
+static const struct pkttypes_xlate supported_types_xlate[] = {
+	{"unicast",	PACKET_HOST},
+	{"broadcast",	PACKET_BROADCAST},
+	{"multicast",	PACKET_MULTICAST},
+};
+
+static void print_pkttype_xlate(const struct xt_pkttype_info *info,
+				struct xt_xlate *xl)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(supported_types_xlate); ++i) {
+		if (supported_types_xlate[i].pkttype == info->pkttype) {
+			xt_xlate_add(xl, "%s", supported_types_xlate[i].name);
+			return;
+		}
+	}
+	xt_xlate_add(xl, "%d", info->pkttype);
+}
+
+static int pkttype_xlate(struct xt_xlate *xl,
+			 const struct xt_xlate_mt_params *params)
+{
+	const struct xt_pkttype_info *info = (const void *)params->match->data;
+
+	xt_xlate_add(xl, "pkttype%s ", info->invert ? " !=" : "");
+	print_pkttype_xlate(info, xl);
+
+	return 1;
+}
+
 static struct xtables_match pkttype_match = {
 	.family		= NFPROTO_UNSPEC,
 	.name		= "pkttype",
@@ -126,6 +162,7 @@
 	.save		= pkttype_save,
 	.x6_parse	= pkttype_parse,
 	.x6_options	= pkttype_opts,
+	.xlate		= pkttype_xlate,
 };
 
 void _init(void)
diff --git a/extensions/libxt_pkttype.t b/extensions/libxt_pkttype.t
new file mode 100644
index 0000000..d93baea
--- /dev/null
+++ b/extensions/libxt_pkttype.t
@@ -0,0 +1,6 @@
+:INPUT,FORWARD,OUTPUT
+-m pkttype --pkt-type unicast;=;OK
+-m pkttype --pkt-type broadcast;=;OK
+-m pkttype --pkt-type multicast;=;OK
+-m pkttype --pkt-type wrong;;FAIL
+-m pkttype;;FAIL
diff --git a/extensions/libxt_pkttype.txlate b/extensions/libxt_pkttype.txlate
new file mode 100644
index 0000000..6506a38
--- /dev/null
+++ b/extensions/libxt_pkttype.txlate
@@ -0,0 +1,8 @@
+iptables-translate -A INPUT -m pkttype --pkt-type broadcast -j DROP
+nft add rule ip filter INPUT pkttype broadcast counter drop
+
+iptables-translate -A INPUT -m pkttype ! --pkt-type unicast -j DROP
+nft add rule ip filter INPUT pkttype != unicast counter drop
+
+iptables-translate -A INPUT -m pkttype --pkt-type multicast -j ACCEPT
+nft add rule ip filter INPUT pkttype multicast counter accept
diff --git a/extensions/libxt_policy.c b/extensions/libxt_policy.c
index 97722d6..f9a4819 100644
--- a/extensions/libxt_policy.c
+++ b/extensions/libxt_policy.c
@@ -1,3 +1,7 @@
+/*
+ * Copyright (c) 2005-2013 Patrick McHardy <kaber@trash.net>
+ */
+
 #include <stdbool.h>
 #include <stdint.h>
 #include <stdio.h>
@@ -40,8 +44,7 @@
 }
 
 static const struct xt_option_entry policy_opts[] = {
-	{.name = "dir", .id = O_DIRECTION, .type = XTTYPE_STRING,
-	 .flags = XTOPT_INVERT},
+	{.name = "dir", .id = O_DIRECTION, .type = XTTYPE_STRING},
 	{.name = "pol", .id = O_POLICY, .type = XTTYPE_STRING},
 	{.name = "strict", .id = O_STRICT, .type = XTTYPE_NONE},
 	{.name = "reqid", .id = O_REQID, .type = XTTYPE_UINT32,
@@ -373,6 +376,31 @@
 	}
 }
 
+static int policy_xlate(struct xt_xlate *xl,
+			const struct xt_xlate_mt_params *params)
+{
+	static const unsigned int allowed = XT_POLICY_MATCH_STRICT |
+					    XT_POLICY_MATCH_NONE |
+					    XT_POLICY_MATCH_IN;
+	static const struct xt_policy_elem empty;
+	const struct xt_policy_info *info = (const void *)params->match->data;
+
+	if ((info->flags & ~allowed) || info->len > 1)
+		return 0;
+
+	if (memcmp(&info->pol[0], &empty, sizeof(empty)))
+		return 0;
+
+	xt_xlate_add(xl, "meta secpath ");
+
+	if (info->flags & XT_POLICY_MATCH_NONE)
+		xt_xlate_add(xl, "missing");
+	else
+		xt_xlate_add(xl, "exists");
+
+	return 1;
+}
+
 static struct xtables_match policy_mt_reg[] = {
 	{
 		.name          = "policy",
@@ -386,6 +414,7 @@
 		.print         = policy4_print,
 		.save          = policy4_save,
 		.x6_options    = policy_opts,
+		.xlate         = policy_xlate,
 	},
 	{
 		.name          = "policy",
@@ -399,6 +428,7 @@
 		.print         = policy6_print,
 		.save          = policy6_save,
 		.x6_options    = policy_opts,
+		.xlate         = policy_xlate,
 	},
 };
 
diff --git a/extensions/libxt_policy.man b/extensions/libxt_policy.man
index 1b834fa..12c01b4 100644
--- a/extensions/libxt_policy.man
+++ b/extensions/libxt_policy.man
@@ -1,4 +1,4 @@
-This modules matches the policy used by IPsec for handling a packet.
+This module matches the policy used by IPsec for handling a packet.
 .TP
 \fB\-\-dir\fP {\fBin\fP|\fBout\fP}
 Used to select whether to match the policy used for decapsulation or the
diff --git a/extensions/libxt_policy.t b/extensions/libxt_policy.t
new file mode 100644
index 0000000..6524122
--- /dev/null
+++ b/extensions/libxt_policy.t
@@ -0,0 +1,8 @@
+:INPUT,FORWARD
+-m policy --dir in --pol ipsec;=;OK
+-m policy --dir in --pol ipsec --proto ipcomp;=;OK
+-m policy --dir in --pol ipsec --strict;;FAIL
+-m policy --dir in --pol ipsec --strict --reqid 1 --spi 0x1 --proto ipcomp;=;OK
+-m policy --dir in --pol ipsec --strict --reqid 1 --spi 0x1 --proto esp --mode tunnel --tunnel-dst 10.0.0.0/8 --tunnel-src 10.0.0.0/8 --next --reqid 2;=;OK
+-m policy --dir in --pol ipsec --strict --reqid 1 --spi 0x1 --proto esp --tunnel-dst 10.0.0.0/8;;FAIL
+-m policy --dir in --pol ipsec --strict --reqid 1 --spi 0x1 --proto ipcomp --mode tunnel --tunnel-dst 10.0.0.0/8 --tunnel-src 10.0.0.0/8 --next --reqid 2;=;OK
diff --git a/extensions/libxt_policy.txlate b/extensions/libxt_policy.txlate
new file mode 100644
index 0000000..66788a7
--- /dev/null
+++ b/extensions/libxt_policy.txlate
@@ -0,0 +1,5 @@
+iptables-translate -A INPUT -m policy --pol ipsec --dir in
+nft add rule ip filter INPUT meta secpath exists counter
+
+iptables-translate -A INPUT -m policy --pol none --dir in
+nft add rule ip filter INPUT meta secpath missing counter
diff --git a/extensions/libxt_quota.c b/extensions/libxt_quota.c
index 26fba0b..bad77d2 100644
--- a/extensions/libxt_quota.c
+++ b/extensions/libxt_quota.c
@@ -37,7 +37,7 @@
 	const struct xt_quota_info *q = (const void *)match->data;
 
 	if (q->flags & XT_QUOTA_INVERT)
-		printf("! ");
+		printf(" !");
 	printf(" --quota %llu", (unsigned long long) q->quota);
 }
 
@@ -48,7 +48,17 @@
 	xtables_option_parse(cb);
 	if (cb->invert)
 		info->flags |= XT_QUOTA_INVERT;
-	info->quota = cb->val.u64;
+}
+
+static int quota_xlate(struct xt_xlate *xl,
+		       const struct xt_xlate_mt_params *params)
+{
+	const struct xt_quota_info *q = (void *)params->match->data;
+
+	xt_xlate_add(xl, "quota %s%llu bytes",
+		     q->flags & XT_QUOTA_INVERT ? "over " : "",
+		     (unsigned long long) q->quota);
+	return 1;
 }
 
 static struct xtables_match quota_match = {
@@ -62,6 +72,7 @@
 	.save		= quota_save,
 	.x6_parse	= quota_parse,
 	.x6_options	= quota_opts,
+	.xlate		= quota_xlate,
 };
 
 void
diff --git a/extensions/libxt_quota.t b/extensions/libxt_quota.t
new file mode 100644
index 0000000..c568427
--- /dev/null
+++ b/extensions/libxt_quota.t
@@ -0,0 +1,7 @@
+:INPUT,FORWARD,OUTPUT
+-m quota --quota 0;=;OK
+-m quota ! --quota 0;=;OK
+-m quota --quota 18446744073709551615;=;OK
+-m quota ! --quota 18446744073709551615;=;OK
+-m quota --quota 18446744073709551616;;FAIL
+-m quota;;FAIL
diff --git a/extensions/libxt_quota.txlate b/extensions/libxt_quota.txlate
new file mode 100644
index 0000000..9114214
--- /dev/null
+++ b/extensions/libxt_quota.txlate
@@ -0,0 +1,5 @@
+iptables-translate -A OUTPUT -m quota --quota 111
+nft add rule ip filter OUTPUT quota 111 bytes counter
+
+iptables-translate -A OUTPUT -m quota ! --quota 111
+nft add rule ip filter OUTPUT quota over 111 bytes counter
diff --git a/extensions/libxt_rateest.c b/extensions/libxt_rateest.c
index 509b3e3..fb24412 100644
--- a/extensions/libxt_rateest.c
+++ b/extensions/libxt_rateest.c
@@ -1,3 +1,7 @@
+/*
+ * Copyright (c) 2008-2013 Patrick McHardy <kaber@trash.net>
+ */
+
 #include <stdbool.h>
 #include <stdio.h>
 #include <string.h>
@@ -8,9 +12,6 @@
 #include <xtables.h>
 #include <linux/netfilter/xt_rateest.h>
 
-/* Ugly hack to pass info to final_check function. We should fix the API */
-static struct xt_rateest_match_info *rateest_info;
-
 static void rateest_help(void)
 {
 	printf(
@@ -115,11 +116,8 @@
 	struct xt_rateest_match_info *info = (void *)(*match)->data;
 	unsigned int val;
 
-	rateest_info = info;
-
 	switch (c) {
 	case OPT_RATEEST1:
-		xtables_check_inverse(optarg, &invert, &optind, 0, argv);
 		if (invert)
 			xtables_error(PARAMETER_PROBLEM,
 				   "rateest: rateest can't be inverted");
@@ -133,7 +131,6 @@
 		break;
 
 	case OPT_RATEEST2:
-		xtables_check_inverse(optarg, &invert, &optind, 0, argv);
 		if (invert)
 			xtables_error(PARAMETER_PROBLEM,
 				   "rateest: rateest can't be inverted");
@@ -148,7 +145,6 @@
 		break;
 
 	case OPT_RATEEST_BPS1:
-		xtables_check_inverse(optarg, &invert, &optind, 0, argv);
 		if (invert)
 			xtables_error(PARAMETER_PROBLEM,
 				   "rateest: rateest-bps can't be inverted");
@@ -172,7 +168,6 @@
 		break;
 
 	case OPT_RATEEST_PPS1:
-		xtables_check_inverse(optarg, &invert, &optind, 0, argv);
 		if (invert)
 			xtables_error(PARAMETER_PROBLEM,
 				   "rateest: rateest-pps can't be inverted");
@@ -197,7 +192,6 @@
 		break;
 
 	case OPT_RATEEST_BPS2:
-		xtables_check_inverse(optarg, &invert, &optind, 0, argv);
 		if (invert)
 			xtables_error(PARAMETER_PROBLEM,
 				   "rateest: rateest-bps can't be inverted");
@@ -221,7 +215,6 @@
 		break;
 
 	case OPT_RATEEST_PPS2:
-		xtables_check_inverse(optarg, &invert, &optind, 0, argv);
 		if (invert)
 			xtables_error(PARAMETER_PROBLEM,
 				   "rateest: rateest-pps can't be inverted");
@@ -246,7 +239,6 @@
 		break;
 
 	case OPT_RATEEST_DELTA:
-		xtables_check_inverse(optarg, &invert, &optind, 0, argv);
 		if (invert)
 			xtables_error(PARAMETER_PROBLEM,
 				   "rateest: rateest-delta can't be inverted");
@@ -260,8 +252,6 @@
 		break;
 
 	case OPT_RATEEST_EQ:
-		xtables_check_inverse(optarg, &invert, &optind, 0, argv);
-
 		if (*flags & (1 << c))
 			xtables_error(PARAMETER_PROBLEM,
 				   "rateest: can't specify lt/gt/eq twice");
@@ -273,8 +263,6 @@
 		break;
 
 	case OPT_RATEEST_LT:
-		xtables_check_inverse(optarg, &invert, &optind, 0, argv);
-
 		if (*flags & (1 << c))
 			xtables_error(PARAMETER_PROBLEM,
 				   "rateest: can't specify lt/gt/eq twice");
@@ -286,8 +274,6 @@
 		break;
 
 	case OPT_RATEEST_GT:
-		xtables_check_inverse(optarg, &invert, &optind, 0, argv);
-
 		if (*flags & (1 << c))
 			xtables_error(PARAMETER_PROBLEM,
 				   "rateest: can't specify lt/gt/eq twice");
@@ -302,10 +288,9 @@
 	return 1;
 }
 
-static void
-rateest_final_check(unsigned int flags)
+static void rateest_final_check(struct xt_fcheck_call *cb)
 {
-	struct xt_rateest_match_info *info = rateest_info;
+	struct xt_rateest_match_info *info = cb->data;
 
 	if (info == NULL)
 		xtables_error(PARAMETER_PROBLEM, "rateest match: "
@@ -367,8 +352,8 @@
 		if (info->flags & XT_RATEEST_MATCH_DELTA)
 			rateest_print_rate(info->bps1, numeric);
 		if (info->flags & XT_RATEEST_MATCH_ABS) {
-			rateest_print_mode(info, "");
 			rateest_print_rate(info->bps2, numeric);
+			rateest_print_mode(info, "");
 		}
 	}
 	if (info->flags & XT_RATEEST_MATCH_PPS) {
@@ -385,8 +370,6 @@
 		rateest_print_mode(info, "");
 
 		printf(" %s", info->name2);
-		if (info->flags & XT_RATEEST_MATCH_DELTA)
-			printf(" delta");
 
 		if (info->flags & XT_RATEEST_MATCH_BPS) {
 			printf(" bps");
@@ -401,33 +384,48 @@
 	}
 }
 
+static void __rateest_save_rate(const struct xt_rateest_match_info *info,
+                                const char *name, uint32_t r1, uint32_t r2,
+                                int numeric)
+{
+	if (info->flags & XT_RATEEST_MATCH_DELTA) {
+		printf(" --rateest-%s1", name);
+		rateest_print_rate(r1, numeric);
+		rateest_print_mode(info, "--rateest-");
+		printf(" --rateest-%s2", name);
+	} else {
+		rateest_print_mode(info, "--rateest-");
+		printf(" --rateest-%s", name);
+	}
+
+	if (info->flags & (XT_RATEEST_MATCH_ABS|XT_RATEEST_MATCH_DELTA))
+		rateest_print_rate(r2, numeric);
+}
+
+static void rateest_save_rates(const struct xt_rateest_match_info *info)
+{
+	if (info->flags & XT_RATEEST_MATCH_BPS)
+		__rateest_save_rate(info, "bps", info->bps1, info->bps2, 0);
+	if (info->flags & XT_RATEEST_MATCH_PPS)
+		__rateest_save_rate(info, "pps", info->pps1, info->pps2, 1);
+}
+
+
 static void
 rateest_save(const void *ip, const struct xt_entry_match *match)
 {
 	const struct xt_rateest_match_info *info = (const void *)match->data;
 
+	if (info->flags & XT_RATEEST_MATCH_DELTA)
+		printf(" --rateest-delta");
+
 	if (info->flags & XT_RATEEST_MATCH_REL) {
 		printf(" --rateest1 %s", info->name1);
-		if (info->flags & XT_RATEEST_MATCH_BPS)
-			printf(" --rateest-bps");
-		if (info->flags & XT_RATEEST_MATCH_PPS)
-			printf(" --rateest-pps");
-		rateest_print_mode(info, " --rateest-");
+		rateest_save_rates(info);
 		printf(" --rateest2 %s", info->name2);
-	} else {
+	} else { /* XT_RATEEST_MATCH_ABS */
 		printf(" --rateest %s", info->name1);
-		if (info->flags & XT_RATEEST_MATCH_BPS) {
-			printf(" --rateest-bps1");
-			rateest_print_rate(info->bps1, 0);
-			printf(" --rateest-bps2");
-			rateest_print_rate(info->bps2, 0);
-			rateest_print_mode(info, "--rateest-");
-		}
-		if (info->flags & XT_RATEEST_MATCH_PPS) {
-			printf(" --rateest-pps");
-			rateest_print_mode(info, "--rateest-");
-			printf(" %u", info->pps2);
-		}
+		rateest_save_rates(info);
 	}
 }
 
@@ -439,7 +437,7 @@
 	.userspacesize	= XT_ALIGN(offsetof(struct xt_rateest_match_info, est1)),
 	.help		= rateest_help,
 	.parse		= rateest_parse,
-	.final_check	= rateest_final_check,
+	.x6_fcheck	= rateest_final_check,
 	.print		= rateest_print,
 	.save		= rateest_save,
 	.extra_opts	= rateest_opts,
diff --git a/extensions/libxt_rateest.t b/extensions/libxt_rateest.t
new file mode 100644
index 0000000..c515861
--- /dev/null
+++ b/extensions/libxt_rateest.t
@@ -0,0 +1,16 @@
+:INPUT,FORWARD,OUTPUT
+%iptables -I INPUT -j RATEEST --rateest-name RE1 --rateest-interval 250.0ms --rateest-ewmalog 500.0ms
+-m rateest --rateest RE1 --rateest-lt --rateest-bps 8bit;=;OK
+-m rateest --rateest RE1 --rateest-eq --rateest-pps 5;=;OK
+-m rateest --rateest RE1 --rateest-gt --rateest-bps 5kbit;-m rateest --rateest RE1 --rateest-gt --rateest-bps 5000bit;OK
+-m rateest --rateest-delta --rateest RE1 --rateest-bps1 8bit --rateest-lt --rateest-bps2 16bit;=;OK
+%iptables -I INPUT -j RATEEST --rateest-name RE2 --rateest-interval 250.0ms --rateest-ewmalog 500.0ms
+-m rateest --rateest1 RE1 --rateest-lt --rateest-bps --rateest2 RE2;=;OK
+-m rateest --rateest-delta --rateest1 RE1 --rateest-pps1 0 --rateest-lt --rateest-pps2 42 --rateest2 RE2;=;OK
+-m rateest --rateest-delta --rateest RE1 --rateest-bps1 8bit --rateest-eq --rateest-bps2 16bit;=;OK
+-m rateest --rateest-delta --rateest RE1 --rateest-bps1 8bit --rateest-gt --rateest-bps2 16bit;=;OK
+-m rateest --rateest-delta --rateest RE1 --rateest-pps1 8 --rateest-lt --rateest-pps2 9;=;OK
+-m rateest --rateest-delta --rateest RE1 --rateest-pps1 8 --rateest-eq --rateest-pps2 9;=;OK
+-m rateest --rateest-delta --rateest RE1 --rateest-pps1 8 --rateest-gt --rateest-pps2 9;=;OK
+%iptables -D INPUT -j RATEEST --rateest-name RE1 --rateest-interval 250.0ms --rateest-ewmalog 500.0ms
+%iptables -D INPUT -j RATEEST --rateest-name RE2 --rateest-interval 250.0ms --rateest-ewmalog 500.0ms
diff --git a/extensions/libxt_recent.c b/extensions/libxt_recent.c
index 1e1a111..055ae35 100644
--- a/extensions/libxt_recent.c
+++ b/extensions/libxt_recent.c
@@ -10,20 +10,23 @@
 	O_UPDATE,
 	O_REMOVE,
 	O_SECONDS,
+	O_REAP,
 	O_HITCOUNT,
 	O_RTTL,
 	O_NAME,
 	O_RSOURCE,
 	O_RDEST,
+	O_MASK,
 	F_SET    = 1 << O_SET,
 	F_RCHECK = 1 << O_RCHECK,
 	F_UPDATE = 1 << O_UPDATE,
 	F_REMOVE = 1 << O_REMOVE,
+	F_SECONDS = 1 << O_SECONDS,
 	F_ANY_OP = F_SET | F_RCHECK | F_UPDATE | F_REMOVE,
 };
 
 #define s struct xt_recent_mtinfo
-static const struct xt_option_entry recent_opts[] = {
+static const struct xt_option_entry recent_opts_v0[] = {
 	{.name = "set", .id = O_SET, .type = XTTYPE_NONE,
 	 .excl = F_ANY_OP, .flags = XTOPT_INVERT},
 	{.name = "rcheck", .id = O_RCHECK, .type = XTTYPE_NONE,
@@ -33,7 +36,9 @@
 	{.name = "remove", .id = O_REMOVE, .type = XTTYPE_NONE,
 	 .excl = F_ANY_OP, .flags = XTOPT_INVERT},
 	{.name = "seconds", .id = O_SECONDS, .type = XTTYPE_UINT32,
-	 .flags = XTOPT_PUT, XTOPT_POINTER(s, seconds)},
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, seconds), .min = 1},
+	{.name = "reap", .id = O_REAP, .type = XTTYPE_NONE,
+	 .also = F_SECONDS },
 	{.name = "hitcount", .id = O_HITCOUNT, .type = XTTYPE_UINT32,
 	 .flags = XTOPT_PUT, XTOPT_POINTER(s, hit_count)},
 	{.name = "rttl", .id = O_RTTL, .type = XTTYPE_NONE,
@@ -46,6 +51,34 @@
 };
 #undef s
 
+#define s struct xt_recent_mtinfo_v1
+static const struct xt_option_entry recent_opts_v1[] = {
+	{.name = "set", .id = O_SET, .type = XTTYPE_NONE,
+	 .excl = F_ANY_OP, .flags = XTOPT_INVERT},
+	{.name = "rcheck", .id = O_RCHECK, .type = XTTYPE_NONE,
+	 .excl = F_ANY_OP, .flags = XTOPT_INVERT},
+	{.name = "update", .id = O_UPDATE, .type = XTTYPE_NONE,
+	 .excl = F_ANY_OP, .flags = XTOPT_INVERT},
+	{.name = "remove", .id = O_REMOVE, .type = XTTYPE_NONE,
+	 .excl = F_ANY_OP, .flags = XTOPT_INVERT},
+	{.name = "seconds", .id = O_SECONDS, .type = XTTYPE_UINT32,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, seconds), .min = 1},
+	{.name = "reap", .id = O_REAP, .type = XTTYPE_NONE,
+	 .also = F_SECONDS },
+	{.name = "hitcount", .id = O_HITCOUNT, .type = XTTYPE_UINT32,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, hit_count)},
+	{.name = "rttl", .id = O_RTTL, .type = XTTYPE_NONE,
+	 .excl = F_SET | F_REMOVE},
+	{.name = "name", .id = O_NAME, .type = XTTYPE_STRING,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, name)},
+	{.name = "rsource", .id = O_RSOURCE, .type = XTTYPE_NONE},
+	{.name = "rdest", .id = O_RDEST, .type = XTTYPE_NONE},
+	{.name = "mask", .id = O_MASK, .type = XTTYPE_HOST,
+	 .flags = XTOPT_PUT, XTOPT_POINTER(s, mask)},
+	XTOPT_TABLEEND,
+};
+#undef s
+
 static void recent_help(void)
 {
 	printf(
@@ -57,6 +90,8 @@
 "    --seconds seconds           For check and update commands above.\n"
 "                                Specifies that the match will only occur if source address last seen within\n"
 "                                the last 'seconds' seconds.\n"
+"    --reap                      Purge entries older then 'seconds'.\n"
+"                                Can only be used in conjunction with the seconds option.\n"
 "    --hitcount hits             For check and update commands above.\n"
 "                                Specifies that the match will only occur if source address seen hits times.\n"
 "                                May be used in conjunction with the seconds option.\n"
@@ -68,18 +103,28 @@
 "    --name name                 Name of the recent list to be used.  DEFAULT used if none given.\n"
 "    --rsource                   Match/Save the source address of each packet in the recent list table (default).\n"
 "    --rdest                     Match/Save the destination address of each packet in the recent list table.\n"
-"xt_recent by: Stephen Frost <sfrost@snowman.net>.  http://snowman.net/projects/ipt_recent/\n");
+"    --mask netmask              Netmask that will be applied to this recent list.\n"
+"xt_recent by: Stephen Frost <sfrost@snowman.net>.\n");
 }
 
-static void recent_init(struct xt_entry_match *match)
+enum {
+	XT_RECENT_REV_0 = 0,
+	XT_RECENT_REV_1,
+};
+
+static void recent_init(struct xt_entry_match *match, unsigned int rev)
 {
-	struct xt_recent_mtinfo *info = (void *)(match)->data;
+	struct xt_recent_mtinfo *info = (struct xt_recent_mtinfo *)match->data;
+	struct xt_recent_mtinfo_v1 *info_v1 =
+		(struct xt_recent_mtinfo_v1 *)match->data;
 
 	strncpy(info->name,"DEFAULT", XT_RECENT_NAME_LEN);
 	/* even though XT_RECENT_NAME_LEN is currently defined as 200,
 	 * better be safe, than sorry */
 	info->name[XT_RECENT_NAME_LEN-1] = '\0';
 	info->side = XT_RECENT_SOURCE;
+	if (rev == XT_RECENT_REV_1)
+		memset(&info_v1->mask, 0xFF, sizeof(info_v1->mask));
 }
 
 static void recent_parse(struct xt_option_call *cb)
@@ -117,6 +162,9 @@
 	case O_RDEST:
 		info->side = XT_RECENT_DEST;
 		break;
+	case O_REAP:
+		info->check_set |= XT_RECENT_REAP;
+		break;
 	}
 }
 
@@ -129,9 +177,9 @@
 }
 
 static void recent_print(const void *ip, const struct xt_entry_match *match,
-                         int numeric)
+                         unsigned int family)
 {
-	const struct xt_recent_mtinfo *info = (const void *)match->data;
+	const struct xt_recent_mtinfo_v1 *info = (const void *)match->data;
 
 	if (info->invert)
 		printf(" !");
@@ -146,19 +194,33 @@
 	if (info->check_set & XT_RECENT_REMOVE)
 		printf(" REMOVE");
 	if(info->seconds) printf(" seconds: %d", info->seconds);
+	if (info->check_set & XT_RECENT_REAP)
+		printf(" reap");
 	if(info->hit_count) printf(" hit_count: %d", info->hit_count);
 	if (info->check_set & XT_RECENT_TTL)
 		printf(" TTL-Match");
-	if(info->name) printf(" name: %s", info->name);
+	printf(" name: %s", info->name);
 	if (info->side == XT_RECENT_SOURCE)
 		printf(" side: source");
 	if (info->side == XT_RECENT_DEST)
 		printf(" side: dest");
+
+	switch(family) {
+	case NFPROTO_IPV4:
+		printf(" mask: %s",
+			xtables_ipaddr_to_numeric(&info->mask.in));
+		break;
+	case NFPROTO_IPV6:
+		printf(" mask: %s",
+			xtables_ip6addr_to_numeric(&info->mask.in6));
+		break;
+	}
 }
 
-static void recent_save(const void *ip, const struct xt_entry_match *match)
+static void recent_save(const void *ip, const struct xt_entry_match *match,
+			unsigned int family)
 {
-	const struct xt_recent_mtinfo *info = (const void *)match->data;
+	const struct xt_recent_mtinfo_v1 *info = (const void *)match->data;
 
 	if (info->invert)
 		printf(" !");
@@ -172,32 +234,122 @@
 	if (info->check_set & XT_RECENT_REMOVE)
 		printf(" --remove");
 	if(info->seconds) printf(" --seconds %d", info->seconds);
+	if (info->check_set & XT_RECENT_REAP)
+		printf(" --reap");
 	if(info->hit_count) printf(" --hitcount %d", info->hit_count);
 	if (info->check_set & XT_RECENT_TTL)
 		printf(" --rttl");
-	if(info->name) printf(" --name %s",info->name);
+	printf(" --name %s",info->name);
+
+	switch(family) {
+	case NFPROTO_IPV4:
+		printf(" --mask %s",
+			xtables_ipaddr_to_numeric(&info->mask.in));
+		break;
+	case NFPROTO_IPV6:
+		printf(" --mask %s",
+			xtables_ip6addr_to_numeric(&info->mask.in6));
+		break;
+	}
+
 	if (info->side == XT_RECENT_SOURCE)
 		printf(" --rsource");
 	if (info->side == XT_RECENT_DEST)
 		printf(" --rdest");
 }
 
-static struct xtables_match recent_mt_reg = {
-	.name          = "recent",
-	.version       = XTABLES_VERSION,
-	.family        = NFPROTO_UNSPEC,
-	.size          = XT_ALIGN(sizeof(struct xt_recent_mtinfo)),
-	.userspacesize = XT_ALIGN(sizeof(struct xt_recent_mtinfo)),
-	.help          = recent_help,
-	.init          = recent_init,
-	.x6_parse      = recent_parse,
-	.x6_fcheck     = recent_check,
-	.print         = recent_print,
-	.save          = recent_save,
-	.x6_options    = recent_opts,
+static void recent_init_v0(struct xt_entry_match *match)
+{
+	recent_init(match, XT_RECENT_REV_0);
+}
+
+static void recent_init_v1(struct xt_entry_match *match)
+{
+	recent_init(match, XT_RECENT_REV_1);
+}
+
+static void recent_save_v0(const void *ip, const struct xt_entry_match *match)
+{
+	recent_save(ip, match, NFPROTO_UNSPEC);
+}
+
+static void recent_save_v4(const void *ip, const struct xt_entry_match *match)
+{
+	recent_save(ip, match, NFPROTO_IPV4);
+}
+
+static void recent_save_v6(const void *ip, const struct xt_entry_match *match)
+{
+	recent_save(ip, match, NFPROTO_IPV6);
+}
+
+static void recent_print_v0(const void *ip, const struct xt_entry_match *match,
+			    int numeric)
+{
+	recent_print(ip, match, NFPROTO_UNSPEC);
+}
+
+static void recent_print_v4(const void *ip, const struct xt_entry_match *match,
+                         int numeric)
+{
+	recent_print(ip, match, NFPROTO_IPV4);
+}
+
+static void recent_print_v6(const void *ip, const struct xt_entry_match *match,
+                         int numeric)
+{
+	recent_print(ip, match, NFPROTO_IPV6);
+}
+
+static struct xtables_match recent_mt_reg[] = {
+	{
+		.name          = "recent",
+		.version       = XTABLES_VERSION,
+		.revision      = 0,
+		.family        = NFPROTO_UNSPEC,
+		.size          = XT_ALIGN(sizeof(struct xt_recent_mtinfo)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_recent_mtinfo)),
+		.help          = recent_help,
+		.init          = recent_init_v0,
+		.x6_parse      = recent_parse,
+		.x6_fcheck     = recent_check,
+		.print         = recent_print_v0,
+		.save          = recent_save_v0,
+		.x6_options    = recent_opts_v0,
+	},
+	{
+		.name          = "recent",
+		.version       = XTABLES_VERSION,
+		.revision      = 1,
+		.family        = NFPROTO_IPV4,
+		.size          = XT_ALIGN(sizeof(struct xt_recent_mtinfo_v1)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_recent_mtinfo_v1)),
+		.help          = recent_help,
+		.init          = recent_init_v1,
+		.x6_parse      = recent_parse,
+		.x6_fcheck     = recent_check,
+		.print         = recent_print_v4,
+		.save          = recent_save_v4,
+		.x6_options    = recent_opts_v1,
+	},
+	{
+		.name          = "recent",
+		.version       = XTABLES_VERSION,
+		.revision      = 1,
+		.family        = NFPROTO_IPV6,
+		.size          = XT_ALIGN(sizeof(struct xt_recent_mtinfo_v1)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_recent_mtinfo_v1)),
+		.help          = recent_help,
+		.init          = recent_init_v1,
+		.x6_parse      = recent_parse,
+		.x6_fcheck     = recent_check,
+		.print         = recent_print_v6,
+		.save          = recent_save_v6,
+		.x6_options    = recent_opts_v1,
+	},
 };
 
 void _init(void)
 {
-	xtables_register_match(&recent_mt_reg);
+	xtables_register_matches(recent_mt_reg, ARRAY_SIZE(recent_mt_reg));
 }
diff --git a/extensions/libxt_recent.man b/extensions/libxt_recent.man
index 0392c2c..419be25 100644
--- a/extensions/libxt_recent.man
+++ b/extensions/libxt_recent.man
@@ -24,6 +24,9 @@
 \fB\-\-rdest\fP
 Match/save the destination address of each packet in the recent list table.
 .TP
+\fB\-\-mask\fP \fInetmask\fP
+Netmask that will be applied to this recent list.
+.TP
 [\fB!\fP] \fB\-\-rcheck\fP
 Check if the source address of the packet is currently in the list.
 .TP
@@ -41,6 +44,11 @@
 \fB\-\-update\fP. When used, this will narrow the match to only happen when the
 address is in the list and was seen within the last given number of seconds.
 .TP
+\fB\-\-reap\fP
+This option can only be used in conjunction with \fB\-\-seconds\fP.
+When used, this will cause entries older than the last given number of seconds
+to be purged.
+.TP
 \fB\-\-hitcount\fP \fIhits\fP
 This option must be used in conjunction with one of \fB\-\-rcheck\fP or
 \fB\-\-update\fP. When used, this will narrow the match to only happen when the
@@ -65,9 +73,6 @@
 .IP
 iptables \-A FORWARD \-p tcp \-i eth0 \-\-dport 139 \-m recent \-\-name badguy \-\-set \-j DROP
 .PP
-Steve's ipt_recent website (http://snowman.net/projects/ipt_recent/) also has
-some examples of usage.
-.PP
 \fB/proc/net/xt_recent/*\fP are the current lists of addresses and information
 about each entry of each list.
 .PP
diff --git a/extensions/libxt_recent.t b/extensions/libxt_recent.t
new file mode 100644
index 0000000..9a83918
--- /dev/null
+++ b/extensions/libxt_recent.t
@@ -0,0 +1,11 @@
+:INPUT,FORWARD,OUTPUT
+-m recent --set;=;OK
+-m recent --rcheck --hitcount 8 --name foo --mask 255.255.255.255 --rsource;=;OK
+-m recent --rcheck --hitcount 12 --name foo --mask 255.255.255.255 --rsource;=;OK
+-m recent --update --rttl;=;OK
+-m recent --set --rttl;;FAIL
+-m recent --rcheck --hitcount 999 --name foo --mask 255.255.255.255 --rsource;;FAIL
+# nonsensical, but all should load successfully:
+-m recent --rcheck --hitcount 3 --name foo --mask 255.255.255.255 --rsource -m recent --rcheck --hitcount 4 --name foo --mask 255.255.255.255 --rsource;=;OK
+-m recent --rcheck --hitcount 4 --name foo --mask 255.255.255.255 --rsource -m recent --rcheck --hitcount 4 --name foo --mask 255.255.255.255 --rsource;=;OK
+-m recent --rcheck --hitcount 8 --name foo --mask 255.255.255.255 --rsource -m recent --rcheck --hitcount 12 --name foo --mask 255.255.255.255 --rsource;=;OK
diff --git a/extensions/libxt_rpfilter.c b/extensions/libxt_rpfilter.c
new file mode 100644
index 0000000..d166baa
--- /dev/null
+++ b/extensions/libxt_rpfilter.c
@@ -0,0 +1,122 @@
+#include <stdio.h>
+#include <xtables.h>
+#include <linux/netfilter/xt_rpfilter.h>
+
+enum {
+	O_RPF_LOOSE = 0,
+	O_RPF_VMARK = 1,
+	O_RPF_ACCEPT_LOCAL = 2,
+	O_RPF_INVERT = 3,
+};
+
+static void rpfilter_help(void)
+{
+	printf(
+"rpfilter match options:\n"
+"    --loose          permit reverse path via any interface\n"
+"    --validmark      use skb nfmark when performing route lookup\n"
+"    --accept-local   do not reject packets with a local source address\n"
+"    --invert         match packets that failed the reverse path test\n"
+	);
+}
+
+static const struct xt_option_entry rpfilter_opts[] = {
+	{.name = "loose", .id = O_RPF_LOOSE, .type = XTTYPE_NONE, },
+	{.name = "validmark", .id = O_RPF_VMARK, .type = XTTYPE_NONE, },
+	{.name = "accept-local", .id = O_RPF_ACCEPT_LOCAL, .type = XTTYPE_NONE, },
+	{.name = "invert", .id = O_RPF_INVERT, .type = XTTYPE_NONE, },
+	XTOPT_TABLEEND,
+};
+
+static void rpfilter_parse(struct xt_option_call *cb)
+{
+	struct xt_rpfilter_info *rpfinfo = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_RPF_LOOSE:
+		rpfinfo->flags |= XT_RPFILTER_LOOSE;
+		break;
+	case O_RPF_VMARK:
+		rpfinfo->flags |= XT_RPFILTER_VALID_MARK;
+		break;
+	case O_RPF_ACCEPT_LOCAL:
+		rpfinfo->flags |= XT_RPFILTER_ACCEPT_LOCAL;
+		break;
+	case O_RPF_INVERT:
+		rpfinfo->flags |= XT_RPFILTER_INVERT;
+		break;
+	}
+}
+
+static void
+rpfilter_print_prefix(const void *ip, const void *matchinfo,
+			const char *prefix)
+{
+	const struct xt_rpfilter_info *info = matchinfo;
+	if (info->flags & XT_RPFILTER_LOOSE)
+		printf(" %s%s", prefix, rpfilter_opts[O_RPF_LOOSE].name);
+	if (info->flags & XT_RPFILTER_VALID_MARK)
+		printf(" %s%s", prefix, rpfilter_opts[O_RPF_VMARK].name);
+	if (info->flags & XT_RPFILTER_ACCEPT_LOCAL)
+		printf(" %s%s", prefix, rpfilter_opts[O_RPF_ACCEPT_LOCAL].name);
+	if (info->flags & XT_RPFILTER_INVERT)
+		printf(" %s%s", prefix, rpfilter_opts[O_RPF_INVERT].name);
+}
+
+
+static void
+rpfilter_print(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	printf(" rpfilter");
+	return rpfilter_print_prefix(ip, match->data, "");
+}
+
+static void rpfilter_save(const void *ip, const struct xt_entry_match *match)
+{
+	return rpfilter_print_prefix(ip, match->data, "--");
+}
+
+static int rpfilter_xlate(struct xt_xlate *xl,
+			  const struct xt_xlate_mt_params *params)
+{
+	const struct xt_rpfilter_info *info = (void *)params->match->data;
+	bool invert = info->flags & XT_RPFILTER_INVERT;
+
+	if (info->flags & XT_RPFILTER_ACCEPT_LOCAL) {
+		if (invert)
+			xt_xlate_add(xl, "fib saddr type != local ");
+		else
+			return 0;
+	}
+
+	xt_xlate_add(xl, "fib saddr ");
+
+	if (info->flags & XT_RPFILTER_VALID_MARK)
+		xt_xlate_add(xl, ". mark ");
+	if (!(info->flags & XT_RPFILTER_LOOSE))
+		xt_xlate_add(xl, ". iif ");
+
+	xt_xlate_add(xl, "oif %s0", invert ? "" : "!= ");
+
+	return 1;
+}
+
+static struct xtables_match rpfilter_match = {
+	.family		= NFPROTO_UNSPEC,
+	.name		= "rpfilter",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct xt_rpfilter_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_rpfilter_info)),
+	.help		= rpfilter_help,
+	.print		= rpfilter_print,
+	.save		= rpfilter_save,
+	.x6_parse	= rpfilter_parse,
+	.x6_options	= rpfilter_opts,
+	.xlate		= rpfilter_xlate,
+};
+
+void _init(void)
+{
+	xtables_register_match(&rpfilter_match);
+}
diff --git a/extensions/libxt_rpfilter.man b/extensions/libxt_rpfilter.man
new file mode 100644
index 0000000..a3c0fcd
--- /dev/null
+++ b/extensions/libxt_rpfilter.man
@@ -0,0 +1,39 @@
+Performs a reverse path filter test on a packet.
+If a reply to the packet would be sent via the same interface
+that the packet arrived on, the packet will match.
+Note that, unlike the in-kernel rp_filter, packets protected
+by IPSec are not treated specially.  Combine this match with
+the policy match if you want this.
+Also, packets arriving via the loopback interface are always permitted.
+This match can only be used in the PREROUTING chain of the raw or mangle table.
+.TP
+\fB\-\-loose\fP
+Used to specify that the reverse path filter test should match
+even if the selected output device is not the expected one.
+.TP
+\fB\-\-validmark\fP
+Also use the packets' nfmark value when performing the reverse path route lookup.
+.TP
+\fB\-\-accept\-local\fP
+This will permit packets arriving from the network with a source address that is also
+assigned to the local machine.
+.TP
+\fB\-\-invert\fP
+This will invert the sense of the match.  Instead of matching packets that passed the
+reverse path filter test, match those that have failed it.
+.PP
+Example to log and drop packets failing the reverse path filter test:
+
+iptables \-t raw \-N RPFILTER
+
+iptables \-t raw \-A RPFILTER \-m rpfilter \-j RETURN
+
+iptables \-t raw \-A RPFILTER \-m limit \-\-limit 10/minute \-j NFLOG \-\-nflog\-prefix "rpfilter drop"
+
+iptables \-t raw \-A RPFILTER \-j DROP
+
+iptables \-t raw \-A PREROUTING \-j RPFILTER
+
+Example to drop failed packets, without logging:
+
+iptables \-t raw \-A RPFILTER \-m rpfilter \-\-invert \-j DROP
diff --git a/extensions/libxt_rpfilter.t b/extensions/libxt_rpfilter.t
new file mode 100644
index 0000000..390268f
--- /dev/null
+++ b/extensions/libxt_rpfilter.t
@@ -0,0 +1,4 @@
+:PREROUTING
+*mangle
+-m rpfilter;=;OK
+-m rpfilter --loose --validmark --accept-local --invert;=;OK
diff --git a/extensions/libxt_rpfilter.txlate b/extensions/libxt_rpfilter.txlate
new file mode 100644
index 0000000..8d7733b
--- /dev/null
+++ b/extensions/libxt_rpfilter.txlate
@@ -0,0 +1,8 @@
+iptables-translate -t mangle -A PREROUTING -m rpfilter
+nft add rule ip mangle PREROUTING fib saddr . iif oif != 0 counter
+
+iptables-translate -t mangle -A PREROUTING -m rpfilter --validmark --loose
+nft add rule ip mangle PREROUTING fib saddr . mark oif != 0 counter
+
+ip6tables-translate -t mangle -A PREROUTING -m rpfilter --validmark --invert
+nft add rule ip6 mangle PREROUTING fib saddr . mark . iif oif 0 counter
diff --git a/extensions/libxt_sctp.c b/extensions/libxt_sctp.c
index 5dbc36f..140de26 100644
--- a/extensions/libxt_sctp.c
+++ b/extensions/libxt_sctp.c
@@ -257,7 +257,6 @@
 			xtables_error(PARAMETER_PROBLEM,
 			           "Only one `--source-port' allowed");
 		einfo->flags |= XT_SCTP_SRC_PORTS;
-		xtables_check_inverse(optarg, &invert, &optind, 0, argv);
 		parse_sctp_ports(optarg, einfo->spts);
 		if (invert)
 			einfo->invflags |= XT_SCTP_SRC_PORTS;
@@ -269,7 +268,6 @@
 			xtables_error(PARAMETER_PROBLEM,
 				   "Only one `--destination-port' allowed");
 		einfo->flags |= XT_SCTP_DEST_PORTS;
-		xtables_check_inverse(optarg, &invert, &optind, 0, argv);
 		parse_sctp_ports(optarg, einfo->dpts);
 		if (invert)
 			einfo->invflags |= XT_SCTP_DEST_PORTS;
@@ -280,8 +278,6 @@
 		if (*flags & XT_SCTP_CHUNK_TYPES)
 			xtables_error(PARAMETER_PROBLEM,
 				   "Only one `--chunk-types' allowed");
-		xtables_check_inverse(optarg, &invert, &optind, 0, argv);
-
 		if (!argv[optind] 
 		    || argv[optind][0] == '-' || argv[optind][0] == '!')
 			xtables_error(PARAMETER_PROBLEM,
@@ -374,7 +370,7 @@
 
 		for (i = 0; i < ARRAY_SIZE(sctp_chunk_names); ++i)
 			if (sctp_chunk_names[i].chunk_type == chunknum)
-				printf("%s", sctp_chunk_names[chunknum].name);
+				printf("%s", sctp_chunk_names[i].name);
 	}
 }
 
@@ -489,6 +485,44 @@
 	}
 }
 
+static int sctp_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_mt_params *params)
+{
+	const struct xt_sctp_info *einfo =
+		(const struct xt_sctp_info *)params->match->data;
+	char *space = "";
+
+	if (!einfo->flags)
+		return 0;
+
+	xt_xlate_add(xl, "sctp ");
+
+	if (einfo->flags & XT_SCTP_SRC_PORTS) {
+		if (einfo->spts[0] != einfo->spts[1])
+			xt_xlate_add(xl, "sport%s %u-%u",
+				     einfo->invflags & XT_SCTP_SRC_PORTS ? " !=" : "",
+				     einfo->spts[0], einfo->spts[1]);
+		else
+			xt_xlate_add(xl, "sport%s %u",
+				     einfo->invflags & XT_SCTP_SRC_PORTS ? " !=" : "",
+				     einfo->spts[0]);
+		space = " ";
+	}
+
+	if (einfo->flags & XT_SCTP_DEST_PORTS) {
+		if (einfo->dpts[0] != einfo->dpts[1])
+			xt_xlate_add(xl, "%sdport%s %u-%u", space,
+				     einfo->invflags & XT_SCTP_DEST_PORTS ? " !=" : "",
+				     einfo->dpts[0], einfo->dpts[1]);
+		else
+			xt_xlate_add(xl, "%sdport%s %u", space,
+				     einfo->invflags & XT_SCTP_DEST_PORTS ? " !=" : "",
+				     einfo->dpts[0]);
+	}
+
+	return 1;
+}
+
 static struct xtables_match sctp_match = {
 	.name		= "sctp",
 	.family		= NFPROTO_UNSPEC,
@@ -501,6 +535,7 @@
 	.print		= sctp_print,
 	.save		= sctp_save,
 	.extra_opts	= sctp_opts,
+	.xlate		= sctp_xlate,
 };
 
 void _init(void)
diff --git a/extensions/libxt_sctp.man b/extensions/libxt_sctp.man
index 9c0bd8c..3779d05 100644
--- a/extensions/libxt_sctp.man
+++ b/extensions/libxt_sctp.man
@@ -1,3 +1,4 @@
+This module matches Stream Control Transmission Protocol headers.
 .TP
 [\fB!\fP] \fB\-\-source\-port\fP,\fB\-\-sport\fP \fIport\fP[\fB:\fP\fIport\fP]
 .TP
diff --git a/extensions/libxt_sctp.t b/extensions/libxt_sctp.t
new file mode 100644
index 0000000..4016e4f
--- /dev/null
+++ b/extensions/libxt_sctp.t
@@ -0,0 +1,29 @@
+:INPUT,FORWARD,OUTPUT
+-p sctp -m sctp --sport 1;=;OK
+-p sctp -m sctp --sport 65535;=;OK
+-p sctp -m sctp --sport 1:65535;=;OK
+-p sctp -m sctp --sport -1;;FAIL
+-p sctp -m sctp --sport 65536;;FAIL
+-p sctp -m sctp --dport 1;=;OK
+-p sctp -m sctp --dport 1:65535;=;OK
+-p sctp -m sctp --dport 65535;=;OK
+-p sctp -m sctp --dport -1;;FAIL
+-p sctp -m sctp --dport 65536;;FAIL
+-p sctp -m sctp --chunk-types all DATA;=;OK
+-p sctp -m sctp --chunk-types all INIT;=;OK
+-p sctp -m sctp --chunk-types all INIT_ACK;=;OK
+-p sctp -m sctp --chunk-types all SACK;=;OK
+-p sctp -m sctp --chunk-types all HEARTBEAT;=;OK
+-p sctp -m sctp --chunk-types all HEARTBEAT_ACK;=;OK
+-p sctp -m sctp --chunk-types all ABORT;=;OK
+-p sctp -m sctp --chunk-types all SHUTDOWN;=;OK
+-p sctp -m sctp --chunk-types all SHUTDOWN_ACK;=;OK
+-p sctp -m sctp --chunk-types all ERROR;=;OK
+-p sctp -m sctp --chunk-types all COOKIE_ECHO;=;OK
+-p sctp -m sctp --chunk-types all COOKIE_ACK;=;OK
+-p sctp -m sctp --chunk-types all ECN_ECNE;=;OK
+-p sctp -m sctp --chunk-types all ECN_CWR;=;OK
+-p sctp -m sctp --chunk-types all ASCONF;=;OK
+-p sctp -m sctp --chunk-types all ASCONF_ACK;=;OK
+-p sctp -m sctp --chunk-types all FORWARD_TSN;=;OK
+-p sctp -m sctp --chunk-types all SHUTDOWN_COMPLETE;=;OK
diff --git a/extensions/libxt_sctp.txlate b/extensions/libxt_sctp.txlate
new file mode 100644
index 0000000..72f4641
--- /dev/null
+++ b/extensions/libxt_sctp.txlate
@@ -0,0 +1,38 @@
+iptables-translate -A INPUT -p sctp --dport 80 -j DROP
+nft add rule ip filter INPUT sctp dport 80 counter drop
+
+iptables-translate -A INPUT -p sctp --sport 50 -j DROP
+nft add rule ip filter INPUT sctp sport 50 counter drop
+
+iptables-translate -A INPUT -p sctp ! --dport 80 -j DROP
+nft add rule ip filter INPUT sctp dport != 80 counter drop
+
+iptables-translate -A INPUT -p sctp ! --sport 50 -j DROP
+nft add rule ip filter INPUT sctp sport != 50 counter drop
+
+iptables-translate -A INPUT -p sctp --sport 80:100 -j ACCEPT
+nft add rule ip filter INPUT sctp sport 80-100 counter accept
+
+iptables-translate -A INPUT -p sctp --dport 50:56 -j ACCEPT
+nft add rule ip filter INPUT sctp dport 50-56 counter accept
+
+iptables-translate -A INPUT -p sctp ! --sport 80:100 -j ACCEPT
+nft add rule ip filter INPUT sctp sport != 80-100 counter accept
+
+iptables-translate -A INPUT -p sctp ! --dport 50:56 -j ACCEPT
+nft add rule ip filter INPUT sctp dport != 50-56 counter accept
+
+iptables-translate -A INPUT -p sctp --dport 80 --sport 50 -j ACCEPT
+nft add rule ip filter INPUT sctp sport 50 dport 80 counter accept
+
+iptables-translate -A INPUT -p sctp --dport 80:100 --sport 50 -j ACCEPT
+nft add rule ip filter INPUT sctp sport 50 dport 80-100 counter accept
+
+iptables-translate -A INPUT -p sctp --dport 80 --sport 50:55 -j ACCEPT
+nft add rule ip filter INPUT sctp sport 50-55 dport 80 counter accept
+
+iptables-translate -A INPUT -p sctp ! --dport 80:100 --sport 50 -j ACCEPT
+nft add rule ip filter INPUT sctp sport 50 dport != 80-100 counter accept
+
+iptables-translate -A INPUT -p sctp --dport 80 ! --sport 50:55 -j ACCEPT
+nft add rule ip filter INPUT sctp sport != 50-55 dport 80 counter accept
diff --git a/extensions/libxt_set.c b/extensions/libxt_set.c
index da722c7..1692102 100644
--- a/extensions/libxt_set.c
+++ b/extensions/libxt_set.c
@@ -52,7 +52,7 @@
 set_parse_v0(int c, char **argv, int invert, unsigned int *flags,
 	     const void *entry, struct xt_entry_match **match)
 {
-	struct xt_set_info_match_v0 *myinfo = 
+	struct xt_set_info_match_v0 *myinfo =
 		(struct xt_set_info_match_v0 *) (*match)->data;
 	struct xt_set_info_v0 *info = &myinfo->match_set;
 
@@ -60,12 +60,11 @@
 	case '2':
 		fprintf(stderr,
 			"--set option deprecated, please use --match-set\n");
+		/* fall through */
 	case '1':		/* --match-set <set> <flag>[,<flag> */
 		if (info->u.flags[0])
 			xtables_error(PARAMETER_PROBLEM,
 				      "--match-set can be specified only once");
-
-		xtables_check_inverse(optarg, &invert, &optind, 0, argv);
 		if (invert)
 			info->u.flags[0] |= IPSET_MATCH_INV;
 
@@ -84,7 +83,7 @@
 		parse_dirs_v0(argv[optind], info);
 		DEBUGP("parse: set index %u\n", info->index);
 		optind++;
-		
+
 		*flags = 1;
 		break;
 	}
@@ -130,16 +129,11 @@
 }
 
 /* Revision 1 */
-
-#define set_help_v1	set_help_v0
-#define set_opts_v1	set_opts_v0
-#define set_check_v1	set_check_v0
-
 static int
 set_parse_v1(int c, char **argv, int invert, unsigned int *flags,
 	     const void *entry, struct xt_entry_match **match)
 {
-	struct xt_set_info_match_v1 *myinfo = 
+	struct xt_set_info_match_v1 *myinfo =
 		(struct xt_set_info_match_v1 *) (*match)->data;
 	struct xt_set_info *info = &myinfo->match_set;
 
@@ -147,12 +141,11 @@
 	case '2':
 		fprintf(stderr,
 			"--set option deprecated, please use --match-set\n");
+		/* fall through */
 	case '1':		/* --match-set <set> <flag>[,<flag> */
 		if (info->dim)
 			xtables_error(PARAMETER_PROBLEM,
 				      "--match-set can be specified only once");
-
-		xtables_check_inverse(optarg, &invert, &optind, 0, argv);
 		if (invert)
 			info->flags |= IPSET_INV_MATCH;
 
@@ -171,7 +164,7 @@
 		parse_dirs(argv[optind], info);
 		DEBUGP("parse: set index %u\n", info->index);
 		optind++;
-		
+
 		*flags = 1;
 		break;
 	}
@@ -214,6 +207,469 @@
 	print_match("--match-set", &info->match_set);
 }
 
+/* Revision 2 */
+static void
+set_help_v2(void)
+{
+	printf("set match options:\n"
+	       " [!] --match-set name flags [--return-nomatch]\n"
+	       "		 'name' is the set name from to match,\n" 
+	       "		 'flags' are the comma separated list of\n"
+	       "		 'src' and 'dst' specifications.\n");
+}
+
+static const struct option set_opts_v2[] = {
+	{.name = "match-set",		.has_arg = true,	.val = '1'},
+	{.name = "set",			.has_arg = true,	.val = '2'},
+	{.name = "return-nomatch",	.has_arg = false,	.val = '3'},
+	XT_GETOPT_TABLEEND,
+};
+
+static int
+set_parse_v2(int c, char **argv, int invert, unsigned int *flags,
+	     const void *entry, struct xt_entry_match **match)
+{
+	struct xt_set_info_match_v1 *myinfo =
+		(struct xt_set_info_match_v1 *) (*match)->data;
+	struct xt_set_info *info = &myinfo->match_set;
+
+	switch (c) {
+	case '3':
+		info->flags |= IPSET_RETURN_NOMATCH;
+		break;
+	case '2':
+		fprintf(stderr,
+			"--set option deprecated, please use --match-set\n");
+		/* fall through */
+	case '1':		/* --match-set <set> <flag>[,<flag> */
+		if (info->dim)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--match-set can be specified only once");
+		if (invert)
+			info->flags |= IPSET_INV_MATCH;
+
+		if (!argv[optind]
+		    || argv[optind][0] == '-'
+		    || argv[optind][0] == '!')
+			xtables_error(PARAMETER_PROBLEM,
+				      "--match-set requires two args.");
+
+		if (strlen(optarg) > IPSET_MAXNAMELEN - 1)
+			xtables_error(PARAMETER_PROBLEM,
+				      "setname `%s' too long, max %d characters.",
+				      optarg, IPSET_MAXNAMELEN - 1);
+
+		get_set_byname(optarg, info);
+		parse_dirs(argv[optind], info);
+		DEBUGP("parse: set index %u\n", info->index);
+		optind++;
+
+		*flags = 1;
+		break;
+	}
+
+	return 1;
+}
+
+/* Prints out the matchinfo. */
+static void
+set_print_v2(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_set_info_match_v1 *info = (const void *)match->data;
+
+	print_match("match-set", &info->match_set);
+	if (info->match_set.flags & IPSET_RETURN_NOMATCH)
+		printf(" return-nomatch");
+}
+
+static void
+set_save_v2(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_set_info_match_v1 *info = (const void *)match->data;
+
+	print_match("--match-set", &info->match_set);
+	if (info->match_set.flags & IPSET_RETURN_NOMATCH)
+		printf(" --return-nomatch");
+}
+
+/* Revision 3 */
+static void
+set_help_v3(void)
+{
+	printf("set match options:\n"
+	       " [!] --match-set name flags [--return-nomatch]\n"
+	       "   [! --update-counters] [! --update-subcounters]\n"
+	       "   [[!] --packets-eq value | --packets-lt value | --packets-gt value\n"
+	       "   [[!] --bytes-eq value | --bytes-lt value | --bytes-gt value\n"
+	       "		 'name' is the set name from to match,\n" 
+	       "		 'flags' are the comma separated list of\n"
+	       "		 'src' and 'dst' specifications.\n");
+}
+
+static const struct option set_opts_v3[] = {
+	{.name = "match-set",		.has_arg = true,	.val = '1'},
+	{.name = "set",			.has_arg = true,	.val = '2'},
+	{.name = "return-nomatch",	.has_arg = false,	.val = '3'},
+	{.name = "update-counters",	.has_arg = false,	.val = '4'},
+	{.name = "packets-eq",		.has_arg = true,	.val = '5'},
+	{.name = "packets-lt",		.has_arg = true,	.val = '6'},
+	{.name = "packets-gt",		.has_arg = true,	.val = '7'},
+	{.name = "bytes-eq",		.has_arg = true,	.val = '8'},
+	{.name = "bytes-lt",		.has_arg = true,	.val = '9'},
+	{.name = "bytes-gt",		.has_arg = true,	.val = '0'},
+	{.name = "update-subcounters",	.has_arg = false,	.val = 'a'},
+	XT_GETOPT_TABLEEND,
+};
+
+static uint64_t
+parse_counter(const char *opt)
+{
+	uintmax_t value;
+
+	if (!xtables_strtoul(opt, NULL, &value, 0, UINT64_MAX))
+		xtables_error(PARAMETER_PROBLEM,
+			      "Cannot parse %s as a counter value\n",
+			      opt);
+	return (uint64_t)value;
+}
+
+static int
+set_parse_v3(int c, char **argv, int invert, unsigned int *flags,
+	     const void *entry, struct xt_entry_match **match)
+{
+	struct xt_set_info_match_v3 *info =
+		(struct xt_set_info_match_v3 *) (*match)->data;
+
+	switch (c) {
+	case 'a':
+		if (invert)
+			info->flags |= IPSET_FLAG_SKIP_SUBCOUNTER_UPDATE;
+		break;
+	case '0':
+		if (info->bytes.op != IPSET_COUNTER_NONE)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only one of the --bytes-[eq|lt|gt]"
+				      " is allowed\n");
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--bytes-gt option cannot be inverted\n");
+		info->bytes.op = IPSET_COUNTER_GT;
+		info->bytes.value = parse_counter(optarg);
+		break;
+	case '9':
+		if (info->bytes.op != IPSET_COUNTER_NONE)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only one of the --bytes-[eq|lt|gt]"
+				      " is allowed\n");
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--bytes-lt option cannot be inverted\n");
+		info->bytes.op = IPSET_COUNTER_LT;
+		info->bytes.value = parse_counter(optarg);
+		break;
+	case '8':
+		if (info->bytes.op != IPSET_COUNTER_NONE)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only one of the --bytes-[eq|lt|gt]"
+				      " is allowed\n");
+		info->bytes.op = invert ? IPSET_COUNTER_NE : IPSET_COUNTER_EQ;
+		info->bytes.value = parse_counter(optarg);
+		break;
+	case '7':
+		if (info->packets.op != IPSET_COUNTER_NONE)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only one of the --packets-[eq|lt|gt]"
+				      " is allowed\n");
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--packets-gt option cannot be inverted\n");
+		info->packets.op = IPSET_COUNTER_GT;
+		info->packets.value = parse_counter(optarg);
+		break;
+	case '6':
+		if (info->packets.op != IPSET_COUNTER_NONE)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only one of the --packets-[eq|lt|gt]"
+				      " is allowed\n");
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--packets-lt option cannot be inverted\n");
+		info->packets.op = IPSET_COUNTER_LT;
+		info->packets.value = parse_counter(optarg);
+		break;
+	case '5':
+		if (info->packets.op != IPSET_COUNTER_NONE)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only one of the --packets-[eq|lt|gt]"
+				      " is allowed\n");
+		info->packets.op = invert ? IPSET_COUNTER_NE : IPSET_COUNTER_EQ;
+		info->packets.value = parse_counter(optarg);
+		break;
+	case '4':
+		if (invert)
+			info->flags |= IPSET_FLAG_SKIP_COUNTER_UPDATE;
+		break;
+	case '3':
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--return-nomatch flag cannot be inverted\n");
+		info->flags |= IPSET_FLAG_RETURN_NOMATCH;
+		break;
+	case '2':
+		fprintf(stderr,
+			"--set option deprecated, please use --match-set\n");
+		/* fall through */
+	case '1':		/* --match-set <set> <flag>[,<flag> */
+		if (info->match_set.dim)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--match-set can be specified only once");
+		if (invert)
+			info->match_set.flags |= IPSET_INV_MATCH;
+
+		if (!argv[optind]
+		    || argv[optind][0] == '-'
+		    || argv[optind][0] == '!')
+			xtables_error(PARAMETER_PROBLEM,
+				      "--match-set requires two args.");
+
+		if (strlen(optarg) > IPSET_MAXNAMELEN - 1)
+			xtables_error(PARAMETER_PROBLEM,
+				      "setname `%s' too long, max %d characters.",
+				      optarg, IPSET_MAXNAMELEN - 1);
+
+		get_set_byname(optarg, &info->match_set);
+		parse_dirs(argv[optind], &info->match_set);
+		DEBUGP("parse: set index %u\n", info->match_set.index);
+		optind++;
+
+		*flags = 1;
+		break;
+	}
+
+	return 1;
+}
+
+static void
+set_printv3_counter(const struct ip_set_counter_match0 *c, const char *name,
+		    const char *sep)
+{
+	switch (c->op) {
+	case IPSET_COUNTER_EQ:
+		printf(" %s%s-eq %llu", sep, name, c->value);
+		break;
+	case IPSET_COUNTER_NE:
+		printf(" ! %s%s-eq %llu", sep, name, c->value);
+		break;
+	case IPSET_COUNTER_LT:
+		printf(" %s%s-lt %llu", sep, name, c->value);
+		break;
+	case IPSET_COUNTER_GT:
+		printf(" %s%s-gt %llu", sep, name, c->value);
+		break;
+	}
+}
+
+static void
+set_print_v3_matchinfo(const struct xt_set_info_match_v3 *info,
+		       const char *opt, const char *sep)
+{
+	print_match(opt, &info->match_set);
+	if (info->flags & IPSET_FLAG_RETURN_NOMATCH)
+		printf(" %sreturn-nomatch", sep);
+	if ((info->flags & IPSET_FLAG_SKIP_COUNTER_UPDATE))
+		printf(" ! %supdate-counters", sep);
+	if ((info->flags & IPSET_FLAG_SKIP_SUBCOUNTER_UPDATE))
+		printf(" ! %supdate-subcounters", sep);
+	set_printv3_counter(&info->packets, "packets", sep);
+	set_printv3_counter(&info->bytes, "bytes", sep);
+}
+
+/* Prints out the matchinfo. */
+static void
+set_print_v3(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_set_info_match_v3 *info = (const void *)match->data;
+
+	set_print_v3_matchinfo(info, "match-set", "");
+}
+
+static void
+set_save_v3(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_set_info_match_v3 *info = (const void *)match->data;
+
+	set_print_v3_matchinfo(info, "--match-set", "--");
+}
+
+/* Revision 4 */
+static int
+set_parse_v4(int c, char **argv, int invert, unsigned int *flags,
+	     const void *entry, struct xt_entry_match **match)
+{
+	struct xt_set_info_match_v4 *info =
+		(struct xt_set_info_match_v4 *) (*match)->data;
+
+	switch (c) {
+	case 'a':
+		if (invert)
+			info->flags |= IPSET_FLAG_SKIP_SUBCOUNTER_UPDATE;
+		break;
+	case '0':
+		if (info->bytes.op != IPSET_COUNTER_NONE)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only one of the --bytes-[eq|lt|gt]"
+				      " is allowed\n");
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--bytes-gt option cannot be inverted\n");
+		info->bytes.op = IPSET_COUNTER_GT;
+		info->bytes.value = parse_counter(optarg);
+		break;
+	case '9':
+		if (info->bytes.op != IPSET_COUNTER_NONE)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only one of the --bytes-[eq|lt|gt]"
+				      " is allowed\n");
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--bytes-lt option cannot be inverted\n");
+		info->bytes.op = IPSET_COUNTER_LT;
+		info->bytes.value = parse_counter(optarg);
+		break;
+	case '8':
+		if (info->bytes.op != IPSET_COUNTER_NONE)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only one of the --bytes-[eq|lt|gt]"
+				      " is allowed\n");
+		info->bytes.op = invert ? IPSET_COUNTER_NE : IPSET_COUNTER_EQ;
+		info->bytes.value = parse_counter(optarg);
+		break;
+	case '7':
+		if (info->packets.op != IPSET_COUNTER_NONE)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only one of the --packets-[eq|lt|gt]"
+				      " is allowed\n");
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--packets-gt option cannot be inverted\n");
+		info->packets.op = IPSET_COUNTER_GT;
+		info->packets.value = parse_counter(optarg);
+		break;
+	case '6':
+		if (info->packets.op != IPSET_COUNTER_NONE)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only one of the --packets-[eq|lt|gt]"
+				      " is allowed\n");
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--packets-lt option cannot be inverted\n");
+		info->packets.op = IPSET_COUNTER_LT;
+		info->packets.value = parse_counter(optarg);
+		break;
+	case '5':
+		if (info->packets.op != IPSET_COUNTER_NONE)
+			xtables_error(PARAMETER_PROBLEM,
+				      "only one of the --packets-[eq|lt|gt]"
+				      " is allowed\n");
+		info->packets.op = invert ? IPSET_COUNTER_NE : IPSET_COUNTER_EQ;
+		info->packets.value = parse_counter(optarg);
+		break;
+	case '4':
+		if (invert)
+			info->flags |= IPSET_FLAG_SKIP_COUNTER_UPDATE;
+		break;
+	case '3':
+		if (invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--return-nomatch flag cannot be inverted\n");
+		info->flags |= IPSET_FLAG_RETURN_NOMATCH;
+		break;
+	case '2':
+		fprintf(stderr,
+			"--set option deprecated, please use --match-set\n");
+		/* fall through */
+	case '1':		/* --match-set <set> <flag>[,<flag> */
+		if (info->match_set.dim)
+			xtables_error(PARAMETER_PROBLEM,
+				      "--match-set can be specified only once");
+		if (invert)
+			info->match_set.flags |= IPSET_INV_MATCH;
+
+		if (!argv[optind]
+		    || argv[optind][0] == '-'
+		    || argv[optind][0] == '!')
+			xtables_error(PARAMETER_PROBLEM,
+				      "--match-set requires two args.");
+
+		if (strlen(optarg) > IPSET_MAXNAMELEN - 1)
+			xtables_error(PARAMETER_PROBLEM,
+				      "setname `%s' too long, max %d characters.",
+				      optarg, IPSET_MAXNAMELEN - 1);
+
+		get_set_byname(optarg, &info->match_set);
+		parse_dirs(argv[optind], &info->match_set);
+		DEBUGP("parse: set index %u\n", info->match_set.index);
+		optind++;
+
+		*flags = 1;
+		break;
+	}
+
+	return 1;
+}
+
+static void
+set_printv4_counter(const struct ip_set_counter_match *c, const char *name,
+		    const char *sep)
+{
+	switch (c->op) {
+	case IPSET_COUNTER_EQ:
+		printf(" %s%s-eq %llu", sep, name, c->value);
+		break;
+	case IPSET_COUNTER_NE:
+		printf(" ! %s%s-eq %llu", sep, name, c->value);
+		break;
+	case IPSET_COUNTER_LT:
+		printf(" %s%s-lt %llu", sep, name, c->value);
+		break;
+	case IPSET_COUNTER_GT:
+		printf(" %s%s-gt %llu", sep, name, c->value);
+		break;
+	}
+}
+
+static void
+set_print_v4_matchinfo(const struct xt_set_info_match_v4 *info,
+		       const char *opt, const char *sep)
+{
+	print_match(opt, &info->match_set);
+	if (info->flags & IPSET_FLAG_RETURN_NOMATCH)
+		printf(" %sreturn-nomatch", sep);
+	if ((info->flags & IPSET_FLAG_SKIP_COUNTER_UPDATE))
+		printf(" ! %supdate-counters", sep);
+	if ((info->flags & IPSET_FLAG_SKIP_SUBCOUNTER_UPDATE))
+		printf(" ! %supdate-subcounters", sep);
+	set_printv4_counter(&info->packets, "packets", sep);
+	set_printv4_counter(&info->bytes, "bytes", sep);
+}
+
+/* Prints out the matchinfo. */
+static void
+set_print_v4(const void *ip, const struct xt_entry_match *match, int numeric)
+{
+	const struct xt_set_info_match_v4 *info = (const void *)match->data;
+
+	set_print_v4_matchinfo(info, "match-set", "");
+}
+
+static void
+set_save_v4(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_set_info_match_v4 *info = (const void *)match->data;
+
+	set_print_v4_matchinfo(info, "--match-set", "--");
+}
+
 static struct xtables_match set_mt_reg[] = {
 	{
 		.name		= "set",
@@ -236,12 +692,54 @@
 		.family		= NFPROTO_UNSPEC,
 		.size		= XT_ALIGN(sizeof(struct xt_set_info_match_v1)),
 		.userspacesize	= XT_ALIGN(sizeof(struct xt_set_info_match_v1)),
-		.help		= set_help_v1,
+		.help		= set_help_v0,
 		.parse		= set_parse_v1,
-		.final_check	= set_check_v1,
+		.final_check	= set_check_v0,
 		.print		= set_print_v1,
 		.save		= set_save_v1,
-		.extra_opts	= set_opts_v1,
+		.extra_opts	= set_opts_v0,
+	},
+	{
+		.name		= "set",
+		.revision	= 2,
+		.version	= XTABLES_VERSION,
+		.family		= NFPROTO_UNSPEC,
+		.size		= XT_ALIGN(sizeof(struct xt_set_info_match_v1)),
+		.userspacesize	= XT_ALIGN(sizeof(struct xt_set_info_match_v1)),
+		.help		= set_help_v2,
+		.parse		= set_parse_v2,
+		.final_check	= set_check_v0,
+		.print		= set_print_v2,
+		.save		= set_save_v2,
+		.extra_opts	= set_opts_v2,
+	},
+	{
+		.name		= "set",
+		.revision	= 3,
+		.version	= XTABLES_VERSION,
+		.family		= NFPROTO_UNSPEC,
+		.size		= XT_ALIGN(sizeof(struct xt_set_info_match_v3)),
+		.userspacesize	= XT_ALIGN(sizeof(struct xt_set_info_match_v3)),
+		.help		= set_help_v3,
+		.parse		= set_parse_v3,
+		.final_check	= set_check_v0,
+		.print		= set_print_v3,
+		.save		= set_save_v3,
+		.extra_opts	= set_opts_v3,
+	},
+	{
+		.name		= "set",
+		.revision	= 4,
+		.version	= XTABLES_VERSION,
+		.family		= NFPROTO_UNSPEC,
+		.size		= XT_ALIGN(sizeof(struct xt_set_info_match_v4)),
+		.userspacesize	= XT_ALIGN(sizeof(struct xt_set_info_match_v4)),
+		.help		= set_help_v3,
+		.parse		= set_parse_v4,
+		.final_check	= set_check_v0,
+		.print		= set_print_v4,
+		.save		= set_save_v4,
+		.extra_opts	= set_opts_v3,
 	},
 };
 
diff --git a/extensions/libxt_set.h b/extensions/libxt_set.h
index 4ac84fa..41dfbd3 100644
--- a/extensions/libxt_set.h
+++ b/extensions/libxt_set.h
@@ -2,15 +2,11 @@
 #define _LIBXT_SET_H
 
 #include <unistd.h>
+#include <fcntl.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <errno.h>
-
-#ifdef DEBUG
-#define DEBUGP(x, args...) fprintf(stderr, x , ## args)
-#else
-#define DEBUGP(x, args...) 
-#endif
+#include "../iptables/xshared.h"
 
 static int
 get_version(unsigned *version)
@@ -23,6 +19,12 @@
 		xtables_error(OTHER_PROBLEM,
 			      "Can't open socket to ipset.\n");
 
+	if (fcntl(sockfd, F_SETFD, FD_CLOEXEC) == -1) {
+		xtables_error(OTHER_PROBLEM,
+			      "Could not set close on exec: %s\n",
+			      strerror(errno));
+	}
+
 	req_version.op = IP_SET_OP_VERSION;
 	res = getsockopt(sockfd, SOL_IP, SO_IP_SET, &req_version, &size);
 	if (res != 0)
@@ -64,13 +66,13 @@
 }
 
 static void
-get_set_byname(const char *setname, struct xt_set_info *info)
+get_set_byname_only(const char *setname, struct xt_set_info *info,
+		    int sockfd, unsigned int version)
 {
-	struct ip_set_req_get_set req;
+	struct ip_set_req_get_set req = { .version = version };
 	socklen_t size = sizeof(struct ip_set_req_get_set);
-	int res, sockfd;
+	int res;
 
-	sockfd = get_version(&req.version);
 	req.op = IP_SET_OP_GET_BYNAME;
 	strncpy(req.set.name, setname, IPSET_MAXNAMELEN);
 	req.set.name[IPSET_MAXNAMELEN - 1] = '\0';
@@ -94,6 +96,49 @@
 }
 
 static void
+get_set_byname(const char *setname, struct xt_set_info *info)
+{
+	struct ip_set_req_get_set_family req;
+	socklen_t size = sizeof(struct ip_set_req_get_set_family);
+	int res, sockfd, version;
+
+	sockfd = get_version(&req.version);
+	version = req.version;
+	req.op = IP_SET_OP_GET_FNAME;
+	strncpy(req.set.name, setname, IPSET_MAXNAMELEN);
+	req.set.name[IPSET_MAXNAMELEN - 1] = '\0';
+	res = getsockopt(sockfd, SOL_IP, SO_IP_SET, &req, &size);
+
+	if (res != 0 && errno == EBADMSG)
+		/* Backward compatibility */
+		return get_set_byname_only(setname, info, sockfd, version);
+
+	close(sockfd);
+	if (res != 0)
+		xtables_error(OTHER_PROBLEM,
+			"Problem when communicating with ipset, errno=%d.\n",
+			errno);
+	if (size != sizeof(struct ip_set_req_get_set_family))
+		xtables_error(OTHER_PROBLEM,
+			"Incorrect return size from kernel during ipset lookup, "
+			"(want %zu, got %zu)\n",
+			sizeof(struct ip_set_req_get_set_family),
+			(size_t)size);
+	if (req.set.index == IPSET_INVALID_ID)
+		xtables_error(PARAMETER_PROBLEM,
+			      "Set %s doesn't exist.\n", setname);
+	if (!(req.family == afinfo->family ||
+	      req.family == NFPROTO_UNSPEC))
+		xtables_error(PARAMETER_PROBLEM,
+			      "The protocol family of set %s is %s, "
+			      "which is not applicable.\n",
+			      setname,
+			      req.family == NFPROTO_IPV4 ? "IPv4" : "IPv6");
+
+	info->index = req.set.index;
+}
+
+static void
 parse_dirs_v0(const char *opt_arg, struct xt_set_info_v0 *info)
 {
 	char *saved = strdup(opt_arg);
diff --git a/extensions/libxt_set.man b/extensions/libxt_set.man
index 01b115f..5c6f64e 100644
--- a/extensions/libxt_set.man
+++ b/extensions/libxt_set.man
@@ -14,10 +14,52 @@
 the set type of the specified set is single dimension (for example ipmap),
 then the command will match packets for which the source address can be
 found in the specified set. 
+.TP
+\fB\-\-return\-nomatch\fP
+If the \fB\-\-return\-nomatch\fP option is specified and the set type
+supports the \fBnomatch\fP flag, then the matching is reversed: a match
+with an element flagged with \fBnomatch\fP returns \fBtrue\fP, while a
+match with a plain element returns \fBfalse\fP.
+.TP
+\fB!\fP \fB\-\-update\-counters\fP
+If the \fB\-\-update\-counters\fP flag is negated, then the packet and
+byte counters of the matching element in the set won't be updated. Default
+the packet and byte counters are updated.
+.TP
+\fB!\fP \fB\-\-update\-subcounters\fP
+If the \fB\-\-update\-subcounters\fP flag is negated, then the packet and
+byte counters of the matching element in the member set of a list type of
+set won't be updated. Default the packet and byte counters are updated.
+.TP
+[\fB!\fP] \fB\-\-packets\-eq\fP \fIvalue\fP
+If the packet is matched an element in the set, match only if the
+packet counter of the element matches the given value too.
+.TP
+\fB\-\-packets\-lt\fP \fIvalue\fP
+If the packet is matched an element in the set, match only if the
+packet counter of the element is less than the given value as well.
+.TP
+\fB\-\-packets\-gt\fP \fIvalue\fP
+If the packet is matched an element in the set, match only if the
+packet counter of the element is greater than the given value as well.
+.TP
+[\fB!\fP] \fB\-\-bytes\-eq\fP \fIvalue\fP
+If the packet is matched an element in the set, match only if the
+byte counter of the element matches the given value too.
+.TP
+\fB\-\-bytes\-lt\fP \fIvalue\fP
+If the packet is matched an element in the set, match only if the
+byte counter of the element is less than the given value as well.
+.TP
+\fB\-\-bytes\-gt\fP \fIvalue\fP
+If the packet is matched an element in the set, match only if the
+byte counter of the element is greater than the given value as well.
+.PP
+The packet and byte counters related options and flags are ignored
+when the set was defined without counter support.
 .PP
 The option \fB\-\-match\-set\fP can be replaced by \fB\-\-set\fP if that does 
 not clash with an option of other extensions.
 .PP
-Use of -m set requires that ipset kernel support is provided. As standard
-kernels do not ship this currently, the ipset or Xtables-addons package needs
-to be installed.
+Use of \-m set requires that ipset kernel support is provided, which, for
+standard kernels, is the case since Linux 2.6.39.
diff --git a/extensions/libxt_set.t b/extensions/libxt_set.t
new file mode 100644
index 0000000..dd9e9f1
--- /dev/null
+++ b/extensions/libxt_set.t
@@ -0,0 +1,4 @@
+:INPUT,FORWARD,OUTPUT
+-m set --match-set foo;;FAIL
+# fails: foo does not exist
+-m set --match-set foo src,dst;;FAIL
diff --git a/extensions/libxt_socket.c b/extensions/libxt_socket.c
index 3901649..a99135c 100644
--- a/extensions/libxt_socket.c
+++ b/extensions/libxt_socket.c
@@ -9,6 +9,8 @@
 
 enum {
 	O_TRANSPARENT = 0,
+	O_NOWILDCARD = 1,
+	O_RESTORESKMARK = 2,
 };
 
 static const struct xt_option_entry socket_mt_opts[] = {
@@ -16,6 +18,19 @@
 	XTOPT_TABLEEND,
 };
 
+static const struct xt_option_entry socket_mt_opts_v2[] = {
+	{.name = "transparent", .id = O_TRANSPARENT, .type = XTTYPE_NONE},
+	{.name = "nowildcard", .id = O_NOWILDCARD, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+
+static const struct xt_option_entry socket_mt_opts_v3[] = {
+	{.name = "transparent", .id = O_TRANSPARENT, .type = XTTYPE_NONE},
+	{.name = "nowildcard", .id = O_NOWILDCARD, .type = XTTYPE_NONE},
+	{.name = "restore-skmark", .id = O_RESTORESKMARK, .type = XTTYPE_NONE},
+	XTOPT_TABLEEND,
+};
+
 static void socket_mt_help(void)
 {
 	printf(
@@ -23,6 +38,25 @@
 		"  --transparent    Ignore non-transparent sockets\n\n");
 }
 
+static void socket_mt_help_v2(void)
+{
+	printf(
+		"socket match options:\n"
+		"  --nowildcard     Do not ignore LISTEN sockets bound on INADDR_ANY\n"
+		"  --transparent    Ignore non-transparent sockets\n\n");
+}
+
+static void socket_mt_help_v3(void)
+{
+	printf(
+		"socket match options:\n"
+		"  --nowildcard     Do not ignore LISTEN sockets bound on INADDR_ANY\n"
+		"  --transparent    Ignore non-transparent sockets\n"
+		"  --restore-skmark Set the packet mark to the socket mark if\n"
+		"                   the socket matches and transparent / \n"
+		"                   nowildcard conditions are satisfied\n\n");
+}
+
 static void socket_mt_parse(struct xt_option_call *cb)
 {
 	struct xt_socket_mtinfo1 *info = cb->data;
@@ -35,6 +69,39 @@
 	}
 }
 
+static void socket_mt_parse_v2(struct xt_option_call *cb)
+{
+	struct xt_socket_mtinfo2 *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_TRANSPARENT:
+		info->flags |= XT_SOCKET_TRANSPARENT;
+		break;
+	case O_NOWILDCARD:
+		info->flags |= XT_SOCKET_NOWILDCARD;
+		break;
+	}
+}
+
+static void socket_mt_parse_v3(struct xt_option_call *cb)
+{
+	struct xt_socket_mtinfo2 *info = cb->data;
+
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_TRANSPARENT:
+		info->flags |= XT_SOCKET_TRANSPARENT;
+		break;
+	case O_NOWILDCARD:
+		info->flags |= XT_SOCKET_NOWILDCARD;
+		break;
+	case O_RESTORESKMARK:
+		info->flags |= XT_SOCKET_RESTORESKMARK;
+		break;
+	}
+}
+
 static void
 socket_mt_save(const void *ip, const struct xt_entry_match *match)
 {
@@ -52,6 +119,46 @@
 	socket_mt_save(ip, match);
 }
 
+static void
+socket_mt_save_v2(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_socket_mtinfo2 *info = (const void *)match->data;
+
+	if (info->flags & XT_SOCKET_TRANSPARENT)
+		printf(" --transparent");
+	if (info->flags & XT_SOCKET_NOWILDCARD)
+		printf(" --nowildcard");
+}
+
+static void
+socket_mt_print_v2(const void *ip, const struct xt_entry_match *match,
+		   int numeric)
+{
+	printf(" socket");
+	socket_mt_save_v2(ip, match);
+}
+
+static void
+socket_mt_save_v3(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_socket_mtinfo3 *info = (const void *)match->data;
+
+	if (info->flags & XT_SOCKET_TRANSPARENT)
+		printf(" --transparent");
+	if (info->flags & XT_SOCKET_NOWILDCARD)
+		printf(" --nowildcard");
+	if (info->flags & XT_SOCKET_RESTORESKMARK)
+		printf(" --restore-skmark");
+}
+
+static void
+socket_mt_print_v3(const void *ip, const struct xt_entry_match *match,
+		   int numeric)
+{
+	printf(" socket");
+	socket_mt_save_v3(ip, match);
+}
+
 static struct xtables_match socket_mt_reg[] = {
 	{
 		.name          = "socket",
@@ -74,6 +181,32 @@
 		.x6_parse      = socket_mt_parse,
 		.x6_options    = socket_mt_opts,
 	},
+	{
+		.name          = "socket",
+		.revision      = 2,
+		.family        = NFPROTO_UNSPEC,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_socket_mtinfo2)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_socket_mtinfo2)),
+		.help          = socket_mt_help_v2,
+		.print         = socket_mt_print_v2,
+		.save          = socket_mt_save_v2,
+		.x6_parse      = socket_mt_parse_v2,
+		.x6_options    = socket_mt_opts_v2,
+	},
+	{
+		.name          = "socket",
+		.revision      = 3,
+		.family        = NFPROTO_UNSPEC,
+		.version       = XTABLES_VERSION,
+		.size          = XT_ALIGN(sizeof(struct xt_socket_mtinfo2)),
+		.userspacesize = XT_ALIGN(sizeof(struct xt_socket_mtinfo2)),
+		.help          = socket_mt_help_v3,
+		.print         = socket_mt_print_v3,
+		.save          = socket_mt_save_v3,
+		.x6_parse      = socket_mt_parse_v3,
+		.x6_options    = socket_mt_opts_v3,
+	},
 };
 
 void _init(void)
diff --git a/extensions/libxt_socket.man b/extensions/libxt_socket.man
index 41e8d67..f809df6 100644
--- a/extensions/libxt_socket.man
+++ b/extensions/libxt_socket.man
@@ -1,5 +1,36 @@
-This matches if an open socket can be found by doing a socket lookup on the
-packet.
+This matches if an open TCP/UDP socket can be found by doing a socket lookup on the
+packet. It matches if there is an established or non\-zero bound listening
+socket (possibly with a non\-local address). The lookup is performed using
+the \fBpacket\fP tuple of TCP/UDP packets, or the original TCP/UDP header
+\fBembedded\fP in an ICMP/ICPMv6 error packet.
 .TP
 \fB\-\-transparent\fP
 Ignore non-transparent sockets.
+.TP
+\fB\-\-nowildcard\fP
+Do not ignore sockets bound to 'any' address.
+The socket match won't accept zero\-bound listeners by default, since
+then local services could intercept traffic that would otherwise be forwarded.
+This option therefore has security implications when used to match traffic being
+forwarded to redirect such packets to local machine with policy routing.
+When using the socket match to implement fully transparent
+proxies bound to non\-local addresses it is recommended to use the \-\-transparent
+option instead.
+.PP
+Example (assuming packets with mark 1 are delivered locally):
+.IP
+\-t mangle \-A PREROUTING \-m socket \-\-transparent \-j MARK \-\-set\-mark 1
+.TP
+\fB\-\-restore\-skmark\fP
+Set the packet mark to the matching socket's mark. Can be combined with the
+\fB\-\-transparent\fP and \fB\-\-nowildcard\fP options to restrict the sockets
+to be matched when restoring the packet mark.
+.PP
+Example: An application opens 2 transparent (\fBIP_TRANSPARENT\fP) sockets and
+sets a mark on them with \fBSO_MARK\fP socket option. We can filter matching packets:
+.IP
+\-t mangle \-I PREROUTING \-m socket \-\-transparent \-\-restore-skmark \-j action
+.IP
+\-t mangle \-A action \-m mark \-\-mark 10 \-j action2
+.IP
+\-t mangle \-A action \-m mark \-\-mark 11 \-j action3
diff --git a/extensions/libxt_socket.t b/extensions/libxt_socket.t
new file mode 100644
index 0000000..fe4eb3e
--- /dev/null
+++ b/extensions/libxt_socket.t
@@ -0,0 +1,8 @@
+:PREROUTING,INPUT
+*mangle
+-m socket;=;OK
+-m socket --transparent --nowildcard;=;OK
+-m socket --transparent --nowildcard --restore-skmark;=;OK
+-m socket --transparent --restore-skmark;=;OK
+-m socket --nowildcard --restore-skmark;=;OK
+-m socket --restore-skmark;=;OK
diff --git a/extensions/libxt_standard.t b/extensions/libxt_standard.t
new file mode 100644
index 0000000..4313f7b
--- /dev/null
+++ b/extensions/libxt_standard.t
@@ -0,0 +1,11 @@
+:INPUT,FORWARD,OUTPUT
+-s 127.0.0.1/32 -d 0.0.0.0/8 -j DROP;=;OK
+! -s 0.0.0.0 -j ACCEPT;! -s 0.0.0.0/32 -j ACCEPT;OK
+! -d 0.0.0.0/32 -j ACCEPT;=;OK
+-s 0.0.0.0/24 -j RETURN;=;OK
+-p tcp -j ACCEPT;=;OK
+! -p udp -j ACCEPT;=;OK
+-j DROP;=;OK
+-j ACCEPT;=;OK
+-j RETURN;=;OK
+! -p 0 -j ACCEPT;=;FAIL
diff --git a/extensions/libxt_state.c b/extensions/libxt_state.c
deleted file mode 100644
index 3fc747d..0000000
--- a/extensions/libxt_state.c
+++ /dev/null
@@ -1,137 +0,0 @@
-#include <stdio.h>
-#include <string.h>
-#include <xtables.h>
-#include <linux/netfilter/nf_conntrack_common.h>
-#include <linux/netfilter/xt_state.h>
-
-#ifndef XT_STATE_UNTRACKED
-#define XT_STATE_UNTRACKED (1 << (IP_CT_NUMBER + 1))
-#endif
-
-enum {
-	O_STATE = 0,
-};
-
-static void
-state_help(void)
-{
-	printf(
-"state match options:\n"
-" [!] --state [INVALID|ESTABLISHED|NEW|RELATED|UNTRACKED][,...]\n"
-"				State(s) to match\n");
-}
-
-static const struct xt_option_entry state_opts[] = {
-	{.name = "state", .id = O_STATE, .type = XTTYPE_STRING,
-	 .flags = XTOPT_MAND},
-	XTOPT_TABLEEND,
-};
-
-static int
-state_parse_state(const char *state, size_t len, struct xt_state_info *sinfo)
-{
-	if (strncasecmp(state, "INVALID", len) == 0)
-		sinfo->statemask |= XT_STATE_INVALID;
-	else if (strncasecmp(state, "NEW", len) == 0)
-		sinfo->statemask |= XT_STATE_BIT(IP_CT_NEW);
-	else if (strncasecmp(state, "ESTABLISHED", len) == 0)
-		sinfo->statemask |= XT_STATE_BIT(IP_CT_ESTABLISHED);
-	else if (strncasecmp(state, "RELATED", len) == 0)
-		sinfo->statemask |= XT_STATE_BIT(IP_CT_RELATED);
-	else if (strncasecmp(state, "UNTRACKED", len) == 0)
-		sinfo->statemask |= XT_STATE_UNTRACKED;
-	else
-		return 0;
-	return 1;
-}
-
-static void
-state_parse_states(const char *arg, struct xt_state_info *sinfo)
-{
-	const char *comma;
-
-	while ((comma = strchr(arg, ',')) != NULL) {
-		if (comma == arg || !state_parse_state(arg, comma-arg, sinfo))
-			xtables_error(PARAMETER_PROBLEM, "Bad state \"%s\"", arg);
-		arg = comma+1;
-	}
-	if (!*arg)
-		xtables_error(PARAMETER_PROBLEM, "\"--state\" requires a list of "
-					      "states with no spaces, e.g. "
-					      "ESTABLISHED,RELATED");
-	if (strlen(arg) == 0 || !state_parse_state(arg, strlen(arg), sinfo))
-		xtables_error(PARAMETER_PROBLEM, "Bad state \"%s\"", arg);
-}
-
-static void state_parse(struct xt_option_call *cb)
-{
-	struct xt_state_info *sinfo = cb->data;
-
-	xtables_option_parse(cb);
-	state_parse_states(cb->arg, sinfo);
-	if (cb->invert)
-		sinfo->statemask = ~sinfo->statemask;
-}
-
-static void state_print_state(unsigned int statemask)
-{
-	const char *sep = "";
-
-	if (statemask & XT_STATE_INVALID) {
-		printf("%sINVALID", sep);
-		sep = ",";
-	}
-	if (statemask & XT_STATE_BIT(IP_CT_NEW)) {
-		printf("%sNEW", sep);
-		sep = ",";
-	}
-	if (statemask & XT_STATE_BIT(IP_CT_RELATED)) {
-		printf("%sRELATED", sep);
-		sep = ",";
-	}
-	if (statemask & XT_STATE_BIT(IP_CT_ESTABLISHED)) {
-		printf("%sESTABLISHED", sep);
-		sep = ",";
-	}
-	if (statemask & XT_STATE_UNTRACKED) {
-		printf("%sUNTRACKED", sep);
-		sep = ",";
-	}
-}
-
-static void
-state_print(const void *ip,
-      const struct xt_entry_match *match,
-      int numeric)
-{
-	const struct xt_state_info *sinfo = (const void *)match->data;
-
-	printf(" state ");
-	state_print_state(sinfo->statemask);
-}
-
-static void state_save(const void *ip, const struct xt_entry_match *match)
-{
-	const struct xt_state_info *sinfo = (const void *)match->data;
-
-	printf(" --state ");
-	state_print_state(sinfo->statemask);
-}
-
-static struct xtables_match state_match = { 
-	.family		= NFPROTO_UNSPEC,
-	.name		= "state",
-	.version	= XTABLES_VERSION,
-	.size		= XT_ALIGN(sizeof(struct xt_state_info)),
-	.userspacesize	= XT_ALIGN(sizeof(struct xt_state_info)),
-	.help		= state_help,
-	.print		= state_print,
-	.save		= state_save,
-	.x6_parse	= state_parse,
-	.x6_options	= state_opts,
-};
-
-void _init(void)
-{
-	xtables_register_match(&state_match);
-}
diff --git a/extensions/libxt_state.man b/extensions/libxt_state.man
index 37d095b..ec096ca 100644
--- a/extensions/libxt_state.man
+++ b/extensions/libxt_state.man
@@ -1,24 +1,8 @@
-This module, when combined with connection tracking, allows access to
-the connection tracking state for this packet.
+The "state" extension is a subset of the "conntrack" module.
+"state" allows access to the connection tracking state for this packet.
 .TP
 [\fB!\fP] \fB\-\-state\fP \fIstate\fP
-Where state is a comma separated list of the connection states to
-match.  Possible states are
-.B INVALID
-meaning that the packet could not be identified for some reason which
-includes running out of memory and ICMP errors which don't correspond to any
-known connection,
-.B ESTABLISHED
-meaning that the packet is associated with a connection which has seen
-packets in both directions,
-.B NEW
-meaning that the packet has started a new connection, or otherwise
-associated with a connection which has not seen packets in both
-directions, and
-.B RELATED
-meaning that the packet is starting a new connection, but is
-associated with an existing connection, such as an FTP data transfer,
-or an ICMP error.
-.B UNTRACKED
-meaning that the packet is not tracked at all, which happens if you use
-the NOTRACK target in raw table.
+Where state is a comma separated list of the connection states to match. Only a
+subset of the states unterstood by "conntrack" are recognized: \fBINVALID\fP,
+\fBESTABLISHED\fP, \fBNEW\fP, \fBRELATED\fP or \fBUNTRACKED\fP. For their
+description, see the "conntrack" heading in this manpage.
diff --git a/extensions/libxt_state.t b/extensions/libxt_state.t
new file mode 100644
index 0000000..8e4bce3
--- /dev/null
+++ b/extensions/libxt_state.t
@@ -0,0 +1,6 @@
+:INPUT,FORWARD,OUTPUT
+-m state --state INVALID;=;OK
+-m state --state NEW,RELATED;=;OK
+-m state --state UNTRACKED;=;OK
+-m state wrong;;FAIL
+-m state;;FAIL
diff --git a/extensions/libxt_statistic.c b/extensions/libxt_statistic.c
index 12a83dd..4f3341a 100644
--- a/extensions/libxt_statistic.c
+++ b/extensions/libxt_statistic.c
@@ -1,3 +1,7 @@
+/*
+ * Copyright (c) 2006-2013 Patrick McHardy <kaber@trash.net>
+ */
+
 #include <math.h>
 #include <stdio.h>
 #include <string.h>
@@ -107,7 +111,7 @@
 		       (info->flags & XT_STATISTIC_INVERT) ? " !" : "",
 		       prefix,
 		       info->u.nth.every + 1);
-		if (info->u.nth.packet)
+		if (info->u.nth.packet || *prefix)
 			printf(" %spacket %u", prefix, info->u.nth.packet);
 		break;
 	}
@@ -129,6 +133,26 @@
 	print_match(info, "--");
 }
 
+static int statistic_xlate(struct xt_xlate *xl,
+			   const struct xt_xlate_mt_params *params)
+{
+	const struct xt_statistic_info *info =
+		(struct xt_statistic_info *)params->match->data;
+
+	switch (info->mode) {
+	case XT_STATISTIC_MODE_RANDOM:
+		return 0;
+	case XT_STATISTIC_MODE_NTH:
+		xt_xlate_add(xl, "numgen inc mod %u %s%u",
+			     info->u.nth.every + 1,
+			     info->flags & XT_STATISTIC_INVERT ? "!= " : "",
+			     info->u.nth.packet);
+		break;
+	}
+
+	return 1;
+}
+
 static struct xtables_match statistic_match = {
 	.family		= NFPROTO_UNSPEC,
 	.name		= "statistic",
@@ -141,6 +165,7 @@
 	.print		= statistic_print,
 	.save		= statistic_save,
 	.x6_options	= statistic_opts,
+	.xlate		= statistic_xlate,
 };
 
 void _init(void)
diff --git a/extensions/libxt_statistic.t b/extensions/libxt_statistic.t
new file mode 100644
index 0000000..bb6673d
--- /dev/null
+++ b/extensions/libxt_statistic.t
@@ -0,0 +1,8 @@
+:INPUT,FORWARD,OUTPUT
+-m statistic;;FAIL
+-m statistic --mode random ! --probability 0.50000000000;=;OK
+-m statistic --mode random ! --probability 1.1;;FAIL
+-m statistic --probability 1;;FAIL
+-m statistic --mode nth ! --every 5 --packet 2;=;OK
+-m statistic --mode nth ! --every 5;;FAIL
+-m statistic --mode nth ! --every 5 --packet 5;;FAIL
diff --git a/extensions/libxt_statistic.txlate b/extensions/libxt_statistic.txlate
new file mode 100644
index 0000000..4c3dea4
--- /dev/null
+++ b/extensions/libxt_statistic.txlate
@@ -0,0 +1,8 @@
+iptables-translate -A OUTPUT -m statistic --mode nth --every 10 --packet 1
+nft add rule ip filter OUTPUT numgen inc mod 10 1 counter
+
+iptables-translate -A OUTPUT -m statistic --mode nth ! --every 10 --packet 5
+nft add rule ip filter OUTPUT numgen inc mod 10 != 5 counter
+
+iptables-translate -A OUTPUT -m statistic --mode random --probability 0.1
+nft # -A OUTPUT -m statistic --mode random --probability 0.1
diff --git a/extensions/libxt_string.c b/extensions/libxt_string.c
index eef0b08..7c6366c 100644
--- a/extensions/libxt_string.c
+++ b/extensions/libxt_string.c
@@ -20,6 +20,7 @@
  *             updated to work with slightly modified
  *             ipt_string_info.
  */
+#define _GNU_SOURCE 1 /* strnlen for older glibcs */
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
@@ -102,6 +103,9 @@
 	}
 
 	while (i < slen) {
+		if (sindex >= XT_STRING_MAX_PATTERN_SIZE)
+			xtables_error(PARAMETER_PROBLEM,
+				      "STRING too long \"%s\"", s);
 		if (s[i] == '\\' && !hex_f) {
 			literal_f = 1;
 		} else if (s[i] == '\\') {
@@ -158,8 +162,6 @@
 			info->pattern[sindex] = s[i];
 			i++;
 		}
-		if (sindex > XT_STRING_MAX_PATTERN_SIZE)
-			xtables_error(PARAMETER_PROBLEM, "STRING too long \"%s\"", s);
 		sindex++;
 	}
 	info->patlen = sindex;
@@ -217,7 +219,7 @@
 		if (! isprint(str[i]))
 			return 1;  /* string contains at least one non-printable char */
 	/* use hex output if the last char is a "\" */
-	if ((unsigned char) str[len-1] == 0x5c)
+	if (str[len-1] == '\\')
 		return 1;
 	return 0;
 }
@@ -228,16 +230,11 @@
 {
 	unsigned int i;
 	/* start hex block */
-	printf("\"|");
-	for (i=0; i < len; i++) {
-		/* see if we need to prepend a zero */
-		if ((unsigned char) str[i] <= 0x0F)
-			printf("0%x", (unsigned char) str[i]);
-		else
-			printf("%x", (unsigned char) str[i]);
-	}
+	printf(" \"|");
+	for (i=0; i < len; i++)
+		printf("%02x", (unsigned char)str[i]);
 	/* close hex block */
-	printf("|\" ");
+	printf("|\"");
 }
 
 static void
@@ -246,8 +243,8 @@
 	unsigned int i;
 	printf(" \"");
 	for (i=0; i < len; i++) {
-		if ((unsigned char) str[i] == 0x22)  /* escape any embedded quotes */
-			printf("%c", 0x5c);
+		if (str[i] == '\"' || str[i] == '\\')
+			putchar('\\');
 		printf("%c", (unsigned char) str[i]);
 	}
 	printf("\"");  /* closing quote */
diff --git a/extensions/libxt_string.man b/extensions/libxt_string.man
index b6b271d..5f1a993 100644
--- a/extensions/libxt_string.man
+++ b/extensions/libxt_string.man
@@ -1,4 +1,4 @@
-This modules matches a given string by using some pattern matching strategy. It requires a linux kernel >= 2.6.14.
+This module matches a given string by using some pattern matching strategy. It requires a linux kernel >= 2.6.14.
 .TP
 \fB\-\-algo\fP {\fBbm\fP|\fBkmp\fP}
 Select the pattern matching strategy. (bm = Boyer-Moore, kmp = Knuth-Pratt-Morris)
@@ -16,3 +16,16 @@
 .TP
 [\fB!\fP] \fB\-\-hex\-string\fP \fIpattern\fP
 Matches the given pattern in hex notation.
+.TP
+\fB\-\-icase\fP
+Ignore case when searching.
+.TP
+Examples:
+.IP
+# The string pattern can be used for simple text characters.
+.br
+iptables \-A INPUT \-p tcp \-\-dport 80 \-m string \-\-algo bm \-\-string 'GET /index.html' \-j LOG
+.IP
+# The hex string pattern can be used for non-printable characters, like |0D 0A| or |0D0A|.
+.br
+iptables \-p udp \-\-dport 53 \-m string \-\-algo bm \-\-from 40 \-\-to 57 \-\-hex\-string '|03|www|09|netfilter|03|org|00|'
diff --git a/extensions/libxt_string.t b/extensions/libxt_string.t
new file mode 100644
index 0000000..d68f099
--- /dev/null
+++ b/extensions/libxt_string.t
@@ -0,0 +1,18 @@
+:INPUT,FORWARD,OUTPUT
+# ERROR: cannot find: iptables -I INPUT -m string --algo bm --string "test"
+# -m string --algo bm --string "test";=;OK
+# ERROR: cannot find: iptables -I INPUT -m string --algo kmp --string "test")
+# -m string --algo kmp --string "test";=;OK
+# ERROR: cannot find: iptables -I INPUT -m string --algo kmp ! --string "test"
+# -m string --algo kmp ! --string "test";=;OK
+# cannot find: iptables -I INPUT -m string --algo bm --string "xxxxxxxxxxx" ....]
+# -m string --algo bm --string "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";=;OK
+# ERROR: cannot load: iptables -A INPUT -m string --algo bm --string "xxxx"
+# -m string --algo bm --string "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";=;OK
+# ERROR: cannot load: iptables -A INPUT -m string --algo bm --hexstring "|0a0a0a0a|"
+# -m string --algo bm --hexstring "|0a0a0a0a|";=;OK
+# ERROR: cannot find: iptables -I INPUT -m string --algo bm --from 0 --to 65535 --string "test"
+# -m string --algo bm --from 0 --to 65535 --string "test";=;OK
+-m string --algo wrong;;FAIL
+-m string --algo bm;;FAIL
+-m string;;FAIL
diff --git a/extensions/libxt_tcp.c b/extensions/libxt_tcp.c
index 4d914e3..58f3c0a 100644
--- a/extensions/libxt_tcp.c
+++ b/extensions/libxt_tcp.c
@@ -148,7 +148,6 @@
 		if (*flags & TCP_SRC_PORTS)
 			xtables_error(PARAMETER_PROBLEM,
 				   "Only one `--source-port' allowed");
-		xtables_check_inverse(optarg, &invert, &optind, 0, argv);
 		parse_tcp_ports(optarg, tcpinfo->spts);
 		if (invert)
 			tcpinfo->invflags |= XT_TCP_INV_SRCPT;
@@ -159,7 +158,6 @@
 		if (*flags & TCP_DST_PORTS)
 			xtables_error(PARAMETER_PROBLEM,
 				   "Only one `--destination-port' allowed");
-		xtables_check_inverse(optarg, &invert, &optind, 0, argv);
 		parse_tcp_ports(optarg, tcpinfo->dpts);
 		if (invert)
 			tcpinfo->invflags |= XT_TCP_INV_DSTPT;
@@ -180,8 +178,6 @@
 			xtables_error(PARAMETER_PROBLEM,
 				   "Only one of `--syn' or `--tcp-flags' "
 				   " allowed");
-		xtables_check_inverse(optarg, &invert, &optind, 0, argv);
-
 		if (!argv[optind]
 		    || argv[optind][0] == '-' || argv[optind][0] == '!')
 			xtables_error(PARAMETER_PROBLEM,
@@ -197,7 +193,6 @@
 		if (*flags & TCP_OPTION)
 			xtables_error(PARAMETER_PROBLEM,
 				   "Only one `--tcp-option' allowed");
-		xtables_check_inverse(optarg, &invert, &optind, 0, argv);
 		parse_tcp_option(optarg, &tcpinfo->option);
 		if (invert)
 			tcpinfo->invflags |= XT_TCP_INV_OPTION;
@@ -283,11 +278,10 @@
 print_flags(uint8_t mask, uint8_t cmp, int invert, int numeric)
 {
 	if (mask || invert) {
-		printf("flags:%s", invert ? "!" : "");
+		printf(" flags:%s", invert ? "!" : "");
 		if (numeric)
-			printf(" 0x%02X/0x%02X", mask, cmp);
+			printf("0x%02X/0x%02X", mask, cmp);
 		else {
-			printf(" ");
 			print_tcpf(mask);
 			printf("/");
 			print_tcpf(cmp);
@@ -362,14 +356,95 @@
 		if (tcpinfo->invflags & XT_TCP_INV_FLAGS)
 			printf(" !");
 		printf(" --tcp-flags ");
-		if (tcpinfo->flg_mask != 0xFF) {
-			print_tcpf(tcpinfo->flg_mask);
-		}
+		print_tcpf(tcpinfo->flg_mask);
 		printf(" ");
 		print_tcpf(tcpinfo->flg_cmp);
 	}
 }
 
+static const struct tcp_flag_names tcp_flag_names_xlate[] = {
+	{ "fin", 0x01 },
+	{ "syn", 0x02 },
+	{ "rst", 0x04 },
+	{ "psh", 0x08 },
+	{ "ack", 0x10 },
+	{ "urg", 0x20 },
+};
+
+static void print_tcp_xlate(struct xt_xlate *xl, uint8_t flags)
+{
+	int have_flag = 0;
+
+	while (flags) {
+		unsigned int i;
+
+		for (i = 0; (flags & tcp_flag_names_xlate[i].flag) == 0; i++);
+
+		if (have_flag)
+			xt_xlate_add(xl, "|");
+
+		xt_xlate_add(xl, "%s", tcp_flag_names_xlate[i].name);
+		have_flag = 1;
+
+		flags &= ~tcp_flag_names_xlate[i].flag;
+	}
+
+	if (!have_flag)
+		xt_xlate_add(xl, "0x0");
+}
+
+static int tcp_xlate(struct xt_xlate *xl,
+		     const struct xt_xlate_mt_params *params)
+{
+	const struct xt_tcp *tcpinfo =
+		(const struct xt_tcp *)params->match->data;
+	char *space= "";
+
+	if (tcpinfo->spts[0] != 0 || tcpinfo->spts[1] != 0xffff) {
+		if (tcpinfo->spts[0] != tcpinfo->spts[1]) {
+			xt_xlate_add(xl, "tcp sport %s%u-%u",
+				   tcpinfo->invflags & XT_TCP_INV_SRCPT ?
+					"!= " : "",
+				   tcpinfo->spts[0], tcpinfo->spts[1]);
+		} else {
+			xt_xlate_add(xl, "tcp sport %s%u",
+				   tcpinfo->invflags & XT_TCP_INV_SRCPT ?
+					"!= " : "",
+				   tcpinfo->spts[0]);
+		}
+		space = " ";
+	}
+
+	if (tcpinfo->dpts[0] != 0 || tcpinfo->dpts[1] != 0xffff) {
+		if (tcpinfo->dpts[0] != tcpinfo->dpts[1]) {
+			xt_xlate_add(xl, "%stcp dport %s%u-%u", space,
+				   tcpinfo->invflags & XT_TCP_INV_DSTPT ?
+					"!= " : "",
+				   tcpinfo->dpts[0], tcpinfo->dpts[1]);
+		} else {
+			xt_xlate_add(xl, "%stcp dport %s%u", space,
+				   tcpinfo->invflags & XT_TCP_INV_DSTPT ?
+					"!= " : "",
+				   tcpinfo->dpts[0]);
+		}
+		space = " ";
+	}
+
+	/* XXX not yet implemented */
+	if (tcpinfo->option || (tcpinfo->invflags & XT_TCP_INV_OPTION))
+		return 0;
+
+	if (tcpinfo->flg_mask || (tcpinfo->invflags & XT_TCP_INV_FLAGS)) {
+		xt_xlate_add(xl, "%stcp flags & (", space);
+		print_tcp_xlate(xl, tcpinfo->flg_mask);
+		xt_xlate_add(xl, ") %s ",
+			   tcpinfo->invflags & XT_TCP_INV_FLAGS ? "!=": "==");
+		print_tcp_xlate(xl, tcpinfo->flg_cmp);
+	}
+
+	return 1;
+}
+
 static struct xtables_match tcp_match = {
 	.family		= NFPROTO_UNSPEC,
 	.name		= "tcp",
@@ -382,6 +457,7 @@
 	.print		= tcp_print,
 	.save		= tcp_save,
 	.extra_opts	= tcp_opts,
+	.xlate		= tcp_xlate,
 };
 
 void
diff --git a/extensions/libxt_tcp.man b/extensions/libxt_tcp.man
index 7a16118..8019461 100644
--- a/extensions/libxt_tcp.man
+++ b/extensions/libxt_tcp.man
@@ -7,7 +7,6 @@
 using the format \fIfirst\fP\fB:\fP\fIlast\fP.
 If the first port is omitted, "0" is assumed; if the last is omitted,
 "65535" is assumed.
-If the first port is greater than the second one they will be swapped.
 The flag
 \fB\-\-sport\fP
 is a convenient alias for this option.
diff --git a/extensions/libxt_tcp.t b/extensions/libxt_tcp.t
new file mode 100644
index 0000000..b0e8006
--- /dev/null
+++ b/extensions/libxt_tcp.t
@@ -0,0 +1,26 @@
+:INPUT,FORWARD,OUTPUT
+-p tcp -m tcp --sport 1;=;OK
+-p tcp -m tcp --sport 65535;=;OK
+-p tcp -m tcp --dport 1;=;OK
+-p tcp -m tcp --dport 65535;=;OK
+-p tcp -m tcp --sport 1:1023;=;OK
+-p tcp -m tcp --sport 1024:65535;=;OK
+-p tcp -m tcp --sport 1024:;-p tcp -m tcp --sport 1024:65535;OK
+-p tcp -m tcp ! --sport 1;=;OK
+-p tcp -m tcp ! --sport 65535;=;OK
+-p tcp -m tcp ! --dport 1;=;OK
+-p tcp -m tcp ! --dport 65535;=;OK
+-p tcp -m tcp --sport 1 --dport 65535;=;OK
+-p tcp -m tcp --sport 65535 --dport 1;=;OK
+-p tcp -m tcp ! --sport 1 --dport 65535;=;OK
+-p tcp -m tcp ! --sport 65535 --dport 1;=;OK
+-p tcp -m tcp --sport 65536;;FAIL
+-p tcp -m tcp --sport -1;;FAIL
+-p tcp -m tcp --dport -1;;FAIL
+-p tcp -m tcp --syn;-p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN;OK
+-p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN;=;OK
+-p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG SYN;=;OK
+-p tcp -m tcp ! --tcp-flags FIN,SYN,RST,PSH,ACK,URG SYN;=;OK
+-p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG RST;=;OK
+# should we accept this below?
+-p tcp -m tcp;=;OK
diff --git a/extensions/libxt_tcp.txlate b/extensions/libxt_tcp.txlate
new file mode 100644
index 0000000..bba6332
--- /dev/null
+++ b/extensions/libxt_tcp.txlate
@@ -0,0 +1,26 @@
+iptables-translate -A INPUT -p tcp -i eth0 --sport 53 -j ACCEPT
+nft add rule ip filter INPUT iifname "eth0" tcp sport 53 counter accept
+
+iptables-translate -A OUTPUT -p tcp -o eth0 --dport 53:66 -j DROP
+nft add rule ip filter OUTPUT oifname "eth0" tcp dport 53-66 counter drop
+
+iptables-translate -I OUTPUT -p tcp -d 8.8.8.8 -j ACCEPT
+nft insert rule ip filter OUTPUT ip protocol tcp ip daddr 8.8.8.8 counter accept
+
+iptables-translate -I OUTPUT -p tcp --dport 1020:1023 --sport 53 -j ACCEPT
+nft insert rule ip filter OUTPUT tcp sport 53 tcp dport 1020-1023 counter accept
+
+iptables -A INPUT -p tcp --tcp-flags ACK,FIN FIN -j DROP
+nft add rule ip filter INPUT tcp flags & fin|ack == fin counter drop
+
+iptables-translate -A INPUT -p tcp --syn -j ACCEPT
+nft add rule ip filter INPUT tcp flags & (fin|syn|rst|ack) == syn counter accept
+
+iptables-translate -A INPUT -p tcp --syn --dport 80 -j ACCEPT
+nft add rule ip filter INPUT tcp dport 80 tcp flags & (fin|syn|rst|ack) == syn counter accept
+
+iptables-translate -A INPUT -f -p tcp
+nft add rule ip filter INPUT ip frag-off & 0x1fff != 0 ip protocol tcp counter
+
+iptables-translate -A INPUT ! -f -p tcp --dport 22
+nft add rule ip filter INPUT ip frag-off & 0x1fff 0 tcp dport 22 counter
diff --git a/extensions/libxt_tcpmss.c b/extensions/libxt_tcpmss.c
index c7c5971..bcd357a 100644
--- a/extensions/libxt_tcpmss.c
+++ b/extensions/libxt_tcpmss.c
@@ -27,8 +27,12 @@
 	xtables_option_parse(cb);
 	mssinfo->mss_min = cb->val.u16_range[0];
 	mssinfo->mss_max = mssinfo->mss_min;
-	if (cb->nvals == 2)
+	if (cb->nvals == 2) {
 		mssinfo->mss_max = cb->val.u16_range[1];
+		if (mssinfo->mss_max < mssinfo->mss_min)
+			xtables_error(PARAMETER_PROBLEM,
+				      "tcpmss: invalid range given");
+	}
 	if (cb->invert)
 		mssinfo->invert = 1;
 }
diff --git a/extensions/libxt_tcpmss.man b/extensions/libxt_tcpmss.man
index 8ee715c..8253c36 100644
--- a/extensions/libxt_tcpmss.man
+++ b/extensions/libxt_tcpmss.man
@@ -1,4 +1,4 @@
 This matches the TCP MSS (maximum segment size) field of the TCP header.  You can only use this on TCP SYN or SYN/ACK packets, since the MSS is only negotiated during the TCP handshake at connection startup time.
 .TP
 [\fB!\fP] \fB\-\-mss\fP \fIvalue\fP[\fB:\fP\fIvalue\fP]
-Match a given TCP MSS value or range.
+Match a given TCP MSS value or range. If a range is given, the second \fIvalue\fP must be greater than or equal to the first \fIvalue\fP.
diff --git a/extensions/libxt_tcpmss.t b/extensions/libxt_tcpmss.t
new file mode 100644
index 0000000..2b41595
--- /dev/null
+++ b/extensions/libxt_tcpmss.t
@@ -0,0 +1,6 @@
+:INPUT,FORWARD,OUTPUT
+-m tcpmss --mss 42;;FAIL
+-p tcp -m tcpmss --mss 42;=;OK
+-p tcp -m tcpmss --mss 42:12345;=;OK
+-p tcp -m tcpmss --mss 42:65536;;FAIL
+-p tcp -m tcpmss --mss 65535:1000;;FAIL
diff --git a/extensions/libxt_time.c b/extensions/libxt_time.c
index 44c05b8..d27d84c 100644
--- a/extensions/libxt_time.c
+++ b/extensions/libxt_time.c
@@ -22,6 +22,7 @@
 	O_DATE_STOP,
 	O_TIME_START,
 	O_TIME_STOP,
+	O_TIME_CONTIGUOUS,
 	O_MONTHDAYS,
 	O_WEEKDAYS,
 	O_LOCAL_TZ,
@@ -30,6 +31,7 @@
 	F_LOCAL_TZ  = 1 << O_LOCAL_TZ,
 	F_UTC       = 1 << O_UTC,
 	F_KERNEL_TZ = 1 << O_KERNEL_TZ,
+	F_TIME_CONTIGUOUS = 1 << O_TIME_CONTIGUOUS,
 };
 
 static const char *const week_days[] = {
@@ -41,6 +43,7 @@
 	{.name = "datestop", .id = O_DATE_STOP, .type = XTTYPE_STRING},
 	{.name = "timestart", .id = O_TIME_START, .type = XTTYPE_STRING},
 	{.name = "timestop", .id = O_TIME_STOP, .type = XTTYPE_STRING},
+	{.name = "contiguous", .id = O_TIME_CONTIGUOUS, .type = XTTYPE_NONE},
 	{.name = "weekdays", .id = O_WEEKDAYS, .type = XTTYPE_STRING,
 	 .flags = XTOPT_INVERT},
 	{.name = "monthdays", .id = O_MONTHDAYS, .type = XTTYPE_STRING,
@@ -85,10 +88,10 @@
 	info->date_stop  = INT_MAX;
 }
 
-static time_t time_parse_date(const char *s, bool end)
+static time_t time_parse_date(const char *s)
 {
 	unsigned int month = 1, day = 1, hour = 0, minute = 0, second = 0;
-	unsigned int year  = end ? 2038 : 1970;
+	unsigned int year;
 	const char *os = s;
 	struct tm tm;
 	time_t ret;
@@ -255,6 +258,16 @@
 	return ret;
 }
 
+static unsigned int time_count_weekdays(unsigned int weekdays_mask)
+{
+	unsigned int ret;
+
+	for (ret = 0; weekdays_mask; weekdays_mask >>= 1)
+		ret += weekdays_mask & 1;
+
+	return ret;
+}
+
 static void time_parse(struct xt_option_call *cb)
 {
 	struct xt_time_info *info = cb->data;
@@ -262,10 +275,10 @@
 	xtables_option_parse(cb);
 	switch (cb->entry->id) {
 	case O_DATE_START:
-		info->date_start = time_parse_date(cb->arg, false);
+		info->date_start = time_parse_date(cb->arg);
 		break;
 	case O_DATE_STOP:
-		info->date_stop = time_parse_date(cb->arg, true);
+		info->date_stop = time_parse_date(cb->arg);
 		break;
 	case O_TIME_START:
 		info->daytime_start = time_parse_minutes(cb->arg);
@@ -273,6 +286,9 @@
 	case O_TIME_STOP:
 		info->daytime_stop = time_parse_minutes(cb->arg);
 		break;
+	case O_TIME_CONTIGUOUS:
+		info->flags |= XT_TIME_CONTIGUOUS;
+		break;
 	case O_LOCAL_TZ:
 		fprintf(stderr, "WARNING: --localtz is being replaced by "
 		        "--kerneltz, since \"local\" is ambiguous. Note the "
@@ -324,7 +340,7 @@
 
 	printf(" ");
 	for (i = 1; i <= 31; ++i)
-		if (mask & (1 << i)) {
+		if (mask & (1u << i)) {
 			if (nbdays++ > 0)
 				printf(",");
 			printf("%u", i);
@@ -403,6 +419,8 @@
 	}
 	if (!(info->flags & XT_TIME_LOCAL_TZ))
 		printf(" UTC");
+	if (info->flags & XT_TIME_CONTIGUOUS)
+		printf(" contiguous");
 }
 
 static void time_save(const void *ip, const struct xt_entry_match *match)
@@ -429,6 +447,78 @@
 	time_print_date(info->date_stop, "--datestop");
 	if (info->flags & XT_TIME_LOCAL_TZ)
 		printf(" --kerneltz");
+	if (info->flags & XT_TIME_CONTIGUOUS)
+		printf(" --contiguous");
+}
+
+static void time_check(struct xt_fcheck_call *cb)
+{
+	const struct xt_time_info *info = (const void *) cb->data;
+	if ((cb->xflags & F_TIME_CONTIGUOUS) &&
+	     info->daytime_start < info->daytime_stop)
+		xtables_error(PARAMETER_PROBLEM,
+			"time: --contiguous only makes sense when stoptime is smaller than starttime");
+}
+
+static int time_xlate(struct xt_xlate *xl,
+		      const struct xt_xlate_mt_params *params)
+{
+	const struct xt_time_info *info =
+		(const struct xt_time_info *)params->match->data;
+	unsigned int h, m, s,
+		     i, sep, mask, count;
+	time_t tt_start, tt_stop;
+	struct tm *t_start, *t_stop;
+
+	if (info->date_start != 0 ||
+	    info->date_stop != INT_MAX) {
+		tt_start = (time_t) info->date_start;
+		tt_stop = (time_t) info->date_stop;
+
+		xt_xlate_add(xl, "meta time ");
+		t_start = gmtime(&tt_start);
+		xt_xlate_add(xl, "\"%04u-%02u-%02u %02u:%02u:%02u\"",
+			     t_start->tm_year + 1900, t_start->tm_mon + 1,
+			     t_start->tm_mday, t_start->tm_hour,
+			     t_start->tm_min, t_start->tm_sec);
+		t_stop = gmtime(&tt_stop);
+		xt_xlate_add(xl, "-\"%04u-%02u-%02u %02u:%02u:%02u\"",
+			     t_stop->tm_year + 1900, t_stop->tm_mon + 1,
+			     t_stop->tm_mday, t_stop->tm_hour,
+			     t_stop->tm_min, t_stop->tm_sec);
+	}
+	if (info->daytime_start != XT_TIME_MIN_DAYTIME ||
+	    info->daytime_stop != XT_TIME_MAX_DAYTIME) {
+		divide_time(info->daytime_start, &h, &m, &s);
+		xt_xlate_add(xl, " meta hour \"%02u:%02u:%02u\"", h, m, s);
+		divide_time(info->daytime_stop, &h, &m, &s);
+		xt_xlate_add(xl, "-\"%02u:%02u:%02u\"", h, m, s);
+	}
+	/* nft_time does not support --monthdays */
+	if (info->monthdays_match != XT_TIME_ALL_MONTHDAYS)
+		return 0;
+	if (info->weekdays_match != XT_TIME_ALL_WEEKDAYS) {
+		sep = 0;
+		mask = info->weekdays_match;
+		count = time_count_weekdays(mask);
+
+		xt_xlate_add(xl, " meta day ");
+		if (count > 1)
+			xt_xlate_add(xl, "{");
+		for (i = 1; i <= 7; ++i)
+			if (mask & (1 << i)) {
+				if (sep)
+					xt_xlate_add(xl, ",%u", i%7);
+				else {
+					xt_xlate_add(xl, "%u", i%7);
+					++sep;
+				}
+			}
+		if (count > 1)
+			xt_xlate_add(xl, "}");
+	}
+
+	return 1;
 }
 
 static struct xtables_match time_match = {
@@ -442,7 +532,9 @@
 	.print         = time_print,
 	.save          = time_save,
 	.x6_parse      = time_parse,
+	.x6_fcheck     = time_check,
 	.x6_options    = time_opts,
+	.xlate	       = time_xlate,
 };
 
 void _init(void)
diff --git a/extensions/libxt_time.man b/extensions/libxt_time.man
index 1d677b9..4c0cae0 100644
--- a/extensions/libxt_time.man
+++ b/extensions/libxt_time.man
@@ -30,6 +30,10 @@
 to \fB7\fP, respectively. You may also use two-character variants (\fBMo\fP,
 \fBTu\fP, etc.).
 .TP
+\fB\-\-contiguous\fP
+When \fB\-\-timestop\fP is smaller than \fB\-\-timestart\fP value, match
+this as a single time period instead distinct intervals.  See EXAMPLES.
+.TP
 \fB\-\-kerneltz\fP
 Use the kernel timezone instead of UTC to determine whether a packet meets the
 time regulations.
@@ -84,3 +88,11 @@
 (Note that this exploits a certain mathematical property. It is not possible to
 say "fourth Thursday OR fourth Friday" in one rule. It is possible with
 multiple rules, though.)
+.PP
+Matching across days might not do what is expected.  For instance,
+.IP
+\-m time \-\-weekdays Mo \-\-timestart 23:00  \-\-timestop 01:00
+Will match Monday, for one hour from midnight to 1 a.m., and then
+again for another hour from 23:00 onwards.  If this is unwanted, e.g. if you
+would like 'match for two hours from Montay 23:00 onwards' you need to also specify
+the \-\-contiguous option in the example above.
diff --git a/extensions/libxt_time.t b/extensions/libxt_time.t
new file mode 100644
index 0000000..673af09
--- /dev/null
+++ b/extensions/libxt_time.t
@@ -0,0 +1,4 @@
+:INPUT,FORWARD,OUTPUT
+-m time --timestart 01:02:03 --timestop 04:05:06 --monthdays 1,2,3,4,5 --weekdays Mon,Fri,Sun --datestart 2001-02-03T04:05:06 --datestop 2012-09-08T09:06:05 --kerneltz;=;OK
+-m time --timestart 01:02:03 --timestop 04:05:06 --monthdays 1,2,3,4,5 --weekdays Mon,Fri,Sun --datestart 2001-02-03T04:05:06 --datestop 2012-09-08T09:06:05;=;OK
+-m time --timestart 02:00:00 --timestop 03:00:00 --datestart 1970-01-01T02:00:00 --datestop 1970-01-01T03:00:00;=;OK
diff --git a/extensions/libxt_time.txlate b/extensions/libxt_time.txlate
new file mode 100644
index 0000000..ff4a7b8
--- /dev/null
+++ b/extensions/libxt_time.txlate
@@ -0,0 +1,26 @@
+iptables-translate -A INPUT -p icmp --icmp-type echo-request -m time --weekdays Sa,Su -j REJECT
+nft add rule ip filter INPUT icmp type echo-request  meta day {6,0} counter reject
+
+iptables-translate -A INPUT -p icmp --icmp-type echo-request -m time --timestart 12:00 -j REJECT
+nft add rule ip filter INPUT icmp type echo-request  meta hour "12:00:00"-"23:59:59" counter reject
+
+iptables-translate -A INPUT -p icmp --icmp-type echo-request -m time --timestop 12:00 -j REJECT
+nft add rule ip filter INPUT icmp type echo-request  meta hour "00:00:00"-"12:00:00" counter reject
+
+iptables-translate -A INPUT -p icmp --icmp-type echo-request -m time --datestart 2021 -j REJECT
+nft add rule ip filter INPUT icmp type echo-request meta time "2021-01-01 00:00:00"-"2038-01-19 03:14:07" counter reject
+
+iptables-translate -A INPUT -p icmp --icmp-type echo-request -m time --datestop 2021 -j REJECT
+nft add rule ip filter INPUT icmp type echo-request meta time "1970-01-01 00:00:00"-"2021-01-01 00:00:00" counter reject
+
+iptables-translate -A INPUT -p icmp --icmp-type echo-request -m time --datestop 2021-01-29T00:00:00 -j REJECT
+nft add rule ip filter INPUT icmp type echo-request meta time "1970-01-01 00:00:00"-"2021-01-29 00:00:00" counter reject
+
+iptables-translate -A INPUT -p icmp --icmp-type echo-request -m time --datestart 2020-01-29T00:00:00 --timestart 12:00 -j REJECT
+nft add rule ip filter INPUT icmp type echo-request meta time "2020-01-29 00:00:00"-"2038-01-19 03:14:07" meta hour "12:00:00"-"23:59:59" counter reject
+
+iptables-translate -A INPUT -p icmp --icmp-type echo-request -m time --datestart 2020-01-29T00:00:00 --timestart 12:00 --timestop 19:00 --weekdays Mon,Tue,Wed,Thu,Fri -j REJECT
+nft add rule ip filter INPUT icmp type echo-request meta time "2020-01-29 00:00:00"-"2038-01-19 03:14:07" meta hour "12:00:00"-"19:00:00" meta day {1,2,3,4,5} counter reject
+
+iptables-translate -A INPUT -p icmp --icmp-type echo-request -m time --datestart 2020-01-29T00:00:00 --timestart 12:00 --timestop 19:00 ! --weekdays Mon,Tue,Wed,Thu,Fri -j REJECT
+nft add rule ip filter INPUT icmp type echo-request meta time "2020-01-29 00:00:00"-"2038-01-19 03:14:07" meta hour "12:00:00"-"19:00:00" meta day {6,0} counter reject
diff --git a/extensions/libxt_tos.t b/extensions/libxt_tos.t
new file mode 100644
index 0000000..ccbe800
--- /dev/null
+++ b/extensions/libxt_tos.t
@@ -0,0 +1,13 @@
+:INPUT,FORWARD,OUTPUT
+-m tos --tos Minimize-Delay;-m tos --tos 0x10/0x3f;OK
+-m tos --tos Maximize-Throughput;-m tos --tos 0x08/0x3f;OK
+-m tos --tos Maximize-Reliability;-m tos --tos 0x04/0x3f;OK
+-m tos --tos Minimize-Cost;-m tos --tos 0x02/0x3f;OK
+-m tos --tos Normal-Service;-m tos --tos 0x00/0x3f;OK
+-m tos --tos 0xff;=;OK
+-m tos ! --tos 0xff;=;OK
+-m tos --tos 0x00;=;OK
+-m tos --tos 0x0f;=;OK
+-m tos --tos 0x0f/0x0f;=;OK
+-m tos --tos wrong;;FAIL
+-m tos;;FAIL
diff --git a/extensions/libxt_u32.c b/extensions/libxt_u32.c
index 774d5ea..2a7f5d8 100644
--- a/extensions/libxt_u32.c
+++ b/extensions/libxt_u32.c
@@ -24,7 +24,7 @@
 
 static const struct xt_option_entry u32_opts[] = {
 	{.name = "u32", .id = O_U32, .type = XTTYPE_STRING,
-	 .flags = XTOPT_MAND},
+	 .flags = XTOPT_MAND | XTOPT_INVERT},
 	XTOPT_TABLEEND,
 };
 
@@ -88,17 +88,13 @@
 /* string_to_number() is not quite what we need here ... */
 static uint32_t parse_number(const char **s, int pos)
 {
-	uint32_t number;
+	unsigned int number;
 	char *end;
 
-	errno  = 0;
-	number = strtoul(*s, &end, 0);
-	if (end == *s)
+	if (!xtables_strtoui(*s, &end, &number, 0, UINT32_MAX) ||
+	    end == *s)
 		xtables_error(PARAMETER_PROBLEM,
-			   "u32: at char %d: expected number", pos);
-	if (errno != 0)
-		xtables_error(PARAMETER_PROBLEM,
-			   "u32: at char %d: error reading number", pos);
+			"u32: at char %d: not a number or out of range", pos);
 	*s = end;
 	return number;
 }
diff --git a/extensions/libxt_u32.man b/extensions/libxt_u32.man
index 7c8615d..40a69f8 100644
--- a/extensions/libxt_u32.man
+++ b/extensions/libxt_u32.man
@@ -40,18 +40,23 @@
 B and C are unsigned 32 bit integers, initially zero
 .PP
 The instructions are:
-.IP
-number B = number;
+.TP
+.B number
+B = number;
 .IP
 C = (*(A+B)<<24) + (*(A+B+1)<<16) + (*(A+B+2)<<8) + *(A+B+3)
-.IP
-&number C = C & number
-.IP
-<< number C = C << number
-.IP
->> number C = C >> number
-.IP
-@number A = A + C; then do the instruction number
+.TP
+.B &number
+C = C & number
+.TP
+.B << number
+C = C << number
+.TP
+.B >> number
+C = C >> number
+.TP
+.B @number
+A = A + C; then do the instruction number
 .PP
 Any access of memory outside [skb\->data,skb\->end] causes the match to fail.
 Otherwise the result of the computation is the final value of C.
diff --git a/extensions/libxt_u32.t b/extensions/libxt_u32.t
new file mode 100644
index 0000000..0d9be47
--- /dev/null
+++ b/extensions/libxt_u32.t
@@ -0,0 +1,2 @@
+:INPUT,FORWARD,OUTPUT
+-m u32 --u32 "0x0=0x0&&0x0=0x1";=;OK
diff --git a/extensions/libxt_udp.c b/extensions/libxt_udp.c
index b9f39ee..0c7a4bc 100644
--- a/extensions/libxt_udp.c
+++ b/extensions/libxt_udp.c
@@ -152,6 +152,44 @@
 	}
 }
 
+static int udp_xlate(struct xt_xlate *xl,
+		     const struct xt_xlate_mt_params *params)
+{
+	const struct xt_udp *udpinfo = (struct xt_udp *)params->match->data;
+	char *space= "";
+
+	if (udpinfo->spts[0] != 0 || udpinfo->spts[1] != 0xFFFF) {
+		if (udpinfo->spts[0] != udpinfo->spts[1]) {
+			xt_xlate_add(xl,"udp sport %s%u-%u",
+				   udpinfo->invflags & XT_UDP_INV_SRCPT ?
+					 "!= ": "",
+				   udpinfo->spts[0], udpinfo->spts[1]);
+		} else {
+			xt_xlate_add(xl, "udp sport %s%u",
+				   udpinfo->invflags & XT_UDP_INV_SRCPT ?
+					 "!= ": "",
+				   udpinfo->spts[0]);
+		}
+		space = " ";
+	}
+
+	if (udpinfo->dpts[0] != 0 || udpinfo->dpts[1] != 0xFFFF) {
+		if (udpinfo->dpts[0]  != udpinfo->dpts[1]) {
+			xt_xlate_add(xl,"%sudp dport %s%u-%u", space,
+				   udpinfo->invflags & XT_UDP_INV_SRCPT ?
+					 "!= ": "",
+				   udpinfo->dpts[0], udpinfo->dpts[1]);
+		} else {
+			xt_xlate_add(xl,"%sudp dport %s%u", space,
+				   udpinfo->invflags & XT_UDP_INV_SRCPT ?
+					 "!= ": "",
+				   udpinfo->dpts[0]);
+		}
+	}
+
+	return 1;
+}
+
 static struct xtables_match udp_match = {
 	.family		= NFPROTO_UNSPEC,
 	.name		= "udp",
@@ -164,6 +202,7 @@
 	.save		= udp_save,
 	.x6_parse	= udp_parse,
 	.x6_options	= udp_opts,
+	.xlate		= udp_xlate,
 };
 
 void
diff --git a/extensions/libxt_udp.t b/extensions/libxt_udp.t
new file mode 100644
index 0000000..1b4d3dd
--- /dev/null
+++ b/extensions/libxt_udp.t
@@ -0,0 +1,22 @@
+:INPUT,OUTPUT,FORWARD
+-p udp -m udp --sport 1;=;OK
+-p udp -m udp --sport 65535;=;OK
+-p udp -m udp --dport 1;=;OK
+-p udp -m udp --dport 65535;=;OK
+-p udp -m udp --sport 1:1023;=;OK
+-p udp -m udp --sport 1024:65535;=;OK
+-p udp -m udp --sport 1024:;-p udp -m udp --sport 1024:65535;OK
+-p udp -m udp ! --sport 1;=;OK
+-p udp -m udp ! --sport 65535;=;OK
+-p udp -m udp ! --dport 1;=;OK
+-p udp -m udp ! --dport 65535;=;OK
+-p udp -m udp --sport 1 --dport 65535;=;OK
+-p udp -m udp --sport 65535 --dport 1;=;OK
+-p udp -m udp ! --sport 1 --dport 65535;=;OK
+-p udp -m udp ! --sport 65535 --dport 1;=;OK
+# ERRROR: should fail: iptables -A INPUT -p udp -m udp --sport 65536
+# -p udp -m udp --sport 65536;;FAIL
+-p udp -m udp --sport -1;;FAIL
+-p udp -m udp --dport -1;;FAIL
+# should we accept this below?
+-p udp -m udp;=;OK
diff --git a/extensions/libxt_udp.txlate b/extensions/libxt_udp.txlate
new file mode 100644
index 0000000..fbca5c1
--- /dev/null
+++ b/extensions/libxt_udp.txlate
@@ -0,0 +1,11 @@
+iptables-translate -A INPUT -p udp -i eth0 --sport 53 -j ACCEPT
+nft add rule ip filter INPUT iifname "eth0" udp sport 53 counter accept
+
+iptables-translate -A OUTPUT -p udp -o eth0 --dport 53:66 -j DROP
+nft add rule ip filter OUTPUT oifname "eth0" udp dport 53-66 counter drop
+
+iptables-translate -I OUTPUT -p udp -d 8.8.8.8 -j ACCEPT
+nft insert rule ip filter OUTPUT ip protocol udp ip daddr 8.8.8.8 counter accept
+
+iptables-translate -I OUTPUT -p udp --dport 1020:1023 --sport 53 -j ACCEPT
+nft insert rule ip filter OUTPUT udp sport 53 udp dport 1020-1023 counter accept
diff --git a/include/Makefile.am b/include/Makefile.am
index 0a1abea..ea34c2f 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -1,7 +1,7 @@
 # -*- Makefile -*-
 
 include_HEADERS =
-nobase_include_HEADERS = xtables.h
+nobase_include_HEADERS = xtables.h xtables-version.h
 
 if ENABLE_LIBIPQ
 include_HEADERS += libipq/libipq.h
@@ -9,4 +9,9 @@
 
 nobase_include_HEADERS += \
 	libiptc/ipt_kernel_headers.h libiptc/libiptc.h \
-	libiptc/libip6tc.h libiptc/libxtc.h
+	libiptc/libip6tc.h libiptc/libxtc.h libiptc/xtcshared.h
+
+uninstall-hook:
+	dir=${includedir}/libiptc; { \
+		test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; \
+	} || rmdir -p --ignore-fail-on-non-empty "$$dir"
diff --git a/include/ip6tables.h b/include/ip6tables.h
index e976361..5f1c5b6 100644
--- a/include/ip6tables.h
+++ b/include/ip6tables.h
@@ -8,12 +8,12 @@
 
 /* Your shared library should call one of these. */
 extern int do_command6(int argc, char *argv[], char **table,
-		       struct ip6tc_handle **handle);
+		       struct xtc_handle **handle, bool restore);
 
-extern int for_each_chain6(int (*fn)(const ip6t_chainlabel, int, struct ip6tc_handle *), int verbose, int builtinstoo, struct ip6tc_handle *handle);
-extern int flush_entries6(const ip6t_chainlabel chain, int verbose, struct ip6tc_handle *handle);
-extern int delete_chain6(const ip6t_chainlabel chain, int verbose, struct ip6tc_handle *handle);
-void print_rule6(const struct ip6t_entry *e, struct ip6tc_handle *h, const char *chain, int counters);
+extern int for_each_chain6(int (*fn)(const xt_chainlabel, int, struct xtc_handle *), int verbose, int builtinstoo, struct xtc_handle *handle);
+extern int flush_entries6(const xt_chainlabel chain, int verbose, struct xtc_handle *handle);
+extern int delete_chain6(const xt_chainlabel chain, int verbose, struct xtc_handle *handle);
+void print_rule6(const struct ip6t_entry *e, struct xtc_handle *h, const char *chain, int counters);
 
 extern struct xtables_globals ip6tables_globals;
 
diff --git a/include/iptables.h b/include/iptables.h
index 65b3290..78c10ab 100644
--- a/include/iptables.h
+++ b/include/iptables.h
@@ -8,24 +8,18 @@
 
 /* Your shared library should call one of these. */
 extern int do_command4(int argc, char *argv[], char **table,
-		      struct iptc_handle **handle);
-extern int delete_chain4(const ipt_chainlabel chain, int verbose,
-			struct iptc_handle *handle);
-extern int flush_entries4(const ipt_chainlabel chain, int verbose, 
-			struct iptc_handle *handle);
-extern int for_each_chain4(int (*fn)(const ipt_chainlabel, int, struct iptc_handle *),
-		int verbose, int builtinstoo, struct iptc_handle *handle);
+		      struct xtc_handle **handle, bool restore);
+extern int delete_chain4(const xt_chainlabel chain, int verbose,
+			struct xtc_handle *handle);
+extern int flush_entries4(const xt_chainlabel chain, int verbose, 
+			struct xtc_handle *handle);
+extern int for_each_chain4(int (*fn)(const xt_chainlabel, int, struct xtc_handle *),
+		int verbose, int builtinstoo, struct xtc_handle *handle);
 extern void print_rule4(const struct ipt_entry *e,
-		struct iptc_handle *handle, const char *chain, int counters);
-
-/* kernel revision handling */
-extern int kernel_version;
-extern void get_kernel_version(void);
-#define LINUX_VERSION(x,y,z)	(0x10000*(x) + 0x100*(y) + z)
-#define LINUX_VERSION_MAJOR(x)	(((x)>>16) & 0xFF)
-#define LINUX_VERSION_MINOR(x)	(((x)>> 8) & 0xFF)
-#define LINUX_VERSION_PATCH(x)	( (x)      & 0xFF)
+		struct xtc_handle *handle, const char *chain, int counters);
 
 extern struct xtables_globals iptables_globals;
 
+extern struct xtables_globals xtables_globals;
+
 #endif /*_IPTABLES_USER_H*/
diff --git a/include/iptables/internal.h b/include/iptables/internal.h
index 8d8f023..86ba074 100644
--- a/include/iptables/internal.h
+++ b/include/iptables/internal.h
@@ -1,8 +1,6 @@
 #ifndef IPTABLES_INTERNAL_H
 #define IPTABLES_INTERNAL_H 1
 
-#define IPTABLES_VERSION "1.4.11.1"
-
 /**
  * Program's own name and version.
  */
diff --git a/include/iptables/internal.h.in b/include/iptables/internal.h.in
deleted file mode 100644
index 8568e58..0000000
--- a/include/iptables/internal.h.in
+++ /dev/null
@@ -1,13 +0,0 @@
-#ifndef IPTABLES_INTERNAL_H
-#define IPTABLES_INTERNAL_H 1
-
-#define IPTABLES_VERSION "@PACKAGE_VERSION@"
-
-/**
- * Program's own name and version.
- */
-extern const char *program_name, *program_version;
-
-extern int line;
-
-#endif /* IPTABLES_INTERNAL_H */
diff --git a/include/libiptc/ipt_kernel_headers.h b/include/libiptc/ipt_kernel_headers.h
index 60c7998..a5963e9 100644
--- a/include/libiptc/ipt_kernel_headers.h
+++ b/include/libiptc/ipt_kernel_headers.h
@@ -5,7 +5,6 @@
 
 #include <limits.h>
 
-#if defined(__ANDROID__) || (defined(__GLIBC__) && __GLIBC__ == 2)
 #include <netinet/ip.h>
 #include <netinet/in.h>
 #include <netinet/ip_icmp.h>
@@ -13,15 +12,4 @@
 #include <netinet/udp.h>
 #include <net/if.h>
 #include <sys/types.h>
-#else /* libc5 */
-#include <sys/socket.h>
-#include <linux/ip.h>
-#include <linux/in.h>
-#include <linux/if.h>
-#include <linux/icmp.h>
-#include <linux/tcp.h>
-#include <linux/udp.h>
-#include <linux/types.h>
-#include <linux/in6.h>
-#endif
 #endif
diff --git a/include/libiptc/libip6tc.h b/include/libiptc/libip6tc.h
index 4f2d1f8..9aed80a 100644
--- a/include/libiptc/libip6tc.h
+++ b/include/libiptc/libip6tc.h
@@ -10,10 +10,10 @@
 #	include <limits.h> /* INT_MAX in ip6_tables.h */
 #endif
 #include <linux/netfilter_ipv6/ip6_tables.h>
+#include <libiptc/xtcshared.h>
 
-struct ip6tc_handle;
-
-typedef char ip6t_chainlabel[32];
+#define ip6tc_handle xtc_handle
+#define ip6t_chainlabel xt_chainlabel
 
 #define IP6TC_LABEL_ACCEPT "ACCEPT"
 #define IP6TC_LABEL_DROP "DROP"
@@ -21,132 +21,132 @@
 #define IP6TC_LABEL_RETURN "RETURN"
 
 /* Does this chain exist? */
-int ip6tc_is_chain(const char *chain, struct ip6tc_handle *const handle);
+int ip6tc_is_chain(const char *chain, struct xtc_handle *const handle);
 
 /* Take a snapshot of the rules. Returns NULL on error. */
-struct ip6tc_handle *ip6tc_init(const char *tablename);
+struct xtc_handle *ip6tc_init(const char *tablename);
 
 /* Cleanup after ip6tc_init(). */
-void ip6tc_free(struct ip6tc_handle *h);
+void ip6tc_free(struct xtc_handle *h);
 
 /* Iterator functions to run through the chains.  Returns NULL at end. */
-const char *ip6tc_first_chain(struct ip6tc_handle *handle);
-const char *ip6tc_next_chain(struct ip6tc_handle *handle);
+const char *ip6tc_first_chain(struct xtc_handle *handle);
+const char *ip6tc_next_chain(struct xtc_handle *handle);
 
 /* Get first rule in the given chain: NULL for empty chain. */
 const struct ip6t_entry *ip6tc_first_rule(const char *chain,
-					  struct ip6tc_handle *handle);
+					  struct xtc_handle *handle);
 
 /* Returns NULL when rules run out. */
 const struct ip6t_entry *ip6tc_next_rule(const struct ip6t_entry *prev,
-					 struct ip6tc_handle *handle);
+					 struct xtc_handle *handle);
 
 /* Returns a pointer to the target name of this position. */
 const char *ip6tc_get_target(const struct ip6t_entry *e,
-			     struct ip6tc_handle *handle);
+			     struct xtc_handle *handle);
 
 /* Is this a built-in chain? */
-int ip6tc_builtin(const char *chain, struct ip6tc_handle *const handle);
+int ip6tc_builtin(const char *chain, struct xtc_handle *const handle);
 
 /* Get the policy of a given built-in chain */
 const char *ip6tc_get_policy(const char *chain,
-			     struct ip6t_counters *counters,
-			     struct ip6tc_handle *handle);
+			     struct xt_counters *counters,
+			     struct xtc_handle *handle);
 
 /* These functions return TRUE for OK or 0 and set errno. If errno ==
    0, it means there was a version error (ie. upgrade libiptc). */
 /* Rule numbers start at 1 for the first rule. */
 
 /* Insert the entry `fw' in chain `chain' into position `rulenum'. */
-int ip6tc_insert_entry(const ip6t_chainlabel chain,
+int ip6tc_insert_entry(const xt_chainlabel chain,
 		       const struct ip6t_entry *e,
 		       unsigned int rulenum,
-		       struct ip6tc_handle *handle);
+		       struct xtc_handle *handle);
 
 /* Atomically replace rule `rulenum' in `chain' with `fw'. */
-int ip6tc_replace_entry(const ip6t_chainlabel chain,
+int ip6tc_replace_entry(const xt_chainlabel chain,
 			const struct ip6t_entry *e,
 			unsigned int rulenum,
-			struct ip6tc_handle *handle);
+			struct xtc_handle *handle);
 
 /* Append entry `fw' to chain `chain'. Equivalent to insert with
    rulenum = length of chain. */
-int ip6tc_append_entry(const ip6t_chainlabel chain,
+int ip6tc_append_entry(const xt_chainlabel chain,
 		       const struct ip6t_entry *e,
-		       struct ip6tc_handle *handle);
+		       struct xtc_handle *handle);
 
 /* Check whether a matching rule exists */
-int ip6tc_check_entry(const ip6t_chainlabel chain,
+int ip6tc_check_entry(const xt_chainlabel chain,
 		       const struct ip6t_entry *origfw,
 		       unsigned char *matchmask,
-		       struct ip6tc_handle *handle);
+		       struct xtc_handle *handle);
 
 /* Delete the first rule in `chain' which matches `fw'. */
-int ip6tc_delete_entry(const ip6t_chainlabel chain,
+int ip6tc_delete_entry(const xt_chainlabel chain,
 		       const struct ip6t_entry *origfw,
 		       unsigned char *matchmask,
-		       struct ip6tc_handle *handle);
+		       struct xtc_handle *handle);
 
 /* Delete the rule in position `rulenum' in `chain'. */
-int ip6tc_delete_num_entry(const ip6t_chainlabel chain,
+int ip6tc_delete_num_entry(const xt_chainlabel chain,
 			   unsigned int rulenum,
-			   struct ip6tc_handle *handle);
+			   struct xtc_handle *handle);
 
 /* Check the packet `fw' on chain `chain'. Returns the verdict, or
    NULL and sets errno. */
-const char *ip6tc_check_packet(const ip6t_chainlabel chain,
+const char *ip6tc_check_packet(const xt_chainlabel chain,
 			       struct ip6t_entry *,
-			       struct ip6tc_handle *handle);
+			       struct xtc_handle *handle);
 
 /* Flushes the entries in the given chain (ie. empties chain). */
-int ip6tc_flush_entries(const ip6t_chainlabel chain,
-			struct ip6tc_handle *handle);
+int ip6tc_flush_entries(const xt_chainlabel chain,
+			struct xtc_handle *handle);
 
 /* Zeroes the counters in a chain. */
-int ip6tc_zero_entries(const ip6t_chainlabel chain,
-		       struct ip6tc_handle *handle);
+int ip6tc_zero_entries(const xt_chainlabel chain,
+		       struct xtc_handle *handle);
 
 /* Creates a new chain. */
-int ip6tc_create_chain(const ip6t_chainlabel chain,
-		       struct ip6tc_handle *handle);
+int ip6tc_create_chain(const xt_chainlabel chain,
+		       struct xtc_handle *handle);
 
 /* Deletes a chain. */
-int ip6tc_delete_chain(const ip6t_chainlabel chain,
-		       struct ip6tc_handle *handle);
+int ip6tc_delete_chain(const xt_chainlabel chain,
+		       struct xtc_handle *handle);
 
 /* Renames a chain. */
-int ip6tc_rename_chain(const ip6t_chainlabel oldname,
-		       const ip6t_chainlabel newname,
-		       struct ip6tc_handle *handle);
+int ip6tc_rename_chain(const xt_chainlabel oldname,
+		       const xt_chainlabel newname,
+		       struct xtc_handle *handle);
 
 /* Sets the policy on a built-in chain. */
-int ip6tc_set_policy(const ip6t_chainlabel chain,
-		     const ip6t_chainlabel policy,
-		     struct ip6t_counters *counters,
-		     struct ip6tc_handle *handle);
+int ip6tc_set_policy(const xt_chainlabel chain,
+		     const xt_chainlabel policy,
+		     struct xt_counters *counters,
+		     struct xtc_handle *handle);
 
 /* Get the number of references to this chain */
-int ip6tc_get_references(unsigned int *ref, const ip6t_chainlabel chain,
-			 struct ip6tc_handle *handle);
+int ip6tc_get_references(unsigned int *ref, const xt_chainlabel chain,
+			 struct xtc_handle *handle);
 
 /* read packet and byte counters for a specific rule */
-struct ip6t_counters *ip6tc_read_counter(const ip6t_chainlabel chain,
+struct xt_counters *ip6tc_read_counter(const xt_chainlabel chain,
 					unsigned int rulenum,
-					struct ip6tc_handle *handle);
+					struct xtc_handle *handle);
 
 /* zero packet and byte counters for a specific rule */
-int ip6tc_zero_counter(const ip6t_chainlabel chain,
+int ip6tc_zero_counter(const xt_chainlabel chain,
 		       unsigned int rulenum,
-		       struct ip6tc_handle *handle);
+		       struct xtc_handle *handle);
 
 /* set packet and byte counters for a specific rule */
-int ip6tc_set_counter(const ip6t_chainlabel chain,
+int ip6tc_set_counter(const xt_chainlabel chain,
 		      unsigned int rulenum,
-		      struct ip6t_counters *counters,
-		      struct ip6tc_handle *handle);
+		      struct xt_counters *counters,
+		      struct xtc_handle *handle);
 
 /* Makes the actual changes. */
-int ip6tc_commit(struct ip6tc_handle *handle);
+int ip6tc_commit(struct xtc_handle *handle);
 
 /* Get raw socket. */
 int ip6tc_get_raw_socket(void);
@@ -154,9 +154,8 @@
 /* Translates errno numbers into more human-readable form than strerror. */
 const char *ip6tc_strerror(int err);
 
-/* Return prefix length, or -1 if not contiguous */
-int ipv6_prefix_length(const struct in6_addr *a);
+extern void dump_entries6(struct xtc_handle *const);
 
-extern void dump_entries6(struct ip6tc_handle *const);
+extern const struct xtc_ops ip6tc_ops;
 
 #endif /* _LIBIP6TC_H */
diff --git a/include/libiptc/libiptc.h b/include/libiptc/libiptc.h
index 3497d6a..24cdbdb 100644
--- a/include/libiptc/libiptc.h
+++ b/include/libiptc/libiptc.h
@@ -10,14 +10,14 @@
 #	include <limits.h> /* INT_MAX in ip_tables.h */
 #endif
 #include <linux/netfilter_ipv4/ip_tables.h>
+#include <libiptc/xtcshared.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-struct iptc_handle;
-
-typedef char ipt_chainlabel[32];
+#define iptc_handle xtc_handle
+#define ipt_chainlabel xt_chainlabel
 
 #define IPTC_LABEL_ACCEPT  "ACCEPT"
 #define IPTC_LABEL_DROP    "DROP"
@@ -25,134 +25,134 @@
 #define IPTC_LABEL_RETURN  "RETURN"
 
 /* Does this chain exist? */
-int iptc_is_chain(const char *chain, struct iptc_handle *const handle);
+int iptc_is_chain(const char *chain, struct xtc_handle *const handle);
 
 /* Take a snapshot of the rules.  Returns NULL on error. */
-struct iptc_handle *iptc_init(const char *tablename);
+struct xtc_handle *iptc_init(const char *tablename);
 
 /* Cleanup after iptc_init(). */
-void iptc_free(struct iptc_handle *h);
+void iptc_free(struct xtc_handle *h);
 
 /* Iterator functions to run through the chains.  Returns NULL at end. */
-const char *iptc_first_chain(struct iptc_handle *handle);
-const char *iptc_next_chain(struct iptc_handle *handle);
+const char *iptc_first_chain(struct xtc_handle *handle);
+const char *iptc_next_chain(struct xtc_handle *handle);
 
 /* Get first rule in the given chain: NULL for empty chain. */
 const struct ipt_entry *iptc_first_rule(const char *chain,
-					struct iptc_handle *handle);
+					struct xtc_handle *handle);
 
 /* Returns NULL when rules run out. */
 const struct ipt_entry *iptc_next_rule(const struct ipt_entry *prev,
-				       struct iptc_handle *handle);
+				       struct xtc_handle *handle);
 
 /* Returns a pointer to the target name of this entry. */
 const char *iptc_get_target(const struct ipt_entry *e,
-			    struct iptc_handle *handle);
+			    struct xtc_handle *handle);
 
 /* Is this a built-in chain? */
-int iptc_builtin(const char *chain, struct iptc_handle *const handle);
+int iptc_builtin(const char *chain, struct xtc_handle *const handle);
 
 /* Get the policy of a given built-in chain */
 const char *iptc_get_policy(const char *chain,
-			    struct ipt_counters *counter,
-			    struct iptc_handle *handle);
+			    struct xt_counters *counter,
+			    struct xtc_handle *handle);
 
 /* These functions return TRUE for OK or 0 and set errno.  If errno ==
    0, it means there was a version error (ie. upgrade libiptc). */
 /* Rule numbers start at 1 for the first rule. */
 
 /* Insert the entry `e' in chain `chain' into position `rulenum'. */
-int iptc_insert_entry(const ipt_chainlabel chain,
+int iptc_insert_entry(const xt_chainlabel chain,
 		      const struct ipt_entry *e,
 		      unsigned int rulenum,
-		      struct iptc_handle *handle);
+		      struct xtc_handle *handle);
 
 /* Atomically replace rule `rulenum' in `chain' with `e'. */
-int iptc_replace_entry(const ipt_chainlabel chain,
+int iptc_replace_entry(const xt_chainlabel chain,
 		       const struct ipt_entry *e,
 		       unsigned int rulenum,
-		       struct iptc_handle *handle);
+		       struct xtc_handle *handle);
 
 /* Append entry `e' to chain `chain'.  Equivalent to insert with
    rulenum = length of chain. */
-int iptc_append_entry(const ipt_chainlabel chain,
+int iptc_append_entry(const xt_chainlabel chain,
 		      const struct ipt_entry *e,
-		      struct iptc_handle *handle);
+		      struct xtc_handle *handle);
 
 /* Check whether a mathching rule exists */
-int iptc_check_entry(const ipt_chainlabel chain,
+int iptc_check_entry(const xt_chainlabel chain,
 		      const struct ipt_entry *origfw,
 		      unsigned char *matchmask,
-		      struct iptc_handle *handle);
+		      struct xtc_handle *handle);
 
 /* Delete the first rule in `chain' which matches `e', subject to
    matchmask (array of length == origfw) */
-int iptc_delete_entry(const ipt_chainlabel chain,
+int iptc_delete_entry(const xt_chainlabel chain,
 		      const struct ipt_entry *origfw,
 		      unsigned char *matchmask,
-		      struct iptc_handle *handle);
+		      struct xtc_handle *handle);
 
 /* Delete the rule in position `rulenum' in `chain'. */
-int iptc_delete_num_entry(const ipt_chainlabel chain,
+int iptc_delete_num_entry(const xt_chainlabel chain,
 			  unsigned int rulenum,
-			  struct iptc_handle *handle);
+			  struct xtc_handle *handle);
 
 /* Check the packet `e' on chain `chain'.  Returns the verdict, or
    NULL and sets errno. */
-const char *iptc_check_packet(const ipt_chainlabel chain,
+const char *iptc_check_packet(const xt_chainlabel chain,
 			      struct ipt_entry *entry,
-			      struct iptc_handle *handle);
+			      struct xtc_handle *handle);
 
 /* Flushes the entries in the given chain (ie. empties chain). */
-int iptc_flush_entries(const ipt_chainlabel chain,
-		       struct iptc_handle *handle);
+int iptc_flush_entries(const xt_chainlabel chain,
+		       struct xtc_handle *handle);
 
 /* Zeroes the counters in a chain. */
-int iptc_zero_entries(const ipt_chainlabel chain,
-		      struct iptc_handle *handle);
+int iptc_zero_entries(const xt_chainlabel chain,
+		      struct xtc_handle *handle);
 
 /* Creates a new chain. */
-int iptc_create_chain(const ipt_chainlabel chain,
-		      struct iptc_handle *handle);
+int iptc_create_chain(const xt_chainlabel chain,
+		      struct xtc_handle *handle);
 
 /* Deletes a chain. */
-int iptc_delete_chain(const ipt_chainlabel chain,
-		      struct iptc_handle *handle);
+int iptc_delete_chain(const xt_chainlabel chain,
+		      struct xtc_handle *handle);
 
 /* Renames a chain. */
-int iptc_rename_chain(const ipt_chainlabel oldname,
-		      const ipt_chainlabel newname,
-		      struct iptc_handle *handle);
+int iptc_rename_chain(const xt_chainlabel oldname,
+		      const xt_chainlabel newname,
+		      struct xtc_handle *handle);
 
 /* Sets the policy on a built-in chain. */
-int iptc_set_policy(const ipt_chainlabel chain,
-		    const ipt_chainlabel policy,
-		    struct ipt_counters *counters,
-		    struct iptc_handle *handle);
+int iptc_set_policy(const xt_chainlabel chain,
+		    const xt_chainlabel policy,
+		    struct xt_counters *counters,
+		    struct xtc_handle *handle);
 
 /* Get the number of references to this chain */
 int iptc_get_references(unsigned int *ref,
-			const ipt_chainlabel chain,
-			struct iptc_handle *handle);
+			const xt_chainlabel chain,
+			struct xtc_handle *handle);
 
 /* read packet and byte counters for a specific rule */
-struct ipt_counters *iptc_read_counter(const ipt_chainlabel chain,
+struct xt_counters *iptc_read_counter(const xt_chainlabel chain,
 				       unsigned int rulenum,
-				       struct iptc_handle *handle);
+				       struct xtc_handle *handle);
 
 /* zero packet and byte counters for a specific rule */
-int iptc_zero_counter(const ipt_chainlabel chain,
+int iptc_zero_counter(const xt_chainlabel chain,
 		      unsigned int rulenum,
-		      struct iptc_handle *handle);
+		      struct xtc_handle *handle);
 
 /* set packet and byte counters for a specific rule */
-int iptc_set_counter(const ipt_chainlabel chain,
+int iptc_set_counter(const xt_chainlabel chain,
 		     unsigned int rulenum,
-		     struct ipt_counters *counters,
-		     struct iptc_handle *handle);
+		     struct xt_counters *counters,
+		     struct xtc_handle *handle);
 
 /* Makes the actual changes. */
-int iptc_commit(struct iptc_handle *handle);
+int iptc_commit(struct xtc_handle *handle);
 
 /* Get raw socket. */
 int iptc_get_raw_socket(void);
@@ -160,7 +160,9 @@
 /* Translates errno numbers into more human-readable form than strerror. */
 const char *iptc_strerror(int err);
 
-extern void dump_entries(struct iptc_handle *const);
+extern void dump_entries(struct xtc_handle *const);
+
+extern const struct xtc_ops iptc_ops;
 
 #ifdef __cplusplus
 }
diff --git a/include/libiptc/xtcshared.h b/include/libiptc/xtcshared.h
new file mode 100644
index 0000000..341f9d4
--- /dev/null
+++ b/include/libiptc/xtcshared.h
@@ -0,0 +1,25 @@
+#ifndef _LIBXTC_SHARED_H
+#define _LIBXTC_SHARED_H 1
+
+typedef char xt_chainlabel[32];
+struct xtc_handle;
+struct xt_counters;
+
+struct xtc_ops {
+	int (*commit)(struct xtc_handle *);
+	struct xtc_handle *(*init)(const char *);
+	void (*free)(struct xtc_handle *);
+	int (*builtin)(const char *, struct xtc_handle *const);
+	int (*is_chain)(const char *, struct xtc_handle *const);
+	int (*flush_entries)(const xt_chainlabel, struct xtc_handle *);
+	int (*create_chain)(const xt_chainlabel, struct xtc_handle *);
+	const char *(*first_chain)(struct xtc_handle *);
+	const char *(*next_chain)(struct xtc_handle *);
+	const char *(*get_policy)(const char *, struct xt_counters *,
+				  struct xtc_handle *);
+	int (*set_policy)(const xt_chainlabel, const xt_chainlabel,
+			  struct xt_counters *, struct xtc_handle *);
+	const char *(*strerror)(int);
+};
+
+#endif /* _LIBXTC_SHARED_H */
diff --git a/include/linux/filter.h b/include/linux/filter.h
new file mode 100644
index 0000000..a9ae93c
--- /dev/null
+++ b/include/linux/filter.h
@@ -0,0 +1,139 @@
+/*
+ * Linux Socket Filter Data Structures
+ */
+
+#ifndef __LINUX_FILTER_H__
+#define __LINUX_FILTER_H__
+
+
+#include <linux/types.h>
+
+
+/*
+ * Current version of the filter code architecture.
+ */
+#define BPF_MAJOR_VERSION 1
+#define BPF_MINOR_VERSION 1
+
+/*
+ *	Try and keep these values and structures similar to BSD, especially
+ *	the BPF code definitions which need to match so you can share filters
+ */
+ 
+struct sock_filter {	/* Filter block */
+	__u16	code;   /* Actual filter code */
+	__u8	jt;	/* Jump true */
+	__u8	jf;	/* Jump false */
+	__u32	k;      /* Generic multiuse field */
+};
+
+struct sock_fprog {	/* Required for SO_ATTACH_FILTER. */
+	unsigned short		len;	/* Number of filter blocks */
+	struct sock_filter *filter;
+};
+
+/*
+ * Instruction classes
+ */
+
+#define BPF_CLASS(code) ((code) & 0x07)
+#define         BPF_LD          0x00
+#define         BPF_LDX         0x01
+#define         BPF_ST          0x02
+#define         BPF_STX         0x03
+#define         BPF_ALU         0x04
+#define         BPF_JMP         0x05
+#define         BPF_RET         0x06
+#define         BPF_MISC        0x07
+
+/* ld/ldx fields */
+#define BPF_SIZE(code)  ((code) & 0x18)
+#define         BPF_W           0x00
+#define         BPF_H           0x08
+#define         BPF_B           0x10
+#define BPF_MODE(code)  ((code) & 0xe0)
+#define         BPF_IMM         0x00
+#define         BPF_ABS         0x20
+#define         BPF_IND         0x40
+#define         BPF_MEM         0x60
+#define         BPF_LEN         0x80
+#define         BPF_MSH         0xa0
+
+/* alu/jmp fields */
+#define BPF_OP(code)    ((code) & 0xf0)
+#define         BPF_ADD         0x00
+#define         BPF_SUB         0x10
+#define         BPF_MUL         0x20
+#define         BPF_DIV         0x30
+#define         BPF_OR          0x40
+#define         BPF_AND         0x50
+#define         BPF_LSH         0x60
+#define         BPF_RSH         0x70
+#define         BPF_NEG         0x80
+#define		BPF_MOD		0x90
+#define		BPF_XOR		0xa0
+
+#define         BPF_JA          0x00
+#define         BPF_JEQ         0x10
+#define         BPF_JGT         0x20
+#define         BPF_JGE         0x30
+#define         BPF_JSET        0x40
+#define BPF_SRC(code)   ((code) & 0x08)
+#define         BPF_K           0x00
+#define         BPF_X           0x08
+
+/* ret - BPF_K and BPF_X also apply */
+#define BPF_RVAL(code)  ((code) & 0x18)
+#define         BPF_A           0x10
+
+/* misc */
+#define BPF_MISCOP(code) ((code) & 0xf8)
+#define         BPF_TAX         0x00
+#define         BPF_TXA         0x80
+
+#ifndef BPF_MAXINSNS
+#define BPF_MAXINSNS 4096
+#endif
+
+/*
+ * Macros for filter block array initializers.
+ */
+#ifndef BPF_STMT
+#define BPF_STMT(code, k) { (unsigned short)(code), 0, 0, k }
+#endif
+#ifndef BPF_JUMP
+#define BPF_JUMP(code, k, jt, jf) { (unsigned short)(code), jt, jf, k }
+#endif
+
+/*
+ * Number of scratch memory words for: BPF_ST and BPF_STX
+ */
+#define BPF_MEMWORDS 16
+
+/* RATIONALE. Negative offsets are invalid in BPF.
+   We use them to reference ancillary data.
+   Unlike introduction new instructions, it does not break
+   existing compilers/optimizers.
+ */
+#define SKF_AD_OFF    (-0x1000)
+#define SKF_AD_PROTOCOL 0
+#define SKF_AD_PKTTYPE 	4
+#define SKF_AD_IFINDEX 	8
+#define SKF_AD_NLATTR	12
+#define SKF_AD_NLATTR_NEST	16
+#define SKF_AD_MARK 	20
+#define SKF_AD_QUEUE	24
+#define SKF_AD_HATYPE	28
+#define SKF_AD_RXHASH	32
+#define SKF_AD_CPU	36
+#define SKF_AD_ALU_XOR_X	40
+#define SKF_AD_VLAN_TAG	44
+#define SKF_AD_VLAN_TAG_PRESENT 48
+#define SKF_AD_PAY_OFFSET	52
+#define SKF_AD_RANDOM	56
+#define SKF_AD_MAX	60
+#define SKF_NET_OFF   (-0x100000)
+#define SKF_LL_OFF    (-0x200000)
+
+
+#endif /* __LINUX_FILTER_H__ */
diff --git a/include/linux/kernel.h b/include/linux/kernel.h
index d1671a0..d4c59f6 100644
--- a/include/linux/kernel.h
+++ b/include/linux/kernel.h
@@ -8,7 +8,6 @@
 #define __ALIGN_KERNEL_MASK(x, mask)	(((x) + (mask)) & ~(mask))
 
 
-
 #define SI_LOAD_SHIFT	16
 struct sysinfo {
 	long uptime;			/* Seconds since boot */
@@ -27,36 +26,4 @@
 	char _f[20-2*sizeof(long)-sizeof(int)];	/* Padding: libc5 uses this.. */
 };
 
-/* Force a compilation error if condition is true */
-#define BUILD_BUG_ON(condition) ((void)BUILD_BUG_ON_ZERO(condition))
-
-/* Force a compilation error if condition is constant and true */
-#define MAYBE_BUILD_BUG_ON(cond) ((void)sizeof(char[1 - 2 * !!(cond)]))
-
-/* Force a compilation error if a constant expression is not a power of 2 */
-#define BUILD_BUG_ON_NOT_POWER_OF_2(n)			\
-	BUILD_BUG_ON((n) == 0 || (((n) & ((n) - 1)) != 0))
-
-/* Force a compilation error if condition is true, but also produce a
-   result (of value 0 and type size_t), so the expression can be used
-   e.g. in a structure initializer (or where-ever else comma expressions
-   aren't permitted). */
-#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
-#define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); }))
-
-/* Trap pasters of __FUNCTION__ at compile-time */
-#define __FUNCTION__ (__func__)
-
-/* This helps us to avoid #ifdef CONFIG_NUMA */
-#ifdef CONFIG_NUMA
-#define NUMA_BUILD 1
-#else
-#define NUMA_BUILD 0
-#endif
-
-/* Rebuild everything on CONFIG_FTRACE_MCOUNT_RECORD */
-#ifdef CONFIG_FTRACE_MCOUNT_RECORD
-# define REBUILD_DUE_TO_FTRACE_MCOUNT_RECORD
-#endif
-
 #endif
diff --git a/include/linux/netfilter.h b/include/linux/netfilter.h
index 2eb00b6..042d8b1 100644
--- a/include/linux/netfilter.h
+++ b/include/linux/netfilter.h
@@ -3,6 +3,11 @@
 
 #include <linux/types.h>
 
+#ifndef _NETINET_IN_H
+#include <linux/in.h>
+#include <linux/in6.h>
+#endif
+#include <limits.h>
 
 /* Responses from hook functions. */
 #define NF_DROP 0
@@ -10,18 +15,24 @@
 #define NF_STOLEN 2
 #define NF_QUEUE 3
 #define NF_REPEAT 4
-#define NF_STOP 5
+#define NF_STOP 5	/* Deprecated, for userspace nf_queue compatibility. */
 #define NF_MAX_VERDICT NF_STOP
 
 /* we overload the higher bits for encoding auxiliary data such as the queue
- * number. Not nice, but better than additional function arguments. */
-#define NF_VERDICT_MASK 0x0000ffff
-#define NF_VERDICT_BITS 16
+ * number or errno values. Not nice, but better than additional function
+ * arguments. */
+#define NF_VERDICT_MASK 0x000000ff
 
+/* extra verdict flags have mask 0x0000ff00 */
+#define NF_VERDICT_FLAG_QUEUE_BYPASS	0x00008000
+
+/* queue number (NF_QUEUE) or errno (NF_DROP) */
 #define NF_VERDICT_QMASK 0xffff0000
 #define NF_VERDICT_QBITS 16
 
-#define NF_QUEUE_NR(x) ((((x) << NF_VERDICT_BITS) & NF_VERDICT_QMASK) | NF_QUEUE)
+#define NF_QUEUE_NR(x) ((((x) << 16) & NF_VERDICT_QMASK) | NF_QUEUE)
+
+#define NF_DROP_ERR(x) (((-x) << 16) | NF_DROP)
 
 /* only for userspace compatibility */
 /* Generic cache responses from hook functions.
@@ -29,6 +40,9 @@
 #define NFC_UNKNOWN 0x4000
 #define NFC_ALTERED 0x8000
 
+/* NF_VERDICT_BITS should be 8 now, but userspace might break if this changes */
+#define NF_VERDICT_BITS 16
+
 enum nf_inet_hooks {
 	NF_INET_PRE_ROUTING,
 	NF_INET_LOCAL_IN,
@@ -38,10 +52,17 @@
 	NF_INET_NUMHOOKS
 };
 
+enum nf_dev_hooks {
+	NF_NETDEV_INGRESS,
+	NF_NETDEV_NUMHOOKS
+};
+
 enum {
 	NFPROTO_UNSPEC =  0,
+	NFPROTO_INET   =  1,
 	NFPROTO_IPV4   =  2,
 	NFPROTO_ARP    =  3,
+	NFPROTO_NETDEV =  5,
 	NFPROTO_BRIDGE =  7,
 	NFPROTO_IPV6   = 10,
 	NFPROTO_DECNET = 12,
@@ -56,4 +77,4 @@
 	struct in6_addr	in6;
 };
 
-#endif /*__LINUX_NETFILTER_H*/
+#endif /* __LINUX_NETFILTER_H */
diff --git a/include/linux/netfilter/ipset/ip_set.h b/include/linux/netfilter/ipset/ip_set.h
new file mode 100644
index 0000000..b69bbc7
--- /dev/null
+++ b/include/linux/netfilter/ipset/ip_set.h
@@ -0,0 +1,284 @@
+/* Copyright (C) 2000-2002 Joakim Axelsson <gozem@linux.nu>
+ *                         Patrick Schaaf <bof@bof.de>
+ *                         Martin Josefsson <gandalf@wlug.westbo.se>
+ * Copyright (C) 2003-2011 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 version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _UAPI_IP_SET_H
+#define _UAPI_IP_SET_H
+
+
+#include <linux/types.h>
+
+/* The protocol version */
+#define IPSET_PROTOCOL		6
+
+/* The max length of strings including NUL: set and type identifiers */
+#define IPSET_MAXNAMELEN	32
+
+/* Message types and commands */
+enum ipset_cmd {
+	IPSET_CMD_NONE,
+	IPSET_CMD_PROTOCOL,	/* 1: Return protocol version */
+	IPSET_CMD_CREATE,	/* 2: Create a new (empty) set */
+	IPSET_CMD_DESTROY,	/* 3: Destroy a (empty) set */
+	IPSET_CMD_FLUSH,	/* 4: Remove all elements from a set */
+	IPSET_CMD_RENAME,	/* 5: Rename a set */
+	IPSET_CMD_SWAP,		/* 6: Swap two sets */
+	IPSET_CMD_LIST,		/* 7: List sets */
+	IPSET_CMD_SAVE,		/* 8: Save sets */
+	IPSET_CMD_ADD,		/* 9: Add an element to a set */
+	IPSET_CMD_DEL,		/* 10: Delete an element from a set */
+	IPSET_CMD_TEST,		/* 11: Test an element in a set */
+	IPSET_CMD_HEADER,	/* 12: Get set header data only */
+	IPSET_CMD_TYPE,		/* 13: Get set type */
+	IPSET_MSG_MAX,		/* Netlink message commands */
+
+	/* Commands in userspace: */
+	IPSET_CMD_RESTORE = IPSET_MSG_MAX, /* 14: Enter restore mode */
+	IPSET_CMD_HELP,		/* 15: Get help */
+	IPSET_CMD_VERSION,	/* 16: Get program version */
+	IPSET_CMD_QUIT,		/* 17: Quit from interactive mode */
+
+	IPSET_CMD_MAX,
+
+	IPSET_CMD_COMMIT = IPSET_CMD_MAX, /* 18: Commit buffered commands */
+};
+
+/* Attributes at command level */
+enum {
+	IPSET_ATTR_UNSPEC,
+	IPSET_ATTR_PROTOCOL,	/* 1: Protocol version */
+	IPSET_ATTR_SETNAME,	/* 2: Name of the set */
+	IPSET_ATTR_TYPENAME,	/* 3: Typename */
+	IPSET_ATTR_SETNAME2 = IPSET_ATTR_TYPENAME, /* Setname at rename/swap */
+	IPSET_ATTR_REVISION,	/* 4: Settype revision */
+	IPSET_ATTR_FAMILY,	/* 5: Settype family */
+	IPSET_ATTR_FLAGS,	/* 6: Flags at command level */
+	IPSET_ATTR_DATA,	/* 7: Nested attributes */
+	IPSET_ATTR_ADT,		/* 8: Multiple data containers */
+	IPSET_ATTR_LINENO,	/* 9: Restore lineno */
+	IPSET_ATTR_PROTOCOL_MIN, /* 10: Minimal supported version number */
+	IPSET_ATTR_REVISION_MIN	= IPSET_ATTR_PROTOCOL_MIN, /* type rev min */
+	__IPSET_ATTR_CMD_MAX,
+};
+#define IPSET_ATTR_CMD_MAX	(__IPSET_ATTR_CMD_MAX - 1)
+
+/* CADT specific attributes */
+enum {
+	IPSET_ATTR_IP = IPSET_ATTR_UNSPEC + 1,
+	IPSET_ATTR_IP_FROM = IPSET_ATTR_IP,
+	IPSET_ATTR_IP_TO,	/* 2 */
+	IPSET_ATTR_CIDR,	/* 3 */
+	IPSET_ATTR_PORT,	/* 4 */
+	IPSET_ATTR_PORT_FROM = IPSET_ATTR_PORT,
+	IPSET_ATTR_PORT_TO,	/* 5 */
+	IPSET_ATTR_TIMEOUT,	/* 6 */
+	IPSET_ATTR_PROTO,	/* 7 */
+	IPSET_ATTR_CADT_FLAGS,	/* 8 */
+	IPSET_ATTR_CADT_LINENO = IPSET_ATTR_LINENO,	/* 9 */
+	/* Reserve empty slots */
+	IPSET_ATTR_CADT_MAX = 16,
+	/* Create-only specific attributes */
+	IPSET_ATTR_GC,
+	IPSET_ATTR_HASHSIZE,
+	IPSET_ATTR_MAXELEM,
+	IPSET_ATTR_NETMASK,
+	IPSET_ATTR_PROBES,
+	IPSET_ATTR_RESIZE,
+	IPSET_ATTR_SIZE,
+	/* Kernel-only */
+	IPSET_ATTR_ELEMENTS,
+	IPSET_ATTR_REFERENCES,
+	IPSET_ATTR_MEMSIZE,
+
+	__IPSET_ATTR_CREATE_MAX,
+};
+#define IPSET_ATTR_CREATE_MAX	(__IPSET_ATTR_CREATE_MAX - 1)
+
+/* ADT specific attributes */
+enum {
+	IPSET_ATTR_ETHER = IPSET_ATTR_CADT_MAX + 1,
+	IPSET_ATTR_NAME,
+	IPSET_ATTR_NAMEREF,
+	IPSET_ATTR_IP2,
+	IPSET_ATTR_CIDR2,
+	IPSET_ATTR_IP2_TO,
+	IPSET_ATTR_IFACE,
+	IPSET_ATTR_BYTES,
+	IPSET_ATTR_PACKETS,
+	IPSET_ATTR_SKBMARK,
+	IPSET_ATTR_SKBPRIO,
+	IPSET_ATTR_SKBQUEUE,
+	__IPSET_ATTR_ADT_MAX,
+};
+#define IPSET_ATTR_ADT_MAX	(__IPSET_ATTR_ADT_MAX - 1)
+
+/* IP specific attributes */
+enum {
+	IPSET_ATTR_IPADDR_IPV4 = IPSET_ATTR_UNSPEC + 1,
+	IPSET_ATTR_IPADDR_IPV6,
+	__IPSET_ATTR_IPADDR_MAX,
+};
+#define IPSET_ATTR_IPADDR_MAX	(__IPSET_ATTR_IPADDR_MAX - 1)
+
+/* Error codes */
+enum ipset_errno {
+	IPSET_ERR_PRIVATE = 4096,
+	IPSET_ERR_PROTOCOL,
+	IPSET_ERR_FIND_TYPE,
+	IPSET_ERR_MAX_SETS,
+	IPSET_ERR_BUSY,
+	IPSET_ERR_EXIST_SETNAME2,
+	IPSET_ERR_TYPE_MISMATCH,
+	IPSET_ERR_EXIST,
+	IPSET_ERR_INVALID_CIDR,
+	IPSET_ERR_INVALID_NETMASK,
+	IPSET_ERR_INVALID_FAMILY,
+	IPSET_ERR_TIMEOUT,
+	IPSET_ERR_REFERENCED,
+	IPSET_ERR_IPADDR_IPV4,
+	IPSET_ERR_IPADDR_IPV6,
+	IPSET_ERR_COUNTER,
+	IPSET_ERR_SKBINFO,
+
+	/* Type specific error codes */
+	IPSET_ERR_TYPE_SPECIFIC = 4352,
+};
+
+/* Flags at command level or match/target flags, lower half of cmdattrs */
+enum ipset_cmd_flags {
+	IPSET_FLAG_BIT_EXIST	= 0,
+	IPSET_FLAG_EXIST	= (1 << IPSET_FLAG_BIT_EXIST),
+	IPSET_FLAG_BIT_LIST_SETNAME = 1,
+	IPSET_FLAG_LIST_SETNAME	= (1 << IPSET_FLAG_BIT_LIST_SETNAME),
+	IPSET_FLAG_BIT_LIST_HEADER = 2,
+	IPSET_FLAG_LIST_HEADER	= (1 << IPSET_FLAG_BIT_LIST_HEADER),
+	IPSET_FLAG_BIT_SKIP_COUNTER_UPDATE = 3,
+	IPSET_FLAG_SKIP_COUNTER_UPDATE =
+		(1 << IPSET_FLAG_BIT_SKIP_COUNTER_UPDATE),
+	IPSET_FLAG_BIT_SKIP_SUBCOUNTER_UPDATE = 4,
+	IPSET_FLAG_SKIP_SUBCOUNTER_UPDATE =
+		(1 << IPSET_FLAG_BIT_SKIP_SUBCOUNTER_UPDATE),
+	IPSET_FLAG_BIT_MATCH_COUNTERS = 5,
+	IPSET_FLAG_MATCH_COUNTERS = (1 << IPSET_FLAG_BIT_MATCH_COUNTERS),
+	IPSET_FLAG_BIT_RETURN_NOMATCH = 7,
+	IPSET_FLAG_RETURN_NOMATCH = (1 << IPSET_FLAG_BIT_RETURN_NOMATCH),
+	IPSET_FLAG_BIT_MAP_SKBMARK = 8,
+	IPSET_FLAG_MAP_SKBMARK = (1 << IPSET_FLAG_BIT_MAP_SKBMARK),
+	IPSET_FLAG_BIT_MAP_SKBPRIO = 9,
+	IPSET_FLAG_MAP_SKBPRIO = (1 << IPSET_FLAG_BIT_MAP_SKBPRIO),
+	IPSET_FLAG_BIT_MAP_SKBQUEUE = 10,
+	IPSET_FLAG_MAP_SKBQUEUE = (1 << IPSET_FLAG_BIT_MAP_SKBQUEUE),
+	IPSET_FLAG_CMD_MAX = 15,
+};
+
+/* Flags at CADT attribute level, upper half of cmdattrs */
+enum ipset_cadt_flags {
+	IPSET_FLAG_BIT_BEFORE	= 0,
+	IPSET_FLAG_BEFORE	= (1 << IPSET_FLAG_BIT_BEFORE),
+	IPSET_FLAG_BIT_PHYSDEV	= 1,
+	IPSET_FLAG_PHYSDEV	= (1 << IPSET_FLAG_BIT_PHYSDEV),
+	IPSET_FLAG_BIT_NOMATCH	= 2,
+	IPSET_FLAG_NOMATCH	= (1 << IPSET_FLAG_BIT_NOMATCH),
+	IPSET_FLAG_BIT_WITH_COUNTERS = 3,
+	IPSET_FLAG_WITH_COUNTERS = (1 << IPSET_FLAG_BIT_WITH_COUNTERS),
+	IPSET_FLAG_CADT_MAX	= 15,
+};
+
+/* Commands with settype-specific attributes */
+enum ipset_adt {
+	IPSET_ADD,
+	IPSET_DEL,
+	IPSET_TEST,
+	IPSET_ADT_MAX,
+	IPSET_CREATE = IPSET_ADT_MAX,
+	IPSET_CADT_MAX,
+};
+
+/* Sets are identified by an index in kernel space. Tweak with ip_set_id_t
+ * and IPSET_INVALID_ID if you want to increase the max number of sets.
+ */
+typedef __u16 ip_set_id_t;
+
+#define IPSET_INVALID_ID		65535
+
+enum ip_set_dim {
+	IPSET_DIM_ZERO = 0,
+	IPSET_DIM_ONE,
+	IPSET_DIM_TWO,
+	IPSET_DIM_THREE,
+	/* Max dimension in elements.
+	 * If changed, new revision of iptables match/target is required.
+	 */
+	IPSET_DIM_MAX = 6,
+	/* Backward compatibility: set match revision 2 */
+	IPSET_BIT_RETURN_NOMATCH = 7,
+};
+
+/* Option flags for kernel operations */
+enum ip_set_kopt {
+	IPSET_INV_MATCH = (1 << IPSET_DIM_ZERO),
+	IPSET_DIM_ONE_SRC = (1 << IPSET_DIM_ONE),
+	IPSET_DIM_TWO_SRC = (1 << IPSET_DIM_TWO),
+	IPSET_DIM_THREE_SRC = (1 << IPSET_DIM_THREE),
+	IPSET_RETURN_NOMATCH = (1 << IPSET_BIT_RETURN_NOMATCH),
+};
+
+enum {
+	IPSET_COUNTER_NONE = 0,
+	IPSET_COUNTER_EQ,
+	IPSET_COUNTER_NE,
+	IPSET_COUNTER_LT,
+	IPSET_COUNTER_GT,
+};
+
+/* Backward compatibility for set match v3 */
+struct ip_set_counter_match0 {
+	__u8 op;
+	__u64 value;
+};
+
+struct ip_set_counter_match {
+	__aligned_u64 value;
+	__u8 op;
+};
+
+/* Interface to iptables/ip6tables */
+
+#define SO_IP_SET		83
+
+union ip_set_name_index {
+	char name[IPSET_MAXNAMELEN];
+	ip_set_id_t index;
+};
+
+#define IP_SET_OP_GET_BYNAME	0x00000006	/* Get set index by name */
+struct ip_set_req_get_set {
+	unsigned int op;
+	unsigned int version;
+	union ip_set_name_index set;
+};
+
+#define IP_SET_OP_GET_BYINDEX	0x00000007	/* Get set name by index */
+/* Uses ip_set_req_get_set */
+
+#define IP_SET_OP_GET_FNAME	0x00000008	/* Get set index and family */
+struct ip_set_req_get_set_family {
+	unsigned int op;
+	unsigned int version;
+	unsigned int family;
+	union ip_set_name_index set;
+};
+
+
+#define IP_SET_OP_VERSION	0x00000100	/* Ask kernel version */
+struct ip_set_req_version {
+	unsigned int op;
+	unsigned int version;
+};
+
+#endif /* _UAPI_IP_SET_H */
diff --git a/include/linux/netfilter/nf_conntrack_common.h b/include/linux/netfilter/nf_conntrack_common.h
index 34a7fc6..38aa52d 100644
--- a/include/linux/netfilter/nf_conntrack_common.h
+++ b/include/linux/netfilter/nf_conntrack_common.h
@@ -18,6 +18,9 @@
 	/* >= this indicates reply direction */
 	IP_CT_IS_REPLY,
 
+	IP_CT_ESTABLISHED_REPLY = IP_CT_ESTABLISHED + IP_CT_IS_REPLY,
+	IP_CT_RELATED_REPLY = IP_CT_RELATED + IP_CT_IS_REPLY,
+	IP_CT_NEW_REPLY = IP_CT_NEW + IP_CT_IS_REPLY,	
 	/* Number of distinct IP_CT types (no NEW in reply dirn). */
 	IP_CT_NUMBER = IP_CT_IS_REPLY * 2 - 1
 };
@@ -76,6 +79,10 @@
 	/* Conntrack is a template */
 	IPS_TEMPLATE_BIT = 11,
 	IPS_TEMPLATE = (1 << IPS_TEMPLATE_BIT),
+
+	/* Conntrack is a fake untracked entry */
+	IPS_UNTRACKED_BIT = 12,
+	IPS_UNTRACKED = (1 << IPS_UNTRACKED_BIT),
 };
 
 /* Connection tracking event types */
@@ -94,6 +101,13 @@
 
 enum ip_conntrack_expect_events {
 	IPEXP_NEW,		/* new expectation */
+	IPEXP_DESTROY,		/* destroyed expectation */
 };
 
+/* expectation flags */
+#define NF_CT_EXPECT_PERMANENT		0x1
+#define NF_CT_EXPECT_INACTIVE		0x2
+#define NF_CT_EXPECT_USERSPACE		0x4
+
+
 #endif /* _NF_CONNTRACK_COMMON_H */
diff --git a/include/linux/netfilter/nf_conntrack_tuple_common.h b/include/linux/netfilter/nf_conntrack_tuple_common.h
index 8e145f0..2f6bbc5 100644
--- a/include/linux/netfilter/nf_conntrack_tuple_common.h
+++ b/include/linux/netfilter/nf_conntrack_tuple_common.h
@@ -1,13 +1,39 @@
 #ifndef _NF_CONNTRACK_TUPLE_COMMON_H
 #define _NF_CONNTRACK_TUPLE_COMMON_H
 
-enum ip_conntrack_dir
-{
+enum ip_conntrack_dir {
 	IP_CT_DIR_ORIGINAL,
 	IP_CT_DIR_REPLY,
 	IP_CT_DIR_MAX
 };
 
+/* The protocol-specific manipulable parts of the tuple: always in
+ * network order
+ */
+union nf_conntrack_man_proto {
+	/* Add other protocols here. */
+	__be16 all;
+
+	struct {
+		__be16 port;
+	} tcp;
+	struct {
+		__be16 port;
+	} udp;
+	struct {
+		__be16 id;
+	} icmp;
+	struct {
+		__be16 port;
+	} dccp;
+	struct {
+		__be16 port;
+	} sctp;
+	struct {
+		__be16 key;	/* GRE key is 32bit, PPtP only uses 16bit */
+	} gre;
+};
+
 #define CTINFO2DIR(ctinfo) ((ctinfo) >= IP_CT_IS_REPLY ? IP_CT_DIR_REPLY : IP_CT_DIR_ORIGINAL)
 
 #endif /* _NF_CONNTRACK_TUPLE_COMMON_H */
diff --git a/include/linux/netfilter/nf_nat.h b/include/linux/netfilter/nf_nat.h
new file mode 100644
index 0000000..b600000
--- /dev/null
+++ b/include/linux/netfilter/nf_nat.h
@@ -0,0 +1,52 @@
+#ifndef _NETFILTER_NF_NAT_H
+#define _NETFILTER_NF_NAT_H
+
+#include <linux/netfilter.h>
+#include <linux/netfilter/nf_conntrack_tuple_common.h>
+
+#define NF_NAT_RANGE_MAP_IPS			(1 << 0)
+#define NF_NAT_RANGE_PROTO_SPECIFIED		(1 << 1)
+#define NF_NAT_RANGE_PROTO_RANDOM		(1 << 2)
+#define NF_NAT_RANGE_PERSISTENT			(1 << 3)
+#define NF_NAT_RANGE_PROTO_RANDOM_FULLY		(1 << 4)
+#define NF_NAT_RANGE_PROTO_OFFSET		(1 << 5)
+
+#define NF_NAT_RANGE_PROTO_RANDOM_ALL		\
+	(NF_NAT_RANGE_PROTO_RANDOM | NF_NAT_RANGE_PROTO_RANDOM_FULLY)
+
+#define NF_NAT_RANGE_MASK					\
+	(NF_NAT_RANGE_MAP_IPS | NF_NAT_RANGE_PROTO_SPECIFIED |	\
+	 NF_NAT_RANGE_PROTO_RANDOM | NF_NAT_RANGE_PERSISTENT |	\
+	 NF_NAT_RANGE_PROTO_RANDOM_FULLY | NF_NAT_RANGE_PROTO_OFFSET)
+
+struct nf_nat_ipv4_range {
+	unsigned int			flags;
+	__be32				min_ip;
+	__be32				max_ip;
+	union nf_conntrack_man_proto	min;
+	union nf_conntrack_man_proto	max;
+};
+
+struct nf_nat_ipv4_multi_range_compat {
+	unsigned int			rangesize;
+	struct nf_nat_ipv4_range	range[1];
+};
+
+struct nf_nat_range {
+	unsigned int			flags;
+	union nf_inet_addr		min_addr;
+	union nf_inet_addr		max_addr;
+	union nf_conntrack_man_proto	min_proto;
+	union nf_conntrack_man_proto	max_proto;
+};
+
+struct nf_nat_range2 {
+	unsigned int			flags;
+	union nf_inet_addr		min_addr;
+	union nf_inet_addr		max_addr;
+	union nf_conntrack_man_proto	min_proto;
+	union nf_conntrack_man_proto	max_proto;
+	union nf_conntrack_man_proto	base_proto;
+};
+
+#endif /* _NETFILTER_NF_NAT_H */
diff --git a/include/linux/netfilter/nf_tables.h b/include/linux/netfilter/nf_tables.h
new file mode 100644
index 0000000..66dceee
--- /dev/null
+++ b/include/linux/netfilter/nf_tables.h
@@ -0,0 +1,1463 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _LINUX_NF_TABLES_H
+#define _LINUX_NF_TABLES_H
+
+#define NFT_NAME_MAXLEN		256
+#define NFT_TABLE_MAXNAMELEN	NFT_NAME_MAXLEN
+#define NFT_CHAIN_MAXNAMELEN	NFT_NAME_MAXLEN
+#define NFT_SET_MAXNAMELEN	NFT_NAME_MAXLEN
+#define NFT_OBJ_MAXNAMELEN	NFT_NAME_MAXLEN
+#define NFT_USERDATA_MAXLEN	256
+
+/**
+ * enum nft_registers - nf_tables registers
+ *
+ * nf_tables used to have five registers: a verdict register and four data
+ * registers of size 16. The data registers have been changed to 16 registers
+ * of size 4. For compatibility reasons, the NFT_REG_[1-4] registers still
+ * map to areas of size 16, the 4 byte registers are addressed using
+ * NFT_REG32_00 - NFT_REG32_15.
+ */
+enum nft_registers {
+	NFT_REG_VERDICT,
+	NFT_REG_1,
+	NFT_REG_2,
+	NFT_REG_3,
+	NFT_REG_4,
+	__NFT_REG_MAX,
+
+	NFT_REG32_00	= 8,
+	NFT_REG32_01,
+	NFT_REG32_02,
+	NFT_REG32_03,
+	NFT_REG32_04,
+	NFT_REG32_05,
+	NFT_REG32_06,
+	NFT_REG32_07,
+	NFT_REG32_08,
+	NFT_REG32_09,
+	NFT_REG32_10,
+	NFT_REG32_11,
+	NFT_REG32_12,
+	NFT_REG32_13,
+	NFT_REG32_14,
+	NFT_REG32_15,
+};
+#define NFT_REG_MAX	(__NFT_REG_MAX - 1)
+
+#define NFT_REG_SIZE	16
+#define NFT_REG32_SIZE	4
+
+/**
+ * enum nft_verdicts - nf_tables internal verdicts
+ *
+ * @NFT_CONTINUE: continue evaluation of the current rule
+ * @NFT_BREAK: terminate evaluation of the current rule
+ * @NFT_JUMP: push the current chain on the jump stack and jump to a chain
+ * @NFT_GOTO: jump to a chain without pushing the current chain on the jump stack
+ * @NFT_RETURN: return to the topmost chain on the jump stack
+ *
+ * The nf_tables verdicts share their numeric space with the netfilter verdicts.
+ */
+enum nft_verdicts {
+	NFT_CONTINUE	= -1,
+	NFT_BREAK	= -2,
+	NFT_JUMP	= -3,
+	NFT_GOTO	= -4,
+	NFT_RETURN	= -5,
+};
+
+/**
+ * enum nf_tables_msg_types - nf_tables netlink message types
+ *
+ * @NFT_MSG_NEWTABLE: create a new table (enum nft_table_attributes)
+ * @NFT_MSG_GETTABLE: get a table (enum nft_table_attributes)
+ * @NFT_MSG_DELTABLE: delete a table (enum nft_table_attributes)
+ * @NFT_MSG_NEWCHAIN: create a new chain (enum nft_chain_attributes)
+ * @NFT_MSG_GETCHAIN: get a chain (enum nft_chain_attributes)
+ * @NFT_MSG_DELCHAIN: delete a chain (enum nft_chain_attributes)
+ * @NFT_MSG_NEWRULE: create a new rule (enum nft_rule_attributes)
+ * @NFT_MSG_GETRULE: get a rule (enum nft_rule_attributes)
+ * @NFT_MSG_DELRULE: delete a rule (enum nft_rule_attributes)
+ * @NFT_MSG_NEWSET: create a new set (enum nft_set_attributes)
+ * @NFT_MSG_GETSET: get a set (enum nft_set_attributes)
+ * @NFT_MSG_DELSET: delete a set (enum nft_set_attributes)
+ * @NFT_MSG_NEWSETELEM: create a new set element (enum nft_set_elem_attributes)
+ * @NFT_MSG_GETSETELEM: get a set element (enum nft_set_elem_attributes)
+ * @NFT_MSG_DELSETELEM: delete a set element (enum nft_set_elem_attributes)
+ * @NFT_MSG_NEWGEN: announce a new generation, only for events (enum nft_gen_attributes)
+ * @NFT_MSG_GETGEN: get the rule-set generation (enum nft_gen_attributes)
+ * @NFT_MSG_TRACE: trace event (enum nft_trace_attributes)
+ * @NFT_MSG_NEWOBJ: create a stateful object (enum nft_obj_attributes)
+ * @NFT_MSG_GETOBJ: get a stateful object (enum nft_obj_attributes)
+ * @NFT_MSG_DELOBJ: delete a stateful object (enum nft_obj_attributes)
+ * @NFT_MSG_GETOBJ_RESET: get and reset a stateful object (enum nft_obj_attributes)
+ * @NFT_MSG_NEWFLOWTABLE: add new flow table (enum nft_flowtable_attributes)
+ * @NFT_MSG_GETFLOWTABLE: get flow table (enum nft_flowtable_attributes)
+ * @NFT_MSG_DELFLOWTABLE: delete flow table (enum nft_flowtable_attributes)
+ */
+enum nf_tables_msg_types {
+	NFT_MSG_NEWTABLE,
+	NFT_MSG_GETTABLE,
+	NFT_MSG_DELTABLE,
+	NFT_MSG_NEWCHAIN,
+	NFT_MSG_GETCHAIN,
+	NFT_MSG_DELCHAIN,
+	NFT_MSG_NEWRULE,
+	NFT_MSG_GETRULE,
+	NFT_MSG_DELRULE,
+	NFT_MSG_NEWSET,
+	NFT_MSG_GETSET,
+	NFT_MSG_DELSET,
+	NFT_MSG_NEWSETELEM,
+	NFT_MSG_GETSETELEM,
+	NFT_MSG_DELSETELEM,
+	NFT_MSG_NEWGEN,
+	NFT_MSG_GETGEN,
+	NFT_MSG_TRACE,
+	NFT_MSG_NEWOBJ,
+	NFT_MSG_GETOBJ,
+	NFT_MSG_DELOBJ,
+	NFT_MSG_GETOBJ_RESET,
+	NFT_MSG_NEWFLOWTABLE,
+	NFT_MSG_GETFLOWTABLE,
+	NFT_MSG_DELFLOWTABLE,
+	NFT_MSG_MAX,
+};
+
+/**
+ * enum nft_list_attributes - nf_tables generic list netlink attributes
+ *
+ * @NFTA_LIST_ELEM: list element (NLA_NESTED)
+ */
+enum nft_list_attributes {
+	NFTA_LIST_UNPEC,
+	NFTA_LIST_ELEM,
+	__NFTA_LIST_MAX
+};
+#define NFTA_LIST_MAX		(__NFTA_LIST_MAX - 1)
+
+/**
+ * enum nft_hook_attributes - nf_tables netfilter hook netlink attributes
+ *
+ * @NFTA_HOOK_HOOKNUM: netfilter hook number (NLA_U32)
+ * @NFTA_HOOK_PRIORITY: netfilter hook priority (NLA_U32)
+ * @NFTA_HOOK_DEV: netdevice name (NLA_STRING)
+ */
+enum nft_hook_attributes {
+	NFTA_HOOK_UNSPEC,
+	NFTA_HOOK_HOOKNUM,
+	NFTA_HOOK_PRIORITY,
+	NFTA_HOOK_DEV,
+	__NFTA_HOOK_MAX
+};
+#define NFTA_HOOK_MAX		(__NFTA_HOOK_MAX - 1)
+
+/**
+ * enum nft_table_flags - nf_tables table flags
+ *
+ * @NFT_TABLE_F_DORMANT: this table is not active
+ */
+enum nft_table_flags {
+	NFT_TABLE_F_DORMANT	= 0x1,
+};
+
+/**
+ * enum nft_table_attributes - nf_tables table netlink attributes
+ *
+ * @NFTA_TABLE_NAME: name of the table (NLA_STRING)
+ * @NFTA_TABLE_FLAGS: bitmask of enum nft_table_flags (NLA_U32)
+ * @NFTA_TABLE_USE: number of chains in this table (NLA_U32)
+ */
+enum nft_table_attributes {
+	NFTA_TABLE_UNSPEC,
+	NFTA_TABLE_NAME,
+	NFTA_TABLE_FLAGS,
+	NFTA_TABLE_USE,
+	NFTA_TABLE_HANDLE,
+	NFTA_TABLE_PAD,
+	__NFTA_TABLE_MAX
+};
+#define NFTA_TABLE_MAX		(__NFTA_TABLE_MAX - 1)
+
+/**
+ * enum nft_chain_attributes - nf_tables chain netlink attributes
+ *
+ * @NFTA_CHAIN_TABLE: name of the table containing the chain (NLA_STRING)
+ * @NFTA_CHAIN_HANDLE: numeric handle of the chain (NLA_U64)
+ * @NFTA_CHAIN_NAME: name of the chain (NLA_STRING)
+ * @NFTA_CHAIN_HOOK: hook specification for basechains (NLA_NESTED: nft_hook_attributes)
+ * @NFTA_CHAIN_POLICY: numeric policy of the chain (NLA_U32)
+ * @NFTA_CHAIN_USE: number of references to this chain (NLA_U32)
+ * @NFTA_CHAIN_TYPE: type name of the string (NLA_NUL_STRING)
+ * @NFTA_CHAIN_COUNTERS: counter specification of the chain (NLA_NESTED: nft_counter_attributes)
+ */
+enum nft_chain_attributes {
+	NFTA_CHAIN_UNSPEC,
+	NFTA_CHAIN_TABLE,
+	NFTA_CHAIN_HANDLE,
+	NFTA_CHAIN_NAME,
+	NFTA_CHAIN_HOOK,
+	NFTA_CHAIN_POLICY,
+	NFTA_CHAIN_USE,
+	NFTA_CHAIN_TYPE,
+	NFTA_CHAIN_COUNTERS,
+	NFTA_CHAIN_PAD,
+	__NFTA_CHAIN_MAX
+};
+#define NFTA_CHAIN_MAX		(__NFTA_CHAIN_MAX - 1)
+
+/**
+ * enum nft_rule_attributes - nf_tables rule netlink attributes
+ *
+ * @NFTA_RULE_TABLE: name of the table containing the rule (NLA_STRING)
+ * @NFTA_RULE_CHAIN: name of the chain containing the rule (NLA_STRING)
+ * @NFTA_RULE_HANDLE: numeric handle of the rule (NLA_U64)
+ * @NFTA_RULE_EXPRESSIONS: list of expressions (NLA_NESTED: nft_expr_attributes)
+ * @NFTA_RULE_COMPAT: compatibility specifications of the rule (NLA_NESTED: nft_rule_compat_attributes)
+ * @NFTA_RULE_POSITION: numeric handle of the previous rule (NLA_U64)
+ * @NFTA_RULE_USERDATA: user data (NLA_BINARY, NFT_USERDATA_MAXLEN)
+ * @NFTA_RULE_ID: uniquely identifies a rule in a transaction (NLA_U32)
+ */
+enum nft_rule_attributes {
+	NFTA_RULE_UNSPEC,
+	NFTA_RULE_TABLE,
+	NFTA_RULE_CHAIN,
+	NFTA_RULE_HANDLE,
+	NFTA_RULE_EXPRESSIONS,
+	NFTA_RULE_COMPAT,
+	NFTA_RULE_POSITION,
+	NFTA_RULE_USERDATA,
+	NFTA_RULE_PAD,
+	NFTA_RULE_ID,
+	__NFTA_RULE_MAX
+};
+#define NFTA_RULE_MAX		(__NFTA_RULE_MAX - 1)
+
+/**
+ * enum nft_rule_compat_flags - nf_tables rule compat flags
+ *
+ * @NFT_RULE_COMPAT_F_INV: invert the check result
+ */
+enum nft_rule_compat_flags {
+	NFT_RULE_COMPAT_F_INV	= (1 << 1),
+	NFT_RULE_COMPAT_F_MASK	= NFT_RULE_COMPAT_F_INV,
+};
+
+/**
+ * enum nft_rule_compat_attributes - nf_tables rule compat attributes
+ *
+ * @NFTA_RULE_COMPAT_PROTO: numeric value of handled protocol (NLA_U32)
+ * @NFTA_RULE_COMPAT_FLAGS: bitmask of enum nft_rule_compat_flags (NLA_U32)
+ */
+enum nft_rule_compat_attributes {
+	NFTA_RULE_COMPAT_UNSPEC,
+	NFTA_RULE_COMPAT_PROTO,
+	NFTA_RULE_COMPAT_FLAGS,
+	__NFTA_RULE_COMPAT_MAX
+};
+#define NFTA_RULE_COMPAT_MAX	(__NFTA_RULE_COMPAT_MAX - 1)
+
+/**
+ * enum nft_set_flags - nf_tables set flags
+ *
+ * @NFT_SET_ANONYMOUS: name allocation, automatic cleanup on unlink
+ * @NFT_SET_CONSTANT: set contents may not change while bound
+ * @NFT_SET_INTERVAL: set contains intervals
+ * @NFT_SET_MAP: set is used as a dictionary
+ * @NFT_SET_TIMEOUT: set uses timeouts
+ * @NFT_SET_EVAL: set contains expressions for evaluation
+ * @NFT_SET_OBJECT: set contains stateful objects
+ */
+enum nft_set_flags {
+	NFT_SET_ANONYMOUS		= 0x1,
+	NFT_SET_CONSTANT		= 0x2,
+	NFT_SET_INTERVAL		= 0x4,
+	NFT_SET_MAP			= 0x8,
+	NFT_SET_TIMEOUT			= 0x10,
+	NFT_SET_EVAL			= 0x20,
+	NFT_SET_OBJECT			= 0x40,
+};
+
+/**
+ * enum nft_set_policies - set selection policy
+ *
+ * @NFT_SET_POL_PERFORMANCE: prefer high performance over low memory use
+ * @NFT_SET_POL_MEMORY: prefer low memory use over high performance
+ */
+enum nft_set_policies {
+	NFT_SET_POL_PERFORMANCE,
+	NFT_SET_POL_MEMORY,
+};
+
+/**
+ * enum nft_set_desc_attributes - set element description
+ *
+ * @NFTA_SET_DESC_SIZE: number of elements in set (NLA_U32)
+ */
+enum nft_set_desc_attributes {
+	NFTA_SET_DESC_UNSPEC,
+	NFTA_SET_DESC_SIZE,
+	__NFTA_SET_DESC_MAX
+};
+#define NFTA_SET_DESC_MAX	(__NFTA_SET_DESC_MAX - 1)
+
+/**
+ * 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)
+ * @NFTA_SET_POLICY: selection policy (NLA_U32)
+ * @NFTA_SET_DESC: set description (NLA_NESTED)
+ * @NFTA_SET_ID: uniquely identifies a set in a transaction (NLA_U32)
+ * @NFTA_SET_TIMEOUT: default timeout value (NLA_U64)
+ * @NFTA_SET_GC_INTERVAL: garbage collection interval (NLA_U32)
+ * @NFTA_SET_USERDATA: user data (NLA_BINARY)
+ * @NFTA_SET_OBJ_TYPE: stateful object type (NLA_U32: NFT_OBJECT_*)
+ * @NFTA_SET_HANDLE: set handle (NLA_U64)
+ */
+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_POLICY,
+	NFTA_SET_DESC,
+	NFTA_SET_ID,
+	NFTA_SET_TIMEOUT,
+	NFTA_SET_GC_INTERVAL,
+	NFTA_SET_USERDATA,
+	NFTA_SET_PAD,
+	NFTA_SET_OBJ_TYPE,
+	NFTA_SET_HANDLE,
+	__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)
+ * @NFTA_SET_ELEM_TIMEOUT: timeout value (NLA_U64)
+ * @NFTA_SET_ELEM_EXPIRATION: expiration time (NLA_U64)
+ * @NFTA_SET_ELEM_USERDATA: user data (NLA_BINARY)
+ * @NFTA_SET_ELEM_EXPR: expression (NLA_NESTED: nft_expr_attributes)
+ * @NFTA_SET_ELEM_OBJREF: stateful object reference (NLA_STRING)
+ */
+enum nft_set_elem_attributes {
+	NFTA_SET_ELEM_UNSPEC,
+	NFTA_SET_ELEM_KEY,
+	NFTA_SET_ELEM_DATA,
+	NFTA_SET_ELEM_FLAGS,
+	NFTA_SET_ELEM_TIMEOUT,
+	NFTA_SET_ELEM_EXPIRATION,
+	NFTA_SET_ELEM_USERDATA,
+	NFTA_SET_ELEM_EXPR,
+	NFTA_SET_ELEM_PAD,
+	NFTA_SET_ELEM_OBJREF,
+	__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)
+ * @NFTA_SET_ELEM_LIST_SET_ID: uniquely identifies a set in a transaction (NLA_U32)
+ */
+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_SET_ID,
+	__NFTA_SET_ELEM_LIST_MAX
+};
+#define NFTA_SET_ELEM_LIST_MAX	(__NFTA_SET_ELEM_LIST_MAX - 1)
+
+/**
+ * enum nft_data_types - nf_tables data types
+ *
+ * @NFT_DATA_VALUE: generic data
+ * @NFT_DATA_VERDICT: netfilter verdict
+ *
+ * The type of data is usually determined by the kernel directly and is not
+ * explicitly specified by userspace. The only difference are sets, where
+ * userspace specifies the key and mapping data types.
+ *
+ * The values 0xffffff00-0xffffffff are reserved for internally used types.
+ * The remaining range can be freely used by userspace to encode types, all
+ * values are equivalent to NFT_DATA_VALUE.
+ */
+enum nft_data_types {
+	NFT_DATA_VALUE,
+	NFT_DATA_VERDICT	= 0xffffff00U,
+};
+
+#define NFT_DATA_RESERVED_MASK	0xffffff00U
+
+/**
+ * enum nft_data_attributes - nf_tables data netlink attributes
+ *
+ * @NFTA_DATA_VALUE: generic data (NLA_BINARY)
+ * @NFTA_DATA_VERDICT: nf_tables verdict (NLA_NESTED: nft_verdict_attributes)
+ */
+enum nft_data_attributes {
+	NFTA_DATA_UNSPEC,
+	NFTA_DATA_VALUE,
+	NFTA_DATA_VERDICT,
+	__NFTA_DATA_MAX
+};
+#define NFTA_DATA_MAX		(__NFTA_DATA_MAX - 1)
+
+/* Maximum length of a value */
+#define NFT_DATA_VALUE_MAXLEN	64
+
+/**
+ * enum nft_verdict_attributes - nf_tables verdict netlink attributes
+ *
+ * @NFTA_VERDICT_CODE: nf_tables verdict (NLA_U32: enum nft_verdicts)
+ * @NFTA_VERDICT_CHAIN: jump target chain name (NLA_STRING)
+ */
+enum nft_verdict_attributes {
+	NFTA_VERDICT_UNSPEC,
+	NFTA_VERDICT_CODE,
+	NFTA_VERDICT_CHAIN,
+	__NFTA_VERDICT_MAX
+};
+#define NFTA_VERDICT_MAX	(__NFTA_VERDICT_MAX - 1)
+
+/**
+ * enum nft_expr_attributes - nf_tables expression netlink attributes
+ *
+ * @NFTA_EXPR_NAME: name of the expression type (NLA_STRING)
+ * @NFTA_EXPR_DATA: type specific data (NLA_NESTED)
+ */
+enum nft_expr_attributes {
+	NFTA_EXPR_UNSPEC,
+	NFTA_EXPR_NAME,
+	NFTA_EXPR_DATA,
+	__NFTA_EXPR_MAX
+};
+#define NFTA_EXPR_MAX		(__NFTA_EXPR_MAX - 1)
+
+/**
+ * enum nft_immediate_attributes - nf_tables immediate expression netlink attributes
+ *
+ * @NFTA_IMMEDIATE_DREG: destination register to load data into (NLA_U32)
+ * @NFTA_IMMEDIATE_DATA: data to load (NLA_NESTED: nft_data_attributes)
+ */
+enum nft_immediate_attributes {
+	NFTA_IMMEDIATE_UNSPEC,
+	NFTA_IMMEDIATE_DREG,
+	NFTA_IMMEDIATE_DATA,
+	__NFTA_IMMEDIATE_MAX
+};
+#define NFTA_IMMEDIATE_MAX	(__NFTA_IMMEDIATE_MAX - 1)
+
+/**
+ * enum nft_bitwise_attributes - nf_tables bitwise expression netlink attributes
+ *
+ * @NFTA_BITWISE_SREG: source register (NLA_U32: nft_registers)
+ * @NFTA_BITWISE_DREG: destination register (NLA_U32: nft_registers)
+ * @NFTA_BITWISE_LEN: length of operands (NLA_U32)
+ * @NFTA_BITWISE_MASK: mask value (NLA_NESTED: nft_data_attributes)
+ * @NFTA_BITWISE_XOR: xor value (NLA_NESTED: nft_data_attributes)
+ *
+ * The bitwise expression performs the following operation:
+ *
+ * dreg = (sreg & mask) ^ xor
+ *
+ * which allow to express all bitwise operations:
+ *
+ * 		mask	xor
+ * NOT:		1	1
+ * OR:		0	x
+ * XOR:		1	x
+ * AND:		x	0
+ */
+enum nft_bitwise_attributes {
+	NFTA_BITWISE_UNSPEC,
+	NFTA_BITWISE_SREG,
+	NFTA_BITWISE_DREG,
+	NFTA_BITWISE_LEN,
+	NFTA_BITWISE_MASK,
+	NFTA_BITWISE_XOR,
+	__NFTA_BITWISE_MAX
+};
+#define NFTA_BITWISE_MAX	(__NFTA_BITWISE_MAX - 1)
+
+/**
+ * enum nft_byteorder_ops - nf_tables byteorder operators
+ *
+ * @NFT_BYTEORDER_NTOH: network to host operator
+ * @NFT_BYTEORDER_HTON: host to network operator
+ */
+enum nft_byteorder_ops {
+	NFT_BYTEORDER_NTOH,
+	NFT_BYTEORDER_HTON,
+};
+
+/**
+ * enum nft_byteorder_attributes - nf_tables byteorder expression netlink attributes
+ *
+ * @NFTA_BYTEORDER_SREG: source register (NLA_U32: nft_registers)
+ * @NFTA_BYTEORDER_DREG: destination register (NLA_U32: nft_registers)
+ * @NFTA_BYTEORDER_OP: operator (NLA_U32: enum nft_byteorder_ops)
+ * @NFTA_BYTEORDER_LEN: length of the data (NLA_U32)
+ * @NFTA_BYTEORDER_SIZE: data size in bytes (NLA_U32: 2 or 4)
+ */
+enum nft_byteorder_attributes {
+	NFTA_BYTEORDER_UNSPEC,
+	NFTA_BYTEORDER_SREG,
+	NFTA_BYTEORDER_DREG,
+	NFTA_BYTEORDER_OP,
+	NFTA_BYTEORDER_LEN,
+	NFTA_BYTEORDER_SIZE,
+	__NFTA_BYTEORDER_MAX
+};
+#define NFTA_BYTEORDER_MAX	(__NFTA_BYTEORDER_MAX - 1)
+
+/**
+ * enum nft_cmp_ops - nf_tables relational operator
+ *
+ * @NFT_CMP_EQ: equal
+ * @NFT_CMP_NEQ: not equal
+ * @NFT_CMP_LT: less than
+ * @NFT_CMP_LTE: less than or equal to
+ * @NFT_CMP_GT: greater than
+ * @NFT_CMP_GTE: greater than or equal to
+ */
+enum nft_cmp_ops {
+	NFT_CMP_EQ,
+	NFT_CMP_NEQ,
+	NFT_CMP_LT,
+	NFT_CMP_LTE,
+	NFT_CMP_GT,
+	NFT_CMP_GTE,
+};
+
+/**
+ * enum nft_cmp_attributes - nf_tables cmp expression netlink attributes
+ *
+ * @NFTA_CMP_SREG: source register of data to compare (NLA_U32: nft_registers)
+ * @NFTA_CMP_OP: cmp operation (NLA_U32: nft_cmp_ops)
+ * @NFTA_CMP_DATA: data to compare against (NLA_NESTED: nft_data_attributes)
+ */
+enum nft_cmp_attributes {
+	NFTA_CMP_UNSPEC,
+	NFTA_CMP_SREG,
+	NFTA_CMP_OP,
+	NFTA_CMP_DATA,
+	__NFTA_CMP_MAX
+};
+#define NFTA_CMP_MAX		(__NFTA_CMP_MAX - 1)
+
+/**
+ * enum nft_range_ops - nf_tables range operator
+ *
+ * @NFT_RANGE_EQ: equal
+ * @NFT_RANGE_NEQ: not equal
+ */
+enum nft_range_ops {
+	NFT_RANGE_EQ,
+	NFT_RANGE_NEQ,
+};
+
+/**
+ * enum nft_range_attributes - nf_tables range expression netlink attributes
+ *
+ * @NFTA_RANGE_SREG: source register of data to compare (NLA_U32: nft_registers)
+ * @NFTA_RANGE_OP: cmp operation (NLA_U32: nft_cmp_ops)
+ * @NFTA_RANGE_FROM_DATA: data range from (NLA_NESTED: nft_data_attributes)
+ * @NFTA_RANGE_TO_DATA: data range to (NLA_NESTED: nft_data_attributes)
+ */
+enum nft_range_attributes {
+	NFTA_RANGE_UNSPEC,
+	NFTA_RANGE_SREG,
+	NFTA_RANGE_OP,
+	NFTA_RANGE_FROM_DATA,
+	NFTA_RANGE_TO_DATA,
+	__NFTA_RANGE_MAX
+};
+#define NFTA_RANGE_MAX		(__NFTA_RANGE_MAX - 1)
+
+enum nft_lookup_flags {
+	NFT_LOOKUP_F_INV = (1 << 0),
+};
+
+/**
+ * enum nft_lookup_attributes - nf_tables set lookup expression netlink attributes
+ *
+ * @NFTA_LOOKUP_SET: name of the set where to look for (NLA_STRING)
+ * @NFTA_LOOKUP_SREG: source register of the data to look for (NLA_U32: nft_registers)
+ * @NFTA_LOOKUP_DREG: destination register (NLA_U32: nft_registers)
+ * @NFTA_LOOKUP_SET_ID: uniquely identifies a set in a transaction (NLA_U32)
+ * @NFTA_LOOKUP_FLAGS: flags (NLA_U32: enum nft_lookup_flags)
+ */
+enum nft_lookup_attributes {
+	NFTA_LOOKUP_UNSPEC,
+	NFTA_LOOKUP_SET,
+	NFTA_LOOKUP_SREG,
+	NFTA_LOOKUP_DREG,
+	NFTA_LOOKUP_SET_ID,
+	NFTA_LOOKUP_FLAGS,
+	__NFTA_LOOKUP_MAX
+};
+#define NFTA_LOOKUP_MAX		(__NFTA_LOOKUP_MAX - 1)
+
+enum nft_dynset_ops {
+	NFT_DYNSET_OP_ADD,
+	NFT_DYNSET_OP_UPDATE,
+};
+
+enum nft_dynset_flags {
+	NFT_DYNSET_F_INV	= (1 << 0),
+};
+
+/**
+ * enum nft_dynset_attributes - dynset expression attributes
+ *
+ * @NFTA_DYNSET_SET_NAME: name of set the to add data to (NLA_STRING)
+ * @NFTA_DYNSET_SET_ID: uniquely identifier of the set in the transaction (NLA_U32)
+ * @NFTA_DYNSET_OP: operation (NLA_U32)
+ * @NFTA_DYNSET_SREG_KEY: source register of the key (NLA_U32)
+ * @NFTA_DYNSET_SREG_DATA: source register of the data (NLA_U32)
+ * @NFTA_DYNSET_TIMEOUT: timeout value for the new element (NLA_U64)
+ * @NFTA_DYNSET_EXPR: expression (NLA_NESTED: nft_expr_attributes)
+ * @NFTA_DYNSET_FLAGS: flags (NLA_U32)
+ */
+enum nft_dynset_attributes {
+	NFTA_DYNSET_UNSPEC,
+	NFTA_DYNSET_SET_NAME,
+	NFTA_DYNSET_SET_ID,
+	NFTA_DYNSET_OP,
+	NFTA_DYNSET_SREG_KEY,
+	NFTA_DYNSET_SREG_DATA,
+	NFTA_DYNSET_TIMEOUT,
+	NFTA_DYNSET_EXPR,
+	NFTA_DYNSET_PAD,
+	NFTA_DYNSET_FLAGS,
+	__NFTA_DYNSET_MAX,
+};
+#define NFTA_DYNSET_MAX		(__NFTA_DYNSET_MAX - 1)
+
+/**
+ * enum nft_payload_bases - nf_tables payload expression offset bases
+ *
+ * @NFT_PAYLOAD_LL_HEADER: link layer header
+ * @NFT_PAYLOAD_NETWORK_HEADER: network header
+ * @NFT_PAYLOAD_TRANSPORT_HEADER: transport header
+ */
+enum nft_payload_bases {
+	NFT_PAYLOAD_LL_HEADER,
+	NFT_PAYLOAD_NETWORK_HEADER,
+	NFT_PAYLOAD_TRANSPORT_HEADER,
+};
+
+/**
+ * enum nft_payload_csum_types - nf_tables payload expression checksum types
+ *
+ * @NFT_PAYLOAD_CSUM_NONE: no checksumming
+ * @NFT_PAYLOAD_CSUM_INET: internet checksum (RFC 791)
+ */
+enum nft_payload_csum_types {
+	NFT_PAYLOAD_CSUM_NONE,
+	NFT_PAYLOAD_CSUM_INET,
+};
+
+enum nft_payload_csum_flags {
+	NFT_PAYLOAD_L4CSUM_PSEUDOHDR = (1 << 0),
+};
+
+/**
+ * enum nft_payload_attributes - nf_tables payload expression netlink attributes
+ *
+ * @NFTA_PAYLOAD_DREG: destination register to load data into (NLA_U32: nft_registers)
+ * @NFTA_PAYLOAD_BASE: payload base (NLA_U32: nft_payload_bases)
+ * @NFTA_PAYLOAD_OFFSET: payload offset relative to base (NLA_U32)
+ * @NFTA_PAYLOAD_LEN: payload length (NLA_U32)
+ * @NFTA_PAYLOAD_SREG: source register to load data from (NLA_U32: nft_registers)
+ * @NFTA_PAYLOAD_CSUM_TYPE: checksum type (NLA_U32)
+ * @NFTA_PAYLOAD_CSUM_OFFSET: checksum offset relative to base (NLA_U32)
+ * @NFTA_PAYLOAD_CSUM_FLAGS: checksum flags (NLA_U32)
+ */
+enum nft_payload_attributes {
+	NFTA_PAYLOAD_UNSPEC,
+	NFTA_PAYLOAD_DREG,
+	NFTA_PAYLOAD_BASE,
+	NFTA_PAYLOAD_OFFSET,
+	NFTA_PAYLOAD_LEN,
+	NFTA_PAYLOAD_SREG,
+	NFTA_PAYLOAD_CSUM_TYPE,
+	NFTA_PAYLOAD_CSUM_OFFSET,
+	NFTA_PAYLOAD_CSUM_FLAGS,
+	__NFTA_PAYLOAD_MAX
+};
+#define NFTA_PAYLOAD_MAX	(__NFTA_PAYLOAD_MAX - 1)
+
+enum nft_exthdr_flags {
+	NFT_EXTHDR_F_PRESENT = (1 << 0),
+};
+
+/**
+ * enum nft_exthdr_op - nf_tables match options
+ *
+ * @NFT_EXTHDR_OP_IPV6: match against ipv6 extension headers
+ * @NFT_EXTHDR_OP_TCP: match against tcp options
+ */
+enum nft_exthdr_op {
+	NFT_EXTHDR_OP_IPV6,
+	NFT_EXTHDR_OP_TCPOPT,
+	__NFT_EXTHDR_OP_MAX
+};
+#define NFT_EXTHDR_OP_MAX	(__NFT_EXTHDR_OP_MAX - 1)
+
+/**
+ * enum nft_exthdr_attributes - nf_tables extension header expression netlink attributes
+ *
+ * @NFTA_EXTHDR_DREG: destination register (NLA_U32: nft_registers)
+ * @NFTA_EXTHDR_TYPE: extension header type (NLA_U8)
+ * @NFTA_EXTHDR_OFFSET: extension header offset (NLA_U32)
+ * @NFTA_EXTHDR_LEN: extension header length (NLA_U32)
+ * @NFTA_EXTHDR_FLAGS: extension header flags (NLA_U32)
+ * @NFTA_EXTHDR_OP: option match type (NLA_U32)
+ * @NFTA_EXTHDR_SREG: option match type (NLA_U32)
+ */
+enum nft_exthdr_attributes {
+	NFTA_EXTHDR_UNSPEC,
+	NFTA_EXTHDR_DREG,
+	NFTA_EXTHDR_TYPE,
+	NFTA_EXTHDR_OFFSET,
+	NFTA_EXTHDR_LEN,
+	NFTA_EXTHDR_FLAGS,
+	NFTA_EXTHDR_OP,
+	NFTA_EXTHDR_SREG,
+	__NFTA_EXTHDR_MAX
+};
+#define NFTA_EXTHDR_MAX		(__NFTA_EXTHDR_MAX - 1)
+
+/**
+ * enum nft_meta_keys - nf_tables meta expression keys
+ *
+ * @NFT_META_LEN: packet length (skb->len)
+ * @NFT_META_PROTOCOL: packet ethertype protocol (skb->protocol), invalid in OUTPUT
+ * @NFT_META_PRIORITY: packet priority (skb->priority)
+ * @NFT_META_MARK: packet mark (skb->mark)
+ * @NFT_META_IIF: packet input interface index (dev->ifindex)
+ * @NFT_META_OIF: packet output interface index (dev->ifindex)
+ * @NFT_META_IIFNAME: packet input interface name (dev->name)
+ * @NFT_META_OIFNAME: packet output interface name (dev->name)
+ * @NFT_META_IIFTYPE: packet input interface type (dev->type)
+ * @NFT_META_OIFTYPE: packet output interface type (dev->type)
+ * @NFT_META_SKUID: originating socket UID (fsuid)
+ * @NFT_META_SKGID: originating socket GID (fsgid)
+ * @NFT_META_NFTRACE: packet nftrace bit
+ * @NFT_META_RTCLASSID: realm value of packet's route (skb->dst->tclassid)
+ * @NFT_META_SECMARK: packet secmark (skb->secmark)
+ * @NFT_META_NFPROTO: netfilter protocol
+ * @NFT_META_L4PROTO: layer 4 protocol number
+ * @NFT_META_BRI_IIFNAME: packet input bridge interface name
+ * @NFT_META_BRI_OIFNAME: packet output bridge interface name
+ * @NFT_META_PKTTYPE: packet type (skb->pkt_type), special handling for loopback
+ * @NFT_META_CPU: cpu id through smp_processor_id()
+ * @NFT_META_IIFGROUP: packet input interface group
+ * @NFT_META_OIFGROUP: packet output interface group
+ * @NFT_META_CGROUP: socket control group (skb->sk->sk_classid)
+ * @NFT_META_PRANDOM: a 32bit pseudo-random number
+ * @NFT_META_SECPATH: boolean, secpath_exists (!!skb->sp)
+ */
+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,
+	NFT_META_NFPROTO,
+	NFT_META_L4PROTO,
+	NFT_META_BRI_IIFNAME,
+	NFT_META_BRI_OIFNAME,
+	NFT_META_PKTTYPE,
+	NFT_META_CPU,
+	NFT_META_IIFGROUP,
+	NFT_META_OIFGROUP,
+	NFT_META_CGROUP,
+	NFT_META_PRANDOM,
+	NFT_META_SECPATH,
+};
+
+/**
+ * enum nft_rt_keys - nf_tables routing expression keys
+ *
+ * @NFT_RT_CLASSID: realm value of packet's route (skb->dst->tclassid)
+ * @NFT_RT_NEXTHOP4: routing nexthop for IPv4
+ * @NFT_RT_NEXTHOP6: routing nexthop for IPv6
+ * @NFT_RT_TCPMSS: fetch current path tcp mss
+ */
+enum nft_rt_keys {
+	NFT_RT_CLASSID,
+	NFT_RT_NEXTHOP4,
+	NFT_RT_NEXTHOP6,
+	NFT_RT_TCPMSS,
+};
+
+/**
+ * enum nft_hash_types - nf_tables hash expression types
+ *
+ * @NFT_HASH_JENKINS: Jenkins Hash
+ * @NFT_HASH_SYM: Symmetric Hash
+ */
+enum nft_hash_types {
+	NFT_HASH_JENKINS,
+	NFT_HASH_SYM,
+};
+
+/**
+ * enum nft_hash_attributes - nf_tables hash expression netlink attributes
+ *
+ * @NFTA_HASH_SREG: source register (NLA_U32)
+ * @NFTA_HASH_DREG: destination register (NLA_U32)
+ * @NFTA_HASH_LEN: source data length (NLA_U32)
+ * @NFTA_HASH_MODULUS: modulus value (NLA_U32)
+ * @NFTA_HASH_SEED: seed value (NLA_U32)
+ * @NFTA_HASH_OFFSET: add this offset value to hash result (NLA_U32)
+ * @NFTA_HASH_TYPE: hash operation (NLA_U32: nft_hash_types)
+ */
+enum nft_hash_attributes {
+	NFTA_HASH_UNSPEC,
+	NFTA_HASH_SREG,
+	NFTA_HASH_DREG,
+	NFTA_HASH_LEN,
+	NFTA_HASH_MODULUS,
+	NFTA_HASH_SEED,
+	NFTA_HASH_OFFSET,
+	NFTA_HASH_TYPE,
+	__NFTA_HASH_MAX,
+};
+#define NFTA_HASH_MAX	(__NFTA_HASH_MAX - 1)
+
+/**
+ * enum nft_meta_attributes - nf_tables meta expression netlink attributes
+ *
+ * @NFTA_META_DREG: destination register (NLA_U32)
+ * @NFTA_META_KEY: meta data item to load (NLA_U32: nft_meta_keys)
+ * @NFTA_META_SREG: source register (NLA_U32)
+ */
+enum nft_meta_attributes {
+	NFTA_META_UNSPEC,
+	NFTA_META_DREG,
+	NFTA_META_KEY,
+	NFTA_META_SREG,
+	__NFTA_META_MAX
+};
+#define NFTA_META_MAX		(__NFTA_META_MAX - 1)
+
+/**
+ * enum nft_rt_attributes - nf_tables routing expression netlink attributes
+ *
+ * @NFTA_RT_DREG: destination register (NLA_U32)
+ * @NFTA_RT_KEY: routing data item to load (NLA_U32: nft_rt_keys)
+ */
+enum nft_rt_attributes {
+	NFTA_RT_UNSPEC,
+	NFTA_RT_DREG,
+	NFTA_RT_KEY,
+	__NFTA_RT_MAX
+};
+#define NFTA_RT_MAX		(__NFTA_RT_MAX - 1)
+
+/**
+ * enum nft_ct_keys - nf_tables ct expression keys
+ *
+ * @NFT_CT_STATE: conntrack state (bitmask of enum ip_conntrack_info)
+ * @NFT_CT_DIRECTION: conntrack direction (enum ip_conntrack_dir)
+ * @NFT_CT_STATUS: conntrack status (bitmask of enum ip_conntrack_status)
+ * @NFT_CT_MARK: conntrack mark value
+ * @NFT_CT_SECMARK: conntrack secmark value
+ * @NFT_CT_EXPIRATION: relative conntrack expiration time in ms
+ * @NFT_CT_HELPER: connection tracking helper assigned to conntrack
+ * @NFT_CT_L3PROTOCOL: conntrack layer 3 protocol
+ * @NFT_CT_SRC: conntrack layer 3 protocol source (IPv4/IPv6 address)
+ * @NFT_CT_DST: conntrack layer 3 protocol destination (IPv4/IPv6 address)
+ * @NFT_CT_PROTOCOL: conntrack layer 4 protocol
+ * @NFT_CT_PROTO_SRC: conntrack layer 4 protocol source
+ * @NFT_CT_PROTO_DST: conntrack layer 4 protocol destination
+ * @NFT_CT_LABELS: conntrack labels
+ * @NFT_CT_PKTS: conntrack packets
+ * @NFT_CT_BYTES: conntrack bytes
+ * @NFT_CT_AVGPKT: conntrack average bytes per packet
+ * @NFT_CT_ZONE: conntrack zone
+ * @NFT_CT_EVENTMASK: ctnetlink events to be generated for this conntrack
+ */
+enum nft_ct_keys {
+	NFT_CT_STATE,
+	NFT_CT_DIRECTION,
+	NFT_CT_STATUS,
+	NFT_CT_MARK,
+	NFT_CT_SECMARK,
+	NFT_CT_EXPIRATION,
+	NFT_CT_HELPER,
+	NFT_CT_L3PROTOCOL,
+	NFT_CT_SRC,
+	NFT_CT_DST,
+	NFT_CT_PROTOCOL,
+	NFT_CT_PROTO_SRC,
+	NFT_CT_PROTO_DST,
+	NFT_CT_LABELS,
+	NFT_CT_PKTS,
+	NFT_CT_BYTES,
+	NFT_CT_AVGPKT,
+	NFT_CT_ZONE,
+	NFT_CT_EVENTMASK,
+};
+
+/**
+ * enum nft_ct_attributes - nf_tables ct expression netlink attributes
+ *
+ * @NFTA_CT_DREG: destination register (NLA_U32)
+ * @NFTA_CT_KEY: conntrack data item to load (NLA_U32: nft_ct_keys)
+ * @NFTA_CT_DIRECTION: direction in case of directional keys (NLA_U8)
+ * @NFTA_CT_SREG: source register (NLA_U32)
+ */
+enum nft_ct_attributes {
+	NFTA_CT_UNSPEC,
+	NFTA_CT_DREG,
+	NFTA_CT_KEY,
+	NFTA_CT_DIRECTION,
+	NFTA_CT_SREG,
+	__NFTA_CT_MAX
+};
+#define NFTA_CT_MAX		(__NFTA_CT_MAX - 1)
+
+/**
+ * enum nft_flow_attributes - ct offload expression attributes
+ * @NFTA_FLOW_TABLE_NAME: flow table name (NLA_STRING)
+ */
+enum nft_offload_attributes {
+	NFTA_FLOW_UNSPEC,
+	NFTA_FLOW_TABLE_NAME,
+	__NFTA_FLOW_MAX,
+};
+#define NFTA_FLOW_MAX		(__NFTA_FLOW_MAX - 1)
+
+enum nft_limit_type {
+	NFT_LIMIT_PKTS,
+	NFT_LIMIT_PKT_BYTES
+};
+
+enum nft_limit_flags {
+	NFT_LIMIT_F_INV	= (1 << 0),
+};
+
+/**
+ * enum nft_limit_attributes - nf_tables limit expression netlink attributes
+ *
+ * @NFTA_LIMIT_RATE: refill rate (NLA_U64)
+ * @NFTA_LIMIT_UNIT: refill unit (NLA_U64)
+ * @NFTA_LIMIT_BURST: burst (NLA_U32)
+ * @NFTA_LIMIT_TYPE: type of limit (NLA_U32: enum nft_limit_type)
+ * @NFTA_LIMIT_FLAGS: flags (NLA_U32: enum nft_limit_flags)
+ */
+enum nft_limit_attributes {
+	NFTA_LIMIT_UNSPEC,
+	NFTA_LIMIT_RATE,
+	NFTA_LIMIT_UNIT,
+	NFTA_LIMIT_BURST,
+	NFTA_LIMIT_TYPE,
+	NFTA_LIMIT_FLAGS,
+	NFTA_LIMIT_PAD,
+	__NFTA_LIMIT_MAX
+};
+#define NFTA_LIMIT_MAX		(__NFTA_LIMIT_MAX - 1)
+
+/**
+ * enum nft_counter_attributes - nf_tables counter expression netlink attributes
+ *
+ * @NFTA_COUNTER_BYTES: number of bytes (NLA_U64)
+ * @NFTA_COUNTER_PACKETS: number of packets (NLA_U64)
+ */
+enum nft_counter_attributes {
+	NFTA_COUNTER_UNSPEC,
+	NFTA_COUNTER_BYTES,
+	NFTA_COUNTER_PACKETS,
+	NFTA_COUNTER_PAD,
+	__NFTA_COUNTER_MAX
+};
+#define NFTA_COUNTER_MAX	(__NFTA_COUNTER_MAX - 1)
+
+/**
+ * enum nft_log_attributes - nf_tables log expression netlink attributes
+ *
+ * @NFTA_LOG_GROUP: netlink group to send messages to (NLA_U32)
+ * @NFTA_LOG_PREFIX: prefix to prepend to log messages (NLA_STRING)
+ * @NFTA_LOG_SNAPLEN: length of payload to include in netlink message (NLA_U32)
+ * @NFTA_LOG_QTHRESHOLD: queue threshold (NLA_U32)
+ * @NFTA_LOG_LEVEL: log level (NLA_U32)
+ * @NFTA_LOG_FLAGS: logging flags (NLA_U32)
+ */
+enum nft_log_attributes {
+	NFTA_LOG_UNSPEC,
+	NFTA_LOG_GROUP,
+	NFTA_LOG_PREFIX,
+	NFTA_LOG_SNAPLEN,
+	NFTA_LOG_QTHRESHOLD,
+	NFTA_LOG_LEVEL,
+	NFTA_LOG_FLAGS,
+	__NFTA_LOG_MAX
+};
+#define NFTA_LOG_MAX		(__NFTA_LOG_MAX - 1)
+
+/**
+ * enum nft_queue_attributes - nf_tables queue expression netlink attributes
+ *
+ * @NFTA_QUEUE_NUM: netlink queue to send messages to (NLA_U16)
+ * @NFTA_QUEUE_TOTAL: number of queues to load balance packets on (NLA_U16)
+ * @NFTA_QUEUE_FLAGS: various flags (NLA_U16)
+ * @NFTA_QUEUE_SREG_QNUM: source register of queue number (NLA_U32: nft_registers)
+ */
+enum nft_queue_attributes {
+	NFTA_QUEUE_UNSPEC,
+	NFTA_QUEUE_NUM,
+	NFTA_QUEUE_TOTAL,
+	NFTA_QUEUE_FLAGS,
+	NFTA_QUEUE_SREG_QNUM,
+	__NFTA_QUEUE_MAX
+};
+#define NFTA_QUEUE_MAX		(__NFTA_QUEUE_MAX - 1)
+
+#define NFT_QUEUE_FLAG_BYPASS		0x01 /* for compatibility with v2 */
+#define NFT_QUEUE_FLAG_CPU_FANOUT	0x02 /* use current CPU (no hashing) */
+#define NFT_QUEUE_FLAG_MASK		0x03
+
+enum nft_quota_flags {
+	NFT_QUOTA_F_INV		= (1 << 0),
+	NFT_QUOTA_F_DEPLETED	= (1 << 1),
+};
+
+/**
+ * enum nft_quota_attributes - nf_tables quota expression netlink attributes
+ *
+ * @NFTA_QUOTA_BYTES: quota in bytes (NLA_U16)
+ * @NFTA_QUOTA_FLAGS: flags (NLA_U32)
+ * @NFTA_QUOTA_CONSUMED: quota already consumed in bytes (NLA_U64)
+ */
+enum nft_quota_attributes {
+	NFTA_QUOTA_UNSPEC,
+	NFTA_QUOTA_BYTES,
+	NFTA_QUOTA_FLAGS,
+	NFTA_QUOTA_PAD,
+	NFTA_QUOTA_CONSUMED,
+	__NFTA_QUOTA_MAX
+};
+#define NFTA_QUOTA_MAX		(__NFTA_QUOTA_MAX - 1)
+
+/**
+ * enum nft_reject_types - nf_tables reject expression reject types
+ *
+ * @NFT_REJECT_ICMP_UNREACH: reject using ICMP unreachable
+ * @NFT_REJECT_TCP_RST: reject using TCP RST
+ * @NFT_REJECT_ICMPX_UNREACH: abstracted ICMP unreachable for bridge and inet
+ */
+enum nft_reject_types {
+	NFT_REJECT_ICMP_UNREACH,
+	NFT_REJECT_TCP_RST,
+	NFT_REJECT_ICMPX_UNREACH,
+};
+
+/**
+ * enum nft_reject_code - Generic reject codes for IPv4/IPv6
+ *
+ * @NFT_REJECT_ICMPX_NO_ROUTE: no route to host / network unreachable
+ * @NFT_REJECT_ICMPX_PORT_UNREACH: port unreachable
+ * @NFT_REJECT_ICMPX_HOST_UNREACH: host unreachable
+ * @NFT_REJECT_ICMPX_ADMIN_PROHIBITED: administratively prohibited
+ *
+ * These codes are mapped to real ICMP and ICMPv6 codes.
+ */
+enum nft_reject_inet_code {
+	NFT_REJECT_ICMPX_NO_ROUTE	= 0,
+	NFT_REJECT_ICMPX_PORT_UNREACH,
+	NFT_REJECT_ICMPX_HOST_UNREACH,
+	NFT_REJECT_ICMPX_ADMIN_PROHIBITED,
+	__NFT_REJECT_ICMPX_MAX
+};
+#define NFT_REJECT_ICMPX_MAX	(__NFT_REJECT_ICMPX_MAX - 1)
+
+/**
+ * enum nft_reject_attributes - nf_tables reject expression netlink attributes
+ *
+ * @NFTA_REJECT_TYPE: packet type to use (NLA_U32: nft_reject_types)
+ * @NFTA_REJECT_ICMP_CODE: ICMP code to use (NLA_U8)
+ */
+enum nft_reject_attributes {
+	NFTA_REJECT_UNSPEC,
+	NFTA_REJECT_TYPE,
+	NFTA_REJECT_ICMP_CODE,
+	__NFTA_REJECT_MAX
+};
+#define NFTA_REJECT_MAX		(__NFTA_REJECT_MAX - 1)
+
+/**
+ * enum nft_nat_types - nf_tables nat expression NAT types
+ *
+ * @NFT_NAT_SNAT: source NAT
+ * @NFT_NAT_DNAT: destination NAT
+ */
+enum nft_nat_types {
+	NFT_NAT_SNAT,
+	NFT_NAT_DNAT,
+};
+
+/**
+ * enum nft_nat_attributes - nf_tables nat expression netlink attributes
+ *
+ * @NFTA_NAT_TYPE: NAT type (NLA_U32: nft_nat_types)
+ * @NFTA_NAT_FAMILY: NAT family (NLA_U32)
+ * @NFTA_NAT_REG_ADDR_MIN: source register of address range start (NLA_U32: nft_registers)
+ * @NFTA_NAT_REG_ADDR_MAX: source register of address range end (NLA_U32: nft_registers)
+ * @NFTA_NAT_REG_PROTO_MIN: source register of proto range start (NLA_U32: nft_registers)
+ * @NFTA_NAT_REG_PROTO_MAX: source register of proto range end (NLA_U32: nft_registers)
+ * @NFTA_NAT_FLAGS: NAT flags (see NF_NAT_RANGE_* in linux/netfilter/nf_nat.h) (NLA_U32)
+ */
+enum nft_nat_attributes {
+	NFTA_NAT_UNSPEC,
+	NFTA_NAT_TYPE,
+	NFTA_NAT_FAMILY,
+	NFTA_NAT_REG_ADDR_MIN,
+	NFTA_NAT_REG_ADDR_MAX,
+	NFTA_NAT_REG_PROTO_MIN,
+	NFTA_NAT_REG_PROTO_MAX,
+	NFTA_NAT_FLAGS,
+	__NFTA_NAT_MAX
+};
+#define NFTA_NAT_MAX		(__NFTA_NAT_MAX - 1)
+
+/**
+ * enum nft_masq_attributes - nf_tables masquerade expression attributes
+ *
+ * @NFTA_MASQ_FLAGS: NAT flags (see NF_NAT_RANGE_* in linux/netfilter/nf_nat.h) (NLA_U32)
+ * @NFTA_MASQ_REG_PROTO_MIN: source register of proto range start (NLA_U32: nft_registers)
+ * @NFTA_MASQ_REG_PROTO_MAX: source register of proto range end (NLA_U32: nft_registers)
+ */
+enum nft_masq_attributes {
+	NFTA_MASQ_UNSPEC,
+	NFTA_MASQ_FLAGS,
+	NFTA_MASQ_REG_PROTO_MIN,
+	NFTA_MASQ_REG_PROTO_MAX,
+	__NFTA_MASQ_MAX
+};
+#define NFTA_MASQ_MAX		(__NFTA_MASQ_MAX - 1)
+
+/**
+ * enum nft_redir_attributes - nf_tables redirect expression netlink attributes
+ *
+ * @NFTA_REDIR_REG_PROTO_MIN: source register of proto range start (NLA_U32: nft_registers)
+ * @NFTA_REDIR_REG_PROTO_MAX: source register of proto range end (NLA_U32: nft_registers)
+ * @NFTA_REDIR_FLAGS: NAT flags (see NF_NAT_RANGE_* in linux/netfilter/nf_nat.h) (NLA_U32)
+ */
+enum nft_redir_attributes {
+	NFTA_REDIR_UNSPEC,
+	NFTA_REDIR_REG_PROTO_MIN,
+	NFTA_REDIR_REG_PROTO_MAX,
+	NFTA_REDIR_FLAGS,
+	__NFTA_REDIR_MAX
+};
+#define NFTA_REDIR_MAX		(__NFTA_REDIR_MAX - 1)
+
+/**
+ * enum nft_dup_attributes - nf_tables dup expression netlink attributes
+ *
+ * @NFTA_DUP_SREG_ADDR: source register of address (NLA_U32: nft_registers)
+ * @NFTA_DUP_SREG_DEV: source register of output interface (NLA_U32: nft_register)
+ */
+enum nft_dup_attributes {
+	NFTA_DUP_UNSPEC,
+	NFTA_DUP_SREG_ADDR,
+	NFTA_DUP_SREG_DEV,
+	__NFTA_DUP_MAX
+};
+#define NFTA_DUP_MAX		(__NFTA_DUP_MAX - 1)
+
+/**
+ * enum nft_fwd_attributes - nf_tables fwd expression netlink attributes
+ *
+ * @NFTA_FWD_SREG_DEV: source register of output interface (NLA_U32: nft_register)
+ */
+enum nft_fwd_attributes {
+	NFTA_FWD_UNSPEC,
+	NFTA_FWD_SREG_DEV,
+	__NFTA_FWD_MAX
+};
+#define NFTA_FWD_MAX	(__NFTA_FWD_MAX - 1)
+
+/**
+ * enum nft_objref_attributes - nf_tables stateful object expression netlink attributes
+ *
+ * @NFTA_OBJREF_IMM_TYPE: object type for immediate reference (NLA_U32: nft_register)
+ * @NFTA_OBJREF_IMM_NAME: object name for immediate reference (NLA_STRING)
+ * @NFTA_OBJREF_SET_SREG: source register of the data to look for (NLA_U32: nft_registers)
+ * @NFTA_OBJREF_SET_NAME: name of the set where to look for (NLA_STRING)
+ * @NFTA_OBJREF_SET_ID: id of the set where to look for in this transaction (NLA_U32)
+ */
+enum nft_objref_attributes {
+	NFTA_OBJREF_UNSPEC,
+	NFTA_OBJREF_IMM_TYPE,
+	NFTA_OBJREF_IMM_NAME,
+	NFTA_OBJREF_SET_SREG,
+	NFTA_OBJREF_SET_NAME,
+	NFTA_OBJREF_SET_ID,
+	__NFTA_OBJREF_MAX
+};
+#define NFTA_OBJREF_MAX	(__NFTA_OBJREF_MAX - 1)
+
+/**
+ * enum nft_gen_attributes - nf_tables ruleset generation attributes
+ *
+ * @NFTA_GEN_ID: Ruleset generation ID (NLA_U32)
+ */
+enum nft_gen_attributes {
+	NFTA_GEN_UNSPEC,
+	NFTA_GEN_ID,
+	NFTA_GEN_PROC_PID,
+	NFTA_GEN_PROC_NAME,
+	__NFTA_GEN_MAX
+};
+#define NFTA_GEN_MAX		(__NFTA_GEN_MAX - 1)
+
+/*
+ * enum nft_fib_attributes - nf_tables fib expression netlink attributes
+ *
+ * @NFTA_FIB_DREG: destination register (NLA_U32)
+ * @NFTA_FIB_RESULT: desired result (NLA_U32)
+ * @NFTA_FIB_FLAGS: flowi fields to initialize when querying the FIB (NLA_U32)
+ *
+ * The FIB expression performs a route lookup according
+ * to the packet data.
+ */
+enum nft_fib_attributes {
+	NFTA_FIB_UNSPEC,
+	NFTA_FIB_DREG,
+	NFTA_FIB_RESULT,
+	NFTA_FIB_FLAGS,
+	__NFTA_FIB_MAX
+};
+#define NFTA_FIB_MAX (__NFTA_FIB_MAX - 1)
+
+enum nft_fib_result {
+	NFT_FIB_RESULT_UNSPEC,
+	NFT_FIB_RESULT_OIF,
+	NFT_FIB_RESULT_OIFNAME,
+	NFT_FIB_RESULT_ADDRTYPE,
+	__NFT_FIB_RESULT_MAX
+};
+#define NFT_FIB_RESULT_MAX	(__NFT_FIB_RESULT_MAX - 1)
+
+enum nft_fib_flags {
+	NFTA_FIB_F_SADDR	= 1 << 0,	/* look up src */
+	NFTA_FIB_F_DADDR	= 1 << 1,	/* look up dst */
+	NFTA_FIB_F_MARK		= 1 << 2,	/* use skb->mark */
+	NFTA_FIB_F_IIF		= 1 << 3,	/* restrict to iif */
+	NFTA_FIB_F_OIF		= 1 << 4,	/* restrict to oif */
+	NFTA_FIB_F_PRESENT	= 1 << 5,	/* check existence only */
+};
+
+enum nft_ct_helper_attributes {
+	NFTA_CT_HELPER_UNSPEC,
+	NFTA_CT_HELPER_NAME,
+	NFTA_CT_HELPER_L3PROTO,
+	NFTA_CT_HELPER_L4PROTO,
+	__NFTA_CT_HELPER_MAX,
+};
+#define NFTA_CT_HELPER_MAX	(__NFTA_CT_HELPER_MAX - 1)
+
+#define NFT_OBJECT_UNSPEC	0
+#define NFT_OBJECT_COUNTER	1
+#define NFT_OBJECT_QUOTA	2
+#define NFT_OBJECT_CT_HELPER	3
+#define NFT_OBJECT_LIMIT	4
+#define __NFT_OBJECT_MAX	5
+#define NFT_OBJECT_MAX		(__NFT_OBJECT_MAX - 1)
+
+/**
+ * enum nft_object_attributes - nf_tables stateful object netlink attributes
+ *
+ * @NFTA_OBJ_TABLE: name of the table containing the expression (NLA_STRING)
+ * @NFTA_OBJ_NAME: name of this expression type (NLA_STRING)
+ * @NFTA_OBJ_TYPE: stateful object type (NLA_U32)
+ * @NFTA_OBJ_DATA: stateful object data (NLA_NESTED)
+ * @NFTA_OBJ_USE: number of references to this expression (NLA_U32)
+ * @NFTA_OBJ_HANDLE: object handle (NLA_U64)
+ */
+enum nft_object_attributes {
+	NFTA_OBJ_UNSPEC,
+	NFTA_OBJ_TABLE,
+	NFTA_OBJ_NAME,
+	NFTA_OBJ_TYPE,
+	NFTA_OBJ_DATA,
+	NFTA_OBJ_USE,
+	NFTA_OBJ_HANDLE,
+	NFTA_OBJ_PAD,
+	__NFTA_OBJ_MAX
+};
+#define NFTA_OBJ_MAX		(__NFTA_OBJ_MAX - 1)
+
+/**
+ * enum nft_flowtable_attributes - nf_tables flow table netlink attributes
+ *
+ * @NFTA_FLOWTABLE_TABLE: name of the table containing the expression (NLA_STRING)
+ * @NFTA_FLOWTABLE_NAME: name of this flow table (NLA_STRING)
+ * @NFTA_FLOWTABLE_HOOK: netfilter hook configuration(NLA_U32)
+ * @NFTA_FLOWTABLE_USE: number of references to this flow table (NLA_U32)
+ * @NFTA_FLOWTABLE_HANDLE: object handle (NLA_U64)
+ */
+enum nft_flowtable_attributes {
+	NFTA_FLOWTABLE_UNSPEC,
+	NFTA_FLOWTABLE_TABLE,
+	NFTA_FLOWTABLE_NAME,
+	NFTA_FLOWTABLE_HOOK,
+	NFTA_FLOWTABLE_USE,
+	NFTA_FLOWTABLE_HANDLE,
+	NFTA_FLOWTABLE_PAD,
+	__NFTA_FLOWTABLE_MAX
+};
+#define NFTA_FLOWTABLE_MAX	(__NFTA_FLOWTABLE_MAX - 1)
+
+/**
+ * enum nft_flowtable_hook_attributes - nf_tables flow table hook netlink attributes
+ *
+ * @NFTA_FLOWTABLE_HOOK_NUM: netfilter hook number (NLA_U32)
+ * @NFTA_FLOWTABLE_HOOK_PRIORITY: netfilter hook priority (NLA_U32)
+ * @NFTA_FLOWTABLE_HOOK_DEVS: input devices this flow table is bound to (NLA_NESTED)
+ */
+enum nft_flowtable_hook_attributes {
+	NFTA_FLOWTABLE_HOOK_UNSPEC,
+	NFTA_FLOWTABLE_HOOK_NUM,
+	NFTA_FLOWTABLE_HOOK_PRIORITY,
+	NFTA_FLOWTABLE_HOOK_DEVS,
+	__NFTA_FLOWTABLE_HOOK_MAX
+};
+#define NFTA_FLOWTABLE_HOOK_MAX	(__NFTA_FLOWTABLE_HOOK_MAX - 1)
+
+/**
+ * enum nft_device_attributes - nf_tables device netlink attributes
+ *
+ * @NFTA_DEVICE_NAME: name of this device (NLA_STRING)
+ */
+enum nft_devices_attributes {
+	NFTA_DEVICE_UNSPEC,
+	NFTA_DEVICE_NAME,
+	__NFTA_DEVICE_MAX
+};
+#define NFTA_DEVICE_MAX		(__NFTA_DEVICE_MAX - 1)
+
+
+/**
+ * enum nft_trace_attributes - nf_tables trace netlink attributes
+ *
+ * @NFTA_TRACE_TABLE: name of the table (NLA_STRING)
+ * @NFTA_TRACE_CHAIN: name of the chain (NLA_STRING)
+ * @NFTA_TRACE_RULE_HANDLE: numeric handle of the rule (NLA_U64)
+ * @NFTA_TRACE_TYPE: type of the event (NLA_U32: nft_trace_types)
+ * @NFTA_TRACE_VERDICT: verdict returned by hook (NLA_NESTED: nft_verdicts)
+ * @NFTA_TRACE_ID: pseudo-id, same for each skb traced (NLA_U32)
+ * @NFTA_TRACE_LL_HEADER: linklayer header (NLA_BINARY)
+ * @NFTA_TRACE_NETWORK_HEADER: network header (NLA_BINARY)
+ * @NFTA_TRACE_TRANSPORT_HEADER: transport header (NLA_BINARY)
+ * @NFTA_TRACE_IIF: indev ifindex (NLA_U32)
+ * @NFTA_TRACE_IIFTYPE: netdev->type of indev (NLA_U16)
+ * @NFTA_TRACE_OIF: outdev ifindex (NLA_U32)
+ * @NFTA_TRACE_OIFTYPE: netdev->type of outdev (NLA_U16)
+ * @NFTA_TRACE_MARK: nfmark (NLA_U32)
+ * @NFTA_TRACE_NFPROTO: nf protocol processed (NLA_U32)
+ * @NFTA_TRACE_POLICY: policy that decided fate of packet (NLA_U32)
+ */
+enum nft_trace_attributes {
+	NFTA_TRACE_UNSPEC,
+	NFTA_TRACE_TABLE,
+	NFTA_TRACE_CHAIN,
+	NFTA_TRACE_RULE_HANDLE,
+	NFTA_TRACE_TYPE,
+	NFTA_TRACE_VERDICT,
+	NFTA_TRACE_ID,
+	NFTA_TRACE_LL_HEADER,
+	NFTA_TRACE_NETWORK_HEADER,
+	NFTA_TRACE_TRANSPORT_HEADER,
+	NFTA_TRACE_IIF,
+	NFTA_TRACE_IIFTYPE,
+	NFTA_TRACE_OIF,
+	NFTA_TRACE_OIFTYPE,
+	NFTA_TRACE_MARK,
+	NFTA_TRACE_NFPROTO,
+	NFTA_TRACE_POLICY,
+	NFTA_TRACE_PAD,
+	__NFTA_TRACE_MAX
+};
+#define NFTA_TRACE_MAX (__NFTA_TRACE_MAX - 1)
+
+enum nft_trace_types {
+	NFT_TRACETYPE_UNSPEC,
+	NFT_TRACETYPE_POLICY,
+	NFT_TRACETYPE_RETURN,
+	NFT_TRACETYPE_RULE,
+	__NFT_TRACETYPE_MAX
+};
+#define NFT_TRACETYPE_MAX (__NFT_TRACETYPE_MAX - 1)
+
+/**
+ * enum nft_ng_attributes - nf_tables number generator expression netlink attributes
+ *
+ * @NFTA_NG_DREG: destination register (NLA_U32)
+ * @NFTA_NG_MODULUS: maximum counter value (NLA_U32)
+ * @NFTA_NG_TYPE: operation type (NLA_U32)
+ * @NFTA_NG_OFFSET: offset to be added to the counter (NLA_U32)
+ */
+enum nft_ng_attributes {
+	NFTA_NG_UNSPEC,
+	NFTA_NG_DREG,
+	NFTA_NG_MODULUS,
+	NFTA_NG_TYPE,
+	NFTA_NG_OFFSET,
+	__NFTA_NG_MAX
+};
+#define NFTA_NG_MAX	(__NFTA_NG_MAX - 1)
+
+enum nft_ng_types {
+	NFT_NG_INCREMENTAL,
+	NFT_NG_RANDOM,
+	__NFT_NG_MAX
+};
+#define NFT_NG_MAX	(__NFT_NG_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..c6d1991
--- /dev/null
+++ b/include/linux/netfilter/nfnetlink.h
@@ -0,0 +1,80 @@
+#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_ACCT_QUOTA,
+#define NFNLGRP_ACCT_QUOTA		NFNLGRP_ACCT_QUOTA
+	NFNLGRP_NFTRACE,
+#define NFNLGRP_NFTRACE			NFNLGRP_NFTRACE
+	__NFNLGRP_MAX,
+};
+#define NFNLGRP_MAX	(__NFNLGRP_MAX - 1)
+
+/* General form of address family dependent message.
+ */
+struct nfgenmsg {
+	__u8  nfgen_family;		/* AF_xxx */
+	__u8  version;		/* nfnetlink version */
+	__be16    res_id;		/* resource id */
+};
+
+#define NFNETLINK_V0	0
+
+/* netfilter netlink message types are split in two pieces:
+ * 8 bit subsystem, 8bit operation.
+ */
+
+#define NFNL_SUBSYS_ID(x)	((x & 0xff00) >> 8)
+#define NFNL_MSG_TYPE(x)	(x & 0x00ff)
+
+/* No enum here, otherwise __stringify() trick of MODULE_ALIAS_NFNL_SUBSYS()
+ * won't work anymore */
+#define NFNL_SUBSYS_NONE 		0
+#define NFNL_SUBSYS_CTNETLINK		1
+#define NFNL_SUBSYS_CTNETLINK_EXP	2
+#define NFNL_SUBSYS_QUEUE		3
+#define NFNL_SUBSYS_ULOG		4
+#define NFNL_SUBSYS_OSF			5
+#define NFNL_SUBSYS_IPSET		6
+#define NFNL_SUBSYS_ACCT		7
+#define NFNL_SUBSYS_CTNETLINK_TIMEOUT	8
+#define NFNL_SUBSYS_CTHELPER		9
+#define NFNL_SUBSYS_NFTABLES		10
+#define NFNL_SUBSYS_NFT_COMPAT		11
+#define NFNL_SUBSYS_COUNT		12
+
+/* Reserved control nfnetlink messages */
+#define NFNL_MSG_BATCH_BEGIN		NLMSG_MIN_TYPE
+#define NFNL_MSG_BATCH_END		NLMSG_MIN_TYPE+1
+
+/**
+ * enum nfnl_batch_attributes - nfnetlink batch netlink attributes
+ *
+ * @NFNL_BATCH_GENID: generation ID for this changeset (NLA_U32)
+ */
+enum nfnl_batch_attributes {
+        NFNL_BATCH_UNSPEC,
+        NFNL_BATCH_GENID,
+        __NFNL_BATCH_MAX
+};
+#define NFNL_BATCH_MAX			(__NFNL_BATCH_MAX - 1)
+
+#endif /* _NFNETLINK_H */
diff --git a/include/linux/netfilter/x_tables.h b/include/linux/netfilter/x_tables.h
index fa2d957..4120970 100644
--- a/include/linux/netfilter/x_tables.h
+++ b/include/linux/netfilter/x_tables.h
@@ -66,6 +66,11 @@
 	int verdict;
 };
 
+struct xt_error_target {
+	struct xt_entry_target target;
+	char errorname[XT_FUNCTION_MAXNAMELEN];
+};
+
 /* The argument to IPT_SO_GET_REVISION_*.  Returns highest revision
  * kernel supports, if >= revision. */
 struct xt_get_revision {
diff --git a/include/linux/netfilter/xt_CT.h b/include/linux/netfilter/xt_CT.h
index fbf4c56..c3907db 100644
--- a/include/linux/netfilter/xt_CT.h
+++ b/include/linux/netfilter/xt_CT.h
@@ -1,7 +1,15 @@
 #ifndef _XT_CT_H
 #define _XT_CT_H
 
-#define XT_CT_NOTRACK	0x1
+#include <linux/types.h>
+
+enum {
+	XT_CT_NOTRACK		= 1 << 0,
+	XT_CT_NOTRACK_ALIAS	= 1 << 1,
+	XT_CT_ZONE_DIR_ORIG	= 1 << 2,
+	XT_CT_ZONE_DIR_REPL	= 1 << 3,
+	XT_CT_ZONE_MARK		= 1 << 4,
+};
 
 struct xt_ct_target_info {
 	__u16 flags;
@@ -14,4 +22,16 @@
 	struct nf_conn	*ct __attribute__((aligned(8)));
 };
 
+struct xt_ct_target_info_v1 {
+	__u16 flags;
+	__u16 zone;
+	__u32 ct_events;
+	__u32 exp_events;
+	char helper[16];
+	char timeout[32];
+
+	/* Used internally by the kernel */
+	struct nf_conn	*ct __attribute__((aligned(8)));
+};
+
 #endif /* _XT_CT_H */
diff --git a/include/linux/netfilter/xt_HMARK.h b/include/linux/netfilter/xt_HMARK.h
new file mode 100644
index 0000000..826fc58
--- /dev/null
+++ b/include/linux/netfilter/xt_HMARK.h
@@ -0,0 +1,50 @@
+#ifndef XT_HMARK_H_
+#define XT_HMARK_H_
+
+#include <linux/types.h>
+
+enum {
+	XT_HMARK_SADDR_MASK,
+	XT_HMARK_DADDR_MASK,
+	XT_HMARK_SPI,
+	XT_HMARK_SPI_MASK,
+	XT_HMARK_SPORT,
+	XT_HMARK_DPORT,
+	XT_HMARK_SPORT_MASK,
+	XT_HMARK_DPORT_MASK,
+	XT_HMARK_PROTO_MASK,
+	XT_HMARK_RND,
+	XT_HMARK_MODULUS,
+	XT_HMARK_OFFSET,
+	XT_HMARK_CT,
+	XT_HMARK_METHOD_L3,
+	XT_HMARK_METHOD_L3_4,
+};
+#define XT_HMARK_FLAG(flag)	(1 << flag)
+
+union hmark_ports {
+	struct {
+		__u16	src;
+		__u16	dst;
+	} p16;
+	struct {
+		__be16	src;
+		__be16	dst;
+	} b16;
+	__u32	v32;
+	__be32	b32;
+};
+
+struct xt_hmark_info {
+	union nf_inet_addr	src_mask;
+	union nf_inet_addr	dst_mask;
+	union hmark_ports	port_mask;
+	union hmark_ports	port_set;
+	__u32			flags;
+	__u16			proto_mask;
+	__u32			hashrnd;
+	__u32			hmodulus;
+	__u32			hoffset;	/* Mark offset to start from */
+};
+
+#endif /* XT_HMARK_H_ */
diff --git a/include/linux/netfilter/xt_IDLETIMER.h b/include/linux/netfilter/xt_IDLETIMER.h
index faaa28b..49ddcdc 100644
--- a/include/linux/netfilter/xt_IDLETIMER.h
+++ b/include/linux/netfilter/xt_IDLETIMER.h
@@ -4,7 +4,6 @@
  * Header file for Xtables timer target module.
  *
  * Copyright (C) 2004, 2010 Nokia Corporation
- *
  * Written by Timo Teras <ext-timo.teras@nokia.com>
  *
  * Converted to x_tables and forward-ported to 2.6.34
@@ -33,21 +32,26 @@
 #include <linux/types.h>
 
 #define MAX_IDLETIMER_LABEL_SIZE 28
-#define NLMSG_MAX_SIZE 64
-
-#define NL_EVENT_TYPE_INACTIVE 0
-#define NL_EVENT_TYPE_ACTIVE 1
+#define XT_IDLETIMER_ALARM 0x01
 
 struct idletimer_tg_info {
 	__u32 timeout;
 
 	char label[MAX_IDLETIMER_LABEL_SIZE];
 
-	/* Use netlink messages for notification in addition to sysfs */
-	__u8 send_nl_msg;
-
 	/* for kernel module internal use only */
 	struct idletimer_tg *timer __attribute__((aligned(8)));
 };
 
+struct idletimer_tg_info_v1 {
+	__u32 timeout;
+
+	char label[MAX_IDLETIMER_LABEL_SIZE];
+
+	__u8 send_nl_msg;   /* unused: for compatibility with Android */
+	__u8 timer_type;
+
+	/* for kernel module internal use only */
+	struct idletimer_tg *timer __attribute__((aligned(8)));
+};
 #endif
diff --git a/include/linux/netfilter/xt_NFLOG.h b/include/linux/netfilter/xt_NFLOG.h
index 87b5831..f330707 100644
--- a/include/linux/netfilter/xt_NFLOG.h
+++ b/include/linux/netfilter/xt_NFLOG.h
@@ -6,9 +6,13 @@
 #define XT_NFLOG_DEFAULT_GROUP		0x1
 #define XT_NFLOG_DEFAULT_THRESHOLD	0
 
-#define XT_NFLOG_MASK			0x0
+#define XT_NFLOG_MASK			0x1
+
+/* This flag indicates that 'len' field in xt_nflog_info is set*/
+#define XT_NFLOG_F_COPY_LEN		0x1
 
 struct xt_nflog_info {
+	/* 'len' will be used iff you set XT_NFLOG_F_COPY_LEN in flags */
 	__u32	len;
 	__u16	group;
 	__u16	threshold;
diff --git a/include/linux/netfilter/xt_NFQUEUE.h b/include/linux/netfilter/xt_NFQUEUE.h
index 9eafdbb..8bb5fe6 100644
--- a/include/linux/netfilter/xt_NFQUEUE.h
+++ b/include/linux/netfilter/xt_NFQUEUE.h
@@ -26,4 +26,13 @@
 	__u16 bypass;
 };
 
+struct xt_NFQ_info_v3 {
+	__u16 queuenum;
+	__u16 queues_total;
+	__u16 flags;
+#define NFQ_FLAG_BYPASS		0x01 /* for compatibility with v2 */
+#define NFQ_FLAG_CPU_FANOUT	0x02 /* use current CPU (no hashing) */
+#define NFQ_FLAG_MASK		0x03
+};
+
 #endif /* _XT_NFQ_TARGET_H */
diff --git a/include/linux/netfilter/xt_SYNPROXY.h b/include/linux/netfilter/xt_SYNPROXY.h
new file mode 100644
index 0000000..2d59fba
--- /dev/null
+++ b/include/linux/netfilter/xt_SYNPROXY.h
@@ -0,0 +1,16 @@
+#ifndef _XT_SYNPROXY_H
+#define _XT_SYNPROXY_H
+
+#define XT_SYNPROXY_OPT_MSS		0x01
+#define XT_SYNPROXY_OPT_WSCALE		0x02
+#define XT_SYNPROXY_OPT_SACK_PERM	0x04
+#define XT_SYNPROXY_OPT_TIMESTAMP	0x08
+#define XT_SYNPROXY_OPT_ECN		0x10
+
+struct xt_synproxy_info {
+	__u8	options;
+	__u8	wscale;
+	__u16	mss;
+};
+
+#endif /* _XT_SYNPROXY_H */
diff --git a/include/linux/netfilter/xt_TCPOPTSTRIP.h b/include/linux/netfilter/xt_TCPOPTSTRIP.h
index 342ef14..7157318 100644
--- a/include/linux/netfilter/xt_TCPOPTSTRIP.h
+++ b/include/linux/netfilter/xt_TCPOPTSTRIP.h
@@ -1,6 +1,8 @@
 #ifndef _XT_TCPOPTSTRIP_H
 #define _XT_TCPOPTSTRIP_H
 
+#include <linux/types.h>
+
 #define tcpoptstrip_set_bit(bmap, idx) \
 	(bmap[(idx) >> 5] |= 1U << (idx & 31))
 #define tcpoptstrip_test_bit(bmap, idx) \
diff --git a/include/linux/netfilter/xt_TPROXY.h b/include/linux/netfilter/xt_TPROXY.h
index 8097e0b..902043c 100644
--- a/include/linux/netfilter/xt_TPROXY.h
+++ b/include/linux/netfilter/xt_TPROXY.h
@@ -1,6 +1,8 @@
 #ifndef _XT_TPROXY_H
 #define _XT_TPROXY_H
 
+#include <linux/types.h>
+
 /* TPROXY target is capable of marking the packet to perform
  * redirection. We can get rid of that whenever we get support for
  * mutliple targets in the same rule. */
diff --git a/include/linux/netfilter/xt_addrtype.h b/include/linux/netfilter/xt_addrtype.h
new file mode 100644
index 0000000..b156baa
--- /dev/null
+++ b/include/linux/netfilter/xt_addrtype.h
@@ -0,0 +1,44 @@
+#ifndef _XT_ADDRTYPE_H
+#define _XT_ADDRTYPE_H
+
+#include <linux/types.h>
+
+enum {
+	XT_ADDRTYPE_INVERT_SOURCE	= 0x0001,
+	XT_ADDRTYPE_INVERT_DEST		= 0x0002,
+	XT_ADDRTYPE_LIMIT_IFACE_IN	= 0x0004,
+	XT_ADDRTYPE_LIMIT_IFACE_OUT	= 0x0008,
+};
+
+
+/* rtn_type enum values from rtnetlink.h, but shifted */
+enum {
+	XT_ADDRTYPE_UNSPEC = 1 << 0,
+	XT_ADDRTYPE_UNICAST = 1 << 1,	/* 1 << RTN_UNICAST */
+	XT_ADDRTYPE_LOCAL  = 1 << 2,	/* 1 << RTN_LOCAL, etc */
+	XT_ADDRTYPE_BROADCAST = 1 << 3,
+	XT_ADDRTYPE_ANYCAST = 1 << 4,
+	XT_ADDRTYPE_MULTICAST = 1 << 5,
+	XT_ADDRTYPE_BLACKHOLE = 1 << 6,
+	XT_ADDRTYPE_UNREACHABLE = 1 << 7,
+	XT_ADDRTYPE_PROHIBIT = 1 << 8,
+	XT_ADDRTYPE_THROW = 1 << 9,
+	XT_ADDRTYPE_NAT = 1 << 10,
+	XT_ADDRTYPE_XRESOLVE = 1 << 11,
+};
+
+struct xt_addrtype_info_v1 {
+	__u16	source;		/* source-type mask */
+	__u16	dest;		/* dest-type mask */
+	__u32	flags;
+};
+
+/* revision 0 */
+struct xt_addrtype_info {
+	__u16	source;		/* source-type mask */
+	__u16	dest;		/* dest-type mask */
+	__u32	invert_source;
+	__u32	invert_dest;
+};
+
+#endif
diff --git a/include/linux/netfilter/xt_bpf.h b/include/linux/netfilter/xt_bpf.h
new file mode 100644
index 0000000..b97725a
--- /dev/null
+++ b/include/linux/netfilter/xt_bpf.h
@@ -0,0 +1,40 @@
+#ifndef _XT_BPF_H
+#define _XT_BPF_H
+
+#include <linux/filter.h>
+#include <linux/limits.h>
+#include <linux/types.h>
+
+#define XT_BPF_MAX_NUM_INSTR	64
+#define XT_BPF_PATH_MAX		(XT_BPF_MAX_NUM_INSTR * sizeof(struct sock_filter))
+
+struct bpf_prog;
+
+struct xt_bpf_info {
+	__u16 bpf_program_num_elem;
+	struct sock_filter bpf_program[XT_BPF_MAX_NUM_INSTR];
+
+	/* only used in the kernel */
+	struct bpf_prog *filter __attribute__((aligned(8)));
+};
+
+enum xt_bpf_modes {
+	XT_BPF_MODE_BYTECODE,
+	XT_BPF_MODE_FD_PINNED,
+	XT_BPF_MODE_FD_ELF,
+};
+
+struct xt_bpf_info_v1 {
+	__u16 mode;
+	__u16 bpf_program_num_elem;
+	__s32 fd;
+	union {
+		struct sock_filter bpf_program[XT_BPF_MAX_NUM_INSTR];
+		char path[XT_BPF_PATH_MAX];
+	};
+
+	/* only used in the kernel */
+	struct bpf_prog *filter __attribute__((aligned(8)));
+};
+
+#endif /*_XT_BPF_H */
diff --git a/include/linux/netfilter/xt_cgroup.h b/include/linux/netfilter/xt_cgroup.h
new file mode 100644
index 0000000..b74e370
--- /dev/null
+++ b/include/linux/netfilter/xt_cgroup.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _UAPI_XT_CGROUP_H
+#define _UAPI_XT_CGROUP_H
+
+#include <linux/types.h>
+#include <linux/limits.h>
+
+struct xt_cgroup_info_v0 {
+	__u32 id;
+	__u32 invert;
+};
+
+struct xt_cgroup_info_v1 {
+	__u8		has_path;
+	__u8		has_classid;
+	__u8		invert_path;
+	__u8		invert_classid;
+	char		path[PATH_MAX];
+	__u32		classid;
+
+	/* kernel internal data */
+	void		*priv __attribute__((aligned(8)));
+};
+
+#define XT_CGROUP_PATH_MAX	512
+
+struct xt_cgroup_info_v2 {
+	__u8		has_path;
+	__u8		has_classid;
+	__u8		invert_path;
+	__u8		invert_classid;
+	union {
+		char	path[XT_CGROUP_PATH_MAX];
+		__u32	classid;
+	};
+
+	/* kernel internal data */
+	void		*priv __attribute__((aligned(8)));
+};
+
+#endif /* _UAPI_XT_CGROUP_H */
diff --git a/include/linux/netfilter/xt_cluster.h b/include/linux/netfilter/xt_cluster.h
index 66cfa3c..9b883c8 100644
--- a/include/linux/netfilter/xt_cluster.h
+++ b/include/linux/netfilter/xt_cluster.h
@@ -1,6 +1,8 @@
 #ifndef _XT_CLUSTER_MATCH_H
 #define _XT_CLUSTER_MATCH_H
 
+#include <linux/types.h>
+
 enum xt_cluster_flags {
 	XT_CLUSTER_F_INV	= (1 << 0)
 };
diff --git a/include/linux/netfilter/xt_connbytes.h b/include/linux/netfilter/xt_connbytes.h
index 92fcbb0..f1d6c15 100644
--- a/include/linux/netfilter/xt_connbytes.h
+++ b/include/linux/netfilter/xt_connbytes.h
@@ -17,8 +17,8 @@
 
 struct xt_connbytes_info {
 	struct {
-		aligned_u64 from;	/* count to be matched */
-		aligned_u64 to;		/* count to be matched */
+		__aligned_u64 from;	/* count to be matched */
+		__aligned_u64 to;	/* count to be matched */
 	} count;
 	__u8 what;		/* ipt_connbytes_what */
 	__u8 direction;	/* ipt_connbytes_direction */
diff --git a/include/linux/netfilter/xt_connlabel.h b/include/linux/netfilter/xt_connlabel.h
new file mode 100644
index 0000000..c4bc9ee
--- /dev/null
+++ b/include/linux/netfilter/xt_connlabel.h
@@ -0,0 +1,12 @@
+#include <linux/types.h>
+
+#define XT_CONNLABEL_MAXBIT 127
+enum xt_connlabel_mtopts {
+	XT_CONNLABEL_OP_INVERT = 1 << 0,
+	XT_CONNLABEL_OP_SET    = 1 << 1,
+};
+
+struct xt_connlabel_mtinfo {
+	__u16 bit;
+	__u16 options;
+};
diff --git a/include/linux/netfilter/xt_connlimit.h b/include/linux/netfilter/xt_connlimit.h
index ba774d3..f9e8c67 100644
--- a/include/linux/netfilter/xt_connlimit.h
+++ b/include/linux/netfilter/xt_connlimit.h
@@ -1,6 +1,8 @@
 #ifndef _XT_CONNLIMIT_H
 #define _XT_CONNLIMIT_H
 
+#include <linux/types.h>
+
 struct xt_connlimit_data;
 
 enum {
diff --git a/include/linux/netfilter/xt_connmark.h b/include/linux/netfilter/xt_connmark.h
index efc17a8..bbf2acc 100644
--- a/include/linux/netfilter/xt_connmark.h
+++ b/include/linux/netfilter/xt_connmark.h
@@ -23,6 +23,11 @@
 	__u8 mode;
 };
 
+struct xt_connmark_tginfo2 {
+	__u32 ctmark, ctmask, nfmask;
+	__u8 shift_dir, shift_bits, mode;
+};
+
 struct xt_connmark_mtinfo1 {
 	__u32 mark, mask;
 	__u8 invert;
diff --git a/include/linux/netfilter/xt_conntrack.h b/include/linux/netfilter/xt_conntrack.h
index 74b904d..e971501 100644
--- a/include/linux/netfilter/xt_conntrack.h
+++ b/include/linux/netfilter/xt_conntrack.h
@@ -30,6 +30,7 @@
 	XT_CONNTRACK_REPLSRC_PORT = 1 << 10,
 	XT_CONNTRACK_REPLDST_PORT = 1 << 11,
 	XT_CONNTRACK_DIRECTION    = 1 << 12,
+	XT_CONNTRACK_STATE_ALIAS  = 1 << 13,
 };
 
 struct xt_conntrack_mtinfo1 {
diff --git a/include/linux/netfilter/xt_ecn.h b/include/linux/netfilter/xt_ecn.h
new file mode 100644
index 0000000..c21cc28
--- /dev/null
+++ b/include/linux/netfilter/xt_ecn.h
@@ -0,0 +1,33 @@
+/* iptables module for matching the ECN header in IPv4 and TCP header
+ *
+ * (C) 2002 Harald Welte <laforge@netfilter.org>
+ *
+ * This software is distributed under GNU GPL v2, 1991
+*/
+#ifndef _XT_ECN_H
+#define _XT_ECN_H
+
+#include <linux/types.h>
+#include <linux/netfilter/xt_dscp.h>
+
+#define XT_ECN_IP_MASK	(~XT_DSCP_MASK)
+
+#define XT_ECN_OP_MATCH_IP	0x01
+#define XT_ECN_OP_MATCH_ECE	0x10
+#define XT_ECN_OP_MATCH_CWR	0x20
+
+#define XT_ECN_OP_MATCH_MASK	0xce
+
+/* match info */
+struct xt_ecn_info {
+	__u8 operation;
+	__u8 invert;
+	__u8 ip_ect;
+	union {
+		struct {
+			__u8 ect;
+		} tcp;
+	} proto;
+};
+
+#endif /* _XT_ECN_H */
diff --git a/include/linux/netfilter/xt_hashlimit.h b/include/linux/netfilter/xt_hashlimit.h
index b1925b5..ade33f6 100644
--- a/include/linux/netfilter/xt_hashlimit.h
+++ b/include/linux/netfilter/xt_hashlimit.h
@@ -5,18 +5,25 @@
 
 /* timings are in milliseconds. */
 #define XT_HASHLIMIT_SCALE 10000
+#define XT_HASHLIMIT_SCALE_v2 1000000llu
 /* 1/10,000 sec period => max of 10,000/sec.  Min rate is then 429490
-   seconds, or one every 59 hours. */
+ * seconds, or one packet every 59 hours.
+ */
+
+/* packet length accounting is done in 16-byte steps */
+#define XT_HASHLIMIT_BYTE_SHIFT 4
 
 /* details of this structure hidden by the implementation */
 struct xt_hashlimit_htable;
 
 enum {
-	XT_HASHLIMIT_HASH_DIP = 1 << 0,
-	XT_HASHLIMIT_HASH_DPT = 1 << 1,
-	XT_HASHLIMIT_HASH_SIP = 1 << 2,
-	XT_HASHLIMIT_HASH_SPT = 1 << 3,
-	XT_HASHLIMIT_INVERT   = 1 << 4,
+	XT_HASHLIMIT_HASH_DIP	= 1 << 0,
+	XT_HASHLIMIT_HASH_DPT	= 1 << 1,
+	XT_HASHLIMIT_HASH_SIP	= 1 << 2,
+	XT_HASHLIMIT_HASH_SPT	= 1 << 3,
+	XT_HASHLIMIT_INVERT	= 1 << 4,
+	XT_HASHLIMIT_BYTES	= 1 << 5,
+	XT_HASHLIMIT_RATE_MATCH	= 1 << 6,
 };
 
 struct hashlimit_cfg {
@@ -57,6 +64,35 @@
 	__u8 srcmask, dstmask;
 };
 
+struct hashlimit_cfg2 {
+	__u64 avg;		/* Average secs between packets * scale */
+	__u64 burst;		/* Period multiplier for upper limit. */
+	__u32 mode;		/* bitmask of XT_HASHLIMIT_HASH_* */
+
+	/* user specified */
+	__u32 size;		/* how many buckets */
+	__u32 max;		/* max number of entries */
+	__u32 gc_interval;	/* gc interval */
+	__u32 expire;		/* when do entries expire? */
+
+	__u8 srcmask, dstmask;
+};
+
+struct hashlimit_cfg3 {
+	__u64 avg;		/* Average secs between packets * scale */
+	__u64 burst;		/* Period multiplier for upper limit. */
+	__u32 mode;		/* bitmask of XT_HASHLIMIT_HASH_* */
+
+	/* user specified */
+	__u32 size;		/* how many buckets */
+	__u32 max;		/* max number of entries */
+	__u32 gc_interval;	/* gc interval */
+	__u32 expire;		/* when do entries expire? */
+
+	__u32 interval;		/* in seconds*/
+	__u8 srcmask, dstmask;
+};
+
 struct xt_hashlimit_mtinfo1 {
 	char name[IFNAMSIZ];
 	struct hashlimit_cfg1 cfg;
@@ -65,4 +101,20 @@
 	struct xt_hashlimit_htable *hinfo __attribute__((aligned(8)));
 };
 
+struct xt_hashlimit_mtinfo2 {
+	char name[NAME_MAX];
+	struct hashlimit_cfg2 cfg;
+
+	/* Used internally by the kernel */
+	struct xt_hashlimit_htable *hinfo __attribute__((aligned(8)));
+};
+
+struct xt_hashlimit_mtinfo3 {
+	char name[NAME_MAX];
+	struct hashlimit_cfg3 cfg;
+
+	/* Used internally by the kernel */
+	struct xt_hashlimit_htable *hinfo __attribute__((aligned(8)));
+};
+
 #endif /*_XT_HASHLIMIT_H*/
diff --git a/include/linux/netfilter/xt_ipcomp.h b/include/linux/netfilter/xt_ipcomp.h
new file mode 100644
index 0000000..45c7e40
--- /dev/null
+++ b/include/linux/netfilter/xt_ipcomp.h
@@ -0,0 +1,16 @@
+#ifndef _XT_IPCOMP_H
+#define _XT_IPCOMP_H
+
+#include <linux/types.h>
+
+struct xt_ipcomp {
+	__u32 spis[2];	/* Security Parameter Index */
+	__u8 invflags;	/* Inverse flags */
+	__u8 hdrres;	/* Test of the Reserved Filed */
+};
+
+/* Values for "invflags" field in struct xt_ipcomp. */
+#define XT_IPCOMP_INV_SPI	0x01	/* Invert the sense of spi. */
+#define XT_IPCOMP_INV_MASK	0x01	/* All possible flags. */
+
+#endif /*_XT_IPCOMP_H*/
diff --git a/include/linux/netfilter/xt_nfacct.h b/include/linux/netfilter/xt_nfacct.h
new file mode 100644
index 0000000..04ec2b0
--- /dev/null
+++ b/include/linux/netfilter/xt_nfacct.h
@@ -0,0 +1,22 @@
+#ifndef _XT_NFACCT_MATCH_H
+#define _XT_NFACCT_MATCH_H
+
+#include <linux/types.h>
+
+#ifndef NFACCT_NAME_MAX
+#define NFACCT_NAME_MAX 32
+#endif
+
+struct nf_acct;
+
+struct xt_nfacct_match_info {
+	char		name[NFACCT_NAME_MAX];
+	struct nf_acct	*nfacct;
+};
+
+struct xt_nfacct_match_info_v1 {
+	char		name[NFACCT_NAME_MAX];
+	struct nf_acct	*nfacct __attribute__((aligned(8)));
+};
+
+#endif /* _XT_NFACCT_MATCH_H */
diff --git a/include/linux/netfilter/xt_osf.h b/include/linux/netfilter/xt_osf.h
index 18afa49..d0c4c76 100644
--- a/include/linux/netfilter/xt_osf.h
+++ b/include/linux/netfilter/xt_osf.h
@@ -14,7 +14,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
 #ifndef _XT_OSF_H
diff --git a/include/linux/netfilter/xt_owner.h b/include/linux/netfilter/xt_owner.h
index 2081761..e7731dc 100644
--- a/include/linux/netfilter/xt_owner.h
+++ b/include/linux/netfilter/xt_owner.h
@@ -4,9 +4,10 @@
 #include <linux/types.h>
 
 enum {
-	XT_OWNER_UID    = 1 << 0,
-	XT_OWNER_GID    = 1 << 1,
-	XT_OWNER_SOCKET = 1 << 2,
+	XT_OWNER_UID          = 1 << 0,
+	XT_OWNER_GID          = 1 << 1,
+	XT_OWNER_SOCKET       = 1 << 2,
+	XT_OWNER_SUPPL_GROUPS = 1 << 3,
 };
 
 struct xt_owner_match_info {
diff --git a/include/linux/netfilter/xt_physdev.h b/include/linux/netfilter/xt_physdev.h
index 8555e39..7d53660 100644
--- a/include/linux/netfilter/xt_physdev.h
+++ b/include/linux/netfilter/xt_physdev.h
@@ -3,9 +3,6 @@
 
 #include <linux/types.h>
 
-#ifdef __KERNEL__
-#include <linux/if.h>
-#endif
 
 #define XT_PHYSDEV_OP_IN		0x01
 #define XT_PHYSDEV_OP_OUT		0x02
diff --git a/include/linux/netfilter/xt_policy.h b/include/linux/netfilter/xt_policy.h
index be8ead0..d246eac 100644
--- a/include/linux/netfilter/xt_policy.h
+++ b/include/linux/netfilter/xt_policy.h
@@ -26,30 +26,19 @@
 			reqid:1;
 };
 
-#ifndef __KERNEL__
 union xt_policy_addr {
 	struct in_addr	a4;
 	struct in6_addr	a6;
 };
-#endif
 
 struct xt_policy_elem {
 	union {
-#ifdef __KERNEL__
-		struct {
-			union nf_inet_addr saddr;
-			union nf_inet_addr smask;
-			union nf_inet_addr daddr;
-			union nf_inet_addr dmask;
-		};
-#else
 		struct {
 			union xt_policy_addr saddr;
 			union xt_policy_addr smask;
 			union xt_policy_addr daddr;
 			union xt_policy_addr dmask;
 		};
-#endif
 	};
 	__be32			spi;
 	__u32		reqid;
diff --git a/include/linux/netfilter/xt_quota.h b/include/linux/netfilter/xt_quota.h
index 8bda65f..9314723 100644
--- a/include/linux/netfilter/xt_quota.h
+++ b/include/linux/netfilter/xt_quota.h
@@ -1,6 +1,8 @@
 #ifndef _XT_QUOTA_H
 #define _XT_QUOTA_H
 
+#include <linux/types.h>
+
 enum xt_quota_flags {
 	XT_QUOTA_INVERT		= 0x1,
 };
@@ -11,7 +13,7 @@
 struct xt_quota_info {
 	__u32 flags;
 	__u32 pad;
-	aligned_u64 quota;
+	__aligned_u64 quota;
 
 	/* Used internally by the kernel */
 	struct xt_quota_priv	*master;
diff --git a/include/linux/netfilter/xt_recent.h b/include/linux/netfilter/xt_recent.h
index 83318e0..6ef36c1 100644
--- a/include/linux/netfilter/xt_recent.h
+++ b/include/linux/netfilter/xt_recent.h
@@ -32,4 +32,14 @@
 	__u8 side;
 };
 
+struct xt_recent_mtinfo_v1 {
+	__u32 seconds;
+	__u32 hit_count;
+	__u8 check_set;
+	__u8 invert;
+	char name[XT_RECENT_NAME_LEN];
+	__u8 side;
+	union nf_inet_addr mask;
+};
+
 #endif /* _LINUX_NETFILTER_XT_RECENT_H */
diff --git a/include/linux/netfilter/xt_rpfilter.h b/include/linux/netfilter/xt_rpfilter.h
new file mode 100644
index 0000000..672b605
--- /dev/null
+++ b/include/linux/netfilter/xt_rpfilter.h
@@ -0,0 +1,17 @@
+#ifndef _XT_RPATH_H
+#define _XT_RPATH_H
+
+#include <linux/types.h>
+
+enum {
+	XT_RPFILTER_LOOSE = 1 << 0,
+	XT_RPFILTER_VALID_MARK = 1 << 1,
+	XT_RPFILTER_ACCEPT_LOCAL = 1 << 2,
+	XT_RPFILTER_INVERT = 1 << 3,
+};
+
+struct xt_rpfilter_info {
+	__u8 flags;
+};
+
+#endif
diff --git a/include/linux/netfilter/xt_sctp.h b/include/linux/netfilter/xt_sctp.h
index 29287be..5b28525 100644
--- a/include/linux/netfilter/xt_sctp.h
+++ b/include/linux/netfilter/xt_sctp.h
@@ -40,19 +40,19 @@
 #define SCTP_CHUNKMAP_SET(chunkmap, type) 		\
 	do { 						\
 		(chunkmap)[type / bytes(__u32)] |= 	\
-			1 << (type % bytes(__u32));	\
+			1u << (type % bytes(__u32));	\
 	} while (0)
 
 #define SCTP_CHUNKMAP_CLEAR(chunkmap, type)		 	\
 	do {							\
 		(chunkmap)[type / bytes(__u32)] &= 		\
-			~(1 << (type % bytes(__u32)));	\
+			~(1u << (type % bytes(__u32)));	\
 	} while (0)
 
 #define SCTP_CHUNKMAP_IS_SET(chunkmap, type) 			\
 ({								\
 	((chunkmap)[type / bytes (__u32)] & 		\
-		(1 << (type % bytes (__u32)))) ? 1: 0;	\
+		(1u << (type % bytes (__u32)))) ? 1: 0;	\
 })
 
 #define SCTP_CHUNKMAP_RESET(chunkmap) \
@@ -66,7 +66,7 @@
 
 #define SCTP_CHUNKMAP_IS_CLEAR(chunkmap) \
 	__sctp_chunkmap_is_clear((chunkmap), ARRAY_SIZE(chunkmap))
-static inline bool
+static __inline__ bool
 __sctp_chunkmap_is_clear(const __u32 *chunkmap, unsigned int n)
 {
 	unsigned int i;
@@ -78,7 +78,7 @@
 
 #define SCTP_CHUNKMAP_IS_ALL_SET(chunkmap) \
 	__sctp_chunkmap_is_all_set((chunkmap), ARRAY_SIZE(chunkmap))
-static inline bool
+static __inline__ bool
 __sctp_chunkmap_is_all_set(const __u32 *chunkmap, unsigned int n)
 {
 	unsigned int i;
diff --git a/include/linux/netfilter/xt_set.h b/include/linux/netfilter/xt_set.h
index 4379ce9..4210c9b 100644
--- a/include/linux/netfilter/xt_set.h
+++ b/include/linux/netfilter/xt_set.h
@@ -1,62 +1,8 @@
 #ifndef _XT_SET_H
 #define _XT_SET_H
 
-/* The protocol version */
-#define IPSET_PROTOCOL		5
-
-/* The max length of strings including NUL: set and type identifiers */
-#define IPSET_MAXNAMELEN	32
-
-/* Sets are identified by an index in kernel space. Tweak with ip_set_id_t
- * and IPSET_INVALID_ID if you want to increase the max number of sets.
- */
-typedef uint16_t ip_set_id_t;
-
-#define IPSET_INVALID_ID	65535
-
-enum ip_set_dim {
-	IPSET_DIM_ZERO = 0,
-	IPSET_DIM_ONE,
-	IPSET_DIM_TWO,
-	IPSET_DIM_THREE,
-	/* Max dimension in elements.
-	 * If changed, new revision of iptables match/target is required.
-	 */
-	IPSET_DIM_MAX = 6,
-};
-
-/* Option flags for kernel operations */
-enum ip_set_kopt {
-	IPSET_INV_MATCH = (1 << IPSET_DIM_ZERO),
-	IPSET_DIM_ONE_SRC = (1 << IPSET_DIM_ONE),
-	IPSET_DIM_TWO_SRC = (1 << IPSET_DIM_TWO),
-	IPSET_DIM_THREE_SRC = (1 << IPSET_DIM_THREE),
-};
-
-/* Interface to iptables/ip6tables */
-
-#define SO_IP_SET 		83
-
-union ip_set_name_index {
-	char name[IPSET_MAXNAMELEN];
-	ip_set_id_t index;
-};
-
-#define IP_SET_OP_GET_BYNAME	0x00000006	/* Get set index by name */
-struct ip_set_req_get_set {
-	unsigned op;
-	unsigned version;
-	union ip_set_name_index set;
-};
-
-#define IP_SET_OP_GET_BYINDEX	0x00000007	/* Get set name by index */
-/* Uses ip_set_req_get_set */
-
-#define IP_SET_OP_VERSION	0x00000100	/* Ask kernel version */
-struct ip_set_req_version {
-	unsigned op;
-	unsigned version;
-};
+#include <linux/types.h>
+#include <linux/netfilter/ipset/ip_set.h>
 
 /* Revision 0 interface: backward compatible with netfilter/iptables */
 
@@ -70,11 +16,11 @@
 struct xt_set_info_v0 {
 	ip_set_id_t index;
 	union {
-		u_int32_t flags[IPSET_DIM_MAX + 1];
+		__u32 flags[IPSET_DIM_MAX + 1];
 		struct {
-			u_int32_t __flags[IPSET_DIM_MAX];
-			u_int8_t dim;
-			u_int8_t flags;
+			__u32 __flags[IPSET_DIM_MAX];
+			__u8 dim;
+			__u8 flags;
 		} compat;
 	} u;
 };
@@ -89,12 +35,12 @@
 	struct xt_set_info_v0 del_set;
 };
 
-/* Revision 1 match and target */
+/* Revision 1  match and target */
 
 struct xt_set_info {
 	ip_set_id_t index;
-	u_int8_t dim;
-	u_int8_t flags;
+	__u8 dim;
+	__u8 flags;
 };
 
 /* match and target infos */
@@ -109,16 +55,39 @@
 
 /* Revision 2 target */
 
-enum ipset_cmd_flags {
-	IPSET_FLAG_BIT_EXIST	= 0,
-	IPSET_FLAG_EXIST	= (1 << IPSET_FLAG_BIT_EXIST),
-};
-
 struct xt_set_info_target_v2 {
 	struct xt_set_info add_set;
 	struct xt_set_info del_set;
-	u_int32_t flags;
-	u_int32_t timeout;
+	__u32 flags;
+	__u32 timeout;
+};
+
+/* Revision 3 match */
+
+struct xt_set_info_match_v3 {
+	struct xt_set_info match_set;
+	struct ip_set_counter_match0 packets;
+	struct ip_set_counter_match0 bytes;
+	__u32 flags;
+};
+
+/* Revision 4 match */
+
+struct xt_set_info_match_v4 {
+	struct xt_set_info match_set;
+	struct ip_set_counter_match packets;
+	struct ip_set_counter_match bytes;
+	__u32 flags;
+};
+
+/* Revision 3 target */
+
+struct xt_set_info_target_v3 {
+	struct xt_set_info add_set;
+	struct xt_set_info del_set;
+	struct xt_set_info map_set;
+	__u32 flags;
+	__u32 timeout;
 };
 
 #endif /*_XT_SET_H*/
diff --git a/include/linux/netfilter/xt_socket.h b/include/linux/netfilter/xt_socket.h
index 6f475b8..87644f8 100644
--- a/include/linux/netfilter/xt_socket.h
+++ b/include/linux/netfilter/xt_socket.h
@@ -1,12 +1,29 @@
 #ifndef _XT_SOCKET_H
 #define _XT_SOCKET_H
 
+#include <linux/types.h>
+
 enum {
 	XT_SOCKET_TRANSPARENT = 1 << 0,
+	XT_SOCKET_NOWILDCARD = 1 << 1,
+	XT_SOCKET_RESTORESKMARK = 1 << 2,
 };
 
 struct xt_socket_mtinfo1 {
 	__u8 flags;
 };
+#define XT_SOCKET_FLAGS_V1 XT_SOCKET_TRANSPARENT
+
+struct xt_socket_mtinfo2 {
+	__u8 flags;
+};
+#define XT_SOCKET_FLAGS_V2 (XT_SOCKET_TRANSPARENT | XT_SOCKET_NOWILDCARD)
+
+struct xt_socket_mtinfo3 {
+	__u8 flags;
+};
+#define XT_SOCKET_FLAGS_V3 (XT_SOCKET_TRANSPARENT \
+			   | XT_SOCKET_NOWILDCARD \
+			   | XT_SOCKET_RESTORESKMARK)
 
 #endif /* _XT_SOCKET_H */
diff --git a/include/linux/netfilter/xt_time.h b/include/linux/netfilter/xt_time.h
index b8bd456..a21d5bf 100644
--- a/include/linux/netfilter/xt_time.h
+++ b/include/linux/netfilter/xt_time.h
@@ -1,6 +1,8 @@
 #ifndef _XT_TIME_H
 #define _XT_TIME_H 1
 
+#include <linux/types.h>
+
 struct xt_time_info {
 	__u32 date_start;
 	__u32 date_stop;
@@ -14,6 +16,7 @@
 enum {
 	/* Match against local time (instead of UTC) */
 	XT_TIME_LOCAL_TZ = 1 << 0,
+	XT_TIME_CONTIGUOUS = 1 << 1,
 
 	/* Shortcuts */
 	XT_TIME_ALL_MONTHDAYS = 0xFFFFFFFE,
diff --git a/include/linux/netfilter/xt_u32.h b/include/linux/netfilter/xt_u32.h
index e8c3d87..04d1bfe 100644
--- a/include/linux/netfilter/xt_u32.h
+++ b/include/linux/netfilter/xt_u32.h
@@ -1,6 +1,8 @@
 #ifndef _XT_U32_H
 #define _XT_U32_H 1
 
+#include <linux/types.h>
+
 enum xt_u32_ops {
 	XT_U32_AND,
 	XT_U32_LEFTSH,
diff --git a/include/linux/netfilter_arp.h b/include/linux/netfilter_arp.h
new file mode 100644
index 0000000..92bc6dd
--- /dev/null
+++ b/include/linux/netfilter_arp.h
@@ -0,0 +1,19 @@
+#ifndef __LINUX_ARP_NETFILTER_H
+#define __LINUX_ARP_NETFILTER_H
+
+/* ARP-specific defines for netfilter.
+ * (C)2002 Rusty Russell IBM -- This code is GPL.
+ */
+
+#include <linux/netfilter.h>
+
+/* There is no PF_ARP. */
+#define NF_ARP		0
+
+/* ARP Hooks */
+#define NF_ARP_IN	0
+#define NF_ARP_OUT	1
+#define NF_ARP_FORWARD	2
+#define NF_ARP_NUMHOOKS	3
+
+#endif /* __LINUX_ARP_NETFILTER_H */
diff --git a/include/linux/netfilter_arp/arp_tables.h b/include/linux/netfilter_arp/arp_tables.h
new file mode 100644
index 0000000..bb1ec64
--- /dev/null
+++ b/include/linux/netfilter_arp/arp_tables.h
@@ -0,0 +1,204 @@
+/*
+ * 	Format of an ARP firewall descriptor
+ *
+ * 	src, tgt, src_mask, tgt_mask, arpop, arpop_mask are always stored in
+ *	network byte order.
+ * 	flags are stored in host byte order (of course).
+ */
+
+#ifndef _ARPTABLES_H
+#define _ARPTABLES_H
+
+#include <linux/types.h>
+
+#include <linux/netfilter_arp.h>
+
+#include <linux/netfilter/x_tables.h>
+
+#define ARPT_FUNCTION_MAXNAMELEN XT_FUNCTION_MAXNAMELEN
+#define ARPT_TABLE_MAXNAMELEN XT_TABLE_MAXNAMELEN
+#define arpt_entry_target xt_entry_target
+#define arpt_standard_target xt_standard_target
+#define arpt_error_target xt_error_target
+#define ARPT_CONTINUE XT_CONTINUE
+#define ARPT_RETURN XT_RETURN
+#define arpt_counters_info xt_counters_info
+#define arpt_counters xt_counters
+#define ARPT_STANDARD_TARGET XT_STANDARD_TARGET
+#define ARPT_ERROR_TARGET XT_ERROR_TARGET
+#define ARPT_ENTRY_ITERATE(entries, size, fn, args...) \
+	XT_ENTRY_ITERATE(struct arpt_entry, entries, size, fn, ## args)
+
+#define ARPT_DEV_ADDR_LEN_MAX 16
+
+struct arpt_devaddr_info {
+	char addr[ARPT_DEV_ADDR_LEN_MAX];
+	char mask[ARPT_DEV_ADDR_LEN_MAX];
+};
+
+/* Yes, Virginia, you have to zero the padding. */
+struct arpt_arp {
+	/* Source and target IP addr */
+	struct in_addr src, tgt;
+	/* Mask for src and target IP addr */
+	struct in_addr smsk, tmsk;
+
+	/* Device hw address length, src+target device addresses */
+	__u8 arhln, arhln_mask;
+	struct arpt_devaddr_info src_devaddr;
+	struct arpt_devaddr_info tgt_devaddr;
+
+	/* ARP operation code. */
+	__be16 arpop, arpop_mask;
+
+	/* ARP hardware address and protocol address format. */
+	__be16 arhrd, arhrd_mask;
+	__be16 arpro, arpro_mask;
+
+	/* The protocol address length is only accepted if it is 4
+	 * so there is no use in offering a way to do filtering on it.
+	 */
+
+	char iniface[IFNAMSIZ], outiface[IFNAMSIZ];
+	unsigned char iniface_mask[IFNAMSIZ], outiface_mask[IFNAMSIZ];
+
+	/* Flags word */
+	__u8 flags;
+	/* Inverse flags */
+	__u16 invflags;
+};
+
+/* Values for "flag" field in struct arpt_ip (general arp structure).
+ * No flags defined yet.
+ */
+#define ARPT_F_MASK		0x00	/* All possible flag bits mask. */
+
+/* Values for "inv" field in struct arpt_arp. */
+#define ARPT_INV_VIA_IN		0x0001	/* Invert the sense of IN IFACE. */
+#define ARPT_INV_VIA_OUT	0x0002	/* Invert the sense of OUT IFACE */
+#define ARPT_INV_SRCIP		0x0004	/* Invert the sense of SRC IP. */
+#define ARPT_INV_TGTIP		0x0008	/* Invert the sense of TGT IP. */
+#define ARPT_INV_SRCDEVADDR	0x0010	/* Invert the sense of SRC DEV ADDR. */
+#define ARPT_INV_TGTDEVADDR	0x0020	/* Invert the sense of TGT DEV ADDR. */
+#define ARPT_INV_ARPOP		0x0040	/* Invert the sense of ARP OP. */
+#define ARPT_INV_ARPHRD		0x0080	/* Invert the sense of ARP HRD. */
+#define ARPT_INV_ARPPRO		0x0100	/* Invert the sense of ARP PRO. */
+#define ARPT_INV_ARPHLN		0x0200	/* Invert the sense of ARP HLN. */
+#define ARPT_INV_MASK		0x03FF	/* All possible flag bits mask. */
+
+/* This structure defines each of the firewall rules.  Consists of 3
+   parts which are 1) general ARP header stuff 2) match specific
+   stuff 3) the target to perform if the rule matches */
+struct arpt_entry
+{
+	struct arpt_arp arp;
+
+	/* Size of arpt_entry + matches */
+	__u16 target_offset;
+	/* Size of arpt_entry + matches + target */
+	__u16 next_offset;
+
+	/* Back pointer */
+	unsigned int comefrom;
+
+	/* Packet and byte counters. */
+	struct xt_counters counters;
+
+	/* The matches (if any), then the target. */
+	unsigned char elems[0];
+};
+
+/*
+ * New IP firewall options for [gs]etsockopt at the RAW IP level.
+ * Unlike BSD Linux inherits IP options so you don't have to use a raw
+ * socket for this. Instead we check rights in the calls.
+ *
+ * ATTENTION: check linux/in.h before adding new number here.
+ */
+#define ARPT_BASE_CTL		96
+
+#define ARPT_SO_SET_REPLACE		(ARPT_BASE_CTL)
+#define ARPT_SO_SET_ADD_COUNTERS	(ARPT_BASE_CTL + 1)
+#define ARPT_SO_SET_MAX			ARPT_SO_SET_ADD_COUNTERS
+
+#define ARPT_SO_GET_INFO		(ARPT_BASE_CTL)
+#define ARPT_SO_GET_ENTRIES		(ARPT_BASE_CTL + 1)
+/* #define ARPT_SO_GET_REVISION_MATCH	(APRT_BASE_CTL + 2) */
+#define ARPT_SO_GET_REVISION_TARGET	(ARPT_BASE_CTL + 3)
+#define ARPT_SO_GET_MAX			(ARPT_SO_GET_REVISION_TARGET)
+
+/* The argument to ARPT_SO_GET_INFO */
+struct arpt_getinfo {
+	/* Which table: caller fills this in. */
+	char name[XT_TABLE_MAXNAMELEN];
+
+	/* Kernel fills these in. */
+	/* Which hook entry points are valid: bitmask */
+	unsigned int valid_hooks;
+
+	/* Hook entry points: one per netfilter hook. */
+	unsigned int hook_entry[NF_ARP_NUMHOOKS];
+
+	/* Underflow points. */
+	unsigned int underflow[NF_ARP_NUMHOOKS];
+
+	/* Number of entries */
+	unsigned int num_entries;
+
+	/* Size of entries. */
+	unsigned int size;
+};
+
+/* The argument to ARPT_SO_SET_REPLACE. */
+struct arpt_replace {
+	/* Which table. */
+	char name[XT_TABLE_MAXNAMELEN];
+
+	/* Which hook entry points are valid: bitmask.  You can't
+           change this. */
+	unsigned int valid_hooks;
+
+	/* Number of entries */
+	unsigned int num_entries;
+
+	/* Total size of new entries */
+	unsigned int size;
+
+	/* Hook entry points. */
+	unsigned int hook_entry[NF_ARP_NUMHOOKS];
+
+	/* Underflow points. */
+	unsigned int underflow[NF_ARP_NUMHOOKS];
+
+	/* Information about old entries: */
+	/* Number of counters (must be equal to current number of entries). */
+	unsigned int num_counters;
+	/* The old entries' counters. */
+	struct xt_counters *counters;
+
+	/* The entries (hang off end: not really an array). */
+	struct arpt_entry entries[0];
+};
+
+/* The argument to ARPT_SO_GET_ENTRIES. */
+struct arpt_get_entries {
+	/* Which table: user fills this in. */
+	char name[XT_TABLE_MAXNAMELEN];
+
+	/* User fills this in: total entry size. */
+	unsigned int size;
+
+	/* The entries. */
+	struct arpt_entry entrytable[0];
+};
+
+/* Helper functions */
+static __inline__ struct xt_entry_target *arpt_get_target(struct arpt_entry *e)
+{
+	return (void *)e + e->target_offset;
+}
+
+/*
+ *	Main firewall chains definitions and global var's definitions.
+ */
+#endif /* _ARPTABLES_H */
diff --git a/include/linux/netfilter_arp/arpt_mangle.h b/include/linux/netfilter_arp/arpt_mangle.h
new file mode 100644
index 0000000..250f502
--- /dev/null
+++ b/include/linux/netfilter_arp/arpt_mangle.h
@@ -0,0 +1,26 @@
+#ifndef _ARPT_MANGLE_H
+#define _ARPT_MANGLE_H
+#include <linux/netfilter_arp/arp_tables.h>
+
+#define ARPT_MANGLE_ADDR_LEN_MAX sizeof(struct in_addr)
+struct arpt_mangle
+{
+	char src_devaddr[ARPT_DEV_ADDR_LEN_MAX];
+	char tgt_devaddr[ARPT_DEV_ADDR_LEN_MAX];
+	union {
+		struct in_addr src_ip;
+	} u_s;
+	union {
+		struct in_addr tgt_ip;
+	} u_t;
+	u_int8_t flags;
+	int target;
+};
+
+#define ARPT_MANGLE_SDEV 0x01
+#define ARPT_MANGLE_TDEV 0x02
+#define ARPT_MANGLE_SIP 0x04
+#define ARPT_MANGLE_TIP 0x08
+#define ARPT_MANGLE_MASK 0x0f
+
+#endif /* _ARPT_MANGLE_H */
diff --git a/include/linux/netfilter_bridge.h b/include/linux/netfilter_bridge.h
new file mode 100644
index 0000000..71e9299
--- /dev/null
+++ b/include/linux/netfilter_bridge.h
@@ -0,0 +1,33 @@
+#ifndef __LINUX_BRIDGE_NETFILTER_H
+#define __LINUX_BRIDGE_NETFILTER_H
+
+/* bridge-specific defines for netfilter. 
+ */
+#include <limits.h>
+
+/* Bridge Hooks */
+/* After promisc drops, checksum checks. */
+#define NF_BR_PRE_ROUTING	0
+/* If the packet is destined for this box. */
+#define NF_BR_LOCAL_IN		1
+/* If the packet is destined for another interface. */
+#define NF_BR_FORWARD		2
+/* Packets coming from a local process. */
+#define NF_BR_LOCAL_OUT		3
+/* Packets about to hit the wire. */
+#define NF_BR_POST_ROUTING	4
+/* Not really a hook, but used for the ebtables broute table */
+#define NF_BR_BROUTING		5
+#define NF_BR_NUMHOOKS		6
+
+enum nf_br_hook_priorities {
+	NF_BR_PRI_FIRST = INT_MIN,
+	NF_BR_PRI_FILTER_BRIDGED = -200,
+	NF_BR_PRI_FILTER_OTHER = 200,
+	NF_BR_PRI_NAT_DST_BRIDGED = -300,
+	NF_BR_PRI_NAT_DST_OTHER = 100,
+	NF_BR_PRI_NAT_SRC = 300,
+	NF_BR_PRI_LAST = INT_MAX,
+};
+
+#endif
diff --git a/include/linux/netfilter_bridge/ebt_802_3.h b/include/linux/netfilter_bridge/ebt_802_3.h
new file mode 100644
index 0000000..f37522a
--- /dev/null
+++ b/include/linux/netfilter_bridge/ebt_802_3.h
@@ -0,0 +1,63 @@
+#ifndef _UAPI__LINUX_BRIDGE_EBT_802_3_H
+#define _UAPI__LINUX_BRIDGE_EBT_802_3_H
+
+#include <linux/types.h>
+#include <linux/if_ether.h>
+
+#define EBT_802_3_SAP 0x01
+#define EBT_802_3_TYPE 0x02
+
+#define EBT_802_3_MATCH "802_3"
+
+/*
+ * If frame has DSAP/SSAP value 0xaa you must check the SNAP type
+ * to discover what kind of packet we're carrying. 
+ */
+#define CHECK_TYPE 0xaa
+
+/*
+ * Control field may be one or two bytes.  If the first byte has
+ * the value 0x03 then the entire length is one byte, otherwise it is two.
+ * One byte controls are used in Unnumbered Information frames.
+ * Two byte controls are used in Numbered Information frames.
+ */
+#define IS_UI 0x03
+
+#define EBT_802_3_MASK (EBT_802_3_SAP | EBT_802_3_TYPE | EBT_802_3)
+
+/* ui has one byte ctrl, ni has two */
+struct hdr_ui {
+	__u8 dsap;
+	__u8 ssap;
+	__u8 ctrl;
+	__u8 orig[3];
+	__be16 type;
+};
+
+struct hdr_ni {
+	__u8 dsap;
+	__u8 ssap;
+	__be16 ctrl;
+	__u8  orig[3];
+	__be16 type;
+};
+
+struct ebt_802_3_hdr {
+	__u8  daddr[ETH_ALEN];
+	__u8  saddr[ETH_ALEN];
+	__be16 len;
+	union {
+		struct hdr_ui ui;
+		struct hdr_ni ni;
+	} llc;
+};
+
+
+struct ebt_802_3_info {
+	__u8  sap;
+	__be16 type;
+	__u8  bitmask;
+	__u8  invflags;
+};
+
+#endif /* _UAPI__LINUX_BRIDGE_EBT_802_3_H */
diff --git a/include/linux/netfilter_bridge/ebt_ip.h b/include/linux/netfilter_bridge/ebt_ip.h
new file mode 100644
index 0000000..c4bbc41
--- /dev/null
+++ b/include/linux/netfilter_bridge/ebt_ip.h
@@ -0,0 +1,44 @@
+/*
+ *  ebt_ip
+ *
+ *	Authors:
+ *	Bart De Schuymer <bart.de.schuymer@pandora.be>
+ *
+ *  April, 2002
+ *
+ *  Changes:
+ *    added ip-sport and ip-dport
+ *    Innominate Security Technologies AG <mhopf@innominate.com>
+ *    September, 2002
+ */
+
+#ifndef __LINUX_BRIDGE_EBT_IP_H
+#define __LINUX_BRIDGE_EBT_IP_H
+
+#include <linux/types.h>
+
+#define EBT_IP_SOURCE 0x01
+#define EBT_IP_DEST 0x02
+#define EBT_IP_TOS 0x04
+#define EBT_IP_PROTO 0x08
+#define EBT_IP_SPORT 0x10
+#define EBT_IP_DPORT 0x20
+#define EBT_IP_MASK (EBT_IP_SOURCE | EBT_IP_DEST | EBT_IP_TOS | EBT_IP_PROTO |\
+ EBT_IP_SPORT | EBT_IP_DPORT )
+#define EBT_IP_MATCH "ip"
+
+/* the same values are used for the invflags */
+struct ebt_ip_info {
+	__be32 saddr;
+	__be32 daddr;
+	__be32 smsk;
+	__be32 dmsk;
+	__u8  tos;
+	__u8  protocol;
+	__u8  bitmask;
+	__u8  invflags;
+	__u16 sport[2];
+	__u16 dport[2];
+};
+
+#endif
diff --git a/include/linux/netfilter_bridge/ebt_mark_m.h b/include/linux/netfilter_bridge/ebt_mark_m.h
new file mode 100644
index 0000000..410f9e5
--- /dev/null
+++ b/include/linux/netfilter_bridge/ebt_mark_m.h
@@ -0,0 +1,16 @@
+#ifndef __LINUX_BRIDGE_EBT_MARK_M_H
+#define __LINUX_BRIDGE_EBT_MARK_M_H
+
+#include <linux/types.h>
+
+#define EBT_MARK_AND 0x01
+#define EBT_MARK_OR 0x02
+#define EBT_MARK_MASK (EBT_MARK_AND | EBT_MARK_OR)
+struct ebt_mark_m_info {
+	unsigned long mark, mask;
+	__u8 invert;
+	__u8 bitmask;
+};
+#define EBT_MARK_MATCH "mark_m"
+
+#endif
diff --git a/include/linux/netfilter_bridge/ebt_mark_t.h b/include/linux/netfilter_bridge/ebt_mark_t.h
new file mode 100644
index 0000000..7d5a268
--- /dev/null
+++ b/include/linux/netfilter_bridge/ebt_mark_t.h
@@ -0,0 +1,23 @@
+#ifndef __LINUX_BRIDGE_EBT_MARK_T_H
+#define __LINUX_BRIDGE_EBT_MARK_T_H
+
+/* The target member is reused for adding new actions, the
+ * value of the real target is -1 to -NUM_STANDARD_TARGETS.
+ * For backward compatibility, the 4 lsb (2 would be enough,
+ * but let's play it safe) are kept to designate this target.
+ * The remaining bits designate the action. By making the set
+ * action 0xfffffff0, the result will look ok for older
+ * versions. [September 2006] */
+#define MARK_SET_VALUE (0xfffffff0)
+#define MARK_OR_VALUE  (0xffffffe0)
+#define MARK_AND_VALUE (0xffffffd0)
+#define MARK_XOR_VALUE (0xffffffc0)
+
+struct ebt_mark_t_info {
+	unsigned long mark;
+	/* EBT_ACCEPT, EBT_DROP, EBT_CONTINUE or EBT_RETURN */
+	int target;
+};
+#define EBT_MARK_TARGET "mark"
+
+#endif
diff --git a/include/linux/netfilter_ipv4/ip_queue.h b/include/linux/netfilter_ipv4/ip_queue.h
new file mode 100644
index 0000000..a03507f
--- /dev/null
+++ b/include/linux/netfilter_ipv4/ip_queue.h
@@ -0,0 +1,72 @@
+/*
+ * This is a module which is used for queueing IPv4 packets and
+ * communicating with userspace via netlink.
+ *
+ * (C) 2000 James Morris, this code is GPL.
+ */
+#ifndef _IP_QUEUE_H
+#define _IP_QUEUE_H
+
+#ifdef __KERNEL__
+#ifdef DEBUG_IPQ
+#define QDEBUG(x...) printk(KERN_DEBUG ## x)
+#else
+#define QDEBUG(x...)
+#endif  /* DEBUG_IPQ */
+#else
+#include <net/if.h>
+#endif	/* ! __KERNEL__ */
+
+/* Messages sent from kernel */
+typedef struct ipq_packet_msg {
+	unsigned long packet_id;	/* ID of queued packet */
+	unsigned long mark;		/* Netfilter mark value */
+	long timestamp_sec;		/* Packet arrival time (seconds) */
+	long timestamp_usec;		/* Packet arrvial time (+useconds) */
+	unsigned int hook;		/* Netfilter hook we rode in on */
+	char indev_name[IFNAMSIZ];	/* Name of incoming interface */
+	char outdev_name[IFNAMSIZ];	/* Name of outgoing interface */
+	__be16 hw_protocol;		/* Hardware protocol (network order) */
+	unsigned short hw_type;		/* Hardware type */
+	unsigned char hw_addrlen;	/* Hardware address length */
+	unsigned char hw_addr[8];	/* Hardware address */
+	size_t data_len;		/* Length of packet data */
+	unsigned char payload[0];	/* Optional packet data */
+} ipq_packet_msg_t;
+
+/* Messages sent from userspace */
+typedef struct ipq_mode_msg {
+	unsigned char value;		/* Requested mode */
+	size_t range;			/* Optional range of packet requested */
+} ipq_mode_msg_t;
+
+typedef struct ipq_verdict_msg {
+	unsigned int value;		/* Verdict to hand to netfilter */
+	unsigned long id;		/* Packet ID for this verdict */
+	size_t data_len;		/* Length of replacement data */
+	unsigned char payload[0];	/* Optional replacement packet */
+} ipq_verdict_msg_t;
+
+typedef struct ipq_peer_msg {
+	union {
+		ipq_verdict_msg_t verdict;
+		ipq_mode_msg_t mode;
+	} msg;
+} ipq_peer_msg_t;
+
+/* Packet delivery modes */
+enum {
+	IPQ_COPY_NONE,		/* Initial mode, packets are dropped */
+	IPQ_COPY_META,		/* Copy metadata */
+	IPQ_COPY_PACKET		/* Copy metadata + packet (range) */
+};
+#define IPQ_COPY_MAX IPQ_COPY_PACKET
+
+/* Types of messages */
+#define IPQM_BASE	0x10	/* standard netlink messages below this */
+#define IPQM_MODE	(IPQM_BASE + 1)		/* Mode request from peer */
+#define IPQM_VERDICT	(IPQM_BASE + 2)		/* Verdict from peer */ 
+#define IPQM_PACKET	(IPQM_BASE + 3)		/* Packet from kernel */
+#define IPQM_MAX	(IPQM_BASE + 4)
+
+#endif /*_IP_QUEUE_H*/
diff --git a/include/linux/netfilter_ipv4/ip_tables.h b/include/linux/netfilter_ipv4/ip_tables.h
index 735f4b1..38542b4 100644
--- a/include/linux/netfilter_ipv4/ip_tables.h
+++ b/include/linux/netfilter_ipv4/ip_tables.h
@@ -27,6 +27,41 @@
 #define ipt_target xt_target
 #define ipt_table xt_table
 #define ipt_get_revision xt_get_revision
+#define ipt_entry_match xt_entry_match
+#define ipt_entry_target xt_entry_target
+#define ipt_standard_target xt_standard_target
+#define ipt_error_target xt_error_target
+#define ipt_counters xt_counters
+#define IPT_CONTINUE XT_CONTINUE
+#define IPT_RETURN XT_RETURN
+
+/* This group is older than old (iptables < v1.4.0-rc1~89) */
+#include <linux/netfilter/xt_tcpudp.h>
+#define ipt_udp xt_udp
+#define ipt_tcp xt_tcp
+#define IPT_TCP_INV_SRCPT	XT_TCP_INV_SRCPT
+#define IPT_TCP_INV_DSTPT	XT_TCP_INV_DSTPT
+#define IPT_TCP_INV_FLAGS	XT_TCP_INV_FLAGS
+#define IPT_TCP_INV_OPTION	XT_TCP_INV_OPTION
+#define IPT_TCP_INV_MASK	XT_TCP_INV_MASK
+#define IPT_UDP_INV_SRCPT	XT_UDP_INV_SRCPT
+#define IPT_UDP_INV_DSTPT	XT_UDP_INV_DSTPT
+#define IPT_UDP_INV_MASK	XT_UDP_INV_MASK
+
+/* The argument to IPT_SO_ADD_COUNTERS. */
+#define ipt_counters_info xt_counters_info
+/* Standard return verdict, or do jump. */
+#define IPT_STANDARD_TARGET XT_STANDARD_TARGET
+/* Error verdict. */
+#define IPT_ERROR_TARGET XT_ERROR_TARGET
+
+/* fn returns 0 to continue iteration */
+#define IPT_MATCH_ITERATE(e, fn, args...) \
+	XT_MATCH_ITERATE(struct ipt_entry, e, fn, ## args)
+
+/* fn returns 0 to continue iteration */
+#define IPT_ENTRY_ITERATE(entries, size, fn, args...) \
+	XT_ENTRY_ITERATE(struct ipt_entry, entries, size, fn, ## args)
 
 /* Yes, Virginia, you have to zero the padding. */
 struct ipt_ip {
@@ -38,20 +73,14 @@
 	unsigned char iniface_mask[IFNAMSIZ], outiface_mask[IFNAMSIZ];
 
 	/* Protocol, 0 = ANY */
-	u_int16_t proto;
+	__u16 proto;
 
 	/* Flags word */
-	u_int8_t flags;
+	__u8 flags;
 	/* Inverse flags */
-	u_int8_t invflags;
+	__u8 invflags;
 };
 
-#define ipt_entry_match xt_entry_match
-#define ipt_entry_target xt_entry_target
-#define ipt_standard_target xt_standard_target
-
-#define ipt_counters xt_counters
-
 /* Values for "flag" field in struct ipt_ip (general ip structure). */
 #define IPT_F_FRAG		0x01	/* Set if rule is a fragment rule */
 #define IPT_F_GOTO		0x02	/* Set if jump is a goto */
@@ -77,9 +106,9 @@
 	unsigned int nfcache;
 
 	/* Size of ipt_entry + matches */
-	u_int16_t target_offset;
+	__u16 target_offset;
 	/* Size of ipt_entry + matches + target */
-	u_int16_t next_offset;
+	__u16 next_offset;
 
 	/* Back pointer */
 	unsigned int comefrom;
@@ -110,28 +139,11 @@
 #define IPT_SO_GET_REVISION_TARGET	(IPT_BASE_CTL + 3)
 #define IPT_SO_GET_MAX			IPT_SO_GET_REVISION_TARGET
 
-#define IPT_CONTINUE XT_CONTINUE
-#define IPT_RETURN XT_RETURN
-
-#include <linux/netfilter/xt_tcpudp.h>
-#define ipt_udp xt_udp
-#define ipt_tcp xt_tcp
-
-#define IPT_TCP_INV_SRCPT	XT_TCP_INV_SRCPT
-#define IPT_TCP_INV_DSTPT	XT_TCP_INV_DSTPT
-#define IPT_TCP_INV_FLAGS	XT_TCP_INV_FLAGS
-#define IPT_TCP_INV_OPTION	XT_TCP_INV_OPTION
-#define IPT_TCP_INV_MASK	XT_TCP_INV_MASK
-
-#define IPT_UDP_INV_SRCPT	XT_UDP_INV_SRCPT
-#define IPT_UDP_INV_DSTPT	XT_UDP_INV_DSTPT
-#define IPT_UDP_INV_MASK	XT_UDP_INV_MASK
-
 /* ICMP matching stuff */
 struct ipt_icmp {
-	u_int8_t type;				/* type to match */
-	u_int8_t code[2];			/* range of code */
-	u_int8_t invflags;			/* Inverse flags */
+	__u8 type;				/* type to match */
+	__u8 code[2];				/* range of code */
+	__u8 invflags;				/* Inverse flags */
 };
 
 /* Values for "inv" field for struct ipt_icmp. */
@@ -140,7 +152,7 @@
 /* The argument to IPT_SO_GET_INFO */
 struct ipt_getinfo {
 	/* Which table: caller fills this in. */
-	char name[IPT_TABLE_MAXNAMELEN];
+	char name[XT_TABLE_MAXNAMELEN];
 
 	/* Kernel fills these in. */
 	/* Which hook entry points are valid: bitmask */
@@ -162,7 +174,7 @@
 /* The argument to IPT_SO_SET_REPLACE. */
 struct ipt_replace {
 	/* Which table. */
-	char name[IPT_TABLE_MAXNAMELEN];
+	char name[XT_TABLE_MAXNAMELEN];
 
 	/* Which hook entry points are valid: bitmask.  You can't
            change this. */
@@ -190,13 +202,10 @@
 	struct ipt_entry entries[0];
 };
 
-/* The argument to IPT_SO_ADD_COUNTERS. */
-#define ipt_counters_info xt_counters_info
-
 /* The argument to IPT_SO_GET_ENTRIES. */
 struct ipt_get_entries {
 	/* Which table: user fills this in. */
-	char name[IPT_TABLE_MAXNAMELEN];
+	char name[XT_TABLE_MAXNAMELEN];
 
 	/* User fills this in: total entry size. */
 	unsigned int size;
@@ -205,26 +214,13 @@
 	struct ipt_entry entrytable[0];
 };
 
-/* Standard return verdict, or do jump. */
-#define IPT_STANDARD_TARGET XT_STANDARD_TARGET
-/* Error verdict. */
-#define IPT_ERROR_TARGET XT_ERROR_TARGET
-
 /* Helper functions */
-static __inline__ struct ipt_entry_target *
+static __inline__ struct xt_entry_target *
 ipt_get_target(struct ipt_entry *e)
 {
 	return (void *)e + e->target_offset;
 }
 
-/* fn returns 0 to continue iteration */
-#define IPT_MATCH_ITERATE(e, fn, args...) \
-	XT_MATCH_ITERATE(struct ipt_entry, e, fn, ## args)
-
-/* fn returns 0 to continue iteration */
-#define IPT_ENTRY_ITERATE(entries, size, fn, args...) \
-	XT_ENTRY_ITERATE(struct ipt_entry, entries, size, fn, ## args)
-
 /*
  *	Main firewall chains definitions and global var's definitions.
  */
diff --git a/include/linux/netfilter_ipv4/ipt_CLUSTERIP.h b/include/linux/netfilter_ipv4/ipt_CLUSTERIP.h
index e5a3687..c6a204c 100644
--- a/include/linux/netfilter_ipv4/ipt_CLUSTERIP.h
+++ b/include/linux/netfilter_ipv4/ipt_CLUSTERIP.h
@@ -1,6 +1,8 @@
 #ifndef _IPT_CLUSTERIP_H_target
 #define _IPT_CLUSTERIP_H_target
 
+#include <linux/types.h>
+
 enum clusterip_hashmode {
     CLUSTERIP_HASHMODE_SIP = 0,
     CLUSTERIP_HASHMODE_SIP_SPT,
@@ -17,15 +19,15 @@
 
 struct ipt_clusterip_tgt_info {
 
-	u_int32_t flags;
+	__u32 flags;
 
 	/* only relevant for new ones */
-	u_int8_t clustermac[6];
-	u_int16_t num_total_nodes;
-	u_int16_t num_local_nodes;
-	u_int16_t local_nodes[CLUSTERIP_MAX_NODES];
-	u_int32_t hash_mode;
-	u_int32_t hash_initval;
+	__u8 clustermac[6];
+	__u16 num_total_nodes;
+	__u16 num_local_nodes;
+	__u16 local_nodes[CLUSTERIP_MAX_NODES];
+	__u32 hash_mode;
+	__u32 hash_initval;
 
 	/* Used internally by the kernel */
 	struct clusterip_config *config;
diff --git a/include/linux/netfilter_ipv4/ipt_ECN.h b/include/linux/netfilter_ipv4/ipt_ECN.h
index 7ca4591..bb88d53 100644
--- a/include/linux/netfilter_ipv4/ipt_ECN.h
+++ b/include/linux/netfilter_ipv4/ipt_ECN.h
@@ -8,6 +8,8 @@
 */
 #ifndef _IPT_ECN_TARGET_H
 #define _IPT_ECN_TARGET_H
+
+#include <linux/types.h>
 #include <linux/netfilter/xt_DSCP.h>
 
 #define IPT_ECN_IP_MASK	(~XT_DSCP_MASK)
@@ -19,11 +21,11 @@
 #define IPT_ECN_OP_MASK		0xce
 
 struct ipt_ECN_info {
-	u_int8_t operation;	/* bitset of operations */
-	u_int8_t ip_ect;	/* ECT codepoint of IPv4 header, pre-shifted */
+	__u8 operation;	/* bitset of operations */
+	__u8 ip_ect;	/* ECT codepoint of IPv4 header, pre-shifted */
 	union {
 		struct {
-			u_int8_t ece:1, cwr:1; /* TCP ECT bits */
+			__u8 ece:1, cwr:1; /* TCP ECT bits */
 		} tcp;
 	} proto;
 };
diff --git a/include/linux/netfilter_ipv4/ipt_SAME.h b/include/linux/netfilter_ipv4/ipt_SAME.h
deleted file mode 100644
index 2529660..0000000
--- a/include/linux/netfilter_ipv4/ipt_SAME.h
+++ /dev/null
@@ -1,18 +0,0 @@
-#ifndef _IPT_SAME_H
-#define _IPT_SAME_H
-
-#define IPT_SAME_MAX_RANGE	10
-
-#define IPT_SAME_NODST		0x01
-
-struct ipt_same_info {
-	unsigned char info;
-	u_int32_t rangesize;
-	u_int32_t ipnum;
-	u_int32_t *iparray;
-
-	/* hangs off end. */
-	struct nf_nat_range range[IPT_SAME_MAX_RANGE];
-};
-
-#endif /*_IPT_SAME_H*/
diff --git a/include/linux/netfilter_ipv4/ipt_TTL.h b/include/linux/netfilter_ipv4/ipt_TTL.h
index ee6611e..f6ac169 100644
--- a/include/linux/netfilter_ipv4/ipt_TTL.h
+++ b/include/linux/netfilter_ipv4/ipt_TTL.h
@@ -4,6 +4,8 @@
 #ifndef _IPT_TTL_H
 #define _IPT_TTL_H
 
+#include <linux/types.h>
+
 enum {
 	IPT_TTL_SET = 0,
 	IPT_TTL_INC,
@@ -13,8 +15,8 @@
 #define IPT_TTL_MAXMODE	IPT_TTL_DEC
 
 struct ipt_TTL_info {
-	u_int8_t	mode;
-	u_int8_t	ttl;
+	__u8	mode;
+	__u8	ttl;
 };
 
 
diff --git a/include/linux/netfilter_ipv4/ipt_addrtype.h b/include/linux/netfilter_ipv4/ipt_addrtype.h
index 446de6a..0da4223 100644
--- a/include/linux/netfilter_ipv4/ipt_addrtype.h
+++ b/include/linux/netfilter_ipv4/ipt_addrtype.h
@@ -1,6 +1,8 @@
 #ifndef _IPT_ADDRTYPE_H
 #define _IPT_ADDRTYPE_H
 
+#include <linux/types.h>
+
 enum {
 	IPT_ADDRTYPE_INVERT_SOURCE	= 0x0001,
 	IPT_ADDRTYPE_INVERT_DEST	= 0x0002,
@@ -9,17 +11,17 @@
 };
 
 struct ipt_addrtype_info_v1 {
-	u_int16_t	source;		/* source-type mask */
-	u_int16_t	dest;		/* dest-type mask */
-	u_int32_t	flags;
+	__u16	source;		/* source-type mask */
+	__u16	dest;		/* dest-type mask */
+	__u32	flags;
 };
 
 /* revision 0 */
 struct ipt_addrtype_info {
-	u_int16_t	source;		/* source-type mask */
-	u_int16_t	dest;		/* dest-type mask */
-	u_int32_t	invert_source;
-	u_int32_t	invert_dest;
+	__u16	source;		/* source-type mask */
+	__u16	dest;		/* dest-type mask */
+	__u32	invert_source;
+	__u32	invert_dest;
 };
 
 #endif
diff --git a/include/linux/netfilter_ipv4/ipt_ah.h b/include/linux/netfilter_ipv4/ipt_ah.h
index 2e555b4..4e02bb0 100644
--- a/include/linux/netfilter_ipv4/ipt_ah.h
+++ b/include/linux/netfilter_ipv4/ipt_ah.h
@@ -1,9 +1,11 @@
 #ifndef _IPT_AH_H
 #define _IPT_AH_H
 
+#include <linux/types.h>
+
 struct ipt_ah {
-	u_int32_t spis[2];			/* Security Parameter Index */
-	u_int8_t  invflags;			/* Inverse flags */
+	__u32 spis[2];			/* Security Parameter Index */
+	__u8  invflags;			/* Inverse flags */
 };
 
 
diff --git a/include/linux/netfilter_ipv4/ipt_ecn.h b/include/linux/netfilter_ipv4/ipt_ecn.h
deleted file mode 100644
index 9945baa..0000000
--- a/include/linux/netfilter_ipv4/ipt_ecn.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/* iptables module for matching the ECN header in IPv4 and TCP header
- *
- * (C) 2002 Harald Welte <laforge@gnumonks.org>
- *
- * This software is distributed under GNU GPL v2, 1991
- * 
- * ipt_ecn.h,v 1.4 2002/08/05 19:39:00 laforge Exp
-*/
-#ifndef _IPT_ECN_H
-#define _IPT_ECN_H
-#include <linux/netfilter/xt_dscp.h>
-
-#define IPT_ECN_IP_MASK	(~XT_DSCP_MASK)
-
-#define IPT_ECN_OP_MATCH_IP	0x01
-#define IPT_ECN_OP_MATCH_ECE	0x10
-#define IPT_ECN_OP_MATCH_CWR	0x20
-
-#define IPT_ECN_OP_MATCH_MASK	0xce
-
-/* match info */
-struct ipt_ecn_info {
-	u_int8_t operation;
-	u_int8_t invert;
-	u_int8_t ip_ect;
-	union {
-		struct {
-			u_int8_t ect;
-		} tcp;
-	} proto;
-};
-
-#endif /* _IPT_ECN_H */
diff --git a/include/linux/netfilter_ipv4/ipt_ttl.h b/include/linux/netfilter_ipv4/ipt_ttl.h
index ee24fd8..37bee44 100644
--- a/include/linux/netfilter_ipv4/ipt_ttl.h
+++ b/include/linux/netfilter_ipv4/ipt_ttl.h
@@ -4,6 +4,8 @@
 #ifndef _IPT_TTL_H
 #define _IPT_TTL_H
 
+#include <linux/types.h>
+
 enum {
 	IPT_TTL_EQ = 0,		/* equals */
 	IPT_TTL_NE,		/* not equals */
@@ -13,8 +15,8 @@
 
 
 struct ipt_ttl_info {
-	u_int8_t	mode;
-	u_int8_t	ttl;
+	__u8	mode;
+	__u8	ttl;
 };
 
 
diff --git a/include/linux/netfilter_ipv6/ip6_tables.h b/include/linux/netfilter_ipv6/ip6_tables.h
index 6179032..640a1d0 100644
--- a/include/linux/netfilter_ipv6/ip6_tables.h
+++ b/include/linux/netfilter_ipv6/ip6_tables.h
@@ -23,11 +23,38 @@
 
 #define IP6T_FUNCTION_MAXNAMELEN XT_FUNCTION_MAXNAMELEN
 #define IP6T_TABLE_MAXNAMELEN XT_TABLE_MAXNAMELEN
-
 #define ip6t_match xt_match
 #define ip6t_target xt_target
 #define ip6t_table xt_table
 #define ip6t_get_revision xt_get_revision
+#define ip6t_entry_match xt_entry_match
+#define ip6t_entry_target xt_entry_target
+#define ip6t_standard_target xt_standard_target
+#define ip6t_error_target xt_error_target
+#define ip6t_counters xt_counters
+#define IP6T_CONTINUE XT_CONTINUE
+#define IP6T_RETURN XT_RETURN
+
+/* Pre-iptables-1.4.0 */
+#include <linux/netfilter/xt_tcpudp.h>
+#define ip6t_tcp xt_tcp
+#define ip6t_udp xt_udp
+#define IP6T_TCP_INV_SRCPT	XT_TCP_INV_SRCPT
+#define IP6T_TCP_INV_DSTPT	XT_TCP_INV_DSTPT
+#define IP6T_TCP_INV_FLAGS	XT_TCP_INV_FLAGS
+#define IP6T_TCP_INV_OPTION	XT_TCP_INV_OPTION
+#define IP6T_TCP_INV_MASK	XT_TCP_INV_MASK
+#define IP6T_UDP_INV_SRCPT	XT_UDP_INV_SRCPT
+#define IP6T_UDP_INV_DSTPT	XT_UDP_INV_DSTPT
+#define IP6T_UDP_INV_MASK	XT_UDP_INV_MASK
+
+#define ip6t_counters_info xt_counters_info
+#define IP6T_STANDARD_TARGET XT_STANDARD_TARGET
+#define IP6T_ERROR_TARGET XT_ERROR_TARGET
+#define IP6T_MATCH_ITERATE(e, fn, args...) \
+	XT_MATCH_ITERATE(struct ip6t_entry, e, fn, ## args)
+#define IP6T_ENTRY_ITERATE(entries, size, fn, args...) \
+	XT_ENTRY_ITERATE(struct ip6t_entry, entries, size, fn, ## args)
 
 /* Yes, Virginia, you have to zero the padding. */
 struct ip6t_ip6 {
@@ -46,22 +73,16 @@
 	 *   MH do not match any packets.
 	 * - You also need to set IP6T_FLAGS_PROTO to "flags" to check protocol.
 	 */
-	u_int16_t proto;
+	__u16 proto;
 	/* TOS to match iff flags & IP6T_F_TOS */
-	u_int8_t tos;
+	__u8 tos;
 
 	/* Flags word */
-	u_int8_t flags;
+	__u8 flags;
 	/* Inverse flags */
-	u_int8_t invflags;
+	__u8 invflags;
 };
 
-#define ip6t_entry_match xt_entry_match
-#define ip6t_entry_target xt_entry_target
-#define ip6t_standard_target xt_standard_target
-
-#define ip6t_counters	xt_counters
-
 /* Values for "flag" field in struct ip6t_ip6 (general ip6 structure). */
 #define IP6T_F_PROTO		0x01	/* Set if rule cares about upper 
 					   protocols */
@@ -89,9 +110,9 @@
 	unsigned int nfcache;
 
 	/* Size of ipt_entry + matches */
-	u_int16_t target_offset;
+	__u16 target_offset;
 	/* Size of ipt_entry + matches + target */
-	u_int16_t next_offset;
+	__u16 next_offset;
 
 	/* Back pointer */
 	unsigned int comefrom;
@@ -106,17 +127,12 @@
 /* Standard entry */
 struct ip6t_standard {
 	struct ip6t_entry entry;
-	struct ip6t_standard_target target;
-};
-
-struct ip6t_error_target {
-	struct ip6t_entry_target target;
-	char errorname[IP6T_FUNCTION_MAXNAMELEN];
+	struct xt_standard_target target;
 };
 
 struct ip6t_error {
 	struct ip6t_entry entry;
-	struct ip6t_error_target target;
+	struct xt_error_target target;
 };
 
 #define IP6T_ENTRY_INIT(__size)						       \
@@ -128,16 +144,16 @@
 #define IP6T_STANDARD_INIT(__verdict)					       \
 {									       \
 	.entry		= IP6T_ENTRY_INIT(sizeof(struct ip6t_standard)),       \
-	.target		= XT_TARGET_INIT(IP6T_STANDARD_TARGET,		       \
-					 sizeof(struct ip6t_standard_target)), \
+	.target		= XT_TARGET_INIT(XT_STANDARD_TARGET,		       \
+					 sizeof(struct xt_standard_target)),   \
 	.target.verdict	= -(__verdict) - 1,				       \
 }
 
 #define IP6T_ERROR_INIT							       \
 {									       \
 	.entry		= IP6T_ENTRY_INIT(sizeof(struct ip6t_error)),	       \
-	.target		= XT_TARGET_INIT(IP6T_ERROR_TARGET,		       \
-					 sizeof(struct ip6t_error_target)),    \
+	.target		= XT_TARGET_INIT(XT_ERROR_TARGET,		       \
+					 sizeof(struct xt_error_target)),      \
 	.target.errorname = "ERROR",					       \
 }
 
@@ -160,35 +176,14 @@
 #define IP6T_SO_GET_REVISION_TARGET	(IP6T_BASE_CTL + 5)
 #define IP6T_SO_GET_MAX			IP6T_SO_GET_REVISION_TARGET
 
-/* CONTINUE verdict for targets */
-#define IP6T_CONTINUE XT_CONTINUE
-
-/* For standard target */
-#define IP6T_RETURN XT_RETURN
-
-/* TCP/UDP matching stuff */
-#include <linux/netfilter/xt_tcpudp.h>
-
-#define ip6t_tcp xt_tcp
-#define ip6t_udp xt_udp
-
-/* Values for "inv" field in struct ipt_tcp. */
-#define IP6T_TCP_INV_SRCPT	XT_TCP_INV_SRCPT
-#define IP6T_TCP_INV_DSTPT	XT_TCP_INV_DSTPT
-#define IP6T_TCP_INV_FLAGS	XT_TCP_INV_FLAGS
-#define IP6T_TCP_INV_OPTION	XT_TCP_INV_OPTION
-#define IP6T_TCP_INV_MASK	XT_TCP_INV_MASK
-
-/* Values for "invflags" field in struct ipt_udp. */
-#define IP6T_UDP_INV_SRCPT	XT_UDP_INV_SRCPT
-#define IP6T_UDP_INV_DSTPT	XT_UDP_INV_DSTPT
-#define IP6T_UDP_INV_MASK	XT_UDP_INV_MASK
+/* obtain original address if REDIRECT'd connection */
+#define IP6T_SO_ORIGINAL_DST            80
 
 /* ICMP matching stuff */
 struct ip6t_icmp {
-	u_int8_t type;				/* type to match */
-	u_int8_t code[2];			/* range of code */
-	u_int8_t invflags;			/* Inverse flags */
+	__u8 type;				/* type to match */
+	__u8 code[2];				/* range of code */
+	__u8 invflags;				/* Inverse flags */
 };
 
 /* Values for "inv" field for struct ipt_icmp. */
@@ -197,7 +192,7 @@
 /* The argument to IP6T_SO_GET_INFO */
 struct ip6t_getinfo {
 	/* Which table: caller fills this in. */
-	char name[IP6T_TABLE_MAXNAMELEN];
+	char name[XT_TABLE_MAXNAMELEN];
 
 	/* Kernel fills these in. */
 	/* Which hook entry points are valid: bitmask */
@@ -219,7 +214,7 @@
 /* The argument to IP6T_SO_SET_REPLACE. */
 struct ip6t_replace {
 	/* Which table. */
-	char name[IP6T_TABLE_MAXNAMELEN];
+	char name[XT_TABLE_MAXNAMELEN];
 
 	/* Which hook entry points are valid: bitmask.  You can't
            change this. */
@@ -247,13 +242,10 @@
 	struct ip6t_entry entries[0];
 };
 
-/* The argument to IP6T_SO_ADD_COUNTERS. */
-#define ip6t_counters_info xt_counters_info
-
 /* The argument to IP6T_SO_GET_ENTRIES. */
 struct ip6t_get_entries {
 	/* Which table: user fills this in. */
-	char name[IP6T_TABLE_MAXNAMELEN];
+	char name[XT_TABLE_MAXNAMELEN];
 
 	/* User fills this in: total entry size. */
 	unsigned int size;
@@ -262,26 +254,13 @@
 	struct ip6t_entry entrytable[0];
 };
 
-/* Standard return verdict, or do jump. */
-#define IP6T_STANDARD_TARGET XT_STANDARD_TARGET
-/* Error verdict. */
-#define IP6T_ERROR_TARGET XT_ERROR_TARGET
-
 /* Helper functions */
-static __inline__ struct ip6t_entry_target *
+static __inline__ struct xt_entry_target *
 ip6t_get_target(struct ip6t_entry *e)
 {
 	return (void *)e + e->target_offset;
 }
 
-/* fn returns 0 to continue iteration */
-#define IP6T_MATCH_ITERATE(e, fn, args...) \
-	XT_MATCH_ITERATE(struct ip6t_entry, e, fn, ## args)
-
-/* fn returns 0 to continue iteration */
-#define IP6T_ENTRY_ITERATE(entries, size, fn, args...) \
-	XT_ENTRY_ITERATE(struct ip6t_entry, entries, size, fn, ## args)
-
 /*
  *	Main firewall chains definitions and global var's definitions.
  */
diff --git a/include/linux/netfilter_ipv6/ip6t_HL.h b/include/linux/netfilter_ipv6/ip6t_HL.h
index afb7813..ebd8ead 100644
--- a/include/linux/netfilter_ipv6/ip6t_HL.h
+++ b/include/linux/netfilter_ipv6/ip6t_HL.h
@@ -5,6 +5,8 @@
 #ifndef _IP6T_HL_H
 #define _IP6T_HL_H
 
+#include <linux/types.h>
+
 enum {
 	IP6T_HL_SET = 0,
 	IP6T_HL_INC,
@@ -14,8 +16,8 @@
 #define IP6T_HL_MAXMODE	IP6T_HL_DEC
 
 struct ip6t_HL_info {
-	u_int8_t	mode;
-	u_int8_t	hop_limit;
+	__u8	mode;
+	__u8	hop_limit;
 };
 
 
diff --git a/include/linux/netfilter_ipv6/ip6t_NPT.h b/include/linux/netfilter_ipv6/ip6t_NPT.h
new file mode 100644
index 0000000..f763355
--- /dev/null
+++ b/include/linux/netfilter_ipv6/ip6t_NPT.h
@@ -0,0 +1,16 @@
+#ifndef __NETFILTER_IP6T_NPT
+#define __NETFILTER_IP6T_NPT
+
+#include <linux/types.h>
+#include <linux/netfilter.h>
+
+struct ip6t_npt_tginfo {
+	union nf_inet_addr	src_pfx;
+	union nf_inet_addr	dst_pfx;
+	__u8			src_pfx_len;
+	__u8			dst_pfx_len;
+	/* Used internally by the kernel */
+	__sum16			adjustment;
+};
+
+#endif /* __NETFILTER_IP6T_NPT */
diff --git a/include/linux/netfilter_ipv6/ip6t_REJECT.h b/include/linux/netfilter_ipv6/ip6t_REJECT.h
index 6be6504..cd2e940 100644
--- a/include/linux/netfilter_ipv6/ip6t_REJECT.h
+++ b/include/linux/netfilter_ipv6/ip6t_REJECT.h
@@ -1,6 +1,8 @@
 #ifndef _IP6T_REJECT_H
 #define _IP6T_REJECT_H
 
+#include <linux/types.h>
+
 enum ip6t_reject_with {
 	IP6T_ICMP6_NO_ROUTE,
 	IP6T_ICMP6_ADM_PROHIBITED,
@@ -8,11 +10,13 @@
 	IP6T_ICMP6_ADDR_UNREACH,
 	IP6T_ICMP6_PORT_UNREACH,
 	IP6T_ICMP6_ECHOREPLY,
-	IP6T_TCP_RESET
+	IP6T_TCP_RESET,
+	IP6T_ICMP6_POLICY_FAIL,
+	IP6T_ICMP6_REJECT_ROUTE
 };
 
 struct ip6t_reject_info {
-	u_int32_t	with;	/* reject type */
+	__u32	with;	/* reject type */
 };
 
 #endif /*_IP6T_REJECT_H*/
diff --git a/include/linux/netfilter_ipv6/ip6t_ah.h b/include/linux/netfilter_ipv6/ip6t_ah.h
index 17a745c..5da2b65 100644
--- a/include/linux/netfilter_ipv6/ip6t_ah.h
+++ b/include/linux/netfilter_ipv6/ip6t_ah.h
@@ -1,11 +1,13 @@
 #ifndef _IP6T_AH_H
 #define _IP6T_AH_H
 
+#include <linux/types.h>
+
 struct ip6t_ah {
-	u_int32_t spis[2];			/* Security Parameter Index */
-	u_int32_t hdrlen;			/* Header Length */
-	u_int8_t  hdrres;			/* Test of the Reserved Filed */
-	u_int8_t  invflags;			/* Inverse flags */
+	__u32 spis[2];			/* Security Parameter Index */
+	__u32 hdrlen;			/* Header Length */
+	__u8  hdrres;			/* Test of the Reserved Filed */
+	__u8  invflags;			/* Inverse flags */
 };
 
 #define IP6T_AH_SPI 0x01
diff --git a/include/linux/netfilter_ipv6/ip6t_frag.h b/include/linux/netfilter_ipv6/ip6t_frag.h
index 3724d08..b47f61b 100644
--- a/include/linux/netfilter_ipv6/ip6t_frag.h
+++ b/include/linux/netfilter_ipv6/ip6t_frag.h
@@ -1,11 +1,13 @@
 #ifndef _IP6T_FRAG_H
 #define _IP6T_FRAG_H
 
+#include <linux/types.h>
+
 struct ip6t_frag {
-	u_int32_t ids[2];			/* Security Parameter Index */
-	u_int32_t hdrlen;			/* Header Length */
-	u_int8_t  flags;			/*  */
-	u_int8_t  invflags;			/* Inverse flags */
+	__u32 ids[2];			/* Security Parameter Index */
+	__u32 hdrlen;			/* Header Length */
+	__u8  flags;			/*  */
+	__u8  invflags;			/* Inverse flags */
 };
 
 #define IP6T_FRAG_IDS 		0x01
diff --git a/include/linux/netfilter_ipv6/ip6t_hl.h b/include/linux/netfilter_ipv6/ip6t_hl.h
index 5ef91b8..6e76dbc 100644
--- a/include/linux/netfilter_ipv6/ip6t_hl.h
+++ b/include/linux/netfilter_ipv6/ip6t_hl.h
@@ -5,6 +5,8 @@
 #ifndef _IP6T_HL_H
 #define _IP6T_HL_H
 
+#include <linux/types.h>
+
 enum {
 	IP6T_HL_EQ = 0,		/* equals */
 	IP6T_HL_NE,		/* not equals */
@@ -14,8 +16,8 @@
 
 
 struct ip6t_hl_info {
-	u_int8_t	mode;
-	u_int8_t	hop_limit;
+	__u8	mode;
+	__u8	hop_limit;
 };
 
 
diff --git a/include/linux/netfilter_ipv6/ip6t_ipv6header.h b/include/linux/netfilter_ipv6/ip6t_ipv6header.h
index 01dfd44..efae3a2 100644
--- a/include/linux/netfilter_ipv6/ip6t_ipv6header.h
+++ b/include/linux/netfilter_ipv6/ip6t_ipv6header.h
@@ -8,10 +8,12 @@
 #ifndef __IPV6HEADER_H
 #define __IPV6HEADER_H
 
+#include <linux/types.h>
+
 struct ip6t_ipv6header_info {
-	u_int8_t matchflags;
-	u_int8_t invflags;
-	u_int8_t modeflag;
+	__u8 matchflags;
+	__u8 invflags;
+	__u8 modeflag;
 };
 
 #define MASK_HOPOPTS    128
diff --git a/include/linux/netfilter_ipv6/ip6t_mh.h b/include/linux/netfilter_ipv6/ip6t_mh.h
index 18549bc..a7729a5 100644
--- a/include/linux/netfilter_ipv6/ip6t_mh.h
+++ b/include/linux/netfilter_ipv6/ip6t_mh.h
@@ -1,10 +1,12 @@
 #ifndef _IP6T_MH_H
 #define _IP6T_MH_H
 
+#include <linux/types.h>
+
 /* MH matching stuff */
 struct ip6t_mh {
-	u_int8_t types[2];	/* MH type range */
-	u_int8_t invflags;	/* Inverse flags */
+	__u8 types[2];	/* MH type range */
+	__u8 invflags;	/* Inverse flags */
 };
 
 /* Values for "invflags" field in struct ip6t_mh. */
diff --git a/include/linux/netfilter_ipv6/ip6t_opts.h b/include/linux/netfilter_ipv6/ip6t_opts.h
index 62d89bc..17d419a 100644
--- a/include/linux/netfilter_ipv6/ip6t_opts.h
+++ b/include/linux/netfilter_ipv6/ip6t_opts.h
@@ -1,14 +1,16 @@
 #ifndef _IP6T_OPTS_H
 #define _IP6T_OPTS_H
 
+#include <linux/types.h>
+
 #define IP6T_OPTS_OPTSNR 16
 
 struct ip6t_opts {
-	u_int32_t hdrlen;			/* Header Length */
-	u_int8_t flags;				/*  */
-	u_int8_t invflags;			/* Inverse flags */
-	u_int16_t opts[IP6T_OPTS_OPTSNR];	/* opts */
-	u_int8_t optsnr;			/* Nr of OPts */
+	__u32 hdrlen;			/* Header Length */
+	__u8 flags;				/*  */
+	__u8 invflags;			/* Inverse flags */
+	__u16 opts[IP6T_OPTS_OPTSNR];	/* opts */
+	__u8 optsnr;			/* Nr of OPts */
 };
 
 #define IP6T_OPTS_LEN 		0x01
diff --git a/include/linux/netfilter_ipv6/ip6t_rt.h b/include/linux/netfilter_ipv6/ip6t_rt.h
index ab91bfd..7605a5f 100644
--- a/include/linux/netfilter_ipv6/ip6t_rt.h
+++ b/include/linux/netfilter_ipv6/ip6t_rt.h
@@ -1,18 +1,19 @@
 #ifndef _IP6T_RT_H
 #define _IP6T_RT_H
 
+#include <linux/types.h>
 /*#include <linux/in6.h>*/
 
 #define IP6T_RT_HOPS 16
 
 struct ip6t_rt {
-	u_int32_t rt_type;			/* Routing Type */
-	u_int32_t segsleft[2];			/* Segments Left */
-	u_int32_t hdrlen;			/* Header Length */
-	u_int8_t  flags;			/*  */
-	u_int8_t  invflags;			/* Inverse flags */
+	__u32 rt_type;			/* Routing Type */
+	__u32 segsleft[2];			/* Segments Left */
+	__u32 hdrlen;			/* Header Length */
+	__u8  flags;			/*  */
+	__u8  invflags;			/* Inverse flags */
 	struct in6_addr addrs[IP6T_RT_HOPS];	/* Hops */
-	u_int8_t addrnr;			/* Nr of Addresses */
+	__u8 addrnr;			/* Nr of Addresses */
 };
 
 #define IP6T_RT_TYP 		0x01
diff --git a/include/linux/netfilter_ipv6/ip6t_srh.h b/include/linux/netfilter_ipv6/ip6t_srh.h
new file mode 100644
index 0000000..3bfe411
--- /dev/null
+++ b/include/linux/netfilter_ipv6/ip6t_srh.h
@@ -0,0 +1,95 @@
+#ifndef _IP6T_SRH_H
+#define _IP6T_SRH_H
+
+#include <linux/types.h>
+#include <linux/netfilter.h>
+
+/* Values for "mt_flags" field in struct ip6t_srh */
+#define IP6T_SRH_NEXTHDR        0x0001
+#define IP6T_SRH_LEN_EQ         0x0002
+#define IP6T_SRH_LEN_GT         0x0004
+#define IP6T_SRH_LEN_LT         0x0008
+#define IP6T_SRH_SEGS_EQ        0x0010
+#define IP6T_SRH_SEGS_GT        0x0020
+#define IP6T_SRH_SEGS_LT        0x0040
+#define IP6T_SRH_LAST_EQ        0x0080
+#define IP6T_SRH_LAST_GT        0x0100
+#define IP6T_SRH_LAST_LT        0x0200
+#define IP6T_SRH_TAG            0x0400
+#define IP6T_SRH_PSID           0x0800
+#define IP6T_SRH_NSID           0x1000
+#define IP6T_SRH_LSID           0x2000
+#define IP6T_SRH_MASK           0x3FFF
+
+/* Values for "mt_invflags" field in struct ip6t_srh */
+#define IP6T_SRH_INV_NEXTHDR    0x0001
+#define IP6T_SRH_INV_LEN_EQ     0x0002
+#define IP6T_SRH_INV_LEN_GT     0x0004
+#define IP6T_SRH_INV_LEN_LT     0x0008
+#define IP6T_SRH_INV_SEGS_EQ    0x0010
+#define IP6T_SRH_INV_SEGS_GT    0x0020
+#define IP6T_SRH_INV_SEGS_LT    0x0040
+#define IP6T_SRH_INV_LAST_EQ    0x0080
+#define IP6T_SRH_INV_LAST_GT    0x0100
+#define IP6T_SRH_INV_LAST_LT    0x0200
+#define IP6T_SRH_INV_TAG        0x0400
+#define IP6T_SRH_INV_PSID       0x0800
+#define IP6T_SRH_INV_NSID       0x1000
+#define IP6T_SRH_INV_LSID       0x2000
+#define IP6T_SRH_INV_MASK       0x3FFF
+
+/**
+ *      struct ip6t_srh - SRH match options
+ *      @ next_hdr: Next header field of SRH
+ *      @ hdr_len: Extension header length field of SRH
+ *      @ segs_left: Segments left field of SRH
+ *      @ last_entry: Last entry field of SRH
+ *      @ tag: Tag field of SRH
+ *      @ mt_flags: match options
+ *      @ mt_invflags: Invert the sense of match options
+ */
+
+struct ip6t_srh {
+	__u8                    next_hdr;
+	__u8                    hdr_len;
+	__u8                    segs_left;
+	__u8                    last_entry;
+	__u16                   tag;
+	__u16                   mt_flags;
+	__u16                   mt_invflags;
+};
+
+/**
+ *      struct ip6t_srh1 - SRH match options (revision 1)
+ *      @ next_hdr: Next header field of SRH
+ *      @ hdr_len: Extension header length field of SRH
+ *      @ segs_left: Segments left field of SRH
+ *      @ last_entry: Last entry field of SRH
+ *      @ tag: Tag field of SRH
+ *      @ psid_addr: Address of previous SID in SRH SID list
+ *      @ nsid_addr: Address of NEXT SID in SRH SID list
+ *      @ lsid_addr: Address of LAST SID in SRH SID list
+ *      @ psid_msk: Mask of previous SID in SRH SID list
+ *      @ nsid_msk: Mask of next SID in SRH SID list
+ *      @ lsid_msk: MAsk of last SID in SRH SID list
+ *      @ mt_flags: match options
+ *      @ mt_invflags: Invert the sense of match options
+ */
+
+struct ip6t_srh1 {
+	__u8                    next_hdr;
+	__u8                    hdr_len;
+	__u8                    segs_left;
+	__u8                    last_entry;
+	__u16                   tag;
+	struct in6_addr         psid_addr;
+	struct in6_addr         nsid_addr;
+	struct in6_addr         lsid_addr;
+	struct in6_addr         psid_msk;
+	struct in6_addr         nsid_msk;
+	struct in6_addr         lsid_msk;
+	__u16                   mt_flags;
+	__u16                   mt_invflags;
+};
+
+#endif /*_IP6T_SRH_H*/
diff --git a/include/linux/types.h b/include/linux/types.h
index 4d3abd9..1ae9250 100644
--- a/include/linux/types.h
+++ b/include/linux/types.h
@@ -36,5 +36,18 @@
 typedef __u16 __bitwise __sum16;
 typedef __u32 __bitwise __wsum;
 
+/*
+ * aligned_u64 should be used in defining kernel<->userspace ABIs to avoid
+ * common 32/64-bit compat problems.
+ * 64-bit values align to 4-byte boundaries on x86_32 (and possibly other
+ * architectures) and to 8-byte boundaries on 64-bit architetures.  The new
+ * aligned_64 type enforces 8-byte alignment so that structs containing
+ * aligned_64 values have the same alignment on 32-bit and 64-bit architectures.
+ * No conversions are necessary between 32-bit user-space and a 64-bit kernel.
+ */
+#define __aligned_u64 __u64 __attribute__((aligned(8)))
+#define __aligned_be64 __be64 __attribute__((aligned(8)))
+#define __aligned_le64 __le64 __attribute__((aligned(8)))
+
 #endif /*  __ASSEMBLY__ */
 #endif /* _LINUX_TYPES_H */
diff --git a/include/net/netfilter/nf_conntrack_tuple.h b/include/net/netfilter/nf_conntrack_tuple.h
deleted file mode 100644
index c40e0b4..0000000
--- a/include/net/netfilter/nf_conntrack_tuple.h
+++ /dev/null
@@ -1,114 +0,0 @@
-/* This file was manually copied from the Linux kernel source
- * and manually stripped from __KERNEL__ sections and unused functions.
- */
-
-/*
- * Definitions and Declarations for tuple.
- *
- * 16 Dec 2003: Yasuyuki Kozakai @USAGI <yasuyuki.kozakai@toshiba.co.jp>
- *	- generalize L3 protocol dependent part.
- *
- * Derived from include/linux/netfiter_ipv4/ip_conntrack_tuple.h
- */
-
-#ifndef _NF_CONNTRACK_TUPLE_H
-#define _NF_CONNTRACK_TUPLE_H
-
-#include <linux/netfilter/x_tables.h>
-#include <linux/netfilter/nf_conntrack_tuple_common.h>
-
-/* A `tuple' is a structure containing the information to uniquely
-  identify a connection.  ie. if two packets have the same tuple, they
-  are in the same connection; if not, they are not.
-
-  We divide the structure along "manipulatable" and
-  "non-manipulatable" lines, for the benefit of the NAT code.
-*/
-
-#define NF_CT_TUPLE_L3SIZE	ARRAY_SIZE(((union nf_inet_addr *)NULL)->all)
-
-/* The protocol-specific manipulable parts of the tuple: always in
-   network order! */
-union nf_conntrack_man_proto
-{
-	/* Add other protocols here. */
-	__be16 all;
-
-	struct {
-		__be16 port;
-	} tcp;
-	struct {
-		__be16 port;
-	} udp;
-	struct {
-		__be16 id;
-	} icmp;
-	struct {
-		__be16 port;
-	} dccp;
-	struct {
-		__be16 port;
-	} sctp;
-	struct {
-		__be16 key;	/* GRE key is 32bit, PPtP only uses 16bit */
-	} gre;
-};
-
-/* The manipulable part of the tuple. */
-struct nf_conntrack_man
-{
-	union nf_inet_addr u3;
-	union nf_conntrack_man_proto u;
-	/* Layer 3 protocol */
-	u_int16_t l3num;
-};
-
-/* This contains the information to distinguish a connection. */
-struct nf_conntrack_tuple
-{
-	struct nf_conntrack_man src;
-
-	/* These are the parts of the tuple which are fixed. */
-	struct {
-		union nf_inet_addr u3;
-		union {
-			/* Add other protocols here. */
-			__be16 all;
-
-			struct {
-				__be16 port;
-			} tcp;
-			struct {
-				__be16 port;
-			} udp;
-			struct {
-				u_int8_t type, code;
-			} icmp;
-			struct {
-				__be16 port;
-			} dccp;
-			struct {
-				__be16 port;
-			} sctp;
-			struct {
-				__be16 key;
-			} gre;
-		} u;
-
-		/* The protocol. */
-		u_int8_t protonum;
-
-		/* The direction (for tuplehash) */
-		u_int8_t dir;
-	} dst;
-};
-
-struct nf_conntrack_tuple_mask
-{
-	struct {
-		union nf_inet_addr u3;
-		union nf_conntrack_man_proto u;
-	} src;
-};
-
-#endif /* _NF_CONNTRACK_TUPLE_H */
diff --git a/include/net/netfilter/nf_nat.h b/include/net/netfilter/nf_nat.h
deleted file mode 100644
index c3e2060..0000000
--- a/include/net/netfilter/nf_nat.h
+++ /dev/null
@@ -1,55 +0,0 @@
-#ifndef _NF_NAT_H
-#define _NF_NAT_H
-#include <linux/netfilter_ipv4.h>
-#include <net/netfilter/nf_conntrack_tuple.h>
-
-#define NF_NAT_MAPPING_TYPE_MAX_NAMELEN 16
-
-enum nf_nat_manip_type
-{
-	IP_NAT_MANIP_SRC,
-	IP_NAT_MANIP_DST
-};
-
-/* SRC manip occurs POST_ROUTING or LOCAL_IN */
-#define HOOK2MANIP(hooknum) ((hooknum) != NF_INET_POST_ROUTING && \
-			     (hooknum) != NF_INET_LOCAL_IN)
-
-#define IP_NAT_RANGE_MAP_IPS 1
-#define IP_NAT_RANGE_PROTO_SPECIFIED 2
-#define IP_NAT_RANGE_PROTO_RANDOM 4
-#define IP_NAT_RANGE_PERSISTENT 8
-
-/* NAT sequence number modifications */
-struct nf_nat_seq {
-	/* position of the last TCP sequence number modification (if any) */
-	u_int32_t correction_pos;
-
-	/* sequence number offset before and after last modification */
-	int16_t offset_before, offset_after;
-};
-
-/* Single range specification. */
-struct nf_nat_range
-{
-	/* Set to OR of flags above. */
-	unsigned int flags;
-
-	/* Inclusive: network order. */
-	__be32 min_ip, max_ip;
-
-	/* Inclusive: network order */
-	union nf_conntrack_man_proto min, max;
-};
-
-/* For backwards compat: don't use in modern code. */
-struct nf_nat_multi_range_compat
-{
-	unsigned int rangesize; /* Must be 1. */
-
-	/* hangs off end. */
-	struct nf_nat_range range[1];
-};
-
-#define nf_nat_multi_range nf_nat_multi_range_compat
-#endif
diff --git a/include/xtables-version.h b/include/xtables-version.h
new file mode 100644
index 0000000..ed31ad8
--- /dev/null
+++ b/include/xtables-version.h
@@ -0,0 +1,2 @@
+#define XTABLES_VERSION "libxtables.so.12"
+#define XTABLES_VERSION_CODE 12
diff --git a/include/xtables-version.h.in b/include/xtables-version.h.in
new file mode 100644
index 0000000..cb13827
--- /dev/null
+++ b/include/xtables-version.h.in
@@ -0,0 +1,2 @@
+#define XTABLES_VERSION "libxtables.so.@libxtables_vmajor@"
+#define XTABLES_VERSION_CODE @libxtables_vmajor@
diff --git a/include/xtables.h b/include/xtables.h
index b5cd9e9..df1eaee 100644
--- a/include/xtables.h
+++ b/include/xtables.h
@@ -31,8 +31,7 @@
 #define IPPROTO_UDPLITE	136
 #endif
 
-#define XTABLES_VERSION "libxtables.so.6"
-#define XTABLES_VERSION_CODE 6
+#include <xtables-version.h>
 
 struct in_addr;
 
@@ -137,11 +136,13 @@
  * @arg:	input from command line
  * @ext_name:	name of extension currently being processed
  * @entry:	current option being processed
- * @data:	per-extension data block
+ * @data:	per-extension kernel data block
  * @xflags:	options of the extension that have been used
  * @invert:	whether option was used with !
  * @nvals:	number of results in uXX_multi
  * @val:	parsed result
+ * @udata:	per-extension private scratch area
+ * 		(cf. xtables_{match,target}->udata_size)
  */
 struct xt_option_call {
 	const char *arg, *ext_name;
@@ -174,16 +175,19 @@
 		struct xt_entry_target **target;
 	};
 	void *xt_entry;
+	void *udata;
 };
 
 /**
  * @ext_name:	name of extension currently being processed
- * @data:	per-extension data block
+ * @data:	per-extension (kernel) data block
+ * @udata:	per-extension private scratch area
+ * 		(cf. xtables_{match,target}->udata_size)
  * @xflags:	options of the extension that have been used
  */
 struct xt_fcheck_call {
 	const char *ext_name;
-	void *data;
+	void *data, *udata;
 	unsigned int xflags;
 };
 
@@ -197,9 +201,28 @@
 	struct xtables_lmap *next;
 };
 
+enum xtables_ext_flags {
+	XTABLES_EXT_ALIAS = 1 << 0,
+};
+
+struct xt_xlate;
+
+struct xt_xlate_mt_params {
+	const void			*ip;
+	const struct xt_entry_match	*match;
+	int				numeric;
+	bool				escape_quotes;
+};
+
+struct xt_xlate_tg_params {
+	const void			*ip;
+	const struct xt_entry_target	*target;
+	int				numeric;
+	bool				escape_quotes;
+};
+
 /* Include file for additions: new matches and targets. */
-struct xtables_match
-{
+struct xtables_match {
 	/*
 	 * ABI/API version this module requires. Must be first member,
 	 * as the rest of this struct may be subject to ABI changes.
@@ -209,16 +232,20 @@
 	struct xtables_match *next;
 
 	const char *name;
+	const char *real_name;
 
 	/* Revision of match (0 by default). */
-	u_int8_t revision;
+	uint8_t revision;
 
-	u_int16_t family;
+	/* Extension flags */
+	uint8_t ext_flags;
+
+	uint16_t family;
 
 	/* Size of match data. */
 	size_t size;
 
-	/* Size of match data relevent for userspace comparison purposes */
+	/* Size of match data relevant for userspace comparison purposes */
 	size_t userspacesize;
 
 	/* Function which prints out usage message. */
@@ -246,6 +273,9 @@
 	/* ip is struct ipt_ip * for example */
 	void (*save)(const void *ip, const struct xt_entry_match *match);
 
+	/* Print match name or alias */
+	const char *(*alias)(const struct xt_entry_match *match);
+
 	/* Pointer to list of extra command-line options */
 	const struct option *extra_opts;
 
@@ -254,15 +284,22 @@
 	void (*x6_fcheck)(struct xt_fcheck_call *);
 	const struct xt_option_entry *x6_options;
 
+	/* Translate iptables to nft */
+	int (*xlate)(struct xt_xlate *xl,
+		     const struct xt_xlate_mt_params *params);
+
+	/* Size of per-extension instance extra "global" scratch space */
+	size_t udata_size;
+
 	/* Ignore these men behind the curtain: */
+	void *udata;
 	unsigned int option_offset;
 	struct xt_entry_match *m;
 	unsigned int mflags;
 	unsigned int loaded; /* simulate loading so options are merged properly */
 };
 
-struct xtables_target
-{
+struct xtables_target {
 	/*
 	 * ABI/API version this module requires. Must be first member,
 	 * as the rest of this struct may be subject to ABI changes.
@@ -274,16 +311,22 @@
 
 	const char *name;
 
-	/* Revision of target (0 by default). */
-	u_int8_t revision;
+	/* Real target behind this, if any. */
+	const char *real_name;
 
-	u_int16_t family;
+	/* Revision of target (0 by default). */
+	uint8_t revision;
+
+	/* Extension flags */
+	uint8_t ext_flags;
+
+	uint16_t family;
 
 
 	/* Size of target data. */
 	size_t size;
 
-	/* Size of target data relevent for userspace comparison purposes */
+	/* Size of target data relevant for userspace comparison purposes */
 	size_t userspacesize;
 
 	/* Function which prints out usage message. */
@@ -310,6 +353,9 @@
 	void (*save)(const void *ip,
 		     const struct xt_entry_target *target);
 
+	/* Print target name or alias */
+	const char *(*alias)(const struct xt_entry_target *target);
+
 	/* Pointer to list of extra command-line options */
 	const struct option *extra_opts;
 
@@ -318,7 +364,14 @@
 	void (*x6_fcheck)(struct xt_fcheck_call *);
 	const struct xt_option_entry *x6_options;
 
+	/* Translate iptables to nft */
+	int (*xlate)(struct xt_xlate *xl,
+		     const struct xt_xlate_tg_params *params);
+
+	size_t udata_size;
+
 	/* Ignore these men behind the curtain: */
+	void *udata;
 	unsigned int option_offset;
 	struct xt_entry_target *t;
 	unsigned int tflags;
@@ -342,7 +395,7 @@
  */
 struct xtables_pprot {
 	const char *name;
-	u_int8_t num;
+	uint8_t num;
 };
 
 enum xtables_tryload {
@@ -370,10 +423,22 @@
 	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}
 
+/*
+ * enum op-
+ *
+ * For writing clean nftables translations code
+ */
+enum xt_op {
+	XT_OP_EQ,
+	XT_OP_NEQ,
+	XT_OP_MAX,
+};
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -383,6 +448,7 @@
 extern struct xtables_target *xtables_targets;
 
 extern void xtables_init(void);
+extern void xtables_fini(void);
 extern void xtables_set_nfproto(uint8_t);
 extern void *xtables_calloc(size_t, size_t);
 extern void *xtables_malloc(size_t);
@@ -399,8 +465,18 @@
 extern int xtables_init_all(struct xtables_globals *xtp, uint8_t nfproto);
 extern struct xtables_match *xtables_find_match(const char *name,
 	enum xtables_tryload, struct xtables_rule_match **match);
+extern struct xtables_match *xtables_find_match_revision(const char *name,
+	enum xtables_tryload tryload, struct xtables_match *match,
+	int revision);
 extern struct xtables_target *xtables_find_target(const char *name,
 	enum xtables_tryload);
+struct xtables_target *xtables_find_target_revision(const char *name,
+	enum xtables_tryload tryload, struct xtables_target *target,
+	int revision);
+extern int xtables_compatible_revision(const char *name, uint8_t revision,
+				       int opt);
+
+extern void xtables_rule_matches_free(struct xtables_rule_match **matches);
 
 /* Your shared library should call one of these. */
 extern void xtables_register_match(struct xtables_match *me);
@@ -413,15 +489,13 @@
 extern bool xtables_strtoui(const char *, char **, unsigned int *,
 	unsigned int, unsigned int);
 extern int xtables_service_to_port(const char *name, const char *proto);
-extern u_int16_t xtables_parse_port(const char *port, const char *proto);
+extern uint16_t xtables_parse_port(const char *port, const char *proto);
 extern void
 xtables_parse_interface(const char *arg, char *vianame, unsigned char *mask);
 
 /* this is a special 64bit data type that is 8-byte aligned */
-#define aligned_u64 u_int64_t __attribute__((aligned(8)))
+#define aligned_u64 uint64_t __attribute__((aligned(8)))
 
-int xtables_check_inverse(const char option[], int *invert,
-	int *my_optind, int argc, char **argv);
 extern struct xtables_globals *xt_params;
 #define xtables_error (xt_params->exit_err)
 
@@ -432,6 +506,7 @@
 extern const char *xtables_ipmask_to_numeric(const struct in_addr *);
 extern struct in_addr *xtables_numeric_to_ipaddr(const char *);
 extern struct in_addr *xtables_numeric_to_ipmask(const char *);
+extern int xtables_ipmask_to_cidr(const struct in_addr *);
 extern void xtables_ipparse_any(const char *, struct in_addr **,
 	struct in_addr *, unsigned int *);
 extern void xtables_ipparse_multiple(const char *, struct in_addr **,
@@ -441,17 +516,74 @@
 extern const char *xtables_ip6addr_to_numeric(const struct in6_addr *);
 extern const char *xtables_ip6addr_to_anyname(const struct in6_addr *);
 extern const char *xtables_ip6mask_to_numeric(const struct in6_addr *);
+extern int xtables_ip6mask_to_cidr(const struct in6_addr *);
 extern void xtables_ip6parse_any(const char *, struct in6_addr **,
 	struct in6_addr *, unsigned int *);
 extern void xtables_ip6parse_multiple(const char *, struct in6_addr **,
 	struct in6_addr **, unsigned int *);
 
+/* Absolute file name for network data base files.  */
+#define XT_PATH_ETHERTYPES     "/etc/ethertypes"
+
+struct xt_ethertypeent {
+	char *e_name;           /* Official ethernet type name.  */
+	char **e_aliases;       /* Alias list.  */
+	int e_ethertype;        /* Ethernet type number.  */
+};
+
+extern struct xt_ethertypeent *xtables_getethertypebyname(const char *name);
+extern struct xt_ethertypeent *xtables_getethertypebynumber(int ethertype);
+
 /**
  * Print the specified value to standard output, quoting dangerous
  * characters if required.
  */
 extern void xtables_save_string(const char *value);
 
+#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_EBT_SAVE		0x0200
+#define FMT_C_COUNTS		0x0400
+
+#define FMT_PRINT_RULE (FMT_NOCOUNTS | FMT_OPTIONS | FMT_VIA \
+                        | FMT_NUMERIC | FMT_NOTABLE)
+#define FMT(tab,notab) ((format) & FMT_NOTABLE ? (notab) : (tab))
+
+extern void xtables_print_num(uint64_t number, unsigned int format);
+extern int xtables_parse_mac_and_mask(const char *from, void *to, void *mask);
+extern int xtables_print_well_known_mac_and_mask(const void *mac,
+						 const void *mask);
+extern void xtables_print_mac(const unsigned char *macaddress);
+extern void xtables_print_mac_and_mask(const unsigned char *mac,
+				       const unsigned char *mask);
+
+extern void xtables_parse_val_mask(struct xt_option_call *cb,
+				   unsigned int *val, unsigned int *mask,
+				   const struct xtables_lmap *lmap);
+
+static inline void xtables_parse_mark_mask(struct xt_option_call *cb,
+					   unsigned int *mark,
+					   unsigned int *mask)
+{
+	xtables_parse_val_mask(cb, mark, mask, NULL);
+}
+
+extern void xtables_print_val_mask(unsigned int val, unsigned int mask,
+				   const struct xtables_lmap *lmap);
+
+static inline void xtables_print_mark_mask(unsigned int mark,
+					   unsigned int mask)
+{
+	xtables_print_val_mask(mark, mask, NULL);
+}
+
 #if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
 #	ifdef _INIT
 #		undef _init
@@ -465,7 +597,15 @@
 #endif
 
 extern const struct xtables_pprot xtables_chain_protos[];
-extern u_int16_t xtables_parse_protocol(const char *s);
+extern uint16_t xtables_parse_protocol(const char *s);
+
+/* kernel revision handling */
+extern int kernel_version;
+extern void get_kernel_version(void);
+#define LINUX_VERSION(x,y,z)	(0x10000*(x) + 0x100*(y) + z)
+#define LINUX_VERSION_MAJOR(x)	(((x)>>16) & 0xFF)
+#define LINUX_VERSION_MINOR(x)	(((x)>> 8) & 0xFF)
+#define LINUX_VERSION_PATCH(x)	( (x)      & 0xFF)
 
 /* xtoptions.c */
 extern void xtables_option_metavalidate(const char *,
@@ -488,6 +628,14 @@
 extern int xtables_lmap_name2id(const struct xtables_lmap *, const char *);
 extern const char *xtables_lmap_id2name(const struct xtables_lmap *, int);
 
+/* xlate infrastructure */
+struct xt_xlate *xt_xlate_alloc(int size);
+void xt_xlate_free(struct xt_xlate *xl);
+void xt_xlate_add(struct xt_xlate *xl, const char *fmt, ...) __attribute__((format(printf,2,3)));
+void xt_xlate_add_comment(struct xt_xlate *xl, const char *comment);
+const char *xt_xlate_get_comment(struct xt_xlate *xl);
+const char *xt_xlate_get(struct xt_xlate *xl);
+
 #ifdef XTABLES_INTERNAL
 
 /* Shipped modules rely on this... */
diff --git a/include/xtables.h.in b/include/xtables.h.in
deleted file mode 100644
index 2565dd2..0000000
--- a/include/xtables.h.in
+++ /dev/null
@@ -1,507 +0,0 @@
-#ifndef _XTABLES_H
-#define _XTABLES_H
-
-/*
- * Changing any structs/functions may incur a needed change
- * in libxtables_vcurrent/vage too.
- */
-
-#include <sys/socket.h> /* PF_* */
-#include <sys/types.h>
-#include <limits.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <netinet/in.h>
-#include <net/if.h>
-#include <linux/types.h>
-#include <linux/netfilter.h>
-#include <linux/netfilter/x_tables.h>
-
-#ifndef IPPROTO_SCTP
-#define IPPROTO_SCTP 132
-#endif
-#ifndef IPPROTO_DCCP
-#define IPPROTO_DCCP 33
-#endif
-#ifndef IPPROTO_MH
-#	define IPPROTO_MH 135
-#endif
-#ifndef IPPROTO_UDPLITE
-#define IPPROTO_UDPLITE	136
-#endif
-
-#define XTABLES_VERSION "libxtables.so.@libxtables_vmajor@"
-#define XTABLES_VERSION_CODE @libxtables_vmajor@
-
-struct in_addr;
-
-/*
- * .size is here so that there is a somewhat reasonable check
- * against the chosen .type.
- */
-#define XTOPT_POINTER(stype, member) \
-	.ptroff = offsetof(stype, member), \
-	.size = sizeof(((stype *)NULL)->member)
-#define XTOPT_TABLEEND {.name = NULL}
-
-/**
- * Select the format the input has to conform to, as well as the target type
- * (area pointed to with XTOPT_POINTER). Note that the storing is not always
- * uniform. @cb->val will be populated with as much as there is space, i.e.
- * exactly 2 items for ranges, but the target area can receive more values
- * (e.g. in case of ranges), or less values (e.g. %XTTYPE_HOSTMASK).
- *
- * %XTTYPE_NONE:	option takes no argument
- * %XTTYPE_UINT*:	standard integer
- * %XTTYPE_UINT*RC:	colon-separated range of standard integers
- * %XTTYPE_DOUBLE:	double-precision floating point number
- * %XTTYPE_STRING:	arbitrary string
- * %XTTYPE_TOSMASK:	8-bit TOS value with optional mask
- * %XTTYPE_MARKMASK32:	32-bit mark with optional mask
- * %XTTYPE_SYSLOGLEVEL:	syslog level by name or number
- * %XTTYPE_HOST:	one host or address (ptr: union nf_inet_addr)
- * %XTTYPE_HOSTMASK:	one host or address, with an optional prefix length
- * 			(ptr: union nf_inet_addr; only host portion is stored)
- * %XTTYPE_PROTOCOL:	protocol number/name from /etc/protocols (ptr: uint8_t)
- * %XTTYPE_PORT:	16-bit port name or number (supports %XTOPT_NBO)
- * %XTTYPE_PORTRC:	colon-separated port range (names acceptable),
- * 			(supports %XTOPT_NBO)
- * %XTTYPE_PLEN:	prefix length
- * %XTTYPE_PLENMASK:	prefix length (ptr: union nf_inet_addr)
- * %XTTYPE_ETHERMAC:	Ethernet MAC address in hex form
- */
-enum xt_option_type {
-	XTTYPE_NONE,
-	XTTYPE_UINT8,
-	XTTYPE_UINT16,
-	XTTYPE_UINT32,
-	XTTYPE_UINT64,
-	XTTYPE_UINT8RC,
-	XTTYPE_UINT16RC,
-	XTTYPE_UINT32RC,
-	XTTYPE_UINT64RC,
-	XTTYPE_DOUBLE,
-	XTTYPE_STRING,
-	XTTYPE_TOSMASK,
-	XTTYPE_MARKMASK32,
-	XTTYPE_SYSLOGLEVEL,
-	XTTYPE_HOST,
-	XTTYPE_HOSTMASK,
-	XTTYPE_PROTOCOL,
-	XTTYPE_PORT,
-	XTTYPE_PORTRC,
-	XTTYPE_PLEN,
-	XTTYPE_PLENMASK,
-	XTTYPE_ETHERMAC,
-};
-
-/**
- * %XTOPT_INVERT:	option is invertible (usable with !)
- * %XTOPT_MAND:		option is mandatory
- * %XTOPT_MULTI:	option may be specified multiple times
- * %XTOPT_PUT:		store value into memory at @ptroff
- * %XTOPT_NBO:		store value in network-byte order
- * 			(only certain XTTYPEs recognize this)
- */
-enum xt_option_flags {
-	XTOPT_INVERT = 1 << 0,
-	XTOPT_MAND   = 1 << 1,
-	XTOPT_MULTI  = 1 << 2,
-	XTOPT_PUT    = 1 << 3,
-	XTOPT_NBO    = 1 << 4,
-};
-
-/**
- * @name:	name of option
- * @type:	type of input and validation method, see %XTTYPE_*
- * @id:		unique number (within extension) for option, 0-31
- * @excl:	bitmask of flags that cannot be used with this option
- * @also:	bitmask of flags that must be used with this option
- * @flags:	bitmask of option flags, see %XTOPT_*
- * @ptroff:	offset into private structure for member
- * @size:	size of the item pointed to by @ptroff; this is a safeguard
- * @min:	lowest allowed value (for singular integral types)
- * @max:	highest allowed value (for singular integral types)
- */
-struct xt_option_entry {
-	const char *name;
-	enum xt_option_type type;
-	unsigned int id, excl, also, flags;
-	unsigned int ptroff;
-	size_t size;
-	unsigned int min, max;
-};
-
-/**
- * @arg:	input from command line
- * @ext_name:	name of extension currently being processed
- * @entry:	current option being processed
- * @data:	per-extension data block
- * @xflags:	options of the extension that have been used
- * @invert:	whether option was used with !
- * @nvals:	number of results in uXX_multi
- * @val:	parsed result
- */
-struct xt_option_call {
-	const char *arg, *ext_name;
-	const struct xt_option_entry *entry;
-	void *data;
-	unsigned int xflags;
-	bool invert;
-	uint8_t nvals;
-	union {
-		uint8_t u8, u8_range[2], syslog_level, protocol;
-		uint16_t u16, u16_range[2], port, port_range[2];
-		uint32_t u32, u32_range[2];
-		uint64_t u64, u64_range[2];
-		double dbl;
-		struct {
-			union nf_inet_addr haddr, hmask;
-			uint8_t hlen;
-		};
-		struct {
-			uint8_t tos_value, tos_mask;
-		};
-		struct {
-			uint32_t mark, mask;
-		};
-		uint8_t ethermac[6];
-	} val;
-	/* Wished for a world where the ones below were gone: */
-	union {
-		struct xt_entry_match **match;
-		struct xt_entry_target **target;
-	};
-	void *xt_entry;
-};
-
-/**
- * @ext_name:	name of extension currently being processed
- * @data:	per-extension data block
- * @xflags:	options of the extension that have been used
- */
-struct xt_fcheck_call {
-	const char *ext_name;
-	void *data;
-	unsigned int xflags;
-};
-
-/**
- * A "linear"/linked-list based name<->id map, for files similar to
- * /etc/iproute2/.
- */
-struct xtables_lmap {
-	char *name;
-	int id;
-	struct xtables_lmap *next;
-};
-
-/* Include file for additions: new matches and targets. */
-struct xtables_match
-{
-	/*
-	 * ABI/API version this module requires. Must be first member,
-	 * as the rest of this struct may be subject to ABI changes.
-	 */
-	const char *version;
-
-	struct xtables_match *next;
-
-	const char *name;
-
-	/* Revision of match (0 by default). */
-	u_int8_t revision;
-
-	u_int16_t family;
-
-	/* Size of match data. */
-	size_t size;
-
-	/* Size of match data relevent for userspace comparison purposes */
-	size_t userspacesize;
-
-	/* Function which prints out usage message. */
-	void (*help)(void);
-
-	/* Initialize the match. */
-	void (*init)(struct xt_entry_match *m);
-
-	/* Function which parses command options; returns true if it
-           ate an option */
-	/* entry is struct ipt_entry for example */
-	int (*parse)(int c, char **argv, int invert, unsigned int *flags,
-		     const void *entry,
-		     struct xt_entry_match **match);
-
-	/* Final check; exit if not ok. */
-	void (*final_check)(unsigned int flags);
-
-	/* Prints out the match iff non-NULL: put space at end */
-	/* ip is struct ipt_ip * for example */
-	void (*print)(const void *ip,
-		      const struct xt_entry_match *match, int numeric);
-
-	/* Saves the match info in parsable form to stdout. */
-	/* ip is struct ipt_ip * for example */
-	void (*save)(const void *ip, const struct xt_entry_match *match);
-
-	/* Pointer to list of extra command-line options */
-	const struct option *extra_opts;
-
-	/* New parser */
-	void (*x6_parse)(struct xt_option_call *);
-	void (*x6_fcheck)(struct xt_fcheck_call *);
-	const struct xt_option_entry *x6_options;
-
-	/* Ignore these men behind the curtain: */
-	unsigned int option_offset;
-	struct xt_entry_match *m;
-	unsigned int mflags;
-	unsigned int loaded; /* simulate loading so options are merged properly */
-};
-
-struct xtables_target
-{
-	/*
-	 * ABI/API version this module requires. Must be first member,
-	 * as the rest of this struct may be subject to ABI changes.
-	 */
-	const char *version;
-
-	struct xtables_target *next;
-
-
-	const char *name;
-
-	/* Revision of target (0 by default). */
-	u_int8_t revision;
-
-	u_int16_t family;
-
-
-	/* Size of target data. */
-	size_t size;
-
-	/* Size of target data relevent for userspace comparison purposes */
-	size_t userspacesize;
-
-	/* Function which prints out usage message. */
-	void (*help)(void);
-
-	/* Initialize the target. */
-	void (*init)(struct xt_entry_target *t);
-
-	/* Function which parses command options; returns true if it
-           ate an option */
-	/* entry is struct ipt_entry for example */
-	int (*parse)(int c, char **argv, int invert, unsigned int *flags,
-		     const void *entry,
-		     struct xt_entry_target **targetinfo);
-
-	/* Final check; exit if not ok. */
-	void (*final_check)(unsigned int flags);
-
-	/* Prints out the target iff non-NULL: put space at end */
-	void (*print)(const void *ip,
-		      const struct xt_entry_target *target, int numeric);
-
-	/* Saves the targinfo in parsable form to stdout. */
-	void (*save)(const void *ip,
-		     const struct xt_entry_target *target);
-
-	/* Pointer to list of extra command-line options */
-	const struct option *extra_opts;
-
-	/* New parser */
-	void (*x6_parse)(struct xt_option_call *);
-	void (*x6_fcheck)(struct xt_fcheck_call *);
-	const struct xt_option_entry *x6_options;
-
-	/* Ignore these men behind the curtain: */
-	unsigned int option_offset;
-	struct xt_entry_target *t;
-	unsigned int tflags;
-	unsigned int used;
-	unsigned int loaded; /* simulate loading so options are merged properly */
-};
-
-struct xtables_rule_match {
-	struct xtables_rule_match *next;
-	struct xtables_match *match;
-	/* Multiple matches of the same type: the ones before
-	   the current one are completed from parsing point of view */
-	bool completed;
-};
-
-/**
- * struct xtables_pprot -
- *
- * A few hardcoded protocols for 'all' and in case the user has no
- * /etc/protocols.
- */
-struct xtables_pprot {
-	const char *name;
-	u_int8_t num;
-};
-
-enum xtables_tryload {
-	XTF_DONT_LOAD,
-	XTF_DURING_LOAD,
-	XTF_TRY_LOAD,
-	XTF_LOAD_MUST_SUCCEED,
-};
-
-enum xtables_exittype {
-	OTHER_PROBLEM = 1,
-	PARAMETER_PROBLEM,
-	VERSION_PROBLEM,
-	RESOURCE_PROBLEM,
-	XTF_ONLY_ONCE,
-	XTF_NO_INVERT,
-	XTF_BAD_VALUE,
-	XTF_ONE_ACTION,
-};
-
-struct xtables_globals
-{
-	unsigned int option_offset;
-	const char *program_name, *program_version;
-	struct option *orig_opts;
-	struct option *opts;
-	void (*exit_err)(enum xtables_exittype status, const char *msg, ...) __attribute__((noreturn, format(printf,2,3)));
-};
-
-#define XT_GETOPT_TABLEEND {.name = NULL, .has_arg = false}
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-extern const char *xtables_modprobe_program;
-extern struct xtables_match *xtables_matches;
-extern struct xtables_target *xtables_targets;
-
-extern void xtables_init(void);
-extern void xtables_set_nfproto(uint8_t);
-extern void *xtables_calloc(size_t, size_t);
-extern void *xtables_malloc(size_t);
-extern void *xtables_realloc(void *, size_t);
-
-extern int xtables_insmod(const char *, const char *, bool);
-extern int xtables_load_ko(const char *, bool);
-extern int xtables_set_params(struct xtables_globals *xtp);
-extern void xtables_free_opts(int reset_offset);
-extern struct option *xtables_merge_options(struct option *origopts,
-	struct option *oldopts, const struct option *newopts,
-	unsigned int *option_offset);
-
-extern int xtables_init_all(struct xtables_globals *xtp, uint8_t nfproto);
-extern struct xtables_match *xtables_find_match(const char *name,
-	enum xtables_tryload, struct xtables_rule_match **match);
-extern struct xtables_target *xtables_find_target(const char *name,
-	enum xtables_tryload);
-
-/* Your shared library should call one of these. */
-extern void xtables_register_match(struct xtables_match *me);
-extern void xtables_register_matches(struct xtables_match *, unsigned int);
-extern void xtables_register_target(struct xtables_target *me);
-extern void xtables_register_targets(struct xtables_target *, unsigned int);
-
-extern bool xtables_strtoul(const char *, char **, uintmax_t *,
-	uintmax_t, uintmax_t);
-extern bool xtables_strtoui(const char *, char **, unsigned int *,
-	unsigned int, unsigned int);
-extern int xtables_service_to_port(const char *name, const char *proto);
-extern u_int16_t xtables_parse_port(const char *port, const char *proto);
-extern void
-xtables_parse_interface(const char *arg, char *vianame, unsigned char *mask);
-
-/* this is a special 64bit data type that is 8-byte aligned */
-#define aligned_u64 u_int64_t __attribute__((aligned(8)))
-
-int xtables_check_inverse(const char option[], int *invert,
-	int *my_optind, int argc, char **argv);
-extern struct xtables_globals *xt_params;
-#define xtables_error (xt_params->exit_err)
-
-extern void xtables_param_act(unsigned int, const char *, ...);
-
-extern const char *xtables_ipaddr_to_numeric(const struct in_addr *);
-extern const char *xtables_ipaddr_to_anyname(const struct in_addr *);
-extern const char *xtables_ipmask_to_numeric(const struct in_addr *);
-extern struct in_addr *xtables_numeric_to_ipaddr(const char *);
-extern struct in_addr *xtables_numeric_to_ipmask(const char *);
-extern void xtables_ipparse_any(const char *, struct in_addr **,
-	struct in_addr *, unsigned int *);
-extern void xtables_ipparse_multiple(const char *, struct in_addr **,
-	struct in_addr **, unsigned int *);
-
-extern struct in6_addr *xtables_numeric_to_ip6addr(const char *);
-extern const char *xtables_ip6addr_to_numeric(const struct in6_addr *);
-extern const char *xtables_ip6addr_to_anyname(const struct in6_addr *);
-extern const char *xtables_ip6mask_to_numeric(const struct in6_addr *);
-extern void xtables_ip6parse_any(const char *, struct in6_addr **,
-	struct in6_addr *, unsigned int *);
-extern void xtables_ip6parse_multiple(const char *, struct in6_addr **,
-	struct in6_addr **, unsigned int *);
-
-/**
- * Print the specified value to standard output, quoting dangerous
- * characters if required.
- */
-extern void xtables_save_string(const char *value);
-
-#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
-#	ifdef _INIT
-#		undef _init
-#		define _init _INIT
-#	endif
-	extern void init_extensions(void);
-	extern void init_extensions4(void);
-	extern void init_extensions6(void);
-#else
-#	define _init __attribute__((constructor)) _INIT
-#endif
-
-extern const struct xtables_pprot xtables_chain_protos[];
-extern u_int16_t xtables_parse_protocol(const char *s);
-
-/* xtoptions.c */
-extern void xtables_option_metavalidate(const char *,
-					const struct xt_option_entry *);
-extern struct option *xtables_options_xfrm(struct option *, struct option *,
-					   const struct xt_option_entry *,
-					   unsigned int *);
-extern void xtables_option_parse(struct xt_option_call *);
-extern void xtables_option_tpcall(unsigned int, char **, bool,
-				  struct xtables_target *, void *);
-extern void xtables_option_mpcall(unsigned int, char **, bool,
-				  struct xtables_match *, void *);
-extern void xtables_option_tfcall(struct xtables_target *);
-extern void xtables_option_mfcall(struct xtables_match *);
-extern void xtables_options_fcheck(const char *, unsigned int,
-				   const struct xt_option_entry *);
-
-extern struct xtables_lmap *xtables_lmap_init(const char *);
-extern void xtables_lmap_free(struct xtables_lmap *);
-extern int xtables_lmap_name2id(const struct xtables_lmap *, const char *);
-extern const char *xtables_lmap_id2name(const struct xtables_lmap *, int);
-
-#ifdef XTABLES_INTERNAL
-
-/* Shipped modules rely on this... */
-
-#	ifndef ARRAY_SIZE
-#		define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x)))
-#	endif
-
-extern void _init(void);
-
-#endif
-
-#ifdef __cplusplus
-} /* extern "C" */
-#endif
-
-#endif /* _XTABLES_H */
diff --git a/iptables-test.py b/iptables-test.py
new file mode 100755
index 0000000..ca5efb1
--- /dev/null
+++ b/iptables-test.py
@@ -0,0 +1,387 @@
+#!/usr/bin/env python
+#
+# (C) 2012-2013 by Pablo Neira Ayuso <pablo@netfilter.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This software has been sponsored by Sophos Astaro <http://www.sophos.com>
+#
+
+from __future__ import print_function
+import sys
+import os
+import subprocess
+import argparse
+
+IPTABLES = "iptables"
+IP6TABLES = "ip6tables"
+ARPTABLES = "arptables"
+EBTABLES = "ebtables"
+
+IPTABLES_SAVE = "iptables-save"
+IP6TABLES_SAVE = "ip6tables-save"
+ARPTABLES_SAVE = "arptables-save"
+EBTABLES_SAVE = "ebtables-save"
+#IPTABLES_SAVE = ['xtables-save','-4']
+#IP6TABLES_SAVE = ['xtables-save','-6']
+
+EXTENSIONS_PATH = "extensions"
+LOGFILE="/tmp/iptables-test.log"
+log_file = None
+
+
+class Colors:
+    HEADER = '\033[95m'
+    BLUE = '\033[94m'
+    GREEN = '\033[92m'
+    YELLOW = '\033[93m'
+    RED = '\033[91m'
+    ENDC = '\033[0m'
+
+
+def print_error(reason, filename=None, lineno=None):
+    '''
+    Prints an error with nice colors, indicating file and line number.
+    '''
+    print(filename + ": " + Colors.RED + "ERROR" +
+        Colors.ENDC + ": line %d (%s)" % (lineno, reason))
+
+
+def delete_rule(iptables, rule, filename, lineno):
+    '''
+    Removes an iptables rule
+    '''
+    cmd = iptables + " -D " + rule
+    ret = execute_cmd(cmd, filename, lineno)
+    if ret == 1:
+        reason = "cannot delete: " + iptables + " -I " + rule
+        print_error(reason, filename, lineno)
+        return -1
+
+    return 0
+
+
+def run_test(iptables, rule, rule_save, res, filename, lineno, netns):
+    '''
+    Executes an unit test. Returns the output of delete_rule().
+
+    Parameters:
+    :param  iptables: string with the iptables command to execute
+    :param rule: string with iptables arguments for the rule to test
+    :param rule_save: string to find the rule in the output of iptables -save
+    :param res: expected result of the rule. Valid values: "OK", "FAIL"
+    :param filename: name of the file tested (used for print_error purposes)
+    :param lineno: line number being tested (used for print_error purposes)
+    '''
+    ret = 0
+
+    cmd = iptables + " -A " + rule
+    if netns:
+            cmd = "ip netns exec ____iptables-container-test " + EXECUTEABLE + " " + cmd
+
+    ret = execute_cmd(cmd, filename, lineno)
+
+    #
+    # report failed test
+    #
+    if ret:
+        if res == "OK":
+            reason = "cannot load: " + cmd
+            print_error(reason, filename, lineno)
+            return -1
+        else:
+            # do not report this error
+            return 0
+    else:
+        if res == "FAIL":
+            reason = "should fail: " + cmd
+            print_error(reason, filename, lineno)
+            delete_rule(iptables, rule, filename, lineno)
+            return -1
+
+    matching = 0
+    splitted = iptables.split(" ")
+    if len(splitted) == 2:
+        if splitted[1] == '-4':
+            command = IPTABLES_SAVE
+        elif splitted[1] == '-6':
+            command = IP6TABLES_SAVE
+    elif len(splitted) == 1:
+        if splitted[0] == IPTABLES:
+            command = IPTABLES_SAVE
+        elif splitted[0] == IP6TABLES:
+            command = IP6TABLES_SAVE
+        elif splitted[0] == ARPTABLES:
+            command = ARPTABLES_SAVE
+        elif splitted[0] == EBTABLES:
+            command = EBTABLES_SAVE
+
+    command = EXECUTEABLE + " " + command
+
+    if netns:
+            command = "ip netns exec ____iptables-container-test " + command
+
+    args = splitted[1:]
+    proc = subprocess.Popen(command, shell=True,
+                            stdin=subprocess.PIPE,
+                            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    out, err = proc.communicate()
+
+    #
+    # check for segfaults
+    #
+    if proc.returncode == -11:
+        reason = "iptables-save segfaults: " + cmd
+        print_error(reason, filename, lineno)
+        delete_rule(iptables, rule, filename, lineno)
+        return -1
+
+    # find the rule
+    matching = out.find(rule_save.encode('utf-8'))
+    if matching < 0:
+        reason = "cannot find: " + iptables + " -I " + rule
+        print_error(reason, filename, lineno)
+        delete_rule(iptables, rule, filename, lineno)
+        return -1
+
+    # Test "ip netns del NETNS" path with rules in place
+    if netns:
+        return 0
+
+    return delete_rule(iptables, rule, filename, lineno)
+
+def execute_cmd(cmd, filename, lineno):
+    '''
+    Executes a command, checking for segfaults and returning the command exit
+    code.
+
+    :param cmd: string with the command to be executed
+    :param filename: name of the file tested (used for print_error purposes)
+    :param lineno: line number being tested (used for print_error purposes)
+    '''
+    global log_file
+    if cmd.startswith('iptables ') or cmd.startswith('ip6tables ') or cmd.startswith('ebtables ') or cmd.startswith('arptables '):
+        cmd = EXECUTEABLE + " " + cmd
+
+    print("command: {}".format(cmd), file=log_file)
+    ret = subprocess.call(cmd, shell=True, universal_newlines=True,
+        stderr=subprocess.STDOUT, stdout=log_file)
+    log_file.flush()
+
+    # generic check for segfaults
+    if ret  == -11:
+        reason = "command segfaults: " + cmd
+        print_error(reason, filename, lineno)
+    return ret
+
+
+def run_test_file(filename, netns):
+    '''
+    Runs a test file
+
+    :param filename: name of the file with the test rules
+    '''
+    #
+    # if this is not a test file, skip.
+    #
+    if not filename.endswith(".t"):
+        return 0, 0
+
+    if "libipt_" in filename:
+        iptables = IPTABLES
+    elif "libip6t_" in filename:
+        iptables = IP6TABLES
+    elif "libxt_"  in filename:
+        iptables = IPTABLES
+    elif "libarpt_" in filename:
+        # only supported with nf_tables backend
+        if EXECUTEABLE != "xtables-nft-multi":
+           return 0, 0
+        iptables = ARPTABLES
+    elif "libebt_" in filename:
+        # only supported with nf_tables backend
+        if EXECUTEABLE != "xtables-nft-multi":
+           return 0, 0
+        iptables = EBTABLES
+    else:
+        # default to iptables if not known prefix
+        iptables = IPTABLES
+
+    f = open(filename)
+
+    tests = 0
+    passed = 0
+    table = ""
+    total_test_passed = True
+
+    if netns:
+        execute_cmd("ip netns add ____iptables-container-test", filename, 0)
+
+    for lineno, line in enumerate(f):
+        if line[0] == "#" or len(line.strip()) == 0:
+            continue
+
+        if line[0] == ":":
+            chain_array = line.rstrip()[1:].split(",")
+            continue
+
+        # external non-iptables invocation, executed as is.
+        if line[0] == "@":
+            external_cmd = line.rstrip()[1:]
+            if netns:
+                external_cmd = "ip netns exec ____iptables-container-test " + external_cmd
+            execute_cmd(external_cmd, filename, lineno)
+            continue
+
+        # external iptables invocation, executed as is.
+        if line[0] == "%":
+            external_cmd = line.rstrip()[1:]
+            if netns:
+                external_cmd = "ip netns exec ____iptables-container-test " + EXECUTEABLE + " " + external_cmd
+            execute_cmd(external_cmd, filename, lineno)
+            continue
+
+        if line[0] == "*":
+            table = line.rstrip()[1:]
+            continue
+
+        if len(chain_array) == 0:
+            print("broken test, missing chain, leaving")
+            sys.exit()
+
+        test_passed = True
+        tests += 1
+
+        for chain in chain_array:
+            item = line.split(";")
+            if table == "":
+                rule = chain + " " + item[0]
+            else:
+                rule = chain + " -t " + table + " " + item[0]
+
+            if item[1] == "=":
+                rule_save = chain + " " + item[0]
+            else:
+                rule_save = chain + " " + item[1]
+
+            res = item[2].rstrip()
+            ret = run_test(iptables, rule, rule_save,
+                           res, filename, lineno + 1, netns)
+
+            if ret < 0:
+                test_passed = False
+                total_test_passed = False
+                break
+
+        if test_passed:
+            passed += 1
+
+    if netns:
+        execute_cmd("ip netns del ____iptables-container-test", filename, 0)
+    if total_test_passed:
+        print(filename + ": " + Colors.GREEN + "OK" + Colors.ENDC)
+
+    f.close()
+    return tests, passed
+
+
+def show_missing():
+    '''
+    Show the list of missing test files
+    '''
+    file_list = os.listdir(EXTENSIONS_PATH)
+    testfiles = [i for i in file_list if i.endswith('.t')]
+    libfiles = [i for i in file_list
+                if i.startswith('lib') and i.endswith('.c')]
+
+    def test_name(x):
+        return x[0:-2] + '.t'
+    missing = [test_name(i) for i in libfiles
+               if not test_name(i) in testfiles]
+
+    print('\n'.join(missing))
+
+
+#
+# main
+#
+def main():
+    parser = argparse.ArgumentParser(description='Run iptables tests')
+    parser.add_argument('filename', nargs='*',
+                        metavar='path/to/file.t',
+                        help='Run only this test')
+    parser.add_argument('-H', '--host', action='store_true',
+                        help='Run tests against installed binaries')
+    parser.add_argument('-l', '--legacy', action='store_true',
+                        help='Test iptables-legacy')
+    parser.add_argument('-m', '--missing', action='store_true',
+                        help='Check for missing tests')
+    parser.add_argument('-n', '--nftables', action='store_true',
+                        help='Test iptables-over-nftables')
+    parser.add_argument('-N', '--netns', action='store_true',
+                        help='Test netnamespace path')
+    args = parser.parse_args()
+
+    #
+    # show list of missing test files
+    #
+    if args.missing:
+        show_missing()
+        return
+
+    global EXECUTEABLE
+    EXECUTEABLE = "xtables-legacy-multi"
+    if args.nftables:
+        EXECUTEABLE = "xtables-nft-multi"
+
+    if os.getuid() != 0:
+        print("You need to be root to run this, sorry")
+        return
+
+    if not args.host:
+        os.putenv("XTABLES_LIBDIR", os.path.abspath(EXTENSIONS_PATH))
+        os.putenv("PATH", "%s/iptables:%s" % (os.path.abspath(os.path.curdir),
+                                              os.getenv("PATH")))
+
+    test_files = 0
+    tests = 0
+    passed = 0
+
+    # setup global var log file
+    global log_file
+    try:
+        log_file = open(LOGFILE, 'w')
+    except IOError:
+        print("Couldn't open log file %s" % LOGFILE)
+        return
+
+    if args.filename:
+        file_list = args.filename
+    else:
+        file_list = [os.path.join(EXTENSIONS_PATH, i)
+                     for i in os.listdir(EXTENSIONS_PATH)
+                     if i.endswith('.t')]
+        file_list.sort()
+
+    if not args.netns:
+        try:
+            import unshare
+            unshare.unshare(unshare.CLONE_NEWNET)
+        except:
+            print("Cannot run in own namespace, connectivity might break")
+
+    for filename in file_list:
+        file_tests, file_passed = run_test_file(filename, args.netns)
+        if file_tests:
+            tests += file_tests
+            passed += file_passed
+            test_files += 1
+
+    print("%d test files, %d unit tests, %d passed" % (test_files, tests, passed))
+
+
+if __name__ == '__main__':
+    main()
diff --git a/iptables/.gitignore b/iptables/.gitignore
index 5a08937..cd7d87b 100644
--- a/iptables/.gitignore
+++ b/iptables/.gitignore
@@ -1,14 +1,25 @@
 /ip6tables
-/ip6tables.8
 /ip6tables-save
 /ip6tables-restore
 /ip6tables-static
+/ip6tables-translate.8
+/ip6tables-restore-translate.8
 /iptables
 /iptables.8
+/iptables-extensions.8
+/iptables-extensions.8.tmpl
 /iptables-save
+/iptables-save.8
 /iptables-restore
+/iptables-restore.8
 /iptables-static
+/iptables-translate.8
+/iptables-restore-translate.8
 /iptables-xml
+/iptables-xml.1
 /xtables-multi
+/xtables-legacy-multi
+/xtables-nft-multi
+/xtables-monitor.8
 
 /xtables.pc
diff --git a/iptables/Android.bp b/iptables/Android.bp
new file mode 100644
index 0000000..2276702
--- /dev/null
+++ b/iptables/Android.bp
@@ -0,0 +1,83 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "external_iptables_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Artistic
+    //   SPDX-license-identifier-Artistic-2.0
+    //   SPDX-license-identifier-GPL
+    //   SPDX-license-identifier-GPL-2.0
+    //   SPDX-license-identifier-LGPL
+    default_applicable_licenses: ["external_iptables_license"],
+}
+
+cc_defaults {
+    name: "iptables_cmd_defaults",
+    defaults: ["iptables_defaults"],
+
+    cflags: [
+        "-Wno-missing-field-initializers",
+        "-Wno-parentheses-equality",
+
+        "-DNO_SHARED_LIBS=1",
+        "-DALL_INCLUSIVE",
+        "-DXTABLES_INTERNAL",
+    ],
+
+    header_libs: ["iptables_config_header"],
+
+    required: ["xtables.lock"],
+
+    srcs: [
+        "xtables-legacy-multi.c",
+        "iptables-xml.c",
+        "xshared.c",
+    ],
+
+    static_libs: [
+        "libext",
+        "libxtables",
+    ],
+}
+
+//----------------------------------------------------------------
+// The iptables lock file
+
+prebuilt_etc {
+    name: "xtables.lock",
+    src: "xtables.lock",
+}
+
+//----------------------------------------------------------------
+// iptables
+
+cc_binary {
+    name: "iptables",
+    defaults: ["iptables_cmd_defaults"],
+
+    srcs: [
+        "iptables-save.c",
+        "iptables-restore.c",
+        "iptables-standalone.c",
+        "iptables.c",
+        "ip6tables-standalone.c",
+        "ip6tables.c",
+    ],
+
+    static_libs: [
+        "libext4",
+        "libext6",
+        "libip4tc",
+        "libip6tc",
+    ],
+
+    symlinks: [
+        "iptables-save",
+        "iptables-restore",
+        "ip6tables",
+        "ip6tables-save",
+        "ip6tables-restore",
+    ],
+}
+
+//----------------------------------------------------------------
diff --git a/iptables/Android.mk b/iptables/Android.mk
index b64eb73..cf61f03 100644
--- a/iptables/Android.mk
+++ b/iptables/Android.mk
@@ -1,196 +1,79 @@
 LOCAL_PATH:= $(call my-dir)
-My_intermediaries := $(call local-intermediates-dir)
+
+commonFlags:= \
+	-Wno-missing-field-initializers \
+	-Wno-sign-compare \
+	-Wno-pointer-arith \
+	-Wno-unused-parameter \
+	-Wno-parentheses-equality \
+	-Werror
+
 #----------------------------------------------------------------
-# libxtables
+# The iptables lock file
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := xtables.lock
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT)/etc
+LOCAL_SRC_FILES := $(LOCAL_MODULE)
+
+include $(BUILD_PREBUILT)
+
+#----------------------------------------------------------------
+# iptables
 
 include $(CLEAR_VARS)
 
 LOCAL_C_INCLUDES:= \
 	$(LOCAL_PATH)/../include/ \
-	$(KERNEL_HEADERS) \
-
-LOCAL_CFLAGS:=-DNO_SHARED_LIBS=1
-LOCAL_CFLAGS+=-DXTABLES_INTERNAL
-LOCAL_CFLAGS+=-DXTABLES_LIBDIR=\"xtables_libdir_not_used\"
-# Accommodate arm-eabi-4.4.3 tools that don't set __ANDROID__
-LOCAL_CFLAGS+=-D__ANDROID__
-
-LOCAL_SRC_FILES:= \
-	xtables.c xtoptions.c
-
-LOCAL_MODULE:=libxtables
-
-include $(BUILD_SHARED_LIBRARY)
-
-#----------------------------------------------------------------
-# iptables
-
-
-include $(CLEAR_VARS)
-
-LOCAL_C_INCLUDES:= \
-	$(LOCAL_PATH)/../include/
+	$(LOCAL_PATH)/../
 
 LOCAL_CFLAGS:=-DNO_SHARED_LIBS=1
 LOCAL_CFLAGS+=-DALL_INCLUSIVE
 LOCAL_CFLAGS+=-DXTABLES_INTERNAL
+LOCAL_CFLAGS+=-D_LARGEFILE_SOURCE=1 -D_LARGE_FILES -D_FILE_OFFSET_BITS=64 -D_REENTRANT -DENABLE_IPV4 -DENABLE_IPV6
 # Accommodate arm-eabi-4.4.3 tools that don't set __ANDROID__
 LOCAL_CFLAGS+=-D__ANDROID__
+LOCAL_CFLAGS += $(commonFlags)
+
+LOCAL_REQUIRED_MODULES := xtables.lock
 
 LOCAL_SRC_FILES:= \
-	iptables-standalone.c iptables.c xshared.c
+	xtables-legacy-multi.c iptables-xml.c xshared.c \
+	iptables-save.c iptables-restore.c \
+	iptables-standalone.c iptables.c \
+	ip6tables-standalone.c ip6tables.c
 
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE:=iptables
 
-LOCAL_SHARED_LIBRARIES := \
+LOCAL_STATIC_LIBRARIES := \
 	libext \
 	libext4 \
-	libip4tc \
-	libxtables
-
-include $(BUILD_EXECUTABLE)
-
-#----------------------------------------------------------------
-# iptables-restore
-
-
-include $(CLEAR_VARS)
-
-LOCAL_C_INCLUDES:= \
-	$(LOCAL_PATH)/../include/
-
-LOCAL_CFLAGS:=-DNO_SHARED_LIBS=1
-LOCAL_CFLAGS+=-DALL_INCLUSIVE
-LOCAL_CFLAGS+=-DXTABLES_INTERNAL
-# Accommodate arm-eabi-4.4.3 tools that don't set __ANDROID__
-LOCAL_CFLAGS+=-D__ANDROID__
-
-LOCAL_SRC_FILES:= \
-	iptables-restore.c iptables.c xshared.c
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE:=iptables-restore
-
-LOCAL_SHARED_LIBRARIES := \
-	libext \
-	libext4 \
-	libip4tc \
-	libxtables
-
-include $(BUILD_EXECUTABLE)
-
-#----------------------------------------------------------------
-# iptables-save
-
-
-include $(CLEAR_VARS)
-
-LOCAL_C_INCLUDES:= \
-	$(LOCAL_PATH)/../include/
-
-LOCAL_CFLAGS:=-DNO_SHARED_LIBS=1
-LOCAL_CFLAGS+=-DALL_INCLUSIVE
-LOCAL_CFLAGS+=-DXTABLES_INTERNAL
-# Accommodate arm-eabi-4.4.3 tools that don't set __ANDROID__
-LOCAL_CFLAGS+=-D__ANDROID__
-
-LOCAL_SRC_FILES:= \
-	iptables-save.c iptables.c xshared.c
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE:=iptables-save
-
-LOCAL_SHARED_LIBRARIES := \
-	libext \
-	libext4 \
-	libip4tc \
-	libxtables
-
-include $(BUILD_EXECUTABLE)
-
-#----------------------------------------------------------------
-# ip6tables
-include $(CLEAR_VARS)
-
-LOCAL_C_INCLUDES:= \
-	$(LOCAL_PATH)/../include/
-
-LOCAL_CFLAGS:=-DNO_SHARED_LIBS=1
-LOCAL_CFLAGS+=-DALL_INCLUSIVE
-LOCAL_CFLAGS+=-DXTABLES_INTERNAL
-# Accommodate arm-eabi-4.4.3 tools that don't set __ANDROID__
-LOCAL_CFLAGS+=-D__ANDROID__
-
-LOCAL_SRC_FILES:= \
-	ip6tables-standalone.c ip6tables.c xshared.c
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE:=ip6tables
-
-LOCAL_SHARED_LIBRARIES := \
-	libext \
 	libext6 \
+	libip4tc \
 	libip6tc \
 	libxtables
 
 include $(BUILD_EXECUTABLE)
 
+IPTABLES_SUBCOMMANDS := \
+  iptables-restore \
+  iptables-save \
+  ip6tables \
+  ip6tables-restore \
+  ip6tables-save
 
-#----------------------------------------------------------------
-# ip6tables-restore
+SYMLINKS := $(addprefix $(TARGET_OUT)/bin/,$(IPTABLES_SUBCOMMANDS))
+$(SYMLINKS): $(LOCAL_INSTALLED_MODULE) $(LOCAL_PATH)/Android.mk
+	@echo "Symlink: $@ -> iptables"
+	@mkdir -p $(dir $@)
+	@rm -rf $@
+	$(hide) ln -sf iptables $@
+
 include $(CLEAR_VARS)
-
-LOCAL_C_INCLUDES:= \
-	$(LOCAL_PATH)/../include/
-
-LOCAL_CFLAGS:=-DNO_SHARED_LIBS=1
-LOCAL_CFLAGS+=-DALL_INCLUSIVE
-LOCAL_CFLAGS+=-DXTABLES_INTERNAL
-# Accommodate arm-eabi-4.4.3 tools that don't set __ANDROID__
-LOCAL_CFLAGS+=-D__ANDROID__
-
-LOCAL_SRC_FILES:= \
-	ip6tables-restore.c ip6tables.c xshared.c
-
+LOCAL_MODULE := iptables_symlinks
 LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE:=ip6tables-restore
-
-LOCAL_SHARED_LIBRARIES := \
-	libext \
-	libext6 \
-	libip6tc \
-	libxtables
-
-include $(BUILD_EXECUTABLE)
-
-
-#----------------------------------------------------------------
-# ip6tables-save
-include $(CLEAR_VARS)
-
-LOCAL_C_INCLUDES:= \
-	$(LOCAL_PATH)/../include/
-
-LOCAL_CFLAGS:=-DNO_SHARED_LIBS=1
-LOCAL_CFLAGS+=-DALL_INCLUSIVE
-LOCAL_CFLAGS+=-DXTABLES_INTERNAL
-# Accommodate arm-eabi-4.4.3 tools that don't set __ANDROID__
-LOCAL_CFLAGS+=-D__ANDROID__
-
-LOCAL_SRC_FILES:= \
-	ip6tables-save.c ip6tables.c xshared.c
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE:=ip6tables-save
-
-LOCAL_SHARED_LIBRARIES := \
-	libext \
-	libext6 \
-	libip6tc \
-	libxtables
-
-include $(BUILD_EXECUTABLE)
-
-
-#----------------------------------------------------------------
+LOCAL_REQUIRED_MODULES := iptables
+include $(BUILD_PHONY_PACKAGE)
+$(LOCAL_BUILT_MODULE): $(SYMLINKS)
diff --git a/iptables/Makefile.am b/iptables/Makefile.am
index 13cca9c..f789521 100644
--- a/iptables/Makefile.am
+++ b/iptables/Makefile.am
@@ -1,60 +1,108 @@
 # -*- 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 -I${top_srcdir} ${kinclude_CPPFLAGS} ${libmnl_CFLAGS} ${libnftnl_CFLAGS} ${libnetfilter_conntrack_CFLAGS}
 
-lib_LTLIBRARIES       = libxtables.la
-libxtables_la_SOURCES = xtables.c xtoptions.c
-libxtables_la_LDFLAGS = -version-info ${libxtables_vcurrent}:0:${libxtables_vage}
-if ENABLE_SHARED
-libxtables_la_CFLAGS  = ${AM_CFLAGS}
-libxtables_la_LIBADD  = -ldl
-else
-libxtables_la_CFLAGS  = ${AM_CFLAGS} -DNO_SHARED_LIBS=1
-libxtables_la_LIBADD  =
-endif
+BUILT_SOURCES =
 
-xtables_multi_SOURCES  = xtables-multi.c iptables-xml.c
-xtables_multi_CFLAGS   = ${AM_CFLAGS} -DIPTABLES_MULTI
-xtables_multi_LDFLAGS  = -rdynamic
-xtables_multi_LDADD    = ../extensions/libext.a
+xtables_legacy_multi_SOURCES  = xtables-legacy-multi.c iptables-xml.c
+xtables_legacy_multi_CFLAGS   = ${AM_CFLAGS}
+xtables_legacy_multi_LDADD    = ../extensions/libext.a
 if ENABLE_STATIC
-xtables_multi_CFLAGS  += -DALL_INCLUSIVE
+xtables_legacy_multi_CFLAGS  += -DALL_INCLUSIVE
 endif
 if ENABLE_IPV4
-xtables_multi_SOURCES += iptables-save.c iptables-restore.c \
-                         iptables-standalone.c iptables.c
-xtables_multi_CFLAGS  += -DENABLE_IPV4
-xtables_multi_LDADD   += ../libiptc/libip4tc.la ../extensions/libext4.a
+xtables_legacy_multi_SOURCES += iptables-standalone.c iptables.c
+xtables_legacy_multi_CFLAGS  += -DENABLE_IPV4
+xtables_legacy_multi_LDADD   += ../libiptc/libip4tc.la ../extensions/libext4.a
 endif
 if ENABLE_IPV6
-xtables_multi_SOURCES += ip6tables-save.c ip6tables-restore.c \
-                          ip6tables-standalone.c ip6tables.c
-xtables_multi_CFLAGS  += -DENABLE_IPV6
-xtables_multi_LDADD   += ../libiptc/libip6tc.la ../extensions/libext6.a
+xtables_legacy_multi_SOURCES += ip6tables-standalone.c ip6tables.c
+xtables_legacy_multi_CFLAGS  += -DENABLE_IPV6
+xtables_legacy_multi_LDADD   += ../libiptc/libip6tc.la ../extensions/libext6.a
 endif
-xtables_multi_SOURCES += xshared.c
-xtables_multi_LDADD   += libxtables.la -lm
+xtables_legacy_multi_SOURCES += xshared.c iptables-restore.c iptables-save.c
+xtables_legacy_multi_LDADD   += ../libxtables/libxtables.la -lm
 
-sbin_PROGRAMS    = xtables-multi
+# iptables using nf_tables api
+if ENABLE_NFTABLES
+xtables_nft_multi_SOURCES  = xtables-nft-multi.c iptables-xml.c
+xtables_nft_multi_CFLAGS   = ${AM_CFLAGS}
+xtables_nft_multi_LDADD    = ../extensions/libext.a ../extensions/libext_ebt.a
+if ENABLE_STATIC
+xtables_nft_multi_CFLAGS  += -DALL_INCLUSIVE
+endif
+xtables_nft_multi_CFLAGS  += -DENABLE_NFTABLES -DENABLE_IPV4 -DENABLE_IPV6
+xtables_nft_multi_SOURCES += xtables-save.c xtables-restore.c \
+				xtables-standalone.c xtables.c nft.c \
+				nft-shared.c nft-ipv4.c nft-ipv6.c nft-arp.c \
+				xtables-monitor.c nft-cache.c \
+				xtables-arp-standalone.c xtables-arp.c \
+				nft-bridge.c nft-cmd.c nft-chain.c \
+				xtables-eb-standalone.c xtables-eb.c \
+				xtables-eb-translate.c \
+				xtables-translate.c
+xtables_nft_multi_LDADD   += ${libmnl_LIBS} ${libnftnl_LIBS} ${libnetfilter_conntrack_LIBS} ../extensions/libext4.a ../extensions/libext6.a ../extensions/libext_ebt.a ../extensions/libext_arpt.a
+xtables_nft_multi_SOURCES += xshared.c
+xtables_nft_multi_LDADD   += ../libxtables/libxtables.la -lm
+endif
+
+sbin_PROGRAMS    = xtables-legacy-multi
+if ENABLE_NFTABLES
+sbin_PROGRAMS	+= xtables-nft-multi
+endif
 man_MANS         = iptables.8 iptables-restore.8 iptables-save.8 \
                    iptables-xml.1 ip6tables.8 ip6tables-restore.8 \
-                   ip6tables-save.8
-CLEANFILES       = iptables.8 ip6tables.8
+                   ip6tables-save.8 iptables-extensions.8 \
+                   iptables-apply.8 ip6tables-apply.8
+
+sbin_SCRIPTS     = iptables-apply
+
+if ENABLE_NFTABLES
+man_MANS	+= xtables-nft.8 xtables-translate.8 xtables-legacy.8 \
+                   iptables-translate.8 ip6tables-translate.8 \
+		   iptables-restore-translate.8 ip6tables-restore-translate.8 \
+                   xtables-monitor.8 \
+                   arptables-nft.8 arptables-nft-restore.8 arptables-nft-save.8 \
+                   ebtables-nft.8
+endif
+CLEANFILES       = iptables.8 xtables-monitor.8 \
+		   iptables-xml.1 iptables-apply.8 \
+		   iptables-extensions.8 iptables-extensions.8.tmpl \
+		   iptables-restore.8 iptables-save.8 \
+		   iptables-restore-translate.8 ip6tables-restore-translate.8 \
+		   iptables-translate.8 ip6tables-translate.8
 
 vx_bin_links   = iptables-xml
 if ENABLE_IPV4
-v4_sbin_links  = iptables iptables-restore iptables-save
+v4_sbin_links  = iptables-legacy iptables-legacy-restore iptables-legacy-save \
+		 iptables iptables-restore iptables-save
 endif
 if ENABLE_IPV6
-v6_sbin_links  = ip6tables ip6tables-restore ip6tables-save
+v6_sbin_links  = ip6tables-legacy ip6tables-legacy-restore ip6tables-legacy-save \
+		 ip6tables ip6tables-restore ip6tables-save
+endif
+if ENABLE_NFTABLES
+x_sbin_links  = iptables-nft iptables-nft-restore iptables-nft-save \
+		ip6tables-nft ip6tables-nft-restore ip6tables-nft-save \
+		iptables-translate ip6tables-translate \
+		iptables-restore-translate ip6tables-restore-translate \
+		arptables-nft arptables \
+		arptables-nft-restore arptables-restore \
+		arptables-nft-save arptables-save \
+		ebtables-nft ebtables \
+		ebtables-nft-restore ebtables-restore \
+		ebtables-nft-save ebtables-save \
+		xtables-monitor
 endif
 
-iptables.8: ${srcdir}/iptables.8.in ../extensions/matches4.man ../extensions/targets4.man
-	${AM_VERBOSE_GEN} sed -e 's/@PACKAGE_AND_VERSION@/${PACKAGE} ${PACKAGE_VERSION}/g' -e '/@MATCH@/ r extensions/matches4.man' -e '/@TARGET@/ r extensions/targets4.man' $< >$@;
+iptables-extensions.8: iptables-extensions.8.tmpl ../extensions/matches.man ../extensions/targets.man
+	${AM_VERBOSE_GEN} sed \
+		-e '/@MATCH@/ r ../extensions/matches.man' \
+		-e '/@TARGET@/ r ../extensions/targets.man' $< >$@;
 
-ip6tables.8: ${srcdir}/ip6tables.8.in ../extensions/matches6.man ../extensions/targets6.man
-	${AM_VERBOSE_GEN} sed -e 's/@PACKAGE_AND_VERSION@/${PACKAGE} ${PACKAGE_VERSION}/g' -e '/@MATCH@/ r extensions/matches6.man' -e '/@TARGET@/ r extensions/targets6.man' $< >$@;
+iptables-translate.8 ip6tables-translate.8 iptables-restore-translate.8 ip6tables-restore-translate.8:
+	${AM_VERBOSE_GEN} echo '.so man8/xtables-translate.8' >$@
 
 pkgconfig_DATA = xtables.pc
 
@@ -62,6 +110,31 @@
 install-exec-hook:
 	-if test -z "${DESTDIR}"; then /sbin/ldconfig; fi;
 	${INSTALL} -dm0755 "${DESTDIR}${bindir}";
-	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 ${vx_bin_links}; do ${LN_S} -f "${sbindir}/xtables-legacy-multi" "${DESTDIR}${bindir}/$$i"; done;
+	for i in ${v4_sbin_links}; do ${LN_S} -f xtables-legacy-multi "${DESTDIR}${sbindir}/$$i"; done;
+	for i in ${v6_sbin_links}; do ${LN_S} -f xtables-legacy-multi "${DESTDIR}${sbindir}/$$i"; done;
+	for i in ${x_sbin_links}; do ${LN_S} -f xtables-nft-multi "${DESTDIR}${sbindir}/$$i"; done;
+	${LN_S} -f iptables-apply "${DESTDIR}${sbindir}/ip6tables-apply"
+
+uninstall-hook:
+	dir=${DESTDIR}${bindir}; { \
+		test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; \
+	} || { \
+		test -z "${vx_bin_links}" || ( \
+			cd "$$dir" && rm -f ${vx_bin_links} \
+		) \
+	}
+	dir=${DESTDIR}${sbindir}; { \
+		test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; \
+	} || { \
+		test -z "${v4_sbin_links}" || ( \
+			cd "$$dir" && rm -f ${v4_sbin_links} \
+		); \
+		test -z "${v6_sbin_links}" || ( \
+			cd "$$dir" && rm -f ${v6_sbin_links} \
+		); \
+		test -z "${x_sbin_links}" || ( \
+			cd "$$dir" && rm -f ${x_sbin_links} \
+		); \
+		( cd "$$dir" && rm -f ip6tables-apply ); \
+	}
diff --git a/iptables/arptables-nft-restore.8 b/iptables/arptables-nft-restore.8
new file mode 100644
index 0000000..09d9082
--- /dev/null
+++ b/iptables/arptables-nft-restore.8
@@ -0,0 +1,39 @@
+.TH ARPTABLES-RESTORE 8 "March 2019" "" ""
+.\"
+.\" Man page written by Jesper Dangaard Brouer <brouer@redhat.com> based on a
+.\" Man page written by Harald Welte <laforge@gnumonks.org>
+.\" It is based on the iptables-restore man page.
+.\"
+.\"	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.
+.\"
+.\"
+.SH NAME
+arptables-restore \- Restore ARP Tables (nft-based)
+.SH SYNOPSIS
+\fBarptables\-restore
+.SH DESCRIPTION
+.PP
+.B arptables-restore
+is used to restore ARP Tables from data specified on STDIN or
+via a file as first argument.
+Use I/O redirection provided by your shell to read from a file
+.TP
+.B arptables-restore
+flushes (deletes) all previous contents of the respective ARP Table.
+.SH AUTHOR
+Jesper Dangaard Brouer <brouer@redhat.com>
+.SH SEE ALSO
+\fBarptables\-save\fP(8), \fBarptables\fP(8)
+.PP
diff --git a/iptables/arptables-nft-save.8 b/iptables/arptables-nft-save.8
new file mode 100644
index 0000000..905e598
--- /dev/null
+++ b/iptables/arptables-nft-save.8
@@ -0,0 +1,47 @@
+.TH ARPTABLES-SAVE 8 "March 2019" "" ""
+.\"
+.\" Man page written by Jesper Dangaard Brouer <brouer@redhat.com> based on a
+.\" Man page written by Harald Welte <laforge@gnumonks.org>
+.\" It is based on the iptables-save man page.
+.\"
+.\"	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.
+.\"
+.\"
+.SH NAME
+arptables-save \- dump arptables rules to stdout (nft-based)
+.SH SYNOPSIS
+\fBarptables\-save\fP [\fB\-M\fP \fImodprobe\fP] [\fB\-c\fP]
+.P
+\fBarptables\-save\fP [\fB\-V\fP]
+.SH DESCRIPTION
+.PP
+.B arptables-save
+is used to dump the contents of an ARP Table in easily parseable format
+to STDOUT. Use I/O-redirection provided by your shell to write to a file.
+.TP
+\fB\-M\fR, \fB\-\-modprobe\fR \fImodprobe_program\fP
+Specify the path to the modprobe program. By default, arptables-save will
+inspect /proc/sys/kernel/modprobe to determine the executable's path.
+.TP
+\fB\-c\fR, \fB\-\-counters\fR
+Include the current values of all packet and byte counters in the output.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Print version information and exit.
+.SH AUTHOR
+Jesper Dangaard Brouer <brouer@redhat.com>
+.SH SEE ALSO
+\fBarptables\-restore\fP(8), \fBarptables\fP(8)
+.PP
diff --git a/iptables/arptables-nft.8 b/iptables/arptables-nft.8
new file mode 100644
index 0000000..ea31e08
--- /dev/null
+++ b/iptables/arptables-nft.8
@@ -0,0 +1,348 @@
+.TH ARPTABLES 8  "March 2019"
+.\"
+.\" Man page originally written by Jochen Friedrich <jochen@scram.de>,
+.\" maintained by Bart De Schuymer.
+.\" It is based on the iptables man page.
+.\"
+.\" Iptables page by Herve Eychenne March 2000.
+.\"
+.\"     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.
+.\"
+.\"
+.SH NAME
+arptables \- ARP table administration (nft-based)
+.SH SYNOPSIS
+.BR "arptables " [ "-t table" ] " -" [ AD ] " chain rule-specification " [ options ]
+.br
+.BR "arptables " [ "-t table" ] " -" [ RI ] " chain rulenum rule-specification " [ options ]
+.br
+.BR "arptables " [ "-t table" ] " -D chain rulenum " [ options ]
+.br
+.BR "arptables " [ "-t table" ] " -" [ "LFZ" ] " " [ chain ] " " [ options ]
+.br
+.BR "arptables " [ "-t table" ] " -" [ "NX" ] " chain"
+.br
+.BR "arptables " [ "-t table" ] " -E old-chain-name new-chain-name"
+.br
+.BR "arptables " [ "-t table" ] " -P chain target " [ options ]
+
+.SH DESCRIPTION
+.B arptables
+is a user space tool, it is used to set up and maintain the
+tables of ARP rules in the Linux kernel. These rules inspect
+the ARP frames which they see.
+.B arptables
+is analogous to the
+.B iptables
+user space tool, but
+.B arptables
+is less complicated.
+
+.SS CHAINS
+The kernel table is used to divide functionality into
+different sets of rules. Each set of rules is called a chain.
+Each chain is an ordered list of rules that can match ARP frames. If a
+rule matches an ARP frame, then a processing specification tells
+what to do with that matching frame. The processing specification is
+called a 'target'. However, if the frame does not match the current
+rule in the chain, then the next rule in the chain is examined and so forth.
+The user can create new (user-defined) chains which can be used as the 'target' of a rule.
+
+.SS TARGETS
+A firewall rule specifies criteria for an ARP frame and a frame
+processing specification called a target.  When a frame matches a rule,
+then the next action performed by the kernel is specified by the target.
+The target can be one of these values:
+.IR ACCEPT ,
+.IR DROP ,
+.IR CONTINUE ,
+.IR RETURN ,
+an 'extension' (see below) or a user-defined chain.
+.PP
+.I ACCEPT
+means to let the frame through.
+.I DROP
+means the frame has to be dropped.
+.I CONTINUE
+means the next rule has to be checked. This can be handy to know how many
+frames pass a certain point in the chain or to log those frames.
+.I RETURN
+means stop traversing this chain and resume at the next rule in the
+previous (calling) chain.
+For the extension targets please see the
+.B "TARGET EXTENSIONS"
+section of this man page.
+.SS TABLES
+There is only one ARP table in the Linux
+kernel.  The table is
+.BR filter.
+You can drop the '-t filter' argument to the arptables command.
+The -t argument must be the
+first argument on the arptables command line, if used.
+.TP
+.B "-t, --table"
+.br
+.BR filter ,
+is the only table and contains two built-in chains:
+.B INPUT 
+(for frames destined for the host) and
+.B OUTPUT 
+(for locally-generated frames).
+.br
+.br
+.SH ARPTABLES COMMAND LINE ARGUMENTS
+After the initial arptables command line argument, the remaining
+arguments can be divided into several different groups.  These groups
+are commands, miscellaneous commands, rule-specifications, match-extensions,
+and watcher-extensions.
+.SS COMMANDS
+The arptables command arguments specify the actions to perform on the table
+defined with the -t argument.  If you do not use the -t argument to name
+a table, the commands apply to the default filter table.
+With the exception of the
+.B "-Z"
+command, only one command may be used on the command line at a time.
+.TP
+.B "-A, --append"
+Append a rule to the end of the selected chain.
+.TP
+.B "-D, --delete"
+Delete the specified rule from the selected chain. There are two ways to
+use this command. The first is by specifying an interval of rule numbers
+to delete, syntax: start_nr[:end_nr]. Using negative numbers is allowed, for more
+details about using negative numbers, see the -I command. The second usage is by
+specifying the complete rule as it would have been specified when it was added.
+.TP
+.B "-I, --insert"
+Insert the specified rule into the selected chain at the specified rule number.
+If the current number of rules equals N, then the specified number can be
+between -N and N+1. For a positive number i, it holds that i and i-N-1 specify the
+same place in the chain where the rule should be inserted. The number 0 specifies
+the place past the last rule in the chain and using this number is therefore
+equivalent with using the -A command.
+.TP
+.B "-R, --replace"
+Replaces the specified rule into the selected chain at the specified rule number.
+If the current number of rules equals N, then the specified number can be
+between 1 and N. i specifies the place in the chain where the rule should be replaced.
+.TP
+.B "-P, --policy"
+Set the policy for the chain to the given target. The policy can be
+.BR ACCEPT ", " DROP " or " RETURN .
+.TP
+.B "-F, --flush"
+Flush the selected chain. If no chain is selected, then every chain will be
+flushed. Flushing the chain does not change the policy of the
+chain, however.
+.TP
+.B "-Z, --zero"
+Set the counters of the selected chain to zero. If no chain is selected, all the counters
+are set to zero. The
+.B "-Z"
+command can be used in conjunction with the 
+.B "-L"
+command.
+When both the
+.B "-Z"
+and
+.B "-L"
+commands are used together in this way, the rule counters are printed on the screen
+before they are set to zero.
+.TP
+.B "-L, --list"
+List all rules in the selected chain. If no chain is selected, all chains
+are listed.
+.TP
+.B "-N, --new-chain"
+Create a new user-defined chain with the given name. The number of
+user-defined chains is unlimited. A user-defined chain name has maximum
+length of 31 characters.
+.TP
+.B "-X, --delete-chain"
+Delete the specified user-defined chain. There must be no remaining references
+to the specified chain, otherwise
+.B arptables
+will refuse to delete it. If no chain is specified, all user-defined
+chains that aren't referenced will be removed.
+.TP
+.B "-E, --rename-chain"
+Rename the specified chain to a new name.  Besides renaming a user-defined
+chain, you may rename a standard chain name to a name that suits your
+taste. For example, if you like PREBRIDGING more than PREROUTING,
+then you can use the -E command to rename the PREROUTING chain. If you do
+rename one of the standard
+.B arptables
+chain names, please be sure to mention
+this fact should you post a question on the
+.B arptables
+mailing lists.
+It would be wise to use the standard name in your post. Renaming a standard
+.B arptables
+chain in this fashion has no effect on the structure or function
+of the
+.B arptables
+kernel table.
+
+.SS MISCELLANOUS COMMANDS
+.TP
+.B "-V, --version"
+Show the version of the arptables userspace program.
+.TP
+.B "-h, --help"
+Give a brief description of the command syntax.
+.TP
+.BR "-j, --jump " "\fItarget\fP"
+The target of the rule. This is one of the following values:
+.BR ACCEPT ,
+.BR DROP ,
+.BR CONTINUE ,
+.BR RETURN ,
+a target extension (see
+.BR "TARGET EXTENSIONS" ")"
+or a user-defined chain name.
+.TP
+.BI "-c, --set-counters " "PKTS BYTES"
+This enables the administrator to initialize the packet and byte
+counters of a rule (during
+.B INSERT,
+.B APPEND,
+.B REPLACE
+operations).
+
+.SS RULE-SPECIFICATIONS
+The following command line arguments make up a rule specification (as used 
+in the add and delete commands). A "!" option before the specification 
+inverts the test for that specification. Apart from these standard rule 
+specifications there are some other command line arguments of interest.
+.TP
+.BR "-s, --source-ip " "[!] \fIaddress\fP[/\fImask]\fP"
+The Source IP specification.
+.TP 
+.BR "-d, --destination-ip " "[!] \fIaddress\fP[/\fImask]\fP"
+The Destination IP specification.
+.TP 
+.BR "--source-mac " "[!] \fIaddress\fP[/\fImask\fP]"
+The source mac address. Both mask and address are written as 6 hexadecimal
+numbers separated by colons.
+.TP
+.BR "--destination-mac " "[!] \fIaddress\fP[/\fImask\fP]"
+The destination mac address. Both mask and address are written as 6 hexadecimal
+numbers separated by colons.
+.TP 
+.BR "-i, --in-interface " "[!] \fIname\fP"
+The interface via which a frame is received (for the
+.B INPUT
+chain). The flag
+.B --in-if
+is an alias for this option.
+.TP
+.BR "-o, --out-interface " "[!] \fIname\fP"
+The interface via which a frame is going to be sent (for the
+.B OUTPUT
+chain). The flag
+.B --out-if
+is an alias for this option.
+.TP
+.BR "-l, --h-length " "\fIlength\fP[/\fImask\fP]"
+The hardware length (nr of bytes)
+.TP
+.BR "--opcode " "\fIcode\fP[/\fImask\fP]
+The operation code (2 bytes). Available values are:
+.BR 1 = Request
+.BR 2 = Reply
+.BR 3 = Request_Reverse
+.BR 4 = Reply_Reverse
+.BR 5 = DRARP_Request
+.BR 6 = DRARP_Reply
+.BR 7 = DRARP_Error
+.BR 8 = InARP_Request
+.BR 9 = ARP_NAK .
+.TP
+.BR "--h-type " "\fItype\fP[/\fImask\fP]"
+The hardware type (2 bytes, hexadecimal). Available values are:
+.BR 1 = Ethernet .
+.TP
+.BR "--proto-type " "\fItype\fP[/\fImask\fP]"
+The protocol type (2 bytes). Available values are:
+.BR 0x800 = IPv4 .
+
+.SS TARGET-EXTENSIONS
+.B arptables
+extensions are precompiled into the userspace tool. So there is no need
+to explicitly load them with a -m option like in
+.BR iptables .
+However, these
+extensions deal with functionality supported by supplemental kernel modules.
+.SS mangle
+.TP
+.BR "--mangle-ip-s IP address"
+Mangles Source IP Address to given value.
+.TP
+.BR "--mangle-ip-d IP address"
+Mangles Destination IP Address to given value.
+.TP
+.BR "--mangle-mac-s MAC address"
+Mangles Source MAC Address to given value.
+.TP
+.BR "--mangle-mac-d MAC address"
+Mangles Destination MAC Address to given value.
+.TP
+.BR "--mangle-target target "
+Target of ARP mangle operation
+.BR "" ( DROP ", " CONTINUE " or " ACCEPT " -- default is " ACCEPT ).
+.SS CLASSIFY
+This  module  allows you to set the skb->priority value (and thus clas-
+sify the packet into a specific CBQ class).
+
+.TP
+.BR "--set-class major:minor"
+
+Set the major and minor  class  value.  The  values  are  always
+interpreted as hexadecimal even if no 0x prefix is given.
+
+.SS MARK
+This  module  allows you to set the skb->mark value (and thus classify
+the packet by the mark in u32)
+
+.TP
+.BR "--set-mark mark"
+Set the mark value. The  values  are  always
+interpreted as hexadecimal even if no 0x prefix is given
+
+.TP
+.BR "--and-mark mark"
+Binary AND the mark with bits.
+
+.TP
+.BR "--or-mark mark"
+Binary OR the mark with bits.
+
+.SH NOTES
+In this nft-based version of
+.BR arptables ,
+support for
+.B FORWARD
+chain has not been implemented. Since ARP packets are "forwarded" only by Linux
+bridges, the same may be achieved using
+.B FORWARD
+chain in
+.BR ebtables .
+
+.SH MAILINGLISTS
+.BR "" "See " http://netfilter.org/mailinglists.html
+.SH SEE ALSO
+.BR xtables-nft "(8), " iptables "(8), " ebtables "(8), " ip (8)
+.PP
+.BR "" "See " https://wiki.nftables.org
diff --git a/iptables/ebtables-nft.8 b/iptables/ebtables-nft.8
new file mode 100644
index 0000000..1fa5ad9
--- /dev/null
+++ b/iptables/ebtables-nft.8
@@ -0,0 +1,1116 @@
+.TH EBTABLES 8  "December 2011"
+.\"
+.\" Man page written by Bart De Schuymer <bdschuym@pandora.be>
+.\" It is based on the iptables man page.
+.\"
+.\" The man page was edited, February 25th 2003, by 
+.\"      Greg Morgan <" dr_kludge_at_users_sourceforge_net >
+.\"
+.\" Iptables page by Herve Eychenne March 2000.
+.\"
+.\"     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.
+.\"     
+.\"
+.SH NAME
+ebtables \- Ethernet bridge frame table administration (nft-based)
+.SH SYNOPSIS
+.BR "ebtables " [ -t " table ] " - [ ACDI "] chain rule specification [match extensions] [watcher extensions] target"
+.br
+.BR "ebtables " [ -t " table ] " -P " chain " ACCEPT " | " DROP " | " RETURN
+.br
+.BR "ebtables " [ -t " table ] " -F " [chain]"
+.br
+.BR "ebtables " [ -t " table ] " -Z " [chain]"
+.br
+.BR "ebtables " [ -t " table ] " -L " [" -Z "] [chain] [ [" --Ln "] | [" --Lx "] ] [" --Lc "] [" --Lmac2 ]
+.br
+.BR "ebtables " [ -t " table ] " -N " chain [" "-P ACCEPT " | " DROP " | " RETURN" ]
+.br
+.BR "ebtables " [ -t " table ] " -X " [chain]"
+.br
+.BR "ebtables " [ -t " table ] " -E " old-chain-name new-chain-name"
+.br
+.BR "ebtables " [ -t " table ] " --init-table
+.br
+.BR "ebtables " [ -t " table ] [" --atomic-file " file] " --atomic-commit
+.br
+.BR "ebtables " [ -t " table ] [" --atomic-file " file] " --atomic-init
+.br
+.BR "ebtables " [ -t " table ] [" --atomic-file " file] " --atomic-save
+.br
+
+.SH DESCRIPTION
+.B ebtables
+is an application program used to set up and maintain the
+tables of rules (inside the Linux kernel) that inspect
+Ethernet frames.
+It is analogous to the
+.B iptables
+application, but less complicated, due to the fact that the Ethernet protocol
+is much simpler than the IP protocol.
+.SS CHAINS
+There are two ebtables tables with built-in chains in the
+Linux kernel. These tables are used to divide functionality into
+different sets of rules. Each set of rules is called a chain.
+Each chain is an ordered list of rules that can match Ethernet frames. If a
+rule matches an Ethernet frame, then a processing specification tells
+what to do with that matching frame. The processing specification is
+called a 'target'. However, if the frame does not match the current
+rule in the chain, then the next rule in the chain is examined and so forth.
+The user can create new (user-defined) chains that can be used as the 'target'
+of a rule. User-defined chains are very useful to get better performance
+over the linear traversal of the rules and are also essential for structuring
+the filtering rules into well-organized and maintainable sets of rules.
+.SS TARGETS
+A firewall rule specifies criteria for an Ethernet frame and a frame
+processing specification called a target.  When a frame matches a rule,
+then the next action performed by the kernel is specified by the target.
+The target can be one of these values:
+.BR ACCEPT ,
+.BR DROP ,
+.BR CONTINUE ,
+.BR RETURN ,
+an 'extension' (see below) or a jump to a user-defined chain.
+.PP
+.B ACCEPT
+means to let the frame through.
+.B DROP
+means the frame has to be dropped.
+.B CONTINUE
+means the next rule has to be checked. This can be handy, f.e., to know how many
+frames pass a certain point in the chain, to log those frames or to apply multiple
+targets on a frame.
+.B RETURN
+means stop traversing this chain and resume at the next rule in the
+previous (calling) chain.
+For the extension targets please refer to the
+.B "TARGET EXTENSIONS"
+section of this man page.
+.SS TABLES
+As stated earlier, there are two ebtables tables in the Linux
+kernel.  The table names are
+.BR filter " and " nat .
+Of these two tables,
+the filter table is the default table that the command operates on.
+If you are working with the filter table, then you can drop the '-t filter'
+argument to the ebtables command.  However, you will need to provide
+the -t argument for
+.B nat
+table.  Moreover, the -t argument must be the
+first argument on the ebtables command line, if used. 
+.TP
+.B "-t, --table"
+.br
+.B filter
+is the default table and contains three built-in chains:
+.B INPUT 
+(for frames destined for the bridge itself, on the level of the MAC destination address), 
+.B OUTPUT 
+(for locally-generated or (b)routed frames) and
+.B FORWARD 
+(for frames being forwarded by the bridge).
+.br
+.br
+.B nat
+is mostly used to change the mac addresses and contains three built-in chains:
+.B PREROUTING 
+(for altering frames as soon as they come in), 
+.B OUTPUT 
+(for altering locally generated or (b)routed frames before they are bridged) and 
+.B POSTROUTING
+(for altering frames as they are about to go out). A small note on the naming
+of chains PREROUTING and POSTROUTING: it would be more accurate to call them
+PREFORWARDING and POSTFORWARDING, but for all those who come from the
+iptables world to ebtables it is easier to have the same names. Note that you
+can change the name
+.BR "" ( -E )
+if you don't like the default.
+.SH EBTABLES COMMAND LINE ARGUMENTS
+After the initial ebtables '-t table' command line argument, the remaining
+arguments can be divided into several groups.  These groups
+are commands, miscellaneous commands, rule specifications, match extensions,
+watcher extensions and target extensions.
+.SS COMMANDS
+The ebtables command arguments specify the actions to perform on the table
+defined with the -t argument.  If you do not use the -t argument to name
+a table, the commands apply to the default filter table.
+Only one command may be used on the command line at a time, except when
+the commands
+.BR -L " and " -Z
+are combined, the commands
+.BR -N " and " -P
+are combined, or when
+.B --atomic-file
+is used.
+.TP
+.B "-A, --append"
+Append a rule to the end of the selected chain.
+.TP
+.B "-D, --delete"
+Delete the specified rule or rules from the selected chain. There are two ways to
+use this command. The first is by specifying an interval of rule numbers
+to delete (directly after
+.BR -D ).
+Syntax: \fIstart_nr\fP[\fI:end_nr\fP] (use
+.B -L --Ln
+to list the rules with their rule number). When \fIend_nr\fP is omitted, all rules starting
+from \fIstart_nr\fP are deleted. Using negative numbers is allowed, for more
+details about using negative numbers, see the
+.B -I
+command. The second usage is by
+specifying the complete rule as it would have been specified when it was added. Only
+the first encountered rule that is the same as this specified rule, in other
+words the matching rule with the lowest (positive) rule number, is deleted.
+.TP
+.B "-C, --change-counters"
+Change the counters of the specified rule or rules from the selected chain. There are two ways to
+use this command. The first is by specifying an interval of rule numbers
+to do the changes on (directly after
+.BR -C ).
+Syntax: \fIstart_nr\fP[\fI:end_nr\fP] (use
+.B -L --Ln
+to list the rules with their rule number). The details are the same as for the
+.BR -D " command. The second usage is by"
+specifying the complete rule as it would have been specified when it was added. Only
+the counters of the first encountered rule that is the same as this specified rule, in other
+words the matching rule with the lowest (positive) rule number, are changed.
+In the first usage, the counters are specified directly after the interval specification,
+in the second usage directly after
+.BR -C .
+First the packet counter is specified, then the byte counter. If the specified counters start
+with a '+', the counter values are added to the respective current counter values.
+If the specified counters start with a '-', the counter values are decreased from the respective
+current counter values. No bounds checking is done. If the counters don't start with '+' or '-',
+the current counters are changed to the specified counters.
+.TP
+.B "-I, --insert"
+Insert the specified rule into the selected chain at the specified rule number. If the
+rule number is not specified, the rule is added at the head of the chain.
+If the current number of rules equals
+.IR N ,
+then the specified number can be
+between
+.IR -N " and " N+1 .
+For a positive number
+.IR i ,
+it holds that
+.IR i " and " i-N-1
+specify the same place in the chain where the rule should be inserted. The rule number
+0 specifies the place past the last rule in the chain and using this number is therefore
+equivalent to using the
+.BR -A " command."
+Rule numbers structly smaller than 0 can be useful when more than one rule needs to be inserted
+in a chain.
+.TP
+.B "-P, --policy"
+Set the policy for the chain to the given target. The policy can be
+.BR ACCEPT ", " DROP " or " RETURN .
+.TP
+.B "-F, --flush"
+Flush the selected chain. If no chain is selected, then every chain will be
+flushed. Flushing a chain does not change the policy of the
+chain, however.
+.TP
+.B "-Z, --zero"
+Set the counters of the selected chain to zero. If no chain is selected, all the counters
+are set to zero. The
+.B "-Z"
+command can be used in conjunction with the 
+.B "-L"
+command.
+When both the
+.B "-Z"
+and
+.B "-L"
+commands are used together in this way, the rule counters are printed on the screen
+before they are set to zero.
+.TP
+.B "-L, --list"
+List all rules in the selected chain. If no chain is selected, all chains
+are listed.
+.br
+The following options change the output of the
+.B "-L"
+command.
+.br
+.B "--Ln"
+.br
+Places the rule number in front of every rule. This option is incompatible with the
+.BR --Lx " option."
+.br
+.B "--Lc"
+.br
+Shows the counters at the end of each rule displayed by the
+.B "-L"
+command. Both a frame counter (pcnt) and a byte counter (bcnt) are displayed.
+The frame counter shows how many frames have matched the specific rule, the byte
+counter shows the sum of the frame sizes of these matching frames. Using this option
+.BR "" "in combination with the " --Lx " option causes the counters to be written out"
+.BR "" "in the '" -c " <pcnt> <bcnt>' option format."
+.br
+.B "--Lx"
+.br
+Changes the output so that it produces a set of ebtables commands that construct
+the contents of the chain, when specified.
+If no chain is specified, ebtables commands to construct the contents of the
+table are given, including commands for creating the user-defined chains (if any).
+You can use this set of commands in an ebtables boot or reload
+script.  For example the output could be used at system startup.
+The 
+.B "--Lx"
+option is incompatible with the
+.B "--Ln"
+listing option. Using the
+.BR --Lx " option together with the " --Lc " option will cause the counters to be written out"
+.BR "" "in the '" -c " <pcnt> <bcnt>' option format."
+.br
+.B "--Lmac2"
+.br
+Shows all MAC addresses with the same length, adding leading zeroes
+if necessary. The default representation omits leading zeroes in the addresses.
+.TP
+.B "-N, --new-chain"
+Create a new user-defined chain with the given name. The number of
+user-defined chains is limited only by the number of possible chain names.
+A user-defined chain name has a maximum
+length of 31 characters. The standard policy of the user-defined chain is
+ACCEPT. The policy of the new chain can be initialized to a different standard
+target by using the
+.B -P
+command together with the
+.B -N
+command. In this case, the chain name does not have to be specified for the
+.B -P
+command.
+.TP
+.B "-X, --delete-chain"
+Delete the specified user-defined chain. There must be no remaining references (jumps)
+to the specified chain, otherwise ebtables will refuse to delete it. If no chain is
+specified, all user-defined chains that aren't referenced will be removed.
+.TP
+.B "-E, --rename-chain"
+Rename the specified chain to a new name.  Besides renaming a user-defined
+chain, you can rename a standard chain to a name that suits your
+taste. For example, if you like PREFORWARDING more than PREROUTING,
+then you can use the -E command to rename the PREROUTING chain. If you do
+rename one of the standard ebtables chain names, please be sure to mention
+this fact should you post a question on the ebtables mailing lists.
+It would be wise to use the standard name in your post. Renaming a standard
+ebtables chain in this fashion has no effect on the structure or functioning
+of the ebtables kernel table.
+.TP
+.B "--init-table"
+Replace the current table data by the initial table data.
+.TP
+.B "--atomic-init"
+Copy the kernel's initial data of the table to the specified
+file. This can be used as the first action, after which rules are added
+to the file. The file can be specified using the
+.B --atomic-file
+command or through the
+.IR EBTABLES_ATOMIC_FILE " environment variable."
+.TP
+.B "--atomic-save"
+Copy the kernel's current data of the table to the specified
+file. This can be used as the first action, after which rules are added
+to the file. The file can be specified using the
+.B --atomic-file
+command or through the
+.IR EBTABLES_ATOMIC_FILE " environment variable."
+.TP
+.B "--atomic-commit"
+Replace the kernel table data with the data contained in the specified
+file. This is a useful command that allows you to load all your rules of a
+certain table into the kernel at once, saving the kernel a lot of precious
+time and allowing atomic updates of the tables. The file which contains
+the table data is constructed by using either the
+.B "--atomic-init"
+or the
+.B "--atomic-save"
+command to generate a starting file. After that, using the
+.B "--atomic-file"
+command when constructing rules or setting the
+.IR EBTABLES_ATOMIC_FILE " environment variable"
+allows you to extend the file and build the complete table before
+committing it to the kernel. This command can be very useful in boot scripts
+to populate the ebtables tables in a fast way.
+.SS MISCELLANOUS COMMANDS
+.TP
+.B "-V, --version"
+Show the version of the ebtables userspace program.
+.TP
+.BR "-h, --help " "[\fIlist of module names\fP]"
+Give a brief description of the command syntax. Here you can also specify
+names of extensions and ebtables will try to write help about those
+extensions. E.g.
+.IR "ebtables -h snat log ip arp" .
+Specify
+.I list_extensions
+to list all extensions supported by the userspace
+utility.
+.TP
+.BR "-j, --jump " "\fItarget\fP"
+The target of the rule. This is one of the following values:
+.BR ACCEPT ,
+.BR DROP ,
+.BR CONTINUE ,
+.BR RETURN ,
+a target extension (see
+.BR "TARGET EXTENSIONS" ")"
+or a user-defined chain name.
+.TP
+.B --atomic-file "\fIfile\fP"
+Let the command operate on the specified
+.IR file .
+The data of the table to
+operate on will be extracted from the file and the result of the operation
+will be saved back into the file. If specified, this option should come
+before the command specification. An alternative that should be preferred,
+is setting the
+.IR EBTABLES_ATOMIC_FILE " environment variable."
+.TP
+.B -M, --modprobe "\fIprogram\fP"
+When talking to the kernel, use this
+.I program
+to try to automatically load missing kernel modules.
+.TP
+.B --concurrent
+Use a file lock to support concurrent scripts updating the ebtables kernel tables.
+
+.SS
+RULE SPECIFICATIONS
+The following command line arguments make up a rule specification (as used 
+in the add and delete commands). A "!" option before the specification 
+inverts the test for that specification. Apart from these standard rule 
+specifications there are some other command line arguments of interest.
+See both the 
+.BR "MATCH EXTENSIONS" 
+and the
+.BR "WATCHER EXTENSIONS" 
+below.
+.TP
+.BR "-p, --protocol " "[!] \fIprotocol\fP"
+The protocol that was responsible for creating the frame. This can be a
+hexadecimal number, above 
+.IR 0x0600 ,
+a name (e.g.
+.I ARP
+) or
+.BR LENGTH .
+The protocol field of the Ethernet frame can be used to denote the
+length of the header (802.2/802.3 networks). When the value of that field is
+below or equals
+.IR 0x0600 ,
+the value equals the size of the header and shouldn't be used as a
+protocol number. Instead, all frames where the protocol field is used as
+the length field are assumed to be of the same 'protocol'. The protocol
+name used in ebtables for these frames is
+.BR LENGTH .
+.br
+The file
+.B /etc/ethertypes
+can be used to show readable
+characters instead of hexadecimal numbers for the protocols. For example,
+.I 0x0800
+will be represented by 
+.IR IPV4 .
+The use of this file is not case sensitive. 
+See that file for more information. The flag 
+.B --proto
+is an alias for this option.
+.TP 
+.BR "-i, --in-interface " "[!] \fIname\fP"
+The interface (bridge port) via which a frame is received (this option is useful in the
+.BR INPUT ,
+.BR FORWARD ,
+.BR PREROUTING " and " BROUTING
+chains). If the interface name ends with '+', then
+any interface name that begins with this name (disregarding '+') will match.
+The flag
+.B --in-if
+is an alias for this option.
+.TP
+.BR "--logical-in " "[!] \fIname\fP"
+The (logical) bridge interface via which a frame is received (this option is useful in the
+.BR INPUT ,
+.BR FORWARD ,
+.BR PREROUTING " and " BROUTING
+chains).
+If the interface name ends with '+', then
+any interface name that begins with this name (disregarding '+') will match.
+.TP
+.BR "-o, --out-interface " "[!] \fIname\fP"
+The interface (bridge port) via which a frame is going to be sent (this option is useful in the
+.BR OUTPUT ,
+.B FORWARD
+and
+.B POSTROUTING
+chains). If the interface name ends with '+', then
+any interface name that begins with this name (disregarding '+') will match.
+The flag
+.B --out-if
+is an alias for this option.
+.TP
+.BR "--logical-out " "[!] \fIname\fP"
+The (logical) bridge interface via which a frame is going to be sent (this option
+is useful in the
+.BR OUTPUT ,
+.B FORWARD
+and
+.B POSTROUTING
+chains).
+If the interface name ends with '+', then
+any interface name that begins with this name (disregarding '+') will match.
+.TP
+.BR "-s, --source " "[!] \fIaddress\fP[/\fImask\fP]"
+The source MAC address. Both mask and address are written as 6 hexadecimal
+numbers separated by colons. Alternatively one can specify Unicast,
+Multicast, Broadcast or BGA (Bridge Group Address):
+.br
+.IR "Unicast" "=00:00:00:00:00:00/01:00:00:00:00:00,"
+.IR "Multicast" "=01:00:00:00:00:00/01:00:00:00:00:00,"
+.IR "Broadcast" "=ff:ff:ff:ff:ff:ff/ff:ff:ff:ff:ff:ff or"
+.IR "BGA" "=01:80:c2:00:00:00/ff:ff:ff:ff:ff:ff."
+Note that a broadcast
+address will also match the multicast specification. The flag
+.B --src
+is an alias for this option.
+.TP
+.BR "-d, --destination " "[!] \fIaddress\fP[/\fImask\fP]"
+The destination MAC address. See
+.B -s
+(above) for more details on MAC addresses. The flag
+.B --dst
+is an alias for this option.
+.TP
+.BR "-c, --set-counter " "\fIpcnt bcnt\fP"
+If used with
+.BR -A " or " -I ", then the packet and byte counters of the new rule will be set to
+.IR pcnt ", resp. " bcnt ".
+If used with the
+.BR -C " or " -D " commands, only rules with a packet and byte count equal to"
+.IR pcnt ", resp. " bcnt " will match."
+
+.SS MATCH EXTENSIONS
+Ebtables extensions are dynamically loaded into the userspace tool,
+there is therefore no need to explicitly load them with a
+-m option like is done in iptables.
+These extensions deal with functionality supported by kernel modules supplemental to
+the core ebtables code.
+.SS 802_3
+Specify 802.3 DSAP/SSAP fields or SNAP type.  The protocol must be specified as
+.IR "LENGTH " "(see the option " " -p " above).
+.TP
+.BR "--802_3-sap " "[!] \fIsap\fP"
+DSAP and SSAP are two one byte 802.3 fields.  The bytes are always
+equal, so only one byte (hexadecimal) is needed as an argument.
+.TP
+.BR "--802_3-type " "[!] \fItype\fP"
+If the 802.3 DSAP and SSAP values are 0xaa then the SNAP type field must
+be consulted to determine the payload protocol.  This is a two byte
+(hexadecimal) argument.  Only 802.3 frames with DSAP/SSAP 0xaa are
+checked for type.
+.SS among
+Match a MAC address or MAC/IP address pair versus a list of MAC addresses
+and MAC/IP address pairs.
+A list entry has the following format:
+.IR xx:xx:xx:xx:xx:xx[=ip.ip.ip.ip][,] ". Multiple"
+list entries are separated by a comma, specifying an IP address corresponding to
+the MAC address is optional. Multiple MAC/IP address pairs with the same MAC address
+but different IP address (and vice versa) can be specified. If the MAC address doesn't
+match any entry from the list, the frame doesn't match the rule (unless "!" was used).
+.TP
+.BR "--among-dst " "[!] \fIlist\fP"
+Compare the MAC destination to the given list. If the Ethernet frame has type
+.IR IPv4 " or " ARP ,
+then comparison with MAC/IP destination address pairs from the
+list is possible.
+.TP
+.BR "--among-src " "[!] \fIlist\fP"
+Compare the MAC source to the given list. If the Ethernet frame has type
+.IR IPv4 " or " ARP ,
+then comparison with MAC/IP source address pairs from the list
+is possible.
+.TP
+.BR "--among-dst-file " "[!] \fIfile\fP"
+Same as
+.BR --among-dst " but the list is read in from the specified file."
+.TP
+.BR "--among-src-file " "[!] \fIfile\fP"
+Same as
+.BR --among-src " but the list is read in from the specified file."
+.SS arp
+Specify (R)ARP fields. The protocol must be specified as
+.IR ARP " or " RARP .
+.TP
+.BR "--arp-opcode " "[!] \fIopcode\fP"
+The (R)ARP opcode (decimal or a string, for more details see
+.BR "ebtables -h arp" ).
+.TP
+.BR "--arp-htype " "[!] \fIhardware type\fP"
+The hardware type, this can be a decimal or the string
+.I Ethernet
+(which sets
+.I type
+to 1). Most (R)ARP packets have Eternet as hardware type.
+.TP
+.BR "--arp-ptype " "[!] \fIprotocol type\fP"
+The protocol type for which the (r)arp is used (hexadecimal or the string
+.IR IPv4 ,
+denoting 0x0800).
+Most (R)ARP packets have protocol type IPv4.
+.TP
+.BR "--arp-ip-src " "[!] \fIaddress\fP[/\fImask\fP]"
+The (R)ARP IP source address specification.
+.TP
+.BR "--arp-ip-dst " "[!] \fIaddress\fP[/\fImask\fP]"
+The (R)ARP IP destination address specification.
+.TP
+.BR "--arp-mac-src " "[!] \fIaddress\fP[/\fImask\fP]"
+The (R)ARP MAC source address specification.
+.TP
+.BR "--arp-mac-dst " "[!] \fIaddress\fP[/\fImask\fP]"
+The (R)ARP MAC destination address specification.
+.TP
+.BR "" "[!]" " --arp-gratuitous"
+Checks for ARP gratuitous packets: checks equality of IPv4 source
+address and IPv4 destination address inside the ARP header.
+.SS ip
+Specify IPv4 fields. The protocol must be specified as
+.IR IPv4 .
+.TP
+.BR "--ip-source " "[!] \fIaddress\fP[/\fImask\fP]"
+The source IP address.
+The flag
+.B --ip-src
+is an alias for this option.
+.TP
+.BR "--ip-destination " "[!] \fIaddress\fP[/\fImask\fP]"
+The destination IP address.
+The flag
+.B --ip-dst
+is an alias for this option.
+.TP
+.BR "--ip-tos " "[!] \fItos\fP"
+The IP type of service, in hexadecimal numbers.
+.BR IPv4 .
+.TP
+.BR "--ip-protocol " "[!] \fIprotocol\fP"
+The IP protocol.
+The flag
+.B --ip-proto
+is an alias for this option.
+.TP
+.BR "--ip-source-port " "[!] \fIport1\fP[:\fIport2\fP]"
+The source port or port range for the IP protocols 6 (TCP), 17
+(UDP), 33 (DCCP) or 132 (SCTP). The
+.B --ip-protocol
+option must be specified as
+.IR TCP ", " UDP ", " DCCP " or " SCTP .
+If
+.IR port1 " is omitted, " 0:port2 " is used; if " port2 " is omitted but a colon is specified, " port1:65535 " is used."
+The flag
+.B --ip-sport
+is an alias for this option.
+.TP
+.BR "--ip-destination-port " "[!] \fIport1\fP[:\fIport2\fP]"
+The destination port or port range for ip protocols 6 (TCP), 17
+(UDP), 33 (DCCP) or 132 (SCTP). The
+.B --ip-protocol
+option must be specified as
+.IR TCP ", " UDP ", " DCCP " or " SCTP .
+If
+.IR port1 " is omitted, " 0:port2 " is used; if " port2 " is omitted but a colon is specified, " port1:65535 " is used."
+The flag
+.B --ip-dport
+is an alias for this option.
+.SS ip6
+Specify IPv6 fields. The protocol must be specified as
+.IR IPv6 .
+.TP
+.BR "--ip6-source " "[!] \fIaddress\fP[/\fImask\fP]"
+The source IPv6 address.
+The flag
+.B --ip6-src
+is an alias for this option.
+.TP
+.BR "--ip6-destination " "[!] \fIaddress\fP[/\fImask\fP]"
+The destination IPv6 address.
+The flag
+.B --ip6-dst
+is an alias for this option.
+.TP
+.BR "--ip6-tclass " "[!] \fItclass\fP"
+The IPv6 traffic class, in hexadecimal numbers.
+.TP
+.BR "--ip6-protocol " "[!] \fIprotocol\fP"
+The IP protocol.
+The flag
+.B --ip6-proto
+is an alias for this option.
+.TP
+.BR "--ip6-source-port " "[!] \fIport1\fP[:\fIport2\fP]"
+The source port or port range for the IPv6 protocols 6 (TCP), 17
+(UDP), 33 (DCCP) or 132 (SCTP). The
+.B --ip6-protocol
+option must be specified as
+.IR TCP ", " UDP ", " DCCP " or " SCTP .
+If
+.IR port1 " is omitted, " 0:port2 " is used; if " port2 " is omitted but a colon is specified, " port1:65535 " is used."
+The flag
+.B --ip6-sport
+is an alias for this option.
+.TP
+.BR "--ip6-destination-port " "[!] \fIport1\fP[:\fIport2\fP]"
+The destination port or port range for IPv6 protocols 6 (TCP), 17
+(UDP), 33 (DCCP) or 132 (SCTP). The
+.B --ip6-protocol
+option must be specified as
+.IR TCP ", " UDP ", " DCCP " or " SCTP .
+If
+.IR port1 " is omitted, " 0:port2 " is used; if " port2 " is omitted but a colon is specified, " port1:65535 " is used."
+The flag
+.B --ip6-dport
+is an alias for this option.
+.TP
+.BR "--ip6-icmp-type " "[!] {\fItype\fP[:\fItype\fP]/\fIcode\fP[:\fIcode\fP]|\fItypename\fP}"
+Specify ipv6\-icmp type and code to match.
+Ranges for both type and code are supported. Type and code are
+separated by a slash. Valid numbers for type and range are 0 to 255.
+To match a single type including all valid codes, symbolic names can
+be used instead of numbers. The list of known type names is shown by the command
+.nf
+  ebtables \-\-help ip6
+.fi
+This option is only valid for \-\-ip6-prococol ipv6-icmp.
+.SS limit
+This module matches at a limited rate using a token bucket filter.
+A rule using this extension will match until this limit is reached.
+It can be used with the
+.B --log
+watcher to give limited logging, for example. Its use is the same
+as the limit match of iptables.
+.TP
+.BR "--limit " "[\fIvalue\fP]"
+Maximum average matching rate: specified as a number, with an optional
+.IR /second ", " /minute ", " /hour ", or " /day " suffix; the default is " 3/hour .
+.TP
+.BR "--limit-burst " "[\fInumber\fP]"
+Maximum initial number of packets to match: this number gets recharged by
+one every time the limit specified above is not reached, up to this
+number; the default is
+.IR 5 .
+.SS mark_m
+.TP
+.BR "--mark " "[!] [\fIvalue\fP][/\fImask\fP]"
+Matches frames with the given unsigned mark value. If a
+.IR value " and " mask " are specified, the logical AND of the mark value of the frame and"
+the user-specified
+.IR mask " is taken before comparing it with the"
+user-specified mark
+.IR value ". When only a mark "
+.IR value " is specified, the packet"
+only matches when the mark value of the frame equals the user-specified
+mark
+.IR value .
+If only a
+.IR mask " is specified, the logical"
+AND of the mark value of the frame and the user-specified
+.IR mask " is taken and the frame matches when the result of this logical AND is"
+non-zero. Only specifying a
+.IR mask " is useful to match multiple mark values."
+.SS pkttype
+.TP
+.BR "--pkttype-type " "[!] \fItype\fP"
+Matches on the Ethernet "class" of the frame, which is determined by the
+generic networking code. Possible values:
+.IR broadcast " (MAC destination is the broadcast address),"
+.IR multicast " (MAC destination is a multicast address),"
+.IR host " (MAC destination is the receiving network device), or "
+.IR otherhost " (none of the above)."
+.SS stp
+Specify stp BPDU (bridge protocol data unit) fields. The destination
+address
+.BR "" ( -d ") must be specified as the bridge group address"
+.IR "" ( BGA ).
+For all options for which a range of values can be specified, it holds that
+if the lower bound is omitted (but the colon is not), then the lowest possible lower bound
+for that option is used, while if the upper bound is omitted (but the colon again is not), the
+highest possible upper bound for that option is used.
+.TP
+.BR "--stp-type " "[!] \fItype\fP"
+The BPDU type (0-255), recognized non-numerical types are
+.IR config ", denoting a configuration BPDU (=0), and"
+.IR tcn ", denothing a topology change notification BPDU (=128)."
+.TP
+.BR "--stp-flags " "[!] \fIflag\fP"
+The BPDU flag (0-255), recognized non-numerical flags are
+.IR topology-change ", denoting the topology change flag (=1), and"
+.IR topology-change-ack ", denoting the topology change acknowledgement flag (=128)."
+.TP
+.BR "--stp-root-prio " "[!] [\fIprio\fP][:\fIprio\fP]"
+The root priority (0-65535) range.
+.TP
+.BR "--stp-root-addr " "[!] [\fIaddress\fP][/\fImask\fP]"
+The root mac address, see the option
+.BR -s " for more details."
+.TP
+.BR "--stp-root-cost " "[!] [\fIcost\fP][:\fIcost\fP]"
+The root path cost (0-4294967295) range.
+.TP
+.BR "--stp-sender-prio " "[!] [\fIprio\fP][:\fIprio\fP]"
+The BPDU's sender priority (0-65535) range.
+.TP
+.BR "--stp-sender-addr " "[!] [\fIaddress\fP][/\fImask\fP]"
+The BPDU's sender mac address, see the option
+.BR -s " for more details."
+.TP
+.BR "--stp-port " "[!] [\fIport\fP][:\fIport\fP]"
+The port identifier (0-65535) range.
+.TP
+.BR "--stp-msg-age " "[!] [\fIage\fP][:\fIage\fP]"
+The message age timer (0-65535) range.
+.TP
+.BR "--stp-max-age " "[!] [\fIage\fP][:\fIage\fP]"
+The max age timer (0-65535) range.
+.TP
+.BR "--stp-hello-time " "[!] [\fItime\fP][:\fItime\fP]"
+The hello time timer (0-65535) range.
+.TP
+.BR "--stp-forward-delay " "[!] [\fIdelay\fP][:\fIdelay\fP]"
+The forward delay timer (0-65535) range.
+.\" .SS string
+.\" This module matches on a given string using some pattern matching strategy.
+.\" .TP
+.\" .BR "--string-algo " "\fIalgorithm\fP"
+.\" The pattern matching strategy. (bm = Boyer-Moore, kmp = Knuth-Pratt-Morris)
+.\" .TP
+.\" .BR "--string-from " "\fIoffset\fP"
+.\" The lowest offset from which a match can start. (default: 0)
+.\" .TP
+.\" .BR "--string-to " "\fIoffset\fP"
+.\" The highest offset from which a match can start. (default: size of frame)
+.\" .TP
+.\" .BR "--string " "[!] \fIpattern\fP"
+.\" Matches the given pattern.
+.\" .TP
+.\" .BR "--string-hex " "[!] \fIpattern\fP"
+.\" Matches the given pattern in hex notation, e.g. '|0D 0A|', '|0D0A|', 'www|09|netfilter|03|org|00|'
+.\" .TP
+.\" .BR "--string-icase"
+.\" Ignore case when searching.
+.SS vlan
+Specify 802.1Q Tag Control Information fields.
+The protocol must be specified as
+.IR 802_1Q " (0x8100)."
+.TP
+.BR "--vlan-id " "[!] \fIid\fP"
+The VLAN identifier field (VID). Decimal number from 0 to 4095.
+.TP
+.BR "--vlan-prio " "[!] \fIprio\fP"
+The user priority field, a decimal number from 0 to 7.
+The VID should be set to 0 ("null VID") or unspecified
+(in the latter case the VID is deliberately set to 0).
+.TP
+.BR "--vlan-encap " "[!] \fItype\fP"
+The encapsulated Ethernet frame type/length.
+Specified as a hexadecimal
+number from 0x0000 to 0xFFFF or as a symbolic name
+from
+.BR /etc/ethertypes .
+
+.SS WATCHER EXTENSIONS
+Watchers only look at frames passing by, they don't modify them nor decide
+to accept the frames or not. These watchers only
+see the frame if the frame matches the rule, and they see it before the
+target is executed.
+.SS log
+The log watcher writes descriptive data about a frame to the syslog.
+.TP
+.B "--log"
+.br
+Log with the default loggin options: log-level=
+.IR info ,
+log-prefix="", no ip logging, no arp logging.
+.TP
+.B --log-level "\fIlevel\fP"
+.br
+Defines the logging level. For the possible values, see
+.BR "ebtables -h log" .
+The default level is 
+.IR info .
+.TP
+.BR --log-prefix " \fItext\fP"
+.br
+Defines the prefix
+.I text
+to be printed at the beginning of the line with the logging information.
+.TP
+.B --log-ip 
+.br
+Will log the ip information when a frame made by the ip protocol matches 
+the rule. The default is no ip information logging.
+.TP
+.B --log-ip6 
+.br
+Will log the ipv6 information when a frame made by the ipv6 protocol matches 
+the rule. The default is no ipv6 information logging.
+.TP
+.B --log-arp
+.br
+Will log the (r)arp information when a frame made by the (r)arp protocols
+matches the rule. The default is no (r)arp information logging.
+.SS nflog
+The nflog watcher passes the packet to the loaded logging backend
+in order to log the packet. This is usually used in combination with
+nfnetlink_log as logging backend, which will multicast the packet
+through a
+.IR netlink
+socket to the specified multicast group. One or more userspace processes
+may subscribe to the group to receive the packets.
+.TP
+.B "--nflog"
+.br
+Log with the default logging options
+.TP
+.B --nflog-group "\fInlgroup\fP"
+.br
+The netlink group (1 - 2^32-1) to which packets are (only applicable for
+nfnetlink_log). The default value is 1.
+.TP
+.B --nflog-prefix "\fIprefix\fP"
+.br
+A prefix string to include in the log message, up to 30 characters
+long, useful for distinguishing messages in the logs.
+.TP
+.B --nflog-range "\fIsize\fP"
+.br
+The number of bytes to be copied to userspace (only applicable for
+nfnetlink_log). nfnetlink_log instances may specify their own
+range, this option overrides it.
+.TP
+.B --nflog-threshold "\fIsize\fP"
+.br
+Number of packets to queue inside the kernel before sending them
+to userspace (only applicable for nfnetlink_log). Higher values
+result in less overhead per packet, but increase delay until the
+packets reach userspace. The default value is 1.
+.SS ulog
+The ulog watcher passes the packet to a userspace
+logging daemon using netlink multicast sockets. This differs
+from the log watcher in the sense that the complete packet is
+sent to userspace instead of a descriptive text and that
+netlink multicast sockets are used instead of the syslog.
+This watcher enables parsing of packets with userspace programs, the
+physical bridge in and out ports are also included in the netlink messages.
+The ulog watcher module accepts 2 parameters when the module is loaded
+into the kernel (e.g. with modprobe):
+.B nlbufsiz
+specifies how big the buffer for each netlink multicast
+group is. If you say
+.IR nlbufsiz=8192 ,
+for example, up to eight kB of packets will
+get accumulated in the kernel until they are sent to userspace. It is
+not possible to allocate more than 128kB. Please also keep in mind that
+this buffer size is allocated for each nlgroup you are using, so the
+total kernel memory usage increases by that factor. The default is 4096.
+.B flushtimeout
+specifies after how many hundredths of a second the queue should be
+flushed, even if it is not full yet. The default is 10 (one tenth of
+a second).
+.TP
+.B "--ulog"
+.br
+Use the default settings: ulog-prefix="", ulog-nlgroup=1,
+ulog-cprange=4096, ulog-qthreshold=1.
+.TP
+.B --ulog-prefix "\fItext\fP"
+.br
+Defines the prefix included with the packets sent to userspace.
+.TP
+.BR --ulog-nlgroup " \fIgroup\fP"
+.br
+Defines which netlink group number to use (a number from 1 to 32).
+Make sure the netlink group numbers used for the iptables ULOG
+target differ from those used for the ebtables ulog watcher.
+The default group number is 1.
+.TP
+.BR --ulog-cprange " \fIrange\fP"
+.br
+Defines the maximum copy range to userspace, for packets matching the
+rule. The default range is 0, which means the maximum copy range is
+given by
+.BR nlbufsiz .
+A maximum copy range larger than
+128*1024 is meaningless as the packets sent to userspace have an upper
+size limit of 128*1024.
+.TP
+.BR --ulog-qthreshold " \fIthreshold\fP"
+.br
+Queue at most
+.I threshold
+number of packets before sending them to
+userspace with a netlink socket. Note that packets can be sent to
+userspace before the queue is full, this happens when the ulog
+kernel timer goes off (the frequency of this timer depends on
+.BR flushtimeout ).
+.SS TARGET EXTENSIONS
+.SS arpreply
+The
+.B arpreply
+target can be used in the
+.BR PREROUTING " chain of the " nat " table."
+If this target sees an ARP request it will automatically reply
+with an ARP reply. The used MAC address for the reply can be specified.
+The protocol must be specified as
+.IR ARP .
+When the ARP message is not an ARP request or when the ARP request isn't
+for an IP address on an Ethernet network, it is ignored by this target
+.BR "" ( CONTINUE ).
+When the ARP request is malformed, it is dropped
+.BR "" ( DROP ).
+.TP
+.BR "--arpreply-mac " "\fIaddress\fP"
+Specifies the MAC address to reply with: the Ethernet source MAC and the
+ARP payload source MAC will be filled in with this address.
+.TP
+.BR "--arpreply-target " "\fItarget\fP"
+Specifies the standard target. After sending the ARP reply, the rule still
+has to give a standard target so ebtables knows what to do with the ARP request.
+The default target
+.BR "" "is " DROP .
+.SS dnat
+The
+.B dnat
+target can only be used in the
+.BR PREROUTING " and " OUTPUT " chains of the " nat " table."
+It specifies that the destination MAC address has to be changed.
+.TP
+.BR "--to-destination " "\fIaddress\fP"
+.br
+Change the destination MAC address to the specified
+.IR address .
+The flag
+.B --to-dst
+is an alias for this option.
+.TP
+.BR "--dnat-target " "\fItarget\fP"
+.br
+Specifies the standard target. After doing the dnat, the rule still has to
+give a standard target so ebtables knows what to do with the dnated frame.
+The default target is
+.BR ACCEPT .
+Making it
+.BR CONTINUE " could let you use"
+multiple target extensions on the same frame. Making it
+.BR DROP " only makes"
+sense in the
+.BR BROUTING " chain but using the " redirect " target is more logical there. " RETURN " is also allowed. Note that using " RETURN
+in a base chain is not allowed (for obvious reasons).
+.SS mark
+.BR "" "The " mark " target can be used in every chain of every table. It is possible"
+to use the marking of a frame/packet in both ebtables and iptables,
+if the bridge-nf code is compiled into the kernel. Both put the marking at the
+same place. This allows for a form of communication between ebtables and iptables.
+.TP
+.BR "--mark-set " "\fIvalue\fP"
+.br
+Mark the frame with the specified non-negative
+.IR value .
+.TP
+.BR "--mark-or " "\fIvalue\fP"
+.br
+Or the frame with the specified non-negative
+.IR value .
+.TP
+.BR "--mark-and " "\fIvalue\fP"
+.br
+And the frame with the specified non-negative
+.IR value .
+.TP
+.BR "--mark-xor " "\fIvalue\fP"
+.br
+Xor the frame with the specified non-negative
+.IR value .
+.TP
+.BR "--mark-target " "\fItarget\fP"
+.br
+Specifies the standard target. After marking the frame, the rule
+still has to give a standard target so ebtables knows what to do.
+The default target is
+.BR ACCEPT ". Making it " CONTINUE " can let you do other"
+things with the frame in subsequent rules of the chain.
+.SS redirect
+The
+.B redirect
+target will change the MAC target address to that of the bridge device the
+frame arrived on. This target can only be used in the
+.BR PREROUTING " chain of the " nat " table."
+The MAC address of the bridge is used as destination address."
+.TP
+.BR "--redirect-target " "\fItarget\fP"
+.br
+Specifies the standard target. After doing the MAC redirect, the rule
+still has to give a standard target so ebtables knows what to do.
+The default target is
+.BR ACCEPT ". Making it " CONTINUE " could let you use"
+multiple target extensions on the same frame. Making it
+.BR DROP " in the " BROUTING " chain will let the frames be routed. " RETURN " is also allowed. Note"
+.BR "" "that using " RETURN " in a base chain is not allowed."
+.SS snat
+The
+.B snat
+target can only be used in the
+.BR POSTROUTING " chain of the " nat " table."
+It specifies that the source MAC address has to be changed.
+.TP
+.BR "--to-source " "\fIaddress\fP"
+.br
+Changes the source MAC address to the specified
+.IR address ". The flag"
+.B --to-src
+is an alias for this option.
+.TP
+.BR "--snat-target " "\fItarget\fP"
+.br
+Specifies the standard target. After doing the snat, the rule still has 
+to give a standard target so ebtables knows what to do.
+.BR "" "The default target is " ACCEPT ". Making it " CONTINUE " could let you use"
+.BR "" "multiple target extensions on the same frame. Making it " DROP " doesn't"
+.BR "" "make sense, but you could do that too. " RETURN " is also allowed. Note"
+.BR "" "that using " RETURN " in a base chain is not allowed."
+.br
+.TP
+.BR "--snat-arp "
+.br
+Also change the hardware source address inside the arp header if the packet is an
+arp message and the hardware address length in the arp header is 6 bytes.
+.br
+.SH FILES
+.I /etc/ethertypes
+.SH ENVIRONMENT VARIABLES
+.I EBTABLES_ATOMIC_FILE
+.SH MAILINGLISTS
+.BR "" "See " http://netfilter.org/mailinglists.html
+.SH BUGS
+The version of ebtables this man page ships with does not support the
+.B broute
+table. Also there is no support for
+.B string
+match. And finally, this list is probably not complete.
+.SH SEE ALSO
+.BR xtables-nft "(8), " iptables "(8), " ip (8)
+.PP
+.BR "" "See " https://wiki.nftables.org
diff --git a/iptables/ip6tables-apply.8 b/iptables/ip6tables-apply.8
new file mode 100644
index 0000000..994b487
--- /dev/null
+++ b/iptables/ip6tables-apply.8
@@ -0,0 +1 @@
+.so man8/iptables-apply.8
diff --git a/iptables/ip6tables-restore.8 b/iptables/ip6tables-restore.8
index 0264807..cf4ea3e 100644
--- a/iptables/ip6tables-restore.8
+++ b/iptables/ip6tables-restore.8
@@ -1,50 +1 @@
-.TH IP6TABLES-RESTORE 8 "Jan 30, 2002" "" ""
-.\"
-.\" Man page written by Harald Welte <laforge@gnumonks.org>
-.\" It is based on the iptables man page.
-.\"
-.\"	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.
-.\"
-.\"
-.SH NAME
-ip6tables-restore \(em Restore IPv6 Tables
-.SH SYNOPSIS
-\fBip6tables\-restore\fP [\fB\-c\fP] [\fB\-n\fP]
-.SH DESCRIPTION
-.PP
-.B ip6tables-restore
-is used to restore IPv6 Tables from data specified on STDIN. Use 
-I/O redirection provided by your shell to read from a file
-.TP
-\fB\-c\fR, \fB\-\-counters\fR
-restore the values of all packet and byte counters
-.TP
-\fB\-n\fR, \fB\-\-noflush\fR 
-.TP
-don't flush the previous contents of the table. If not specified, 
-.B ip6tables-restore
-flushes (deletes) all previous contents of the respective IPv6 Table.
-.SH BUGS
-None known as of iptables-1.2.1 release
-.SH AUTHORS
-Harald Welte <laforge@gnumonks.org>
-.br
-Andras Kis-Szabo <kisza@sch.bme.hu>
-.SH SEE ALSO
-\fBip6tables\-save\fP(8), \fBip6tables\fP(8)
-.PP
-The iptables-HOWTO, which details more iptables usage, the NAT-HOWTO,
-which details NAT, and the netfilter-hacking-HOWTO which details the
-internals.
+.so man8/iptables-restore.8
diff --git a/iptables/ip6tables-restore.c b/iptables/ip6tables-restore.c
deleted file mode 100644
index 420bc52..0000000
--- a/iptables/ip6tables-restore.c
+++ /dev/null
@@ -1,466 +0,0 @@
-/* Code to restore the iptables state, from file by ip6tables-save.
- * Author:  Andras Kis-Szabo <kisza@sch.bme.hu>
- *
- * based on iptables-restore
- * Authors:
- *      Harald Welte <laforge@gnumonks.org>
- *      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 "ip6tables.h"
-#include "xtables.h"
-#include "libiptc/libip6tc.h"
-#include "ip6tables-multi.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'},
-	{NULL},
-};
-
-static void print_usage(const char *name, const char *version) __attribute__((noreturn));
-
-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"
-			"          [ --modprobe=<command>]\n", name);
-
-	exit(1);
-}
-
-static struct ip6tc_handle *create_handle(const char *tablename)
-{
-	struct ip6tc_handle *handle;
-
-	handle = ip6tc_init(tablename);
-
-	if (!handle) {
-		/* try to insmod the module if iptc_init failed */
-		xtables_load_ko(xtables_modprobe_program, false);
-		handle = ip6tc_init(tablename);
-	}
-
-	if (!handle) {
-		xtables_error(PARAMETER_PROBLEM, "%s: unable to initialize "
-			"table '%s'\n", ip6tables_globals.program_name,
-			tablename);
-		exit(1);
-	}
-	return handle;
-}
-
-static int parse_counters(char *string, struct ip6t_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);
-		newargc++;
-		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]);
-}
-
-#ifdef IPTABLES_MULTI
-int ip6tables_restore_main(int argc, char *argv[])
-#else
-int main(int argc, char *argv[])
-#endif
-{
-	struct ip6tc_handle *handle = NULL;
-	char buffer[10240];
-	int c;
-	char curtable[IP6T_TABLE_MAXNAMELEN + 1];
-	FILE *in;
-	int in_table = 0, testing = 0;
-
-	line = 0;
-
-	ip6tables_globals.program_name = "ip6tables-restore";
-	c = xtables_init_all(&ip6tables_globals, NFPROTO_IPV6);
-	if (c < 0) {
-		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
-				ip6tables_globals.program_name,
-				ip6tables_globals.program_version);
-		exit(1);
-	}
-#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
-	init_extensions();
-	init_extensions6();
-#endif
-
-	while ((c = getopt_long(argc, argv, "bcvthnM:", 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("ip6tables-restore",
-					    IPTABLES_VERSION);
-				break;
-			case 'n':
-				noflush = 1;
-				break;
-			case 'M':
-				xtables_modprobe_program = 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)) {
-			if (!testing) {
-				DEBUGP("Calling commit\n");
-				ret = ip6tc_commit(handle);
-				ip6tc_free(handle);
-				handle = NULL;
-			} 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",
-					ip6tables_globals.program_name,
-					line);
-				exit(1);
-			}
-			strncpy(curtable, table, IP6T_TABLE_MAXNAMELEN);
-			curtable[IP6T_TABLE_MAXNAMELEN] = '\0';
-
-			if (handle)
-				ip6tc_free(handle);
-
-			handle = create_handle(table);
-			if (noflush == 0) {
-				DEBUGP("Cleaning all chains of table '%s'\n",
-					table);
-				for_each_chain6(flush_entries6, verbose, 1,
-						handle);
-
-				DEBUGP("Deleting all user-defined chains "
-				       "of table '%s'\n", table);
-				for_each_chain6(delete_chain6, verbose, 0,
-						handle);
-			}
-
-			ret = 1;
-			in_table = 1;
-
-		} else if ((buffer[0] == ':') && (in_table)) {
-			/* New chain. */
-			char *policy, *chain;
-
-			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",
-					   ip6tables_globals.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);
-
-			if (ip6tc_builtin(chain, handle) <= 0) {
-				if (noflush && ip6tc_is_chain(chain, handle)) {
-					DEBUGP("Flushing existing user defined chain '%s'\n", chain);
-					if (!ip6tc_flush_entries(chain, handle))
-						xtables_error(PARAMETER_PROBLEM,
-							   "error flushing chain "
-							   "'%s':%s\n", chain,
-							   strerror(errno));
-				} else {
-					DEBUGP("Creating new chain '%s'\n", chain);
-					if (!ip6tc_create_chain(chain, handle))
-						xtables_error(PARAMETER_PROBLEM,
-							   "error creating chain "
-							   "'%s':%s\n", chain,
-							   strerror(errno));
-				}
-			}
-
-			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",
-					   ip6tables_globals.program_name,
-					   line);
-				exit(1);
-			}
-
-			if (strcmp(policy, "-") != 0) {
-				struct ip6t_counters count;
-
-				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);
-
-				} else {
-					memset(&count, 0,
-					       sizeof(struct ip6t_counters));
-				}
-
-				DEBUGP("Setting policy of chain %s to %s\n",
-					chain, policy);
-
-				if (!ip6tc_set_policy(chain, policy, &count,
-						     handle))
-					xtables_error(OTHER_PROBLEM,
-						"Can't set policy `%s'"
-						" on `%s' line %u: %s\n",
-						policy, chain, line,
-						ip6tc_strerror(errno));
-			}
-
-			ret = 1;
-
-		} else if (in_table) {
-			int a;
-			char *ptr = buffer;
-			char *pcnt = NULL;
-			char *bcnt = NULL;
-			char *parsestart;
-
-			/* the parser */
-			char *curchar;
-			int quote_open, escaped;
-			size_t param_len;
-
-			/* 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);
-			}
-
-			/* 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 */
-
-			quote_open = 0;
-			escaped = 0;
-			param_len = 0;
-
-			for (curchar = parsestart; *curchar; curchar++) {
-				char param_buffer[1024];
-
-				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,
-						   "Line %u seems to have a "
-						   "-t table option.\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!");
-				}
-			}
-
-			DEBUGP("calling do_command6(%u, argv, &%s, handle):\n",
-				newargc, curtable);
-
-			for (a = 0; a < newargc; a++)
-				DEBUGP("argv[%u]: %s\n", a, newargv[a]);
-
-			ret = do_command6(newargc, newargv,
-					 &newargv[2], &handle);
-
-			free_argv();
-			fflush(stdout);
-		}
-		if (!ret) {
-			fprintf(stderr, "%s: line %u failed\n",
-					ip6tables_globals.program_name,
-					line);
-			exit(1);
-		}
-	}
-	if (in_table) {
-		fprintf(stderr, "%s: COMMIT expected at line %u\n",
-				ip6tables_globals.program_name,
-				line + 1);
-		exit(1);
-	}
-
-	if (in != NULL)
-		fclose(in);
-	return 0;
-}
diff --git a/iptables/ip6tables-save.8 b/iptables/ip6tables-save.8
index 457be82..182f55c 100644
--- a/iptables/ip6tables-save.8
+++ b/iptables/ip6tables-save.8
@@ -1,53 +1 @@
-.TH IP6TABLES-SAVE 8 "Jan 30, 2002" "" ""
-.\"
-.\" Man page written by Harald Welte <laforge@gnumonks.org>
-.\" It is based on the iptables man page.
-.\"
-.\"	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.
-.\"
-.\"
-.SH NAME
-ip6tables-save \(em dump iptables rules to stdout
-.SH SYNOPSIS
-\fBip6tables\-save\fP [\fB\-M\fP \fImodprobe\fP] [\fB\-c\fP]
-[\fB\-t\fP \fItable\fP
-.SH DESCRIPTION
-.PP
-.B ip6tables-save
-is used to dump the contents of an IPv6 Table in easily parseable format
-to STDOUT. Use I/O-redirection provided by your shell to write to a file.
-.TP
-\fB\-M\fP \fImodprobe_program\fP
-Specify the path to the modprobe program. By default, iptables-save will
-inspect /proc/sys/kernel/modprobe to determine the executable's path.
-.TP
-\fB\-c\fR, \fB\-\-counters\fR
-include the current values of all packet and byte counters in the output
-.TP
-\fB\-t\fR, \fB\-\-table\fR \fItablename\fP
-restrict output to only one table. If not specified, output includes all
-available tables.
-.SH BUGS
-None known as of iptables-1.2.1 release
-.SH AUTHORS
-Harald Welte <laforge@gnumonks.org>
-.br
-Andras Kis-Szabo <kisza@sch.bme.hu>
-.SH SEE ALSO
-\fBip6tables\-restore\fP(8), \fBip6tables\fP(8)
-.PP
-The iptables-HOWTO, which details more iptables usage, the NAT-HOWTO,
-which details NAT, and the netfilter-hacking-HOWTO which details the
-internals.
+.so man8/iptables-save.8
diff --git a/iptables/ip6tables-save.c b/iptables/ip6tables-save.c
deleted file mode 100644
index 39a3325..0000000
--- a/iptables/ip6tables-save.c
+++ /dev/null
@@ -1,185 +0,0 @@
-/* Code to save the ip6tables state, in human readable-form. */
-/* Author:  Andras Kis-Szabo <kisza@sch.bme.hu>
- * Original code: iptables-save
- * Authors: Paul 'Rusty' Russel <rusty@linuxcare.com.au> and
- *          Harald Welte <laforge@gnumonks.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 <arpa/inet.h>
-#include "libiptc/libip6tc.h"
-#include "ip6tables.h"
-#include "ip6tables-multi.h"
-
-#ifndef NO_SHARED_LIBS
-#include <dlfcn.h>
-#endif
-
-static int show_binary = 0, show_counters = 0;
-
-static const struct option options[] = {
-	{.name = "binary",   .has_arg = false, .val = 'b'},
-	{.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},
-};
-
-
-/* Debugging prototype. */
-static int for_each_table(int (*func)(const char *tablename))
-{
-	int ret = 1;
-	FILE *procfile = NULL;
-	char tablename[IP6T_TABLE_MAXNAMELEN+1];
-
-	procfile = fopen("/proc/net/ip6_tables_names", "re");
-	if (!procfile)
-		return ret;
-
-	while (fgets(tablename, sizeof(tablename), procfile)) {
-		if (tablename[strlen(tablename) - 1] != '\n')
-			xtables_error(OTHER_PROBLEM,
-				   "Badly formed tablename `%s'\n",
-				   tablename);
-		tablename[strlen(tablename) - 1] = '\0';
-		ret &= func(tablename);
-	}
-
-	fclose(procfile);
-	return ret;
-}
-
-
-static int do_output(const char *tablename)
-{
-	struct ip6tc_handle *h;
-	const char *chain = NULL;
-
-	if (!tablename)
-		return for_each_table(&do_output);
-
-	h = ip6tc_init(tablename);
-	if (h == NULL) {
-		xtables_load_ko(xtables_modprobe_program, false);
-		h = ip6tc_init(tablename);
-	}
-	if (!h)
-		xtables_error(OTHER_PROBLEM, "Cannot initialize: %s\n",
-			   ip6tc_strerror(errno));
-
-	if (!show_binary) {
-		time_t now = time(NULL);
-
-		printf("# Generated by ip6tables-save v%s on %s",
-		       IPTABLES_VERSION, ctime(&now));
-		printf("*%s\n", tablename);
-
-		/* Dump out chain names first,
-		 * thereby preventing dependency conflicts */
-		for (chain = ip6tc_first_chain(h);
-		     chain;
-		     chain = ip6tc_next_chain(h)) {
-
-			printf(":%s ", chain);
-			if (ip6tc_builtin(chain, h)) {
-				struct ip6t_counters count;
-				printf("%s ",
-				       ip6tc_get_policy(chain, &count, h));
-				printf("[%llu:%llu]\n", (unsigned long long)count.pcnt, (unsigned long long)count.bcnt);
-			} else {
-				printf("- [0:0]\n");
-			}
-		}
-
-
-		for (chain = ip6tc_first_chain(h);
-		     chain;
-		     chain = ip6tc_next_chain(h)) {
-			const struct ip6t_entry *e;
-
-			/* Dump out rules */
-			e = ip6tc_first_rule(chain, h);
-			while(e) {
-				print_rule6(e, h, chain, show_counters);
-				e = ip6tc_next_rule(e, h);
-			}
-		}
-
-		now = time(NULL);
-		printf("COMMIT\n");
-		printf("# Completed on %s", ctime(&now));
-	} else {
-		/* Binary, huh?  OK. */
-		xtables_error(OTHER_PROBLEM, "Binary NYI\n");
-	}
-
-	ip6tc_free(h);
-
-	return 1;
-}
-
-/* Format:
- * :Chain name POLICY packets bytes
- * rule
- */
-#ifdef IPTABLES_MULTI
-int ip6tables_save_main(int argc, char *argv[])
-#else
-int main(int argc, char *argv[])
-#endif
-{
-	const char *tablename = NULL;
-	int c;
-
-	ip6tables_globals.program_name = "ip6tables-save";
-	c = xtables_init_all(&ip6tables_globals, NFPROTO_IPV6);
-	if (c < 0) {
-		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
-				ip6tables_globals.program_name,
-				ip6tables_globals.program_version);
-		exit(1);
-	}
-#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
-	init_extensions();
-	init_extensions6();
-#endif
-
-	while ((c = getopt_long(argc, argv, "bcdt:", options, NULL)) != -1) {
-		switch (c) {
-		case 'b':
-			show_binary = 1;
-			break;
-
-		case 'c':
-			show_counters = 1;
-			break;
-
-		case 't':
-			/* Select specific table. */
-			tablename = optarg;
-			break;
-		case 'M':
-			xtables_modprobe_program = optarg;
-			break;
-		case 'd':
-			do_output(tablename);
-			exit(0);
-		}
-	}
-
-	if (optind < argc) {
-		fprintf(stderr, "Unknown arguments found on commandline\n");
-		exit(1);
-	}
-
-	return !do_output(tablename);
-}
diff --git a/iptables/ip6tables-standalone.c b/iptables/ip6tables-standalone.c
index 9d8d5a0..105b83b 100644
--- a/iptables/ip6tables-standalone.c
+++ b/iptables/ip6tables-standalone.c
@@ -37,17 +37,12 @@
 #include <ip6tables.h>
 #include "ip6tables-multi.h"
 
-#ifdef IPTABLES_MULTI
 int
 ip6tables_main(int argc, char *argv[])
-#else
-int
-main(int argc, char *argv[])
-#endif
 {
 	int ret;
 	char *table = "filter";
-	struct ip6tc_handle *handle = NULL;
+	struct xtc_handle *handle = NULL;
 
 	ip6tables_globals.program_name = "ip6tables";
 	ret = xtables_init_all(&ip6tables_globals, NFPROTO_IPV6);
@@ -63,12 +58,14 @@
 	init_extensions6();
 #endif
 
-	ret = do_command6(argc, argv, &table, &handle);
+	ret = do_command6(argc, argv, &table, &handle, false);
 	if (ret) {
 		ret = ip6tc_commit(handle);
 		ip6tc_free(handle);
 	}
 
+	xtables_fini();
+
 	if (!ret) {
 		if (errno == EINVAL) {
 			fprintf(stderr, "ip6tables: %s. "
@@ -78,6 +75,8 @@
 			fprintf(stderr, "ip6tables: %s.\n",
 				ip6tc_strerror(errno));
 		}
+		if (errno == EAGAIN)
+			exit(RESOURCE_PROBLEM);
 	}
 
 	exit(!ret);
diff --git a/iptables/ip6tables.8 b/iptables/ip6tables.8
new file mode 100644
index 0000000..0dee41a
--- /dev/null
+++ b/iptables/ip6tables.8
@@ -0,0 +1 @@
+.so man8/iptables.8
diff --git a/iptables/ip6tables.8.in b/iptables/ip6tables.8.in
deleted file mode 100644
index 48ba18e..0000000
--- a/iptables/ip6tables.8.in
+++ /dev/null
@@ -1,440 +0,0 @@
-.TH IP6TABLES 8 "" "iptables 1.4.4" "iptables 1.4.4"
-.\"
-.\" Man page written by Andras Kis-Szabo <kisza@sch.bme.hu>
-.\" It is based on iptables man page.
-.\"
-.\" iptables page by Herve Eychenne <rv@wallfire.org>
-.\" It is based on ipchains man page.
-.\"
-.\" ipchains page by Paul ``Rusty'' Russell March 1997
-.\" Based on the original ipfwadm man page by Jos Vos <jos@xos.nl>
-.\"
-.\"	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.
-.\"
-.\"
-.SH NAME
-ip6tables \(em IPv6 packet filter administration
-.SH SYNOPSIS
-\fBip6tables\fP [\fB\-t\fP \fItable\fP] {\fB\-A\fP|\fB\-C\fP|\fB\-D\fP}
-\fIchain rule-specification\fP [\fIoptions...\fP]
-.PP
-\fBip6tables\fP [\fB\-t\fP \fItable\fP] \fB\-I\fP \fIchain\fP [\fIrulenum\fP]
-\fIrule-specification\fP [\fIoptions...\fP]
-.PP
-\fBip6tables\fP [\fB\-t\fP \fItable\fP] \fB\-R\fP \fIchain rulenum
-rule-specification\fP [\fIoptions...\fP]
-.PP
-\fBip6tables\fP [\fB\-t\fP \fItable\fP] \fB\-D\fP \fIchain rulenum\fP
-[\fIoptions...\fP]
-.PP
-\fBip6tables\fP [\fB\-t\fP \fItable\fP] \fB\-S\fP [\fIchain\fP [\fIrulenum\fP]]
-.PP
-\fBip6tables\fP [\fB\-t\fP \fItable\fP] {\fB\-F\fP|\fB\-L\fP|\fB\-Z\fP}
-[\fIchain\fP [\fIrulenum\fP]] [\fIoptions...\fP]
-.PP
-\fBip6tables\fP [\fB\-t\fP \fItable\fP] \fB\-N\fP \fIchain\fP
-.PP
-\fBip6tables\fP [\fB\-t\fP \fItable\fP] \fB\-X\fP [\fIchain\fP]
-.PP
-\fBip6tables\fP [\fB\-t\fP \fItable\fP] \fB\-P\fP \fIchain target\fP
-[\fIoptions...\fP]
-.PP
-\fBip6tables\fP [\fB\-t\fP \fItable\fP] \fB\-E\fP \fIold-chain-name new-chain-name\fP
-.SH DESCRIPTION
-\fBIp6tables\fP is used to set up, maintain, and inspect the
-tables of IPv6 packet
-filter rules in the Linux kernel.  Several different tables
-may be defined.  Each table contains a number of built-in
-chains and may also contain user-defined chains.
-.PP
-Each chain is a list of rules which can match a set of packets.  Each
-rule specifies what to do with a packet that matches.  This is called
-a `target', which may be a jump to a user-defined chain in the same
-table.
-.SH TARGETS
-A firewall rule specifies criteria for a packet and a target.  If the
-packet does not match, the next rule in the chain is the examined; if
-it does match, then the next rule is specified by the value of the
-target, which can be the name of a user-defined chain or one of the
-special values \fBACCEPT\fP, \fBDROP\fP, \fBQUEUE\fP or \fBRETURN\fP.
-.PP
-\fBACCEPT\fP means to let the packet through.
-\fBDROP\fP means to drop the packet on the floor.
-\fBQUEUE\fP means to pass the packet to userspace.
-(How the packet can be received
-by a userspace process differs by the particular queue handler.  2.4.x
-and 2.6.x kernels up to 2.6.13 include the \fBip_queue\fP
-queue handler.  Kernels 2.6.14 and later additionally include the
-\fBnfnetlink_queue\fP queue handler.  Packets with a target of QUEUE will be
-sent to queue number '0' in this case. Please also see the \fBNFQUEUE\fP
-target as described later in this man page.)
-\fBRETURN\fP means stop traversing this chain and resume at the next
-rule in the
-previous (calling) chain.  If the end of a built-in chain is reached
-or a rule in a built-in chain with target \fBRETURN\fP
-is matched, the target specified by the chain policy determines the
-fate of the packet.
-.SH TABLES
-There are currently three independent tables (which tables are present
-at any time depends on the kernel configuration options and which
-modules are present).
-.TP
-\fB\-t\fP, \fB\-\-table\fP \fItable\fP
-This option specifies the packet matching table which the command
-should operate on.  If the kernel is configured with automatic module
-loading, an attempt will be made to load the appropriate module for
-that table if it is not already there.
-
-The tables are as follows:
-.RS
-.TP .4i
-\fBfilter\fP:
-This is the default table (if no \-t option is passed). It contains
-the built-in chains \fBINPUT\fP (for packets destined to local sockets),
-\fBFORWARD\fP (for packets being routed through the box), and
-\fBOUTPUT\fP (for locally-generated packets).
-.TP
-\fBmangle\fP:
-This table is used for specialized packet alteration.  Until kernel
-2.4.17 it had two built-in chains: \fBPREROUTING\fP
-(for altering incoming packets before routing) and \fBOUTPUT\fP
-(for altering locally-generated packets before routing).
-Since kernel 2.4.18, three other built-in chains are also supported:
-\fBINPUT\fP (for packets coming into the box itself), \fBFORWARD\fP
-(for altering packets being routed through the box), and \fBPOSTROUTING\fP
-(for altering packets as they are about to go out).
-.TP
-\fBraw\fP:
-This table is used mainly for configuring exemptions from connection
-tracking in combination with the NOTRACK target.  It registers at the netfilter
-hooks with higher priority and is thus called before ip_conntrack, or any other
-IP tables.  It provides the following built-in chains: \fBPREROUTING\fP
-(for packets arriving via any network interface) \fBOUTPUT\fP
-(for packets generated by local processes)
-.TP
-\fBsecurity\fP:
-This table is used for Mandatory Access Control (MAC) networking rules, such
-as those enabled by the \fBSECMARK\fP and \fBCONNSECMARK\fP targets.
-Mandatory Access Control is implemented by Linux Security Modules such as
-SELinux.  The security table is called after the filter table, allowing any
-Discretionary Access Control (DAC) rules in the filter table to take effect
-before MAC rules.  This table provides the following built-in chains:
-\fBINPUT\fP (for packets coming into the box itself),
-\fBOUTPUT\fP (for altering locally-generated packets before routing), and
-\fBFORWARD\fP (for altering packets being routed through the box).
-.RE
-.SH OPTIONS
-The options that are recognized by
-\fBip6tables\fP can be divided into several different groups.
-.SS COMMANDS
-These options specify the specific action to perform.  Only one of them
-can be specified on the command line unless otherwise specified
-below.  For all the long versions of the command and option names, you
-need to use only enough letters to ensure that
-\fBip6tables\fP can differentiate it from all other options.
-.TP
-\fB\-A\fP, \fB\-\-append\fP \fIchain rule-specification\fP
-Append one or more rules to the end of the selected chain.
-When the source and/or destination names resolve to more than one
-address, a rule will be added for each possible address combination.
-.TP
-\fB\-C\fP, \fB\-\-check\fP \fIchain rule-specification\fP
-Check whether a rule matching the specification does exist in the
-selected chain. This command uses the same logic as \fB\-D\fP to
-find a matching entry, but does not alter the existing iptables
-configuration and uses its exit code to indicate success or failure.
-.TP
-\fB\-D\fP, \fB\-\-delete\fP \fIchain rule-specification\fP
-.ns
-.TP
-\fB\-D\fP, \fB\-\-delete\fP \fIchain rulenum\fP
-Delete one or more rules from the selected chain.  There are two
-versions of this command: the rule can be specified as a number in the
-chain (starting at 1 for the first rule) or a rule to match.
-.TP
-\fB\-I\fP, \fB\-\-insert\fP \fIchain\fP [\fIrulenum\fP] \fIrule-specification\fP
-Insert one or more rules in the selected chain as the given rule
-number.  So, if the rule number is 1, the rule or rules are inserted
-at the head of the chain.  This is also the default if no rule number
-is specified.
-.TP
-\fB\-R\fP, \fB\-\-replace\fP \fIchain rulenum rule-specification\fP
-Replace a rule in the selected chain.  If the source and/or
-destination names resolve to multiple addresses, the command will
-fail.  Rules are numbered starting at 1.
-.TP
-\fB\-L\fP, \fB\-\-list\fP [\fIchain\fP]
-List all rules in the selected chain.  If no chain is selected, all
-chains are listed. Like every other ip6tables command, it applies to the
-specified table (filter is the default).
-.IP ""
-Please note that it is often used with the \fB\-n\fP
-option, in order to avoid long reverse DNS lookups.
-It is legal to specify the \fB\-Z\fP
-(zero) option as well, in which case the chain(s) will be atomically
-listed and zeroed.  The exact output is affected by the other
-arguments given. The exact rules are suppressed until you use
-.nf
- ip6tables \-L \-v
-.fi
-.TP
-\fB\-S\fP, \fB\-\-list\-rules\fP [\fIchain\fP]
-Print all rules in the selected chain.  If no chain is selected, all
-chains are printed like ip6tables-save. Like every other ip6tables command,
-it applies to the specified table (filter is the default).
-.TP
-\fB\-F\fP, \fB\-\-flush\fP [\fIchain\fP]
-Flush the selected chain (all the chains in the table if none is given).
-This is equivalent to deleting all the rules one by one.
-.TP
-\fB\-Z\fP, \fB\-\-zero\fP [\fIchain\fP [\fIrulenum\fP]]
-Zero the packet and byte counters in all chains, or only the given chain,
-or only the given rule in a chain. It is legal to
-specify the
-\fB\-L\fP, \fB\-\-list\fP
-(list) option as well, to see the counters immediately before they are
-cleared. (See above.)
-.TP
-\fB\-N\fP, \fB\-\-new\-chain\fP \fIchain\fP
-Create a new user-defined chain by the given name.  There must be no
-target of that name already.
-.TP
-\fB\-X\fP, \fB\-\-delete\-chain\fP [\fIchain\fP]
-Delete the optional user-defined chain specified.  There must be no references
-to the chain.  If there are, you must delete or replace the referring rules
-before the chain can be deleted.  The chain must be empty, i.e. not contain
-any rules.  If no argument is given, it will attempt to delete every
-non-builtin chain in the table.
-.TP
-\fB\-P\fP, \fB\-\-policy\fP \fIchain target\fP
-Set the policy for the chain to the given target.  See the section \fBTARGETS\fP
-for the legal targets.  Only built-in (non-user-defined) chains can have
-policies, and neither built-in nor user-defined chains can be policy
-targets.
-.TP
-\fB\-E\fP, \fB\-\-rename\-chain\fP \fIold\-chain new\-chain\fP
-Rename the user specified chain to the user supplied name.  This is
-cosmetic, and has no effect on the structure of the table.
-.TP
-\fB\-A\fP, \fB\-\-append\fP \fIchain rule-specification\fP
-Append one or more rules to the end of the selected chain.
-When the source and/or destination names resolve to more than one
-address, a rule will be added for each possible address combination.
-.TP
-\fB\-h\fP
-Help.
-Give a (currently very brief) description of the command syntax.
-.SS PARAMETERS
-The following parameters make up a rule specification (as used in the
-add, delete, insert, replace and append commands).
-.TP
-[\fB!\fP] \fB\-p\fP, \fB\-\-protocol\fP \fIprotocol\fP
-The protocol of the rule or of the packet to check.
-The specified protocol can be one of \fBtcp\fP, \fBudp\fP, \fBudplite\fP,
-\fBicmpv6\fP, \fBesp\fP, \fBmh\fP or the special keyword "\fBall\fP",
-or it can be a numeric value, representing one of these protocols or a
-different one. A protocol name from /etc/protocols is also allowed.
-But IPv6 extension headers except \fBesp\fP are not allowed.
-\fBesp\fP and \fBipv6\-nonext\fP
-can be used with Kernel version 2.6.11 or later.
-A "!" argument before the protocol inverts the
-test.  The number zero is equivalent to \fBall\fP. "\fBall\fP"
-will match with all protocols and is taken as default when this
-option is omitted.
-.TP
-[\fB!\fP] \fB\-s\fP, \fB\-\-source\fP \fIaddress\fP[\fB/\fP\fImask\fP]
-Source specification.
-\fIAddress\fP can be either be a hostname,
-a network IP address (with \fB/\fP\fImask\fP), or a plain IP address.
-Names will be resolved once only, before the rule is submitted to the kernel.
-Please note that specifying any name to be resolved with a remote query such as
-DNS is a really bad idea.
-(Resolving network names is not supported at this time.)
-The \fImask\fP is a plain number,
-specifying the number of 1's at the left side of the network mask.
-A "!" argument before the address specification inverts the sense of
-the address. The flag \fB\-\-src\fP
-is an alias for this option.
-Multiple addresses can be specified, but this will \fBexpand to multiple
-rules\fP (when adding with \-A), or will cause multiple rules to be
-deleted (with \-D).
-.TP
-[\fB!\fP] \fB\-d\fP, \fB\-\-destination\fP \fIaddress\fP[\fB/\fP\fImask\fP]
-Destination specification. 
-See the description of the \fB\-s\fP
-(source) flag for a detailed description of the syntax.  The flag
-\fB\-\-dst\fP is an alias for this option.
-.TP
-\fB\-j\fP, \fB\-\-jump\fP \fItarget\fP
-This specifies the target of the rule; i.e., what to do if the packet
-matches it.  The target can be a user-defined chain (other than the
-one this rule is in), one of the special builtin targets which decide
-the fate of the packet immediately, or an extension (see \fBEXTENSIONS\fP
-below).  If this
-option is omitted in a rule (and \fB\-g\fP
-is not used), then matching the rule will have no
-effect on the packet's fate, but the counters on the rule will be
-incremented.
-.TP
-\fB\-g\fP, \fB\-\-goto\fP \fIchain\fP
-This specifies that the processing should continue in a user
-specified chain. Unlike the \-\-jump option return will not continue
-processing in this chain but instead in the chain that called us via
-\-\-jump.
-.TP
-[\fB!\fP] \fB\-i\fP, \fB\-\-in\-interface\fP \fIname\fP
-Name of an interface via which a packet was received (only for
-packets entering the \fBINPUT\fP, \fBFORWARD\fP and \fBPREROUTING\fP
-chains).  When the "!" argument is used before the interface name, the
-sense is inverted.  If the interface name ends in a "+", then any
-interface which begins with this name will match.  If this option is
-omitted, any interface name will match.
-.TP
-[\fB!\fP] \fB\-o\fP, \fB\-\-out\-interface\fP \fIname\fP
-Name of an interface via which a packet is going to be sent (for packets
-entering the \fBFORWARD\fP, \fBOUTPUT\fP and \fBPOSTROUTING\fP
-chains).  When the "!" argument is used before the interface name, the
-sense is inverted.  If the interface name ends in a "+", then any
-interface which begins with this name will match.  If this option is
-omitted, any interface name will match.
-.\" Currently not supported (header-based)
-.\" .TP
-.\" [\fB!\fP] \fB\-f\fP, \fB\-\-fragment\fP
-.\" This means that the rule only refers to second and further fragments
-.\" of fragmented packets.  Since there is no way to tell the source or
-.\" destination ports of such a packet (or ICMP type), such a packet will
-.\" not match any rules which specify them.  When the "!" argument
-.\" precedes the "\-f" flag, the rule will only match head fragments, or
-.\" unfragmented packets.
-.TP
-\fB\-c\fP, \fB\-\-set\-counters\fP \fIpackets bytes\fP
-This enables the administrator to initialize the packet and byte
-counters of a rule (during \fBINSERT\fP, \fBAPPEND\fP, \fBREPLACE\fP
-operations).
-.SS "OTHER OPTIONS"
-The following additional options can be specified:
-.TP
-\fB\-v\fP, \fB\-\-verbose\fP
-Verbose output.  This option makes the list command show the interface
-name, the rule options (if any), and the TOS masks.  The packet and
-byte counters are also listed, with the suffix 'K', 'M' or 'G' for
-1000, 1,000,000 and 1,000,000,000 multipliers respectively (but see
-the \fB\-x\fP flag to change this).
-For appending, insertion, deletion and replacement, this causes
-detailed information on the rule or rules to be printed.
-.TP
-\fB\-n\fP, \fB\-\-numeric\fP
-Numeric output.
-IP addresses and port numbers will be printed in numeric format.
-By default, the program will try to display them as host names,
-network names, or services (whenever applicable).
-.TP
-\fB\-x\fP, \fB\-\-exact\fP
-Expand numbers.
-Display the exact value of the packet and byte counters,
-instead of only the rounded number in K's (multiples of 1000)
-M's (multiples of 1000K) or G's (multiples of 1000M).  This option is
-only relevant for the \fB\-L\fP command.
-.TP
-\fB\-\-line\-numbers\fP
-When listing rules, add line numbers to the beginning of each rule,
-corresponding to that rule's position in the chain.
-.TP
-\fB\-\-modprobe=\fP\fIcommand\fP
-When adding or inserting rules into a chain, use \fIcommand\fP
-to load any necessary modules (targets, match extensions, etc).
-.SH MATCH EXTENSIONS
-ip6tables can use extended packet matching modules.  These are loaded
-in two ways: implicitly, when \fB\-p\fP or \fB\-\-protocol\fP
-is specified, or with the \fB\-m\fP or \fB\-\-match\fP
-options, followed by the matching module name; after these, various
-extra command line options become available, depending on the specific
-module.  You can specify multiple extended match modules in one line,
-and you can use the \fB\-h\fP or \fB\-\-help\fP
-options after the module has been specified to receive help specific
-to that module.
-.PP
-The following are included in the base package, and most of these can
-be preceded by a "\fB!\fP" to invert the sense of the match.
-.\" @MATCH@
-.SH TARGET EXTENSIONS
-ip6tables can use extended target modules: the following are included
-in the standard distribution.
-.\" @TARGET@
-.SH DIAGNOSTICS
-Various error messages are printed to standard error.  The exit code
-is 0 for correct functioning.  Errors which appear to be caused by
-invalid or abused command line parameters cause an exit code of 2, and
-other errors cause an exit code of 1.
-.SH BUGS
-Bugs?  What's this? ;-)
-Well... the counters are not reliable on sparc64.
-.SH COMPATIBILITY WITH IPCHAINS
-This \fBip6tables\fP
-is very similar to ipchains by Rusty Russell.  The main difference is
-that the chains \fBINPUT\fP and \fBOUTPUT\fP
-are only traversed for packets coming into the local host and
-originating from the local host respectively.  Hence every packet only
-passes through one of the three chains (except loopback traffic, which
-involves both INPUT and OUTPUT chains); previously a forwarded packet
-would pass through all three.
-.PP
-The other main difference is that \fB\-i\fP refers to the input interface;
-\fB\-o\fP refers to the output interface, and both are available for packets
-entering the \fBFORWARD\fP chain.
-There are several other changes in ip6tables.
-.SH SEE ALSO
-\fBip6tables\-save\fP(8),
-\fBip6tables\-restore\fP(8),
-\fBiptables\fP(8),
-\fBiptables\-save\fP(8),
-\fBiptables\-restore\fP(8),
-\fBlibipq\fP(3).
-.PP
-The packet-filtering-HOWTO details iptables usage for
-packet filtering,
-the netfilter-extensions-HOWTO details the extensions that are
-not in the standard distribution,
-and the netfilter-hacking-HOWTO details the netfilter internals.
-.br
-See
-.BR "http://www.netfilter.org/" .
-.SH AUTHORS
-Rusty Russell wrote iptables, in early consultation with Michael
-Neuling.
-.PP
-Marc Boucher made Rusty abandon ipnatctl by lobbying for a generic packet
-selection framework in iptables, then wrote the mangle table, the owner match,
-the mark stuff, and ran around doing cool stuff everywhere.
-.PP
-James Morris wrote the TOS target, and tos match.
-.PP
-Jozsef Kadlecsik wrote the REJECT target.
-.PP
-Harald Welte wrote the ULOG and NFQUEUE target, the new libiptc, as well as TTL match+target and libipulog.
-.PP
-The Netfilter Core Team is: Marc Boucher, Martin Josefsson, Yasuyuki Kozakai,
-Jozsef Kadlecsik, Patrick McHardy, James Morris, Pablo Neira Ayuso,
-Harald Welte and Rusty Russell.
-.PP
-ip6tables man page created by Andras Kis-Szabo, based on
-iptables man page written by Herve Eychenne <rv@wallfire.org>.
-.\" .. and did I mention that we are incredibly cool people?
-.\" .. sexy, too ..
-.\" .. witty, charming, powerful ..
-.\" .. and most of all, modest ..
-.SH VERSION
-.PP
-This manual page applies to ip6tables @PACKAGE_VERSION@.
diff --git a/iptables/ip6tables.c b/iptables/ip6tables.c
index 4037acf..c95355b 100644
--- a/iptables/ip6tables.c
+++ b/iptables/ip6tables.c
@@ -24,7 +24,7 @@
  *	along with this program; if not, write to the Free Software
  *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
-
+#include "config.h"
 #include <getopt.h>
 #include <string.h>
 #include <netdb.h>
@@ -45,51 +45,7 @@
 #include "ip6tables-multi.h"
 #include "xshared.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',
-				 'Z', 'N', 'X', 'P', 'E', 'S', 'C' };
-
-#define NUMBER_OF_OPT	ARRAY_SIZE(optflags)
-static const char optflags[]
-= { 'n', 's', 'd', 'p', 'j', 'v', 'x', 'i', 'o', '0', 'c'};
+static const char unsupported_rev[] = " [unsupported revision]";
 
 static struct option original_opts[] = {
 	{.name = "append",        .has_arg = 1, .val = 'A'},
@@ -117,6 +73,8 @@
 	{.name = "numeric",       .has_arg = 0, .val = 'n'},
 	{.name = "out-interface", .has_arg = 1, .val = 'o'},
 	{.name = "verbose",       .has_arg = 0, .val = 'v'},
+	{.name = "wait",          .has_arg = 2, .val = 'w'},
+	{.name = "wait-interval", .has_arg = 2, .val = 'W'},
 	{.name = "exact",         .has_arg = 0, .val = 'x'},
 	{.name = "version",       .has_arg = 0, .val = 'V'},
 	{.name = "help",          .has_arg = 2, .val = 'h'},
@@ -132,39 +90,10 @@
 void ip6tables_exit_error(enum xtables_exittype status, const char *msg, ...) __attribute__((noreturn, format(printf,2,3)));
 struct xtables_globals ip6tables_globals = {
 	.option_offset = 0,
-	.program_version = IPTABLES_VERSION,
+	.program_version = PACKAGE_VERSION,
 	.orig_opts = original_opts,
 	.exit_err = ip6tables_exit_error,
-};
-
-/* 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 */
-/*INSERT*/    {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' '},
-/*DELETE*/    {'x',' ',' ',' ',' ',' ','x',' ',' ','x','x'},
-/*DELETE_NUM*/{'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'},
-/*FLUSH*/     {'x','x','x','x','x',' ','x','x','x','x','x'},
-/*ZERO*/      {'x','x','x','x','x',' ','x','x','x','x','x'},
-/*ZERO_NUM*/  {'x','x','x','x','x',' ','x','x','x','x','x'},
-/*NEW_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x','x'},
-/*DEL_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x','x'},
-/*SET_POLICY*/{'x','x','x','x','x',' ','x','x','x','x',' '},
-/*RENAME*/    {'x','x','x','x','x',' ','x','x','x','x','x'},
-/*LIST_RULES*/{'x','x','x','x','x',' ','x','x','x','x','x'},
-/*CHECK*/     {'x',' ',' ',' ',' ',' ','x',' ',' ','x','x'},
+	.compat_rev = xtables_compatible_revision,
 };
 
 static const unsigned int inverse_for_options[NUMBER_OF_OPT] =
@@ -172,7 +101,7 @@
 /* -n */ 0,
 /* -s */ IP6T_INV_SRCIP,
 /* -d */ IP6T_INV_DSTIP,
-/* -p */ IP6T_INV_PROTO,
+/* -p */ XT_INV_PROTO,
 /* -j */ 0,
 /* -v */ 0,
 /* -x */ 0,
@@ -185,12 +114,6 @@
 #define opts ip6tables_globals.opts
 #define prog_name ip6tables_globals.program_name
 #define prog_vers ip6tables_globals.program_version
-/* A few hardcoded protocols for 'all' and in case the user has no
-   /etc/protocols */
-struct pprot {
-	const char *name;
-	uint8_t num;
-};
 
 static void __attribute__((noreturn))
 exit_tryhelp(int status)
@@ -252,7 +175,7 @@
 "Options:\n"
 "    --ipv4	-4		Error (line is ignored by ip6tables-restore)\n"
 "    --ipv6	-6		Nothing (line is ignored by iptables-restore)\n"
-"[!] --proto	-p proto	protocol: by number or name, eg. `tcp'\n"
+"[!] --protocol	-p proto	protocol: by number or name, eg. `tcp'\n"
 "[!] --source	-s address[/mask][,...]\n"
 "				source specification\n"
 "[!] --destination -d address[/mask][,...]\n"
@@ -272,6 +195,10 @@
 "				network interface name ([+] for wildcard)\n"
 "  --table	-t table	table to manipulate (default: `filter')\n"
 "  --verbose	-v		verbose mode\n"
+"  --wait	-w [seconds]	maximum wait to acquire xtables lock before give up\n"
+"  --wait-interval -W [usecs]	wait time to try to acquire xtables lock\n"
+"				interval to wait for xtables lock\n"
+"				default is 1 second\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"*/
@@ -289,7 +216,7 @@
 	va_list args;
 
 	va_start(args, msg);
-	fprintf(stderr, "%s v%s: ", prog_name, prog_vers);
+	fprintf(stderr, "%s v%s (legacy): ", prog_name, prog_vers);
 	vfprintf(stderr, msg, args);
 	va_end(args);
 	fprintf(stderr, "\n");
@@ -303,72 +230,6 @@
 	exit(status);
 }
 
-static void
-generic_opt_check(int command, int options)
-{
-	int i, j, legal = 0;
-
-	/* Check that commands are valid with options.  Complicated by the
-	 * fact that if an option is legal with *any* command given, it is
-	 * legal overall (ie. -z and -l).
-	 */
-	for (i = 0; i < NUMBER_OF_OPT; i++) {
-		legal = 0; /* -1 => illegal, 1 => legal, 0 => undecided. */
-
-		for (j = 0; j < NUMBER_OF_CMD; j++) {
-			if (!(command & (1<<j)))
-				continue;
-
-			if (!(options & (1<<i))) {
-				if (commands_v_options[j][i] == '+')
-					xtables_error(PARAMETER_PROBLEM,
-						   "You need to supply the `-%c' "
-						   "option for this command\n",
-						   optflags[i]);
-			} else {
-				if (commands_v_options[j][i] != 'x')
-					legal = 1;
-				else if (legal == 0)
-					legal = -1;
-			}
-		}
-		if (legal == -1)
-			xtables_error(PARAMETER_PROBLEM,
-				   "Illegal option `-%c' with this command\n",
-				   optflags[i]);
-	}
-}
-
-static char
-opt2char(int option)
-{
-	const char *ptr;
-	for (ptr = optflags; option > 1; option >>= 1, ptr++);
-
-	return *ptr;
-}
-
-static char
-cmd2char(int option)
-{
-	const char *ptr;
-	for (ptr = cmdflags; option > 1; option >>= 1, ptr++);
-
-	return *ptr;
-}
-
-static void
-add_command(unsigned int *cmd, const int newcmd, const int othercmds,
-	    int invert)
-{
-	if (invert)
-		xtables_error(PARAMETER_PROBLEM, "unexpected '!' flag");
-	if (*cmd & (~othercmds))
-		xtables_error(PARAMETER_PROBLEM, "Cannot use -%c with -%c\n",
-			   cmd2char(newcmd), cmd2char(*cmd & (~othercmds)));
-	*cmd |= newcmd;
-}
-
 /*
  *	All functions starting with "parse" should succeed, otherwise
  *	the program fails.
@@ -387,38 +248,30 @@
 		proto == IPPROTO_DSTOPTS);
 }
 
-/* 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)
+static void
+parse_chain(const char *chainname)
 {
 	const char *ptr;
 
-	if (strlen(targetname) < 1)
+	if (strlen(chainname) >= XT_EXTENSION_MAXNAMELEN)
 		xtables_error(PARAMETER_PROBLEM,
-			   "Invalid target name (too short)");
+			   "chain name `%s' too long (must be under %u chars)",
+			   chainname, XT_EXTENSION_MAXNAMELEN);
 
-	if (strlen(targetname) >= XT_EXTENSION_MAXNAMELEN)
+	if (*chainname == '-' || *chainname == '!')
 		xtables_error(PARAMETER_PROBLEM,
-			   "Invalid target name `%s' (%u chars max)",
-			   targetname, XT_EXTENSION_MAXNAMELEN - 1);
+			   "chain name not allowed to start "
+			   "with `%c'\n", *chainname);
 
-	for (ptr = targetname; *ptr; ptr++)
+	if (xtables_find_target(chainname, XTF_TRY_LOAD))
+		xtables_error(PARAMETER_PROBLEM,
+			   "chain name may not clash "
+			   "with target name\n");
+
+	for (ptr = chainname; *ptr; ptr++)
 		if (isspace(*ptr))
 			xtables_error(PARAMETER_PROBLEM,
-				   "Invalid target name `%s'", targetname);
-	return targetname;
+				   "Invalid chain name `%s'", chainname);
 }
 
 static void
@@ -442,45 +295,20 @@
 	}
 }
 
-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, struct ip6tc_handle *handle)
+print_header(unsigned int format, const char *chain, struct xtc_handle *handle)
 {
-	struct ip6t_counters counters;
+	struct xt_counters counters;
 	const char *pol = ip6tc_get_policy(chain, &counters, handle);
 	printf("Chain %s", chain);
 	if (pol) {
 		printf(" (policy %s", pol);
 		if (!(format & FMT_NOCOUNTS)) {
 			fputc(' ', stdout);
-			print_num(counters.pcnt, (format|FMT_NOTABLE));
+			xtables_print_num(counters.pcnt, (format|FMT_NOTABLE));
 			fputs("packets, ", stdout);
-			print_num(counters.bcnt, (format|FMT_NOTABLE));
+			xtables_print_num(counters.bcnt, (format|FMT_NOTABLE));
 			fputs("bytes", stdout);
 		}
 		printf(")\n");
@@ -519,21 +347,27 @@
 
 
 static int
-print_match(const struct ip6t_entry_match *m,
+print_match(const struct xt_entry_match *m,
 	    const struct ip6t_ip6 *ip,
 	    int numeric)
 {
-	const struct xtables_match *match =
-		xtables_find_match(m->u.user.name, XTF_TRY_LOAD, NULL);
+	const char *name = m->u.user.name;
+	const int revision = m->u.user.revision;
+	struct xtables_match *match, *mt;
 
+	match = xtables_find_match(name, XTF_TRY_LOAD, NULL);
 	if (match) {
-		if (match->print)
-			match->print(ip, m, numeric);
+		mt = xtables_find_match_revision(name, XTF_TRY_LOAD,
+						 match, revision);
+		if (mt && mt->print)
+			mt->print(ip, m, numeric);
+		else if (match->print)
+			printf("%s%s ", match->name, unsupported_rev);
 		else
 			printf("%s ", match->name);
 	} else {
-		if (m->u.user.name[0])
-			printf("UNKNOWN match `%s' ", m->u.user.name);
+		if (name[0])
+			printf("UNKNOWN match `%s' ", name);
 	}
 	/* Don't stop iterating. */
 	return 0;
@@ -545,16 +379,15 @@
 	       const char *targname,
 	       unsigned int num,
 	       unsigned int format,
-	       struct ip6tc_handle *const handle)
+	       struct xtc_handle *const handle)
 {
-	const struct xtables_target *target = NULL;
-	const struct ip6t_entry_target *t;
-	char buf[BUFSIZ];
+	struct xtables_target *target, *tg;
+	const struct xt_entry_target *t;
 
 	if (!ip6tc_is_chain(targname, handle))
 		target = xtables_find_target(targname, XTF_TRY_LOAD);
 	else
-		target = xtables_find_target(IP6T_STANDARD_TARGET,
+		target = xtables_find_target(XT_STANDARD_TARGET,
 		         XTF_LOAD_MUST_SUCCEED);
 
 	t = ip6t_get_target((struct ip6t_entry *)fw);
@@ -563,14 +396,14 @@
 		printf(FMT("%-4u ", "%u "), num);
 
 	if (!(format & FMT_NOCOUNTS)) {
-		print_num(fw->counters.pcnt, format);
-		print_num(fw->counters.bcnt, format);
+		xtables_print_num(fw->counters.pcnt, format);
+		xtables_print_num(fw->counters.bcnt, format);
 	}
 
 	if (!(format & FMT_NOTARGET))
 		printf(FMT("%-9s ", "%s "), targname);
 
-	fputc(fw->ipv6.invflags & IP6T_INV_PROTO ? '!' : ' ', stdout);
+	fputc(fw->ipv6.invflags & XT_INV_PROTO ? '!' : ' ', stdout);
 	{
 		const char *pname = proto_to_name(fw->ipv6.proto, format&FMT_NUMERIC);
 		if (pname)
@@ -587,61 +420,10 @@
 		fputc(' ', stdout);
 	}
 
-	if (format & FMT_VIA) {
-		char iface[IFNAMSIZ+2];
+	print_ifaces(fw->ipv6.iniface, fw->ipv6.outiface,
+		     fw->ipv6.invflags, format);
 
-		if (fw->ipv6.invflags & IP6T_INV_VIA_IN) {
-			iface[0] = '!';
-			iface[1] = '\0';
-		}
-		else iface[0] = '\0';
-
-		if (fw->ipv6.iniface[0] != '\0') {
-			strcat(iface, fw->ipv6.iniface);
-		}
-		else if (format & FMT_NUMERIC) strcat(iface, "*");
-		else strcat(iface, "any");
-		printf(FMT(" %-6s ","in %s "), iface);
-
-		if (fw->ipv6.invflags & IP6T_INV_VIA_OUT) {
-			iface[0] = '!';
-			iface[1] = '\0';
-		}
-		else iface[0] = '\0';
-
-		if (fw->ipv6.outiface[0] != '\0') {
-			strcat(iface, fw->ipv6.outiface);
-		}
-		else if (format & FMT_NUMERIC) strcat(iface, "*");
-		else strcat(iface, "any");
-		printf(FMT("%-6s ","out %s "), iface);
-	}
-
-	fputc(fw->ipv6.invflags & IP6T_INV_SRCIP ? '!' : ' ', stdout);
-	if (!memcmp(&fw->ipv6.smsk, &in6addr_any, sizeof in6addr_any)
-	    && !(format & FMT_NUMERIC))
-		printf(FMT("%-19s ","%s "), "anywhere");
-	else {
-		if (format & FMT_NUMERIC)
-			strcpy(buf, xtables_ip6addr_to_numeric(&fw->ipv6.src));
-		else
-			strcpy(buf, xtables_ip6addr_to_anyname(&fw->ipv6.src));
-		strcat(buf, xtables_ip6mask_to_numeric(&fw->ipv6.smsk));
-		printf(FMT("%-19s ","%s "), buf);
-	}
-
-	fputc(fw->ipv6.invflags & IP6T_INV_DSTIP ? '!' : ' ', stdout);
-	if (!memcmp(&fw->ipv6.dmsk, &in6addr_any, sizeof in6addr_any)
-	    && !(format & FMT_NUMERIC))
-		printf(FMT("%-19s ","-> %s"), "anywhere");
-	else {
-		if (format & FMT_NUMERIC)
-			strcpy(buf, xtables_ip6addr_to_numeric(&fw->ipv6.dst));
-		else
-			strcpy(buf, xtables_ip6addr_to_anyname(&fw->ipv6.dst));
-		strcat(buf, xtables_ip6mask_to_numeric(&fw->ipv6.dmsk));
-		printf(FMT("%-19s ","-> %s"), buf);
-	}
+	print_ipv6_addresses(fw, format);
 
 	if (format & FMT_NOTABLE)
 		fputs("  ", stdout);
@@ -654,9 +436,15 @@
 	IP6T_MATCH_ITERATE(fw, print_match, &fw->ipv6, format & FMT_NUMERIC);
 
 	if (target) {
-		if (target->print)
+		const int revision = t->u.user.revision;
+
+		tg = xtables_find_target_revision(targname, XTF_TRY_LOAD,
+						  target, revision);
+		if (tg && tg->print)
 			/* Print the target information. */
-			target->print(&fw->ipv6, t, format & FMT_NUMERIC);
+			tg->print(&fw->ipv6, t, format & FMT_NUMERIC);
+		else if (target->print)
+			printf(" %s%s", target->name, unsupported_rev);
 	} else if (t->u.target_size != sizeof(*t))
 		printf("[%u bytes of unknown target data] ",
 		       (unsigned int)(t->u.target_size - sizeof(*t)));
@@ -667,16 +455,16 @@
 
 static void
 print_firewall_line(const struct ip6t_entry *fw,
-		    struct ip6tc_handle *const h)
+		    struct xtc_handle *const h)
 {
-	struct ip6t_entry_target *t;
+	struct xt_entry_target *t;
 
 	t = ip6t_get_target((struct ip6t_entry *)fw);
 	print_firewall(fw, t->u.user.name, 0, FMT_PRINT_RULE, h);
 }
 
 static int
-append_entry(const ip6t_chainlabel chain,
+append_entry(const xt_chainlabel chain,
 	     struct ip6t_entry *fw,
 	     unsigned int nsaddrs,
 	     const struct in6_addr saddrs[],
@@ -685,7 +473,7 @@
 	     const struct in6_addr daddrs[],
 	     const struct in6_addr dmasks[],
 	     int verbose,
-	     struct ip6tc_handle *handle)
+	     struct xtc_handle *handle)
 {
 	unsigned int i, j;
 	int ret = 1;
@@ -706,13 +494,13 @@
 }
 
 static int
-replace_entry(const ip6t_chainlabel chain,
+replace_entry(const xt_chainlabel chain,
 	      struct ip6t_entry *fw,
 	      unsigned int rulenum,
 	      const struct in6_addr *saddr, const struct in6_addr *smask,
 	      const struct in6_addr *daddr, const struct in6_addr *dmask,
 	      int verbose,
-	      struct ip6tc_handle *handle)
+	      struct xtc_handle *handle)
 {
 	fw->ipv6.src = *saddr;
 	fw->ipv6.dst = *daddr;
@@ -725,7 +513,7 @@
 }
 
 static int
-insert_entry(const ip6t_chainlabel chain,
+insert_entry(const xt_chainlabel chain,
 	     struct ip6t_entry *fw,
 	     unsigned int rulenum,
 	     unsigned int nsaddrs,
@@ -735,7 +523,7 @@
 	     const struct in6_addr daddrs[],
 	     const struct in6_addr dmasks[],
 	     int verbose,
-	     struct ip6tc_handle *handle)
+	     struct xtc_handle *handle)
 {
 	unsigned int i, j;
 	int ret = 1;
@@ -766,10 +554,10 @@
 
 	size = sizeof(struct ip6t_entry);
 	for (matchp = matches; matchp; matchp = matchp->next)
-		size += XT_ALIGN(sizeof(struct ip6t_entry_match)) + matchp->match->size;
+		size += XT_ALIGN(sizeof(struct xt_entry_match)) + matchp->match->size;
 
 	mask = xtables_calloc(1, size
-			 + XT_ALIGN(sizeof(struct ip6t_entry_target))
+			 + XT_ALIGN(sizeof(struct xt_entry_target))
 			 + target->size);
 
 	memset(mask, 0xFF, sizeof(struct ip6t_entry));
@@ -777,20 +565,20 @@
 
 	for (matchp = matches; matchp; matchp = matchp->next) {
 		memset(mptr, 0xFF,
-		       XT_ALIGN(sizeof(struct ip6t_entry_match))
+		       XT_ALIGN(sizeof(struct xt_entry_match))
 		       + matchp->match->userspacesize);
-		mptr += XT_ALIGN(sizeof(struct ip6t_entry_match)) + matchp->match->size;
+		mptr += XT_ALIGN(sizeof(struct xt_entry_match)) + matchp->match->size;
 	}
 
 	memset(mptr, 0xFF,
-	       XT_ALIGN(sizeof(struct ip6t_entry_target))
+	       XT_ALIGN(sizeof(struct xt_entry_target))
 	       + target->userspacesize);
 
 	return mask;
 }
 
 static int
-delete_entry(const ip6t_chainlabel chain,
+delete_entry(const xt_chainlabel chain,
 	     struct ip6t_entry *fw,
 	     unsigned int nsaddrs,
 	     const struct in6_addr saddrs[],
@@ -799,7 +587,7 @@
 	     const struct in6_addr daddrs[],
 	     const struct in6_addr dmasks[],
 	     int verbose,
-	     struct ip6tc_handle *handle,
+	     struct xtc_handle *handle,
 	     struct xtables_rule_match *matches,
 	     const struct xtables_target *target)
 {
@@ -825,11 +613,11 @@
 }
 
 static int
-check_entry(const ip6t_chainlabel chain, struct ip6t_entry *fw,
+check_entry(const xt_chainlabel chain, struct ip6t_entry *fw,
 	    unsigned int nsaddrs, const struct in6_addr *saddrs,
 	    const struct in6_addr *smasks, unsigned int ndaddrs,
 	    const struct in6_addr *daddrs, const struct in6_addr *dmasks,
-	    bool verbose, struct ip6tc_handle *handle,
+	    bool verbose, struct xtc_handle *handle,
 	    struct xtables_rule_match *matches,
 	    const struct xtables_target *target)
 {
@@ -855,8 +643,8 @@
 }
 
 int
-for_each_chain6(int (*fn)(const ip6t_chainlabel, int, struct ip6tc_handle *),
-	       int verbose, int builtinstoo, struct ip6tc_handle *handle)
+for_each_chain6(int (*fn)(const xt_chainlabel, int, struct xtc_handle *),
+	       int verbose, int builtinstoo, struct xtc_handle *handle)
 {
 	int ret = 1;
 	const char *chain;
@@ -869,21 +657,21 @@
 		chain = ip6tc_next_chain(handle);
 	}
 
-	chains = xtables_malloc(sizeof(ip6t_chainlabel) * chaincount);
+	chains = xtables_malloc(sizeof(xt_chainlabel) * chaincount);
 	i = 0;
 	chain = ip6tc_first_chain(handle);
 	while (chain) {
-		strcpy(chains + i*sizeof(ip6t_chainlabel), chain);
+		strcpy(chains + i*sizeof(xt_chainlabel), chain);
 		i++;
 		chain = ip6tc_next_chain(handle);
 	}
 
 	for (i = 0; i < chaincount; i++) {
 		if (!builtinstoo
-		    && ip6tc_builtin(chains + i*sizeof(ip6t_chainlabel),
+		    && ip6tc_builtin(chains + i*sizeof(xt_chainlabel),
 				    handle) == 1)
 			continue;
-		ret &= fn(chains + i*sizeof(ip6t_chainlabel), verbose, handle);
+		ret &= fn(chains + i*sizeof(xt_chainlabel), verbose, handle);
 	}
 
 	free(chains);
@@ -891,8 +679,8 @@
 }
 
 int
-flush_entries6(const ip6t_chainlabel chain, int verbose,
-	      struct ip6tc_handle *handle)
+flush_entries6(const xt_chainlabel chain, int verbose,
+	      struct xtc_handle *handle)
 {
 	if (!chain)
 		return for_each_chain6(flush_entries6, verbose, 1, handle);
@@ -903,8 +691,8 @@
 }
 
 static int
-zero_entries(const ip6t_chainlabel chain, int verbose,
-	     struct ip6tc_handle *handle)
+zero_entries(const xt_chainlabel chain, int verbose,
+	     struct xtc_handle *handle)
 {
 	if (!chain)
 		return for_each_chain6(zero_entries, verbose, 1, handle);
@@ -915,8 +703,8 @@
 }
 
 int
-delete_chain6(const ip6t_chainlabel chain, int verbose,
-	     struct ip6tc_handle *handle)
+delete_chain6(const xt_chainlabel chain, int verbose,
+	     struct xtc_handle *handle)
 {
 	if (!chain)
 		return for_each_chain6(delete_chain6, verbose, 0, handle);
@@ -927,8 +715,8 @@
 }
 
 static int
-list_entries(const ip6t_chainlabel chain, int rulenum, int verbose, int numeric,
-	     int expanded, int linenumbers, struct ip6tc_handle *handle)
+list_entries(const xt_chainlabel chain, int rulenum, int verbose, int numeric,
+	     int expanded, int linenumbers, struct xtc_handle *handle)
 {
 	int found = 0;
 	unsigned int format;
@@ -1033,35 +821,43 @@
 	}
 }
 
-static int print_match_save(const struct ip6t_entry_match *e,
+static int print_match_save(const struct xt_entry_match *e,
 			const struct ip6t_ip6 *ip)
 {
-	const struct xtables_match *match =
-		xtables_find_match(e->u.user.name, XTF_TRY_LOAD, NULL);
+	const char *name = e->u.user.name;
+	const int revision = e->u.user.revision;
+	struct xtables_match *match, *mt, *mt2;
 
+	match = xtables_find_match(name, XTF_TRY_LOAD, NULL);
 	if (match) {
-		printf(" -m %s", e->u.user.name);
+		mt = mt2 = xtables_find_match_revision(name, XTF_TRY_LOAD,
+						       match, revision);
+		if (!mt2)
+			mt2 = match;
+		printf(" -m %s", mt2->alias ? mt2->alias(e) : name);
 
 		/* some matches don't provide a save function */
-		if (match->save)
-			match->save(ip, e);
+		if (mt && mt->save)
+			mt->save(ip, e);
+		else if (match->save)
+			printf(unsupported_rev);
 	} else {
 		if (e->u.match_size) {
 			fprintf(stderr,
 				"Can't find library for match `%s'\n",
-				e->u.user.name);
+				name);
 			exit(1);
 		}
 	}
 	return 0;
 }
 
-/* print a given ip including mask if neccessary */
+/* Print a given ip including mask if necessary. */
 static void print_ip(const char *prefix, const struct in6_addr *ip,
 		     const struct in6_addr *mask, int invert)
 {
 	char buf[51];
-	int l = ipv6_prefix_length(mask);
+	int l = xtables_ip6mask_to_cidr(mask);
 
 	if (l == 0 && !invert)
 		return;
@@ -1077,12 +873,13 @@
 		printf("/%d", l);
 }
 
-/* We want this to be readable, so only print out neccessary fields.
- * Because that's the kind of world I want to live in.  */
+/* We want this to be readable, so only print out necessary fields.
+ * Because that's the kind of world I want to live in.
+ */
 void print_rule6(const struct ip6t_entry *e,
-		       struct ip6tc_handle *h, const char *chain, int counters)
+		       struct xtc_handle *h, const char *chain, int counters)
 {
-	const struct ip6t_entry_target *t;
+	const struct xt_entry_target *t;
 	const char *target_name;
 
 	/* print counters for iptables-save */
@@ -1105,7 +902,7 @@
 	print_iface('o', e->ipv6.outiface, e->ipv6.outiface_mask,
 		    e->ipv6.invflags & IP6T_INV_VIA_OUT);
 
-	print_proto(e->ipv6.proto, e->ipv6.invflags & IP6T_INV_PROTO);
+	print_proto(e->ipv6.proto, e->ipv6.invflags & XT_INV_PROTO);
 
 #if 0
 	/* not definied in ipv6
@@ -1129,48 +926,56 @@
 	if (counters < 0)
 		printf(" -c %llu %llu", (unsigned long long)e->counters.pcnt, (unsigned long long)e->counters.bcnt);
 
-	/* Print target name */
+	/* Print target name and targinfo part */
 	target_name = ip6tc_get_target(e, h);
-	if (target_name && (*target_name != '\0'))
+	t = ip6t_get_target((struct ip6t_entry *)e);
+	if (t->u.user.name[0]) {
+		const char *name = t->u.user.name;
+		const int revision = t->u.user.revision;
+		struct xtables_target *target, *tg, *tg2;
+
+		target = xtables_find_target(name, XTF_TRY_LOAD);
+		if (!target) {
+			fprintf(stderr, "Can't find library for target `%s'\n",
+				name);
+			exit(1);
+		}
+
+		tg = tg2 = xtables_find_target_revision(name, XTF_TRY_LOAD,
+							target, revision);
+		if (!tg2)
+			tg2 = target;
+		printf(" -j %s", tg2->alias ? tg2->alias(t) : target_name);
+
+		if (tg && tg->save)
+			tg->save(&e->ipv6, t);
+		else if (target->save)
+			printf(unsupported_rev);
+		else {
+			/* If the target size is greater than xt_entry_target
+			 * there is something to be saved, we just don't know
+			 * how to print it */
+			if (t->u.target_size !=
+			    sizeof(struct xt_entry_target)) {
+				fprintf(stderr, "Target `%s' is missing "
+						"save function\n",
+					name);
+				exit(1);
+			}
+		}
+	} else if (target_name && (*target_name != '\0'))
 #ifdef IP6T_F_GOTO
 		printf(" -%c %s", e->ipv6.flags & IP6T_F_GOTO ? 'g' : 'j', target_name);
 #else
 		printf(" -j %s", target_name);
 #endif
 
-	/* Print targinfo part */
-	t = ip6t_get_target((struct ip6t_entry *)e);
-	if (t->u.user.name[0]) {
-		struct xtables_target *target =
-			xtables_find_target(t->u.user.name, XTF_TRY_LOAD);
-
-		if (!target) {
-			fprintf(stderr, "Can't find library for target `%s'\n",
-				t->u.user.name);
-			exit(1);
-		}
-
-		if (target->save)
-			target->save(&e->ipv6, t);
-		else {
-			/* If the target size is greater than ip6t_entry_target
-			 * there is something to be saved, we just don't know
-			 * how to print it */
-			if (t->u.target_size !=
-			    sizeof(struct ip6t_entry_target)) {
-				fprintf(stderr, "Target `%s' is missing "
-						"save function\n",
-					t->u.user.name);
-				exit(1);
-			}
-		}
-	}
 	printf("\n");
 }
 
 static int
-list_rules(const ip6t_chainlabel chain, int rulenum, int counters,
-	     struct ip6tc_handle *handle)
+list_rules(const xt_chainlabel chain, int rulenum, int counters,
+	     struct xtc_handle *handle)
 {
 	const char *this = NULL;
 	int found = 0;
@@ -1187,7 +992,7 @@
 			continue;
 
 		if (ip6tc_builtin(this, handle)) {
-			struct ip6t_counters count;
+			struct xt_counters count;
 			printf("-P %s %s", this, ip6tc_get_policy(this, &count, handle));
 			if (counters)
 			    printf(" -c %llu %llu", (unsigned long long)count.pcnt, (unsigned long long)count.bcnt);
@@ -1224,7 +1029,7 @@
 static struct ip6t_entry *
 generate_entry(const struct ip6t_entry *fw,
 	       struct xtables_rule_match *matches,
-	       struct ip6t_entry_target *target)
+	       struct xt_entry_target *target)
 {
 	unsigned int size;
 	struct xtables_rule_match *matchp;
@@ -1249,96 +1054,24 @@
 	return e;
 }
 
-static void clear_rule_matches(struct xtables_rule_match **matches)
+int do_command6(int argc, char *argv[], char **table,
+		struct xtc_handle **handle, bool restore)
 {
-	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->fw6.ipv6.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 ip6t_entry_target)) + cs->target->size;
-
-	cs->target->t = xtables_calloc(1, size);
-	cs->target->t->u.target_size = size;
-	strcpy(cs->target->t->u.user.name, cs->jumpto);
-	cs->target->t->u.user.revision = cs->target->revision;
-	if (cs->target->init != NULL)
-		cs->target->init(cs->target->t);
-	if (cs->target->x6_options != NULL)
-		opts = xtables_options_xfrm(ip6tables_globals.orig_opts, opts,
-					    cs->target->x6_options,
-					    &cs->target->option_offset);
-	else
-		opts = xtables_merge_options(ip6tables_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 ip6t_entry_match)) + m->size;
-	m->m = xtables_calloc(1, size);
-	m->m->u.match_size = size;
-	strcpy(m->m->u.user.name, m->name);
-	m->m->u.user.revision = m->revision;
-	if (m->init != NULL)
-		m->init(m->m);
-	if (m == m->next)
-		return;
-	/* Merge options for non-cloned matches */
-	if (m->x6_options != NULL)
-		opts = xtables_options_xfrm(ip6tables_globals.orig_opts, opts,
-					    m->x6_options, &m->option_offset);
-	else if (m->extra_opts != NULL)
-		opts = xtables_merge_options(ip6tables_globals.orig_opts, opts,
-					     m->extra_opts, &m->option_offset);
-}
-
-int do_command6(int argc, char *argv[], char **table, struct ip6tc_handle **handle)
-{
-	struct iptables_command_state cs;
+	struct iptables_command_state cs = {
+		.jumpto	= "",
+		.argv	= argv,
+	};
 	struct ip6t_entry *e = NULL;
 	unsigned int nsaddrs = 0, ndaddrs = 0;
 	struct in6_addr *saddrs = NULL, *daddrs = NULL;
 	struct in6_addr *smasks = NULL, *dmasks = NULL;
 
 	int verbose = 0;
+	int wait = 0;
+	struct timeval wait_interval = {
+		.tv_sec	= 1,
+	};
+	bool wait_interval_set = false;
 	const char *chain = NULL;
 	const char *shostnetworkmask = NULL, *dhostnetworkmask = NULL;
 	const char *policy = NULL, *newname = NULL;
@@ -1349,10 +1082,7 @@
 	struct xtables_rule_match *matchp;
 	struct xtables_target *t;
 	unsigned long long cnt;
-
-	memset(&cs, 0, sizeof(cs));
-	cs.jumpto = "";
-	cs.argv = argv;
+	bool table_set = false;
 
 	/* re-set optind to 0 in case do_command6 gets called
 	 * a second time */
@@ -1374,7 +1104,7 @@
 
 	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:bvnt:m:xc:g:46",
+	   "-:A:C:D:R:I:L::S::M:F::Z::N:X::E:P:Vh::o:p:s:d:j:i:bvw::W::nt:m:xc:g:46",
 					   opts, NULL)) != -1) {
 		switch (cs.c) {
 			/*
@@ -1396,8 +1126,7 @@
 			add_command(&command, CMD_DELETE, CMD_NONE,
 				    cs.invert);
 			chain = optarg;
-			if (optind < argc && argv[optind][0] != '-'
-			    && argv[optind][0] != '!') {
+			if (xs_has_arg(argc, argv)) {
 				rulenum = parse_rulenumber(argv[optind++]);
 				command = CMD_DELETE_NUM;
 			}
@@ -1407,8 +1136,7 @@
 			add_command(&command, CMD_REPLACE, CMD_NONE,
 				    cs.invert);
 			chain = optarg;
-			if (optind < argc && argv[optind][0] != '-'
-			    && argv[optind][0] != '!')
+			if (xs_has_arg(argc, argv))
 				rulenum = parse_rulenumber(argv[optind++]);
 			else
 				xtables_error(PARAMETER_PROBLEM,
@@ -1420,8 +1148,7 @@
 			add_command(&command, CMD_INSERT, CMD_NONE,
 				    cs.invert);
 			chain = optarg;
-			if (optind < argc && argv[optind][0] != '-'
-			    && argv[optind][0] != '!')
+			if (xs_has_arg(argc, argv))
 				rulenum = parse_rulenumber(argv[optind++]);
 			else rulenum = 1;
 			break;
@@ -1430,11 +1157,9 @@
 			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] != '!')
+			else if (xs_has_arg(argc, argv))
 				chain = argv[optind++];
-			if (optind < argc && argv[optind][0] != '-'
-			    && argv[optind][0] != '!')
+			if (xs_has_arg(argc, argv))
 				rulenum = parse_rulenumber(argv[optind++]);
 			break;
 
@@ -1442,11 +1167,9 @@
 			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] != '!')
+			else if (xs_has_arg(argc, argv))
 				chain = argv[optind++];
-			if (optind < argc && argv[optind][0] != '-'
-			    && argv[optind][0] != '!')
+			if (xs_has_arg(argc, argv))
 				rulenum = parse_rulenumber(argv[optind++]);
 			break;
 
@@ -1454,8 +1177,7 @@
 			add_command(&command, CMD_FLUSH, CMD_NONE,
 				    cs.invert);
 			if (optarg) chain = optarg;
-			else if (optind < argc && argv[optind][0] != '-'
-				 && argv[optind][0] != '!')
+			else if (xs_has_arg(argc, argv))
 				chain = argv[optind++];
 			break;
 
@@ -1463,25 +1185,16 @@
 			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] != '!')
+			else if (xs_has_arg(argc, argv))
 				chain = argv[optind++];
-			if (optind < argc && argv[optind][0] != '-'
-				&& argv[optind][0] != '!') {
+			if (xs_has_arg(argc, argv)) {
 				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");
+			parse_chain(optarg);
 			add_command(&command, CMD_NEW_CHAIN, CMD_NONE,
 				    cs.invert);
 			chain = optarg;
@@ -1491,8 +1204,7 @@
 			add_command(&command, CMD_DELETE_CHAIN, CMD_NONE,
 				    cs.invert);
 			if (optarg) chain = optarg;
-			else if (optind < argc && argv[optind][0] != '-'
-				 && argv[optind][0] != '!')
+			else if (xs_has_arg(argc, argv))
 				chain = argv[optind++];
 			break;
 
@@ -1500,8 +1212,7 @@
 			add_command(&command, CMD_RENAME_CHAIN, CMD_NONE,
 				    cs.invert);
 			chain = optarg;
-			if (optind < argc && argv[optind][0] != '-'
-			    && argv[optind][0] != '!')
+			if (xs_has_arg(argc, argv))
 				newname = argv[optind++];
 			else
 				xtables_error(PARAMETER_PROBLEM,
@@ -1514,8 +1225,7 @@
 			add_command(&command, CMD_SET_POLICY, CMD_NONE,
 				    cs.invert);
 			chain = optarg;
-			if (optind < argc && argv[optind][0] != '-'
-			    && argv[optind][0] != '!')
+			if (xs_has_arg(argc, argv))
 				policy = argv[optind++];
 			else
 				xtables_error(PARAMETER_PROBLEM,
@@ -1538,7 +1248,6 @@
 			 * Option selection
 			 */
 		case 'p':
-			xtables_check_inverse(optarg, &cs.invert, &optind, argc, argv);
 			set_option(&cs.options, OPT_PROTOCOL, &cs.fw6.ipv6.invflags,
 				   cs.invert);
 
@@ -1551,12 +1260,12 @@
 			cs.fw6.ipv6.flags |= IP6T_F_PROTO;
 
 			if (cs.fw6.ipv6.proto == 0
-			    && (cs.fw6.ipv6.invflags & IP6T_INV_PROTO))
+			    && (cs.fw6.ipv6.invflags & XT_INV_PROTO))
 				xtables_error(PARAMETER_PROBLEM,
 					   "rule would never match protocol");
 
 			if (is_exthdr(cs.fw6.ipv6.proto)
-			    && (cs.fw6.ipv6.invflags & IP6T_INV_PROTO) == 0)
+			    && (cs.fw6.ipv6.invflags & XT_INV_PROTO) == 0)
 				fprintf(stderr,
 					"Warning: never matched protocol: %s. "
 					"use extension match instead.\n",
@@ -1564,14 +1273,12 @@
 			break;
 
 		case 's':
-			xtables_check_inverse(optarg, &cs.invert, &optind, argc, argv);
 			set_option(&cs.options, OPT_SOURCE, &cs.fw6.ipv6.invflags,
 				   cs.invert);
 			shostnetworkmask = optarg;
 			break;
 
 		case 'd':
-			xtables_check_inverse(optarg, &cs.invert, &optind, argc, argv);
 			set_option(&cs.options, OPT_DESTINATION, &cs.fw6.ipv6.invflags,
 				   cs.invert);
 			dhostnetworkmask = optarg;
@@ -1582,12 +1289,14 @@
 			set_option(&cs.options, OPT_JUMP, &cs.fw6.ipv6.invflags,
 					cs.invert);
 			cs.fw6.ipv6.flags |= IP6T_F_GOTO;
-			cs.jumpto = parse_target(optarg);
+			cs.jumpto = xt_parse_target(optarg);
 			break;
 #endif
 
 		case 'j':
-			command_jump(&cs);
+			set_option(&cs.options, OPT_JUMP, &cs.fw6.ipv6.invflags,
+					cs.invert);
+			command_jump(&cs, optarg);
 			break;
 
 
@@ -1596,7 +1305,6 @@
 				xtables_error(PARAMETER_PROBLEM,
 					"Empty interface is likely to be "
 					"undesired");
-			xtables_check_inverse(optarg, &cs.invert, &optind, argc, argv);
 			set_option(&cs.options, OPT_VIANAMEIN, &cs.fw6.ipv6.invflags,
 				   cs.invert);
 			xtables_parse_interface(optarg,
@@ -1609,7 +1317,6 @@
 				xtables_error(PARAMETER_PROBLEM,
 					"Empty interface is likely to be "
 					"undesired");
-			xtables_check_inverse(optarg, &cs.invert, &optind, argc, argv);
 			set_option(&cs.options, OPT_VIANAMEOUT, &cs.fw6.ipv6.invflags,
 				   cs.invert);
 			xtables_parse_interface(optarg,
@@ -1624,6 +1331,25 @@
 			verbose++;
 			break;
 
+		case 'w':
+			if (restore) {
+				xtables_error(PARAMETER_PROBLEM,
+					      "You cannot use `-w' from "
+					      "ip6tables-restore");
+			}
+			wait = parse_wait_time(argc, argv);
+			break;
+
+		case 'W':
+			if (restore) {
+				xtables_error(PARAMETER_PROBLEM,
+					      "You cannot use `-W' from "
+					      "ip6tables-restore");
+			}
+			parse_wait_interval(argc, argv, &wait_interval);
+			wait_interval_set = true;
+			break;
+
 		case 'm':
 			command_match(&cs);
 			break;
@@ -1637,7 +1363,12 @@
 			if (cs.invert)
 				xtables_error(PARAMETER_PROBLEM,
 					   "unexpected ! flag before --table");
+			if (restore && table_set)
+				xtables_error(PARAMETER_PROBLEM,
+					      "The -t option (seen in line %u) cannot be used in %s.\n",
+					      line, xt_params->program_name);
 			*table = optarg;
+			table_set = true;
 			break;
 
 		case 'x':
@@ -1649,7 +1380,7 @@
 			if (cs.invert)
 				printf("Not %s ;-)\n", prog_vers);
 			else
-				printf("%s v%s\n",
+				printf("%s v%s (legacy)\n",
 				       prog_name, prog_vers);
 			exit(0);
 
@@ -1670,8 +1401,7 @@
 			bcnt = strchr(pcnt + 1, ',');
 			if (bcnt)
 			    bcnt++;
-			if (!bcnt && optind < argc && argv[optind][0] != '-'
-			    && argv[optind][0] != '!')
+			if (!bcnt && xs_has_arg(argc, argv))
 				bcnt = argv[optind++];
 			if (!bcnt)
 				xtables_error(PARAMETER_PROBLEM,
@@ -1708,7 +1438,7 @@
 					xtables_error(PARAMETER_PROBLEM,
 						   "multiple consecutive ! not"
 						   " allowed");
-				cs.invert = TRUE;
+				cs.invert = true;
 				optarg[0] = '\0';
 				continue;
 			}
@@ -1720,14 +1450,25 @@
 				/*
 				 * If new options were loaded, we must retry
 				 * getopt immediately and not allow
-				 * cs.invert=FALSE to be executed.
+				 * cs.invert=false to be executed.
 				 */
 				continue;
 			break;
 		}
-		cs.invert = FALSE;
+		cs.invert = false;
 	}
 
+	if (!wait && wait_interval_set)
+		xtables_error(PARAMETER_PROBLEM,
+			      "--wait-interval only makes sense with --wait\n");
+
+	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)
@@ -1770,10 +1511,9 @@
 
 	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);
+	/* Attempt to acquire the xtables lock */
+	if (!restore)
+		xtables_lock_or_exit(wait, &wait_interval);
 
 	/* only allocate handle if we weren't called with a handle */
 	if (!*handle)
@@ -1831,23 +1571,23 @@
 			|| ip6tc_is_chain(cs.jumpto, *handle))) {
 			size_t size;
 
-			cs.target = xtables_find_target(IP6T_STANDARD_TARGET,
+			cs.target = xtables_find_target(XT_STANDARD_TARGET,
 					XTF_LOAD_MUST_SUCCEED);
 
-			size = sizeof(struct ip6t_entry_target)
+			size = sizeof(struct xt_entry_target)
 				+ cs.target->size;
 			cs.target->t = xtables_calloc(1, size);
 			cs.target->t->u.target_size = size;
 			strcpy(cs.target->t->u.user.name, cs.jumpto);
-			if (cs.target->init != NULL)
-				cs.target->init(cs.target->t);
+			xs_init_target(cs.target);
 		}
 
 		if (!cs.target) {
-			/* it is no chain, and we can't load a plugin.
+			/* It is no chain, and we can't load a plugin.
 			 * We cannot know if the plugin is corrupt, non
-			 * existant OR if the user just misspelled a
-			 * chain. */
+			 * existent OR if the user just misspelled a
+			 * chain.
+			 */
 #ifdef IP6T_F_GOTO
 			if (cs.fw6.ipv6.flags & IP6T_F_GOTO)
 				xtables_error(PARAMETER_PROBLEM,
@@ -1956,7 +1696,7 @@
 	if (verbose > 1)
 		dump_entries6(*handle);
 
-	clear_rule_matches(&cs.matches);
+	xtables_rule_matches_free(&cs.matches);
 
 	if (e != NULL) {
 		free(e);
diff --git a/iptables/iptables-apply b/iptables/iptables-apply
index 5fec76b..4683b1b 100755
--- a/iptables/iptables-apply
+++ b/iptables/iptables-apply
@@ -1,172 +1,294 @@
 #!/bin/bash
-#
 # iptables-apply -- a safer way to update iptables remotely
 #
-# Copyright © Martin F. Krafft <madduck@madduck.net>
+# Usage:
+#   iptables-apply [-hV] [-t timeout] [-w savefile] {[rulesfile]|-c [runcmd]}
+#
+# Versions:
+#   * 1.0 Copyright 2006 Martin F. Krafft <madduck@madduck.net>
+#         Original version
+#   * 1.1 Copyright 2010 GW <gw.2010@tnode.com or http://gw.tnode.com/>
+#         Added parameter -c (run command)
+#         Added parameter -w (save successfully applied rules to file)
+#         Major code cleanup
+#
 # Released under the terms of the Artistic Licence 2.0
 #
 set -eu
 
-PROGNAME="${0##*/}";
-VERSION=1.0
+PROGNAME="${0##*/}"
+VERSION=1.1
 
-TIMEOUT=10
-DEFAULT_FILE=/etc/network/iptables
 
-function blurb()
-{
-	cat <<-_eof
+### Default settings
+
+DEF_TIMEOUT=10
+
+MODE=0  # apply rulesfile mode
+# MODE=1  # run command mode
+
+case "$PROGNAME" in
+	(*6*)
+		SAVE=ip6tables-save
+		RESTORE=ip6tables-restore
+		DEF_RULESFILE="/etc/network/ip6tables.up.rules"
+		DEF_SAVEFILE="$DEF_RULESFILE"
+		DEF_RUNCMD="/etc/network/ip6tables.up.run"
+		;;
+	(*)
+		SAVE=iptables-save
+		RESTORE=iptables-restore
+		DEF_RULESFILE="/etc/network/iptables.up.rules"
+		DEF_SAVEFILE="$DEF_RULESFILE"
+		DEF_RUNCMD="/etc/network/iptables.up.run"
+		;;
+esac
+
+
+### Functions
+
+function blurb() {
+	cat <<-__EOF__
 	$PROGNAME $VERSION -- a safer way to update iptables remotely
-	_eof
+	__EOF__
 }
 
-function copyright()
-{
-	cat <<-_eof
-	$PROGNAME is C Martin F. Krafft <madduck@madduck.net>.
+function copyright() {
+	cat <<-__EOF__
+	$PROGNAME has been published under the terms of the Artistic Licence 2.0.
 
-	The program has been published under the terms of the Artistic Licence 2.0
-	_eof
+	Original version - Copyright 2006 Martin F. Krafft <madduck@madduck.net>.
+	Version 1.1 - Copyright 2010 GW <gw.2010@tnode.com or http://gw.tnode.com/>.
+	__EOF__
 }
 
-function about()
-{
+function about() {
 	blurb
 	echo
 	copyright
 }
 
-function usage()
-{
-	cat <<-_eof
-	Usage: $PROGNAME [options] ruleset
+function usage() {
+	blurb
+	echo
+	cat <<-__EOF__
+	Usage:
+	  $PROGNAME [-hV] [-t timeout] [-w savefile] {[rulesfile]|-c [runcmd]}
 
-	The script will try to apply a new ruleset (as output by iptables-save/read
-	by iptables-restore) to iptables, then prompt the user whether the changes
-	are okay. If the new ruleset cut the existing connection, the user will not
-	be able to answer affirmatively. In this case, the script rolls back to the
-	previous ruleset.
+	The script will try to apply a new rulesfile (as output by iptables-save,
+	read by iptables-restore) or run a command to configure iptables and then
+	prompt the user whether the changes are okay. If the new iptables rules cut
+	the existing connection, the user will not be able to answer affirmatively.
+	In this case, the script rolls back to the previous working iptables rules
+	after the timeout expires.
 
-	The following options may be specified, using standard conventions:
+	Successfully applied rules can also be written to savefile and later used
+	to roll back to this state. This can be used to implement a store last good
+	configuration mechanism when experimenting with an iptables setup script:
+	  $PROGNAME -w $DEF_SAVEFILE -c $DEF_RUNCMD
 
-	-t | --timeout	Specify the timeout in seconds (default: $TIMEOUT)
-	-V | --version	Display version information
-	-h | --help	Display this help text
-	_eof
+	When called as ip6tables-apply, the script will use ip6tables-save/-restore
+	and IPv6 default values instead. Default value for rulesfile is
+	'$DEF_RULESFILE'.
+
+	Options:
+
+	-t seconds, --timeout seconds
+	  Specify the timeout in seconds (default: $DEF_TIMEOUT).
+	-w savefile, --write savefile
+	  Specify the savefile where successfully applied rules will be written to
+	  (default if empty string is given: $DEF_SAVEFILE).
+	-c runcmd, --command runcmd
+	  Run command runcmd to configure iptables instead of applying a rulesfile
+	  (default: $DEF_RUNCMD).
+	-h, --help
+	  Display this help text.
+	-V, --version
+	  Display version information.
+
+	__EOF__
 }
 
-SHORTOPTS="t:Vh";
-LONGOPTS="timeout:,version,help";
+function checkcommands() {
+	for cmd in "${COMMANDS[@]}"; do
+		if ! command -v "$cmd" >/dev/null; then
+			echo "Error: needed command not found: $cmd" >&2
+			exit 127
+		fi
+	done
+}
+
+function revertrules() {
+	echo -n "Reverting to old iptables rules... "
+	"$RESTORE" <"$TMPFILE"
+	echo "done."
+}
+
+
+### Parsing and checking parameters
+
+TIMEOUT="$DEF_TIMEOUT"
+SAVEFILE=""
+
+SHORTOPTS="t:w:chV";
+LONGOPTS="timeout:,write:,command,help,version";
 
 OPTS=$(getopt -s bash -o "$SHORTOPTS" -l "$LONGOPTS" -n "$PROGNAME" -- "$@") || exit $?
 for opt in $OPTS; do
 	case "$opt" in
-		(-*) unset OPT_STATE;;
+		(-*)
+			unset OPT_STATE
+			;;
 		(*)
 			case "${OPT_STATE:-}" in
-				(SET_TIMEOUT)
-					eval TIMEOUT=$opt
-					case "$TIMEOUT" in
-						([0-9]*) :;;
-						(*)
-							echo "E: non-numeric timeout value." >&2
-							exit 1
-							;;
-					esac
+				(SET_TIMEOUT) eval TIMEOUT=$opt;;
+				(SET_SAVEFILE)
+					eval SAVEFILE=$opt
+					[ -z "$SAVEFILE" ] && SAVEFILE="$DEF_SAVEFILE"
 					;;
 			esac
 			;;
 	esac
 
 	case "$opt" in
+		(-t|--timeout) OPT_STATE="SET_TIMEOUT";;
+		(-w|--write) OPT_STATE="SET_SAVEFILE";;
+		(-c|--command) MODE=1;;
 		(-h|--help) usage >&2; exit 0;;
 		(-V|--version) about >&2; exit 0;;
-		(-t|--timeout) OPT_STATE=SET_TIMEOUT;;
 		(--) break;;
 	esac
 	shift
 done
 
-FILE="${1:-$DEFAULT_FILE}";
-
-if [[ -z "$FILE" ]]; then
-	echo "E: missing file argument." >&2
+# Validate parameters
+if [ "$TIMEOUT" -ge 0 ] 2>/dev/null; then
+	TIMEOUT=$(($TIMEOUT))
+else
+	echo "Error: timeout must be a positive number" >&2
 	exit 1
 fi
 
-if [[ ! -r "$FILE" ]]; then
-	echo "E: cannot read $FILE" >&2
-	exit 2
+if [ -n "$SAVEFILE" -a -e "$SAVEFILE" -a ! -w "$SAVEFILE" ]; then
+	echo "Error: savefile not writable: $SAVEFILE" >&2
+	exit 8
 fi
 
-case "${0##*/}" in
-	(*6*)
-		SAVE=ip6tables-save
-		RESTORE=ip6tables-restore
+case "$MODE" in
+	(1)
+		# Treat parameter as runcmd (run command mode)
+		RUNCMD="${1:-$DEF_RUNCMD}"
+		if [ ! -x "$RUNCMD" ]; then
+			echo "Error: runcmd not executable: $RUNCMD" >&2
+			exit 6
+		fi
+
+		# Needed commands
+		COMMANDS=(mktemp "$SAVE" "$RESTORE" "$RUNCMD")
+		checkcommands
 		;;
 	(*)
-		SAVE=iptables-save
-		RESTORE=iptables-restore
+		# Treat parameter as rulesfile (apply rulesfile mode)
+		RULESFILE="${1:-$DEF_RULESFILE}";
+		if [ ! -r "$RULESFILE" ]; then
+			echo "Error: rulesfile not readable: $RULESFILE" >&2
+			exit 2
+		fi
+
+		# Needed commands
+		COMMANDS=(mktemp "$SAVE" "$RESTORE")
+		checkcommands
 		;;
 esac
 
-COMMANDS=(tempfile "$SAVE" "$RESTORE")
 
-for cmd in "${COMMANDS[@]}"; do
-	if ! command -v $cmd >/dev/null; then
-		echo "E: command not found: $cmd" >&2
-		exit 127
-	fi
-done
+### Begin work
 
-umask 0700
-
-TMPFILE=$(tempfile -p iptap)
-trap "rm -f $TMPFILE" EXIT 1 2 3 4 5 6 7 8 10 11 12 13 14 15
+# Store old iptables rules to temporary file
+TMPFILE=`mktemp /tmp/$PROGNAME-XXXXXXXX`
+trap "rm -f $TMPFILE" EXIT HUP INT QUIT ILL TRAP ABRT BUS \
+		      FPE USR1 SEGV USR2 PIPE ALRM TERM
 
 if ! "$SAVE" >"$TMPFILE"; then
+	# An error occured
 	if ! grep -q ipt /proc/modules 2>/dev/null; then
-		echo "E: iptables support lacking from the kernel." >&2
+		echo "Error: iptables support lacking from the kernel" >&2
 		exit 3
 	else
-		echo "E: unknown error saving current iptables ruleset." >&2
+		echo "Error: unknown error saving old iptables rules: $TMPFILE" >&2
 		exit 4
 	fi
 fi
 
+# Legacy to stop the fail2ban daemon if present
 [ -x /etc/init.d/fail2ban ] && /etc/init.d/fail2ban stop
 
-echo -n "Applying new ruleset... "
-if ! "$RESTORE" <"$FILE"; then
-	echo "failed."
-	echo "E: unknown error applying new iptables ruleset." >&2
-	exit 5
-else
-	echo done.
-fi
-
-echo -n "Can you establish NEW connections to the machine? (y/N) "
-
-read -n1 -t "${TIMEOUT:-15}" ret 2>&1 || :
-case "${ret:-}" in
-	(y*|Y*)
-		echo
-		echo ... then my job is done. See you next time.
+# Configure iptables
+case "$MODE" in
+	(1)
+		# Run command in background and kill it if it times out
+		echo -n "Running command '$RUNCMD'... "
+		"$RUNCMD" &
+		CMD_PID=$!
+		( sleep "$TIMEOUT"; kill "$CMD_PID" 2>/dev/null; exit 0 ) &
+		CMDTIMEOUT_PID=$!
+		if ! wait "$CMD_PID"; then
+			echo "failed."
+			echo "Error: unknown error running command: $RUNCMD" >&2
+			revertrules
+			exit 7
+		else
+			echo "done."
+		fi
 		;;
 	(*)
-		if [[ -z "${ret:-}" ]]; then
-			echo "apparently not..."
+		# Apply iptables rulesfile
+		echo -n "Applying new iptables rules from '$RULESFILE'... "
+		if ! "$RESTORE" <"$RULESFILE"; then
+			echo "failed."
+			echo "Error: unknown error applying new iptables rules: $RULESFILE" >&2
+			revertrules
+			exit 5
 		else
-			echo
+			echo "done."
 		fi
-		echo "Timeout. Something happened (or did not). Better play it safe..."
-		echo -n "Reverting to old ruleset... "
-		"$RESTORE" <"$TMPFILE";
-		echo done.
+		;;
+esac
+
+# Prompt user for confirmation
+echo -n "Can you establish NEW connections to the machine? (y/N) "
+
+read -n1 -t "$TIMEOUT" ret 2>&1 || :
+case "${ret:-}" in
+	(y*|Y*)
+		# Success
+		echo
+
+		if [ ! -z "$SAVEFILE" ]; then
+			# Write successfully applied rules to the savefile
+			echo "Writing successfully applied rules to '$SAVEFILE'..."
+			if ! "$SAVE" >"$SAVEFILE"; then
+				echo "Error: unknown error writing successfully applied rules: $SAVEFILE" >&2
+				exit 9
+			fi
+		fi
+
+		echo "... then my job is done. See you next time."
+		;;
+	(*)
+		# Failed
+		echo
+		if [ -z "${ret:-}" ]; then
+			echo "Timeout! Something happened (or did not). Better play it safe..."
+		else
+			echo "No affirmative response! Better play it safe..."
+		fi
+		revertrules
 		exit 255
 		;;
 esac
 
+# Legacy to start the fail2ban daemon again
 [ -x /etc/init.d/fail2ban ] && /etc/init.d/fail2ban start
 
 exit 0
diff --git a/iptables/iptables-apply.8 b/iptables/iptables-apply.8
deleted file mode 100644
index 8208fd0..0000000
--- a/iptables/iptables-apply.8
+++ /dev/null
@@ -1,44 +0,0 @@
-.\"     Title: iptables-apply
-.\"    Author: Martin F. Krafft
-.\"      Date: Jun 04, 2006
-.\"
-.TH iptables\-apply 8 2006-06-04
-.\" disable hyphenation
-.nh
-.SH NAME
-iptables-apply \- a safer way to update iptables remotely
-.SH SYNOPSIS
-\fBiptables\-apply\fP [\-\fBhV\fP] [\fB-t\fP \fItimeout\fP] \fIruleset\-file\fP
-.SH "DESCRIPTION"
-.PP
-iptables\-apply will try to apply a new ruleset (as output by
-iptables\-save/read by iptables\-restore) to iptables, then prompt the
-user whether the changes are okay. If the new ruleset cut the existing
-connection, the user will not be able to answer affirmatively. In this
-case, the script rolls back to the previous ruleset after the timeout
-expired. The timeout can be set with \fB\-t\fP.
-.PP
-When called as ip6tables\-apply, the script will use
-ip6tables\-save/\-restore instead.
-.SH OPTIONS
-.TP
-\fB\-t\fP \fIseconds\fR, \fB\-\-timeout\fP \fIseconds\fR
-Sets the timeout after which the script will roll back to the previous
-ruleset.
-.TP
-\fB\-h\fP, \fB\-\-help\fP
-Display usage information.
-.TP
-\fB\-V\fP, \fB\-\-version\fP
-Display version information.
-.SH "SEE ALSO"
-.PP
-\fBiptables-restore\fP(8), \fBiptables-save\fP(8), \fBiptables\fR(8).
-.SH LEGALESE
-.PP
-iptables\-apply is copyright by Martin F. Krafft.
-.PP
-This manual page was written by Martin F. Krafft <madduck@madduck.net>
-.PP
-Permission is granted to copy, distribute and/or modify this document
-under the terms of the Artistic License 2.0.
diff --git a/iptables/iptables-apply.8.in b/iptables/iptables-apply.8.in
new file mode 100644
index 0000000..f0ed4e5
--- /dev/null
+++ b/iptables/iptables-apply.8.in
@@ -0,0 +1,60 @@
+.\"     Title: iptables-apply
+.\"    Author: Martin F. Krafft, GW
+.\"      Date: May 10, 2010
+.\"
+.TH IPTABLES\-APPLY 8 "" "@PACKAGE_STRING@" "@PACKAGE_STRING@"
+.\" disable hyphenation
+.nh
+.SH NAME
+iptables-apply \- a safer way to update iptables remotely
+.SH SYNOPSIS
+\fBiptables\-apply\fP [\-\fBhV\fP] [\fB-t\fP \fItimeout\fP] [\fB-w\fP \fIsavefile\fP] {[\fIrulesfile]|-c [runcmd]}\fP
+.SH "DESCRIPTION"
+.PP
+iptables\-apply will try to apply a new rulesfile (as output by
+iptables-save, read by iptables-restore) or run a command to configure
+iptables and then prompt the user whether the changes are okay. If the
+new iptables rules cut the existing connection, the user will not be
+able to answer affirmatively. In this case, the script rolls back to
+the previous working iptables rules after the timeout expires.
+.PP
+Successfully applied rules can also be written to savefile and later used
+to roll back to this state. This can be used to implement a store last good
+configuration mechanism when experimenting with an iptables setup script:
+iptables-apply \-w /etc/network/iptables.up.rules \-c /etc/network/iptables.up.run
+.PP
+When called as ip6tables\-apply, the script will use
+ip6tables\-save/\-restore and IPv6 default values instead. Default
+value for rulesfile is '/etc/network/iptables.up.rules'.
+.SH OPTIONS
+.TP
+\fB\-t\fP \fIseconds\fR, \fB\-\-timeout\fP \fIseconds\fR
+Sets the timeout in seconds after which the script will roll back
+to the previous ruleset (default: 10).
+.TP
+\fB\-w\fP \fIsavefile\fR, \fB\-\-write\fP \fIsavefile\fR
+Specify the savefile where successfully applied rules will be written to
+(default if empty string is given: /etc/network/iptables.up.rules).
+.TP
+\fB\-c\fP \fIruncmd\fR, \fB\-\-command\fP \fIruncmd\fR
+Run command runcmd to configure iptables instead of applying a rulesfile
+(default: /etc/network/iptables.up.run).
+.TP
+\fB\-h\fP, \fB\-\-help\fP
+Display usage information.
+.TP
+\fB\-V\fP, \fB\-\-version\fP
+Display version information.
+.SH "SEE ALSO"
+.PP
+\fBiptables-restore\fP(8), \fBiptables-save\fP(8), \fBiptables\fR(8).
+.SH LEGALESE
+.PP
+Original iptables-apply - Copyright 2006 Martin F. Krafft <madduck@madduck.net>.
+Version 1.1 - Copyright 2010 GW <gw.2010@tnode.com or http://gw.tnode.com/>.
+.PP
+This manual page was written by Martin F. Krafft <madduck@madduck.net> and
+extended by GW <gw.2010@tnode.com or http://gw.tnode.com/>.
+.PP
+Permission is granted to copy, distribute and/or modify this document
+under the terms of the Artistic License 2.0.
diff --git a/iptables/iptables-extensions.8.tmpl.in b/iptables/iptables-extensions.8.tmpl.in
new file mode 100644
index 0000000..99d89a1
--- /dev/null
+++ b/iptables/iptables-extensions.8.tmpl.in
@@ -0,0 +1,28 @@
+.TH iptables-extensions 8 "" "@PACKAGE_STRING@" "@PACKAGE_STRING@"
+.SH NAME
+iptables-extensions \(em list of extensions in the standard iptables distribution
+.SH SYNOPSIS
+\fBip6tables\fP [\fB\-m\fP \fIname\fP [\fImodule-options\fP...]]
+[\fB\-j\fP \fItarget-name\fP [\fItarget-options\fP...]
+.PP
+\fBiptables\fP [\fB\-m\fP \fIname\fP [\fImodule-options\fP...]]
+[\fB\-j\fP \fItarget-name\fP [\fItarget-options\fP...]
+.SH MATCH EXTENSIONS
+iptables can use extended packet matching modules
+with the \fB\-m\fP or \fB\-\-match\fP
+options, followed by the matching module name; after these, various
+extra command line options become available, depending on the specific
+module.  You can specify multiple extended match modules in one line,
+and you can use the \fB\-h\fP or \fB\-\-help\fP
+options after the module has been specified to receive help specific
+to that module.  The extended match modules are evaluated in the order
+they are specified in the rule.
+.PP
+If the \fB\-p\fP or \fB\-\-protocol\fP was specified and if and only if an
+unknown option is encountered, iptables will try load a match module of the
+same name as the protocol, to try making the option available.
+.\" @MATCH@
+.SH TARGET EXTENSIONS
+iptables can use extended target modules: the following are included
+in the standard distribution.
+.\" @TARGET@
diff --git a/iptables/iptables-restore.8 b/iptables/iptables-restore.8
deleted file mode 100644
index a52bceb..0000000
--- a/iptables/iptables-restore.8
+++ /dev/null
@@ -1,47 +0,0 @@
-.TH IPTABLES-RESTORE 8 "Jan 04, 2001" "" ""
-.\"
-.\" Man page written by Harald Welte <laforge@gnumonks.org>
-.\" It is based on the iptables man page.
-.\"
-.\"	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.
-.\"
-.\"
-.SH NAME
-iptables-restore \(em Restore IP Tables
-.SH SYNOPSIS
-\fBiptables\-restore\fP [\fB\-c\fP] [\fB\-n\fP]
-.SH DESCRIPTION
-.PP
-.B iptables-restore
-is used to restore IP Tables from data specified on STDIN. Use 
-I/O redirection provided by your shell to read from a file
-.TP
-\fB\-c\fR, \fB\-\-counters\fR
-restore the values of all packet and byte counters
-.TP
-\fB\-n\fR, \fB\-\-noflush\fR 
-don't flush the previous contents of the table. If not specified, 
-.B iptables-restore
-flushes (deletes) all previous contents of the respective IP Table.
-.SH BUGS
-None known as of iptables-1.2.1 release
-.SH AUTHOR
-Harald Welte <laforge@gnumonks.org>
-.SH SEE ALSO
-\fBiptables\-save\fP(8), \fBiptables\fP(8)
-.PP
-The iptables-HOWTO, which details more iptables usage, the NAT-HOWTO,
-which details NAT, and the netfilter-hacking-HOWTO which details the
-internals.
diff --git a/iptables/iptables-restore.8.in b/iptables/iptables-restore.8.in
new file mode 100644
index 0000000..b4b62f9
--- /dev/null
+++ b/iptables/iptables-restore.8.in
@@ -0,0 +1,94 @@
+.TH IPTABLES-RESTORE 8 "" "@PACKAGE_STRING@" "@PACKAGE_STRING@"
+.\"
+.\" Man page written by Harald Welte <laforge@gnumonks.org>
+.\" It is based on the iptables man page.
+.\"
+.\"	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.
+.\"
+.\"
+.SH NAME
+iptables-restore \(em Restore IP Tables
+.P
+ip6tables-restore \(em Restore IPv6 Tables
+.SH SYNOPSIS
+\fBiptables\-restore\fP [\fB\-chntvV\fP] [\fB\-w\fP \fIsecs\fP]
+[\fB\-W\fP \fIusecs\fP] [\fB\-M\fP \fImodprobe\fP] [\fB\-T\fP \fIname\fP]
+[\fBfile\fP]
+.P
+\fBip6tables\-restore\fP [\fB\-chntvV\fP] [\fB\-w\fP \fIsecs\fP]
+[\fB\-W\fP \fIusecs\fP] [\fB\-M\fP \fImodprobe\fP] [\fB\-T\fP \fIname\fP]
+[\fBfile\fP]
+.SH DESCRIPTION
+.PP
+.B iptables-restore
+and
+.B ip6tables-restore
+are used to restore IP and IPv6 Tables from data specified on STDIN or in
+\fIfile\fP. Use I/O redirection provided by your shell to read from a file or
+specify \fIfile\fP as an argument.
+.TP
+\fB\-c\fR, \fB\-\-counters\fR
+restore the values of all packet and byte counters
+.TP
+\fB\-h\fP, \fB\-\-help\fP
+Print a short option summary.
+.TP
+\fB\-n\fR, \fB\-\-noflush\fR
+don't flush the previous contents of the table. If not specified,
+both commands flush (delete) all previous contents of the respective table.
+.TP
+\fB\-t\fP, \fB\-\-test\fP
+Only parse and construct the ruleset, but do not commit it.
+.TP
+\fB\-v\fP, \fB\-\-verbose\fP
+Print additional debug info during ruleset processing.
+.TP
+\fB\-V\fP, \fB\-\-version\fP
+Print the program version number.
+.TP
+\fB\-w\fP, \fB\-\-wait\fP [\fIseconds\fP]
+Wait for the xtables lock.
+To prevent multiple instances of the program from running concurrently,
+an attempt will be made to obtain an exclusive lock at launch.  By default,
+the program will exit if the lock cannot be obtained.  This option will
+make the program wait (indefinitely or for optional \fIseconds\fP) until
+the exclusive lock can be obtained.
+.TP
+\fB\-W\fP, \fB\-\-wait-interval\fP \fImicroseconds\fP
+Interval to wait per each iteration.
+When running latency sensitive applications, waiting for the xtables lock
+for extended durations may not be acceptable. This option will make each
+iteration take the amount of time specified. The default interval is
+1 second. This option only works with \fB\-w\fP.
+.TP
+\fB\-M\fP, \fB\-\-modprobe\fP \fImodprobe_program\fP
+Specify the path to the modprobe program. By default, iptables-restore will
+inspect /proc/sys/kernel/modprobe to determine the executable's path.
+.TP
+\fB\-T\fP, \fB\-\-table\fP \fIname\fP
+Restore only the named table even if the input stream contains other ones.
+.SH BUGS
+None known as of iptables-1.2.1 release
+.SH AUTHORS
+Harald Welte <laforge@gnumonks.org> wrote iptables-restore based on code
+from Rusty Russell.
+.br
+Andras Kis-Szabo <kisza@sch.bme.hu> contributed ip6tables-restore.
+.SH SEE ALSO
+\fBiptables\-apply\fP(8),\fBiptables\-save\fP(8), \fBiptables\fP(8)
+.PP
+The iptables-HOWTO, which details more iptables usage, the NAT-HOWTO,
+which details NAT, and the netfilter-hacking-HOWTO which details the
+internals.
diff --git a/iptables/iptables-restore.c b/iptables/iptables-restore.c
index 2624599..cc2c2b8 100644
--- a/iptables/iptables-restore.c
+++ b/iptables/iptables-restore.c
@@ -4,150 +4,111 @@
  *
  * This code is distributed under the terms of GNU GPL v2
  */
-
+#include "config.h"
 #include <getopt.h>
-#include <sys/errno.h>
+#include <errno.h>
 #include <stdbool.h>
 #include <string.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include "iptables.h"
+#include "ip6tables.h"
+#include "xshared.h"
 #include "xtables.h"
 #include "libiptc/libiptc.h"
+#include "libiptc/libip6tc.h"
 #include "iptables-multi.h"
+#include "ip6tables-multi.h"
 
-#ifdef DEBUG
-#define DEBUGP(x, args...) fprintf(stderr, x, ## args)
-#else
-#define DEBUGP(x, args...)
-#endif
+static int counters, verbose, noflush, wait;
 
-static int binary = 0, counters = 0, verbose = 0, noflush = 0;
+static struct timeval wait_interval = {
+	.tv_sec	= 1,
+};
 
 /* Keeping track of external matches and targets.  */
 static const struct option options[] = {
-	{.name = "binary",   .has_arg = false, .val = 'b'},
-	{.name = "counters", .has_arg = false, .val = 'c'},
-	{.name = "verbose",  .has_arg = false, .val = 'v'},
-	{.name = "test",     .has_arg = false, .val = 't'},
-	{.name = "help",     .has_arg = false, .val = 'h'},
-	{.name = "noflush",  .has_arg = false, .val = 'n'},
-	{.name = "modprobe", .has_arg = true,  .val = 'M'},
-	{.name = "table",    .has_arg = true,  .val = 'T'},
+	{.name = "counters",      .has_arg = 0, .val = 'c'},
+	{.name = "verbose",       .has_arg = 0, .val = 'v'},
+	{.name = "version",       .has_arg = 0, .val = 'V'},
+	{.name = "test",          .has_arg = 0, .val = 't'},
+	{.name = "help",          .has_arg = 0, .val = 'h'},
+	{.name = "noflush",       .has_arg = 0, .val = 'n'},
+	{.name = "modprobe",      .has_arg = 1, .val = 'M'},
+	{.name = "table",         .has_arg = 1, .val = 'T'},
+	{.name = "wait",          .has_arg = 2, .val = 'w'},
+	{.name = "wait-interval", .has_arg = 2, .val = 'W'},
 	{NULL},
 };
 
-static void print_usage(const char *name, const char *version) __attribute__((noreturn));
-
-#define prog_name iptables_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"
+	fprintf(stderr, "Usage: %s [-c] [-v] [-V] [-t] [-h] [-n] [-w secs] [-W usecs] [-T table] [-M command] [file]\n"
 			"	   [ --counters ]\n"
 			"	   [ --verbose ]\n"
+			"	   [ --version]\n"
 			"	   [ --test ]\n"
 			"	   [ --help ]\n"
 			"	   [ --noflush ]\n"
+			"	   [ --wait=<seconds>\n"
+			"	   [ --wait-interval=<usecs>\n"
 			"	   [ --table=<TABLE> ]\n"
-			"          [ --modprobe=<command>]\n", name);
-
-	exit(1);
+			"	   [ --modprobe=<command> ]\n", name);
 }
 
-static struct iptc_handle *create_handle(const char *tablename)
-{
-	struct iptc_handle *handle;
+struct iptables_restore_cb {
+	const struct xtc_ops *ops;
 
-	handle = iptc_init(tablename);
+	int (*for_each_chain)(int (*fn)(const xt_chainlabel,
+					int, struct xtc_handle *),
+			      int verbose, int builtinstoo,
+			      struct xtc_handle *handle);
+	int (*flush_entries)(const xt_chainlabel, int, struct xtc_handle *);
+	int (*delete_chain)(const xt_chainlabel, int, struct xtc_handle *);
+	int (*do_command)(int argc, char *argv[], char **table,
+			  struct xtc_handle **handle, bool restore);
+};
+
+static struct xtc_handle *
+create_handle(const struct iptables_restore_cb *cb, const char *tablename)
+{
+	struct xtc_handle *handle;
+
+	handle = cb->ops->init(tablename);
 
 	if (!handle) {
 		/* try to insmod the module if iptc_init failed */
 		xtables_load_ko(xtables_modprobe_program, false);
-		handle = iptc_init(tablename);
+		handle = cb->ops->init(tablename);
 	}
 
-	if (!handle) {
+	if (!handle)
 		xtables_error(PARAMETER_PROBLEM, "%s: unable to initialize "
-			"table '%s'\n", prog_name, tablename);
-		exit(1);
-	}
+			"table '%s'\n", xt_params->program_name, tablename);
+
 	return handle;
 }
 
-static int parse_counters(char *string, struct ipt_counters *ctr)
+static int
+ip46tables_restore_main(const struct iptables_restore_cb *cb,
+			int argc, char *argv[])
 {
-	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);
-		newargc++;
-		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]);
-}
-
-#ifdef IPTABLES_MULTI
-int
-iptables_restore_main(int argc, char *argv[])
-#else
-int
-main(int argc, char *argv[])
-#endif
-{
-	struct iptc_handle *handle = NULL;
+	struct xtc_handle *handle = NULL;
+	struct argv_store av_store = {};
 	char buffer[10240];
-	int c;
-	char curtable[IPT_TABLE_MAXNAMELEN + 1];
+	int c, lock;
+	char curtable[XT_TABLE_MAXNAMELEN + 1] = {};
 	FILE *in;
 	int in_table = 0, testing = 0;
 	const char *tablename = NULL;
 
 	line = 0;
+	lock = XT_LOCK_NOT_ACQUIRED;
 
-	iptables_globals.program_name = "iptables-restore";
-	c = xtables_init_all(&iptables_globals, NFPROTO_IPV4);
-	if (c < 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
-
-	while ((c = getopt_long(argc, argv, "bcvthnM:T:", options, NULL)) != -1) {
+	while ((c = getopt_long(argc, argv, "bcvVthnwWM:T:", options, NULL)) != -1) {
 		switch (c) {
 			case 'b':
-				binary = 1;
+				fprintf(stderr, "-b/--binary option is not implemented\n");
 				break;
 			case 'c':
 				counters = 1;
@@ -155,22 +116,38 @@
 			case 'v':
 				verbose = 1;
 				break;
+			case 'V':
+				printf("%s v%s (legacy)\n",
+				       xt_params->program_name,
+				       xt_params->program_version);
+				exit(0);
 			case 't':
 				testing = 1;
 				break;
 			case 'h':
-				print_usage("iptables-restore",
-					    IPTABLES_VERSION);
-				break;
+				print_usage(xt_params->program_name,
+					    PACKAGE_VERSION);
+				exit(0);
 			case 'n':
 				noflush = 1;
 				break;
+			case 'w':
+				wait = parse_wait_time(argc, argv);
+				break;
+			case 'W':
+				parse_wait_interval(argc, argv, &wait_interval);
+				break;
 			case 'M':
 				xtables_modprobe_program = optarg;
 				break;
 			case 'T':
 				tablename = optarg;
 				break;
+			default:
+				fprintf(stderr,
+					"Try `%s -h' for more information.\n",
+					xt_params->program_name);
+				exit(1);
 		}
 	}
 
@@ -188,6 +165,11 @@
 	}
 	else in = stdin;
 
+	if (!wait_interval.tv_sec && !wait) {
+		fprintf(stderr, "Option --wait-interval requires option --wait\n");
+		exit(1);
+	}
+
 	/* Grab standard input. */
 	while (fgets(buffer, sizeof(buffer), in)) {
 		int ret = 0;
@@ -196,50 +178,66 @@
 		if (buffer[0] == '\n')
 			continue;
 		else if (buffer[0] == '#') {
-			if (verbose)
+			if (verbose) {
 				fputs(buffer, stdout);
+				fflush(stdout);
+			}
 			continue;
 		} else if ((strcmp(buffer, "COMMIT\n") == 0) && (in_table)) {
 			if (!testing) {
 				DEBUGP("Calling commit\n");
-				ret = iptc_commit(handle);
-				iptc_free(handle);
+				ret = cb->ops->commit(handle);
+				cb->ops->free(handle);
 				handle = NULL;
 			} else {
 				DEBUGP("Not calling commit, testing\n");
 				ret = 1;
 			}
+
+			/* Done with the current table, release the lock. */
+			if (lock >= 0) {
+				xtables_unlock(lock);
+				lock = XT_LOCK_NOT_ACQUIRED;
+			}
+
 			in_table = 0;
 		} else if ((buffer[0] == '*') && (!in_table)) {
+			/* Acquire a lock before we create a new table handle */
+			lock = xtables_lock_or_exit(wait, &wait_interval);
+
 			/* New table */
 			char *table;
 
 			table = strtok(buffer+1, " \t\n");
 			DEBUGP("line %u, table '%s'\n", line, table);
-			if (!table) {
+			if (!table)
 				xtables_error(PARAMETER_PROBLEM,
 					"%s: line %u table name invalid\n",
-					prog_name, line);
-				exit(1);
-			}
-			strncpy(curtable, table, IPT_TABLE_MAXNAMELEN);
-			curtable[IPT_TABLE_MAXNAMELEN] = '\0';
+					xt_params->program_name, line);
 
-			if (tablename && (strcmp(tablename, table) != 0))
+			strncpy(curtable, table, XT_TABLE_MAXNAMELEN);
+			curtable[XT_TABLE_MAXNAMELEN] = '\0';
+
+			if (tablename && strcmp(tablename, table) != 0) {
+				if (lock >= 0) {
+					xtables_unlock(lock);
+					lock = XT_LOCK_NOT_ACQUIRED;
+				}
 				continue;
+			}
 			if (handle)
-				iptc_free(handle);
+				cb->ops->free(handle);
 
-			handle = create_handle(table);
+			handle = create_handle(cb, table);
 			if (noflush == 0) {
 				DEBUGP("Cleaning all chains of table '%s'\n",
 					table);
-				for_each_chain4(flush_entries4, verbose, 1,
+				cb->for_each_chain(cb->flush_entries, verbose, 1,
 						handle);
 
 				DEBUGP("Deleting all user-defined chains "
 				       "of table '%s'\n", table);
-				for_each_chain4(delete_chain4, verbose, 0,
+				cb->for_each_chain(cb->delete_chain, verbose, 0,
 						handle);
 			}
 
@@ -252,12 +250,10 @@
 
 			chain = strtok(buffer+1, " \t\n");
 			DEBUGP("line %u, chain '%s'\n", line, chain);
-			if (!chain) {
+			if (!chain)
 				xtables_error(PARAMETER_PROBLEM,
 					   "%s: line %u chain name invalid\n",
-					   prog_name, line);
-				exit(1);
-			}
+					   xt_params->program_name, line);
 
 			if (strlen(chain) >= XT_EXTENSION_MAXNAMELEN)
 				xtables_error(PARAMETER_PROBLEM,
@@ -265,17 +261,17 @@
 					   "(%u chars max)",
 					   chain, XT_EXTENSION_MAXNAMELEN - 1);
 
-			if (iptc_builtin(chain, handle) <= 0) {
-				if (noflush && iptc_is_chain(chain, handle)) {
+			if (cb->ops->builtin(chain, handle) <= 0) {
+				if (noflush && cb->ops->is_chain(chain, handle)) {
 					DEBUGP("Flushing existing user defined chain '%s'\n", chain);
-					if (!iptc_flush_entries(chain, handle))
+					if (!cb->ops->flush_entries(chain, handle))
 						xtables_error(PARAMETER_PROBLEM,
 							   "error flushing chain "
 							   "'%s':%s\n", chain,
 							   strerror(errno));
 				} else {
 					DEBUGP("Creating new chain '%s'\n", chain);
-					if (!iptc_create_chain(chain, handle))
+					if (!cb->ops->create_chain(chain, handle))
 						xtables_error(PARAMETER_PROBLEM,
 							   "error creating chain "
 							   "'%s':%s\n", chain,
@@ -285,15 +281,13 @@
 
 			policy = strtok(NULL, " \t\n");
 			DEBUGP("line %u, policy '%s'\n", line, policy);
-			if (!policy) {
+			if (!policy)
 				xtables_error(PARAMETER_PROBLEM,
 					   "%s: line %u policy invalid\n",
-					   prog_name, line);
-				exit(1);
-			}
+					   xt_params->program_name, line);
 
 			if (strcmp(policy, "-") != 0) {
-				struct ipt_counters count;
+				struct xt_counters count = {};
 
 				if (counters) {
 					char *ctrs;
@@ -301,171 +295,135 @@
 
 					if (!ctrs || !parse_counters(ctrs, &count))
 						xtables_error(PARAMETER_PROBLEM,
-							   "invalid policy counters "
-							   "for chain '%s'\n", chain);
-
-				} else {
-					memset(&count, 0,
-					       sizeof(struct ipt_counters));
+							  "invalid policy counters "
+							  "for chain '%s'\n", chain);
 				}
 
 				DEBUGP("Setting policy of chain %s to %s\n",
 					chain, policy);
 
-				if (!iptc_set_policy(chain, policy, &count,
+				if (!cb->ops->set_policy(chain, policy, &count,
 						     handle))
 					xtables_error(OTHER_PROBLEM,
 						"Can't set policy `%s'"
 						" on `%s' line %u: %s\n",
 						policy, chain, line,
-						iptc_strerror(errno));
+						cb->ops->strerror(errno));
 			}
 
 			ret = 1;
 
 		} else if (in_table) {
-			int a;
-			char *ptr = buffer;
 			char *pcnt = NULL;
 			char *bcnt = NULL;
-			char *parsestart;
+			char *parsestart = buffer;
 
-			/* the parser */
-			char *curchar;
-			int quote_open, escaped;
-			size_t param_len;
+			add_argv(&av_store, argv[0], 0);
+			add_argv(&av_store, "-t", 0);
+			add_argv(&av_store, curtable, 0);
 
-			/* 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);
-
+			tokenize_rule_counters(&parsestart, &pcnt, &bcnt, line);
 			if (counters && pcnt && bcnt) {
-				add_argv("--set-counters");
-				add_argv((char *) pcnt);
-				add_argv((char *) bcnt);
+				add_argv(&av_store, "--set-counters", 0);
+				add_argv(&av_store, pcnt, 0);
+				add_argv(&av_store, bcnt, 0);
 			}
 
-			/* 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 */
+			add_param_to_argv(&av_store, parsestart, line);
 
-			quote_open = 0;
-			escaped = 0;
-			param_len = 0;
+			DEBUGP("calling do_command(%u, argv, &%s, handle):\n",
+				av_store.argc, curtable);
+			debug_print_argv(&av_store);
 
-			for (curchar = parsestart; *curchar; curchar++) {
-				char param_buffer[1024];
+			ret = cb->do_command(av_store.argc, av_store.argv,
+					 &av_store.argv[2], &handle, true);
 
-				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,
-						   "Line %u seems to have a "
-						   "-t table option.\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!");
-				}
-			}
-
-			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_command4(newargc, newargv,
-					 &newargv[2], &handle);
-
-			free_argv();
+			free_argv(&av_store);
 			fflush(stdout);
 		}
-		if (tablename && (strcmp(tablename, curtable) != 0))
+		if (tablename && strcmp(tablename, curtable) != 0)
 			continue;
 		if (!ret) {
 			fprintf(stderr, "%s: line %u failed\n",
-					prog_name, line);
+					xt_params->program_name, line);
 			exit(1);
 		}
 	}
 	if (in_table) {
 		fprintf(stderr, "%s: COMMIT expected at line %u\n",
-				prog_name, line + 1);
+				xt_params->program_name, line + 1);
 		exit(1);
 	}
 
-	if (in != NULL)
-		fclose(in);
+	fclose(in);
 	return 0;
 }
+
+
+#if defined ENABLE_IPV4
+static const struct iptables_restore_cb ipt_restore_cb = {
+	.ops		= &iptc_ops,
+	.for_each_chain	= for_each_chain4,
+	.flush_entries	= flush_entries4,
+	.delete_chain	= delete_chain4,
+	.do_command	= do_command4,
+};
+
+int
+iptables_restore_main(int argc, char *argv[])
+{
+	int c, ret;
+
+	iptables_globals.program_name = "iptables-restore";
+	c = xtables_init_all(&iptables_globals, NFPROTO_IPV4);
+	if (c < 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
+
+	ret = ip46tables_restore_main(&ipt_restore_cb, argc, argv);
+
+	xtables_fini();
+	return ret;
+}
+#endif
+
+#if defined ENABLE_IPV6
+static const struct iptables_restore_cb ip6t_restore_cb = {
+	.ops		= &ip6tc_ops,
+	.for_each_chain	= for_each_chain6,
+	.flush_entries	= flush_entries6,
+	.delete_chain	= delete_chain6,
+	.do_command	= do_command6,
+};
+
+int
+ip6tables_restore_main(int argc, char *argv[])
+{
+	int c, ret;
+
+	ip6tables_globals.program_name = "ip6tables-restore";
+	c = xtables_init_all(&ip6tables_globals, NFPROTO_IPV6);
+	if (c < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
+				ip6tables_globals.program_name,
+				ip6tables_globals.program_version);
+		exit(1);
+	}
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+	init_extensions();
+	init_extensions6();
+#endif
+
+	ret = ip46tables_restore_main(&ip6t_restore_cb, argc, argv);
+
+	xtables_fini();
+	return ret;
+}
+#endif
diff --git a/iptables/iptables-save.8 b/iptables/iptables-save.8
deleted file mode 100644
index c2e0a94..0000000
--- a/iptables/iptables-save.8
+++ /dev/null
@@ -1,51 +0,0 @@
-.TH IPTABLES-SAVE 8 "Jan 04, 2001" "" ""
-.\"
-.\" Man page written by Harald Welte <laforge@gnumonks.org>
-.\" It is based on the iptables man page.
-.\"
-.\"	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.
-.\"
-.\"
-.SH NAME
-iptables-save \(em dump iptables rules to stdout
-.SH SYNOPSIS
-\fBiptables\-save\fP [\fB\-M\fP \fImodprobe\fP] [\fB\-c\fP]
-[\fB\-t\fP \fItable\fP]
-.SH DESCRIPTION
-.PP
-.B iptables-save
-is used to dump the contents of an IP Table in easily parseable format
-to STDOUT. Use I/O-redirection provided by your shell to write to a file.
-.TP
-\fB\-M\fP \fImodprobe_program\fP
-Specify the path to the modprobe program. By default, iptables-save will
-inspect /proc/sys/kernel/modprobe to determine the executable's path.
-.TP
-\fB\-c\fR, \fB\-\-counters\fR
-include the current values of all packet and byte counters in the output
-.TP
-\fB\-t\fR, \fB\-\-table\fR \fItablename\fP
-restrict output to only one table. If not specified, output includes all
-available tables.
-.SH BUGS
-None known as of iptables-1.2.1 release
-.SH AUTHOR
-Harald Welte <laforge@gnumonks.org>
-.SH SEE ALSO
-\fBiptables\-restore\fP(8), \fBiptables\fP(8)
-.PP
-The iptables-HOWTO, which details more iptables usage, the NAT-HOWTO,
-which details NAT, and the netfilter-hacking-HOWTO which details the
-internals.
diff --git a/iptables/iptables-save.8.in b/iptables/iptables-save.8.in
new file mode 100644
index 0000000..7683fd3
--- /dev/null
+++ b/iptables/iptables-save.8.in
@@ -0,0 +1,69 @@
+.TH IPTABLES-SAVE 8 "" "@PACKAGE_STRING@" "@PACKAGE_STRING@"
+.\"
+.\" Man page written by Harald Welte <laforge@gnumonks.org>
+.\" It is based on the iptables man page.
+.\"
+.\"	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.
+.\"
+.\"
+.SH NAME
+iptables-save \(em dump iptables rules
+.P
+ip6tables-save \(em dump iptables rules
+.SH SYNOPSIS
+\fBiptables\-save\fP [\fB\-M\fP \fImodprobe\fP] [\fB\-c\fP]
+[\fB\-t\fP \fItable\fP] [\fB\-f\fP \fIfilename\fP]
+.P
+\fBip6tables\-save\fP [\fB\-M\fP \fImodprobe\fP] [\fB\-c\fP]
+[\fB\-t\fP \fItable\fP] [\fB\-f\fP \fIfilename\fP]
+.SH DESCRIPTION
+.PP
+.B iptables-save
+and
+.B ip6tables-save
+are used to dump the contents of IP or IPv6 Table in easily parseable format
+either to STDOUT or to a specified file.
+.TP
+\fB\-M\fR, \fB\-\-modprobe\fR \fImodprobe_program\fP
+Specify the path to the modprobe program. By default, iptables-save will
+inspect /proc/sys/kernel/modprobe to determine the executable's path.
+.TP
+\fB\-f\fR, \fB\-\-file\fR \fIfilename\fP
+Specify a filename to log the output to. If not specified, iptables-save
+will log to STDOUT.
+.TP
+\fB\-c\fR, \fB\-\-counters\fR
+include the current values of all packet and byte counters in the output
+.TP
+\fB\-t\fR, \fB\-\-table\fR \fItablename\fP
+restrict output to only one table. If the kernel is configured with automatic
+module loading, an attempt will be made to load the appropriate module for
+that table if it is not already there.
+.br
+If not specified, output includes all available tables.
+.SH BUGS
+None known as of iptables-1.2.1 release
+.SH AUTHORS
+Harald Welte <laforge@gnumonks.org>
+.br
+Rusty Russell <rusty@rustcorp.com.au>
+.br
+Andras Kis-Szabo <kisza@sch.bme.hu> contributed ip6tables-save.
+.SH SEE ALSO
+\fBiptables\-apply\fP(8),\fBiptables\-restore\fP(8), \fBiptables\fP(8)
+.PP
+The iptables-HOWTO, which details more iptables usage, the NAT-HOWTO,
+which details NAT, and the netfilter-hacking-HOWTO which details the
+internals.
diff --git a/iptables/iptables-save.c b/iptables/iptables-save.c
index 7542bdc..4efd666 100644
--- a/iptables/iptables-save.c
+++ b/iptables/iptables-save.c
@@ -5,43 +5,58 @@
  * This code is distributed under the terms of GNU GPL v2
  *
  */
+#include "config.h"
 #include <getopt.h>
-#include <sys/errno.h>
+#include <errno.h>
 #include <stdio.h>
 #include <fcntl.h>
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
 #include <netdb.h>
+#include <unistd.h>
 #include "libiptc/libiptc.h"
+#include "libiptc/libip6tc.h"
 #include "iptables.h"
+#include "ip6tables.h"
 #include "iptables-multi.h"
+#include "ip6tables-multi.h"
+#include "xshared.h"
 
-#ifndef NO_SHARED_LIBS
-#include <dlfcn.h>
-#endif
-
-static int show_binary = 0, show_counters = 0;
+static int show_counters;
 
 static const struct option options[] = {
-	{.name = "binary",   .has_arg = false, .val = 'b'},
 	{.name = "counters", .has_arg = false, .val = 'c'},
 	{.name = "dump",     .has_arg = false, .val = 'd'},
 	{.name = "table",    .has_arg = true,  .val = 't'},
 	{.name = "modprobe", .has_arg = true,  .val = 'M'},
+	{.name = "file",     .has_arg = true,  .val = 'f'},
+	{.name = "version",  .has_arg = false, .val = 'V'},
 	{NULL},
 };
 
-/* Debugging prototype. */
-static int for_each_table(int (*func)(const char *tablename))
+struct iptables_save_cb {
+	const struct xtc_ops *ops;
+
+	void (*dump_rules)(const char *chain, struct xtc_handle *handle);
+};
+
+static int
+for_each_table(int (*func)(struct iptables_save_cb *cb, const char *tablename),
+	       struct iptables_save_cb *cb)
 {
 	int ret = 1;
 	FILE *procfile = NULL;
-	char tablename[IPT_TABLE_MAXNAMELEN+1];
+	char tablename[XT_TABLE_MAXNAMELEN+1];
 
-	procfile = fopen("/proc/net/ip_tables_names", "re");
-	if (!procfile)
-		return ret;
+	procfile = fopen(afinfo->proc_exists, "re");
+	if (!procfile) {
+		if (errno == ENOENT)
+			return ret;
+		fprintf(stderr, "Failed to list table names in %s: %s\n",
+		        afinfo->proc_exists, strerror(errno));
+		exit(1);
+	}
 
 	while (fgets(tablename, sizeof(tablename), procfile)) {
 		if (tablename[strlen(tablename) - 1] != '\n')
@@ -49,78 +64,65 @@
 				   "Badly formed tablename `%s'\n",
 				   tablename);
 		tablename[strlen(tablename) - 1] = '\0';
-		ret &= func(tablename);
+		ret &= func(cb, tablename);
 	}
 
 	fclose(procfile);
 	return ret;
 }
 
-
-static int do_output(const char *tablename)
+static int do_output(struct iptables_save_cb *cb, const char *tablename)
 {
-	struct iptc_handle *h;
+	struct xtc_handle *h;
 	const char *chain = NULL;
 
 	if (!tablename)
-		return for_each_table(&do_output);
+		return for_each_table(&do_output, cb);
 
-	h = iptc_init(tablename);
+	h = cb->ops->init(tablename);
 	if (h == NULL) {
 		xtables_load_ko(xtables_modprobe_program, false);
-		h = iptc_init(tablename);
+		h = cb->ops->init(tablename);
 	}
 	if (!h)
 		xtables_error(OTHER_PROBLEM, "Cannot initialize: %s\n",
-			   iptc_strerror(errno));
+			      cb->ops->strerror(errno));
 
-	if (!show_binary) {
-		time_t now = time(NULL);
+	time_t now = time(NULL);
 
-		printf("# Generated by iptables-save v%s on %s",
-		       IPTABLES_VERSION, ctime(&now));
-		printf("*%s\n", tablename);
+	printf("# Generated by %s v%s on %s",
+	       xt_params->program_name, PACKAGE_VERSION, ctime(&now));
+	printf("*%s\n", tablename);
 
-		/* Dump out chain names first,
-		 * thereby preventing dependency conflicts */
-		for (chain = iptc_first_chain(h);
-		     chain;
-		     chain = iptc_next_chain(h)) {
+	/* Dump out chain names first,
+	 * thereby preventing dependency conflicts */
+	for (chain = cb->ops->first_chain(h);
+	     chain;
+	     chain = cb->ops->next_chain(h)) {
 
-			printf(":%s ", chain);
-			if (iptc_builtin(chain, h)) {
-				struct ipt_counters count;
-				printf("%s ",
-				       iptc_get_policy(chain, &count, h));
-				printf("[%llu:%llu]\n", (unsigned long long)count.pcnt, (unsigned long long)count.bcnt);
-			} else {
-				printf("- [0:0]\n");
-			}
+		printf(":%s ", chain);
+		if (cb->ops->builtin(chain, h)) {
+			struct xt_counters count;
+
+			printf("%s ", cb->ops->get_policy(chain, &count, h));
+			printf("[%llu:%llu]\n",
+			       (unsigned long long)count.pcnt,
+			       (unsigned long long)count.bcnt);
+		} else {
+			printf("- [0:0]\n");
 		}
-
-
-		for (chain = iptc_first_chain(h);
-		     chain;
-		     chain = iptc_next_chain(h)) {
-			const struct ipt_entry *e;
-
-			/* Dump out rules */
-			e = iptc_first_rule(chain, h);
-			while(e) {
-				print_rule4(e, h, chain, show_counters);
-				e = iptc_next_rule(e, h);
-			}
-		}
-
-		now = time(NULL);
-		printf("COMMIT\n");
-		printf("# Completed on %s", ctime(&now));
-	} else {
-		/* Binary, huh?  OK. */
-		xtables_error(OTHER_PROBLEM, "Binary NYI\n");
 	}
 
-	iptc_free(h);
+	for (chain = cb->ops->first_chain(h);
+	     chain;
+	     chain = cb->ops->next_chain(h)) {
+		cb->dump_rules(chain, h);
+	}
+
+	now = time(NULL);
+	printf("COMMIT\n");
+	printf("# Completed on %s", ctime(&now));
+	cb->ops->free(h);
 
 	return 1;
 }
@@ -129,36 +131,18 @@
  * :Chain name POLICY packets bytes
  * rule
  */
-#ifdef IPTABLES_MULTI
-int
-iptables_save_main(int argc, char *argv[])
-#else
-int
-main(int argc, char *argv[])
-#endif
+static int
+do_iptables_save(struct iptables_save_cb *cb, int argc, char *argv[])
 {
 	const char *tablename = NULL;
-	int c;
+	FILE *file = NULL;
+	int ret, c;
 
-	iptables_globals.program_name = "iptables-save";
-	c = xtables_init_all(&iptables_globals, NFPROTO_IPV4);
-	if (c < 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
-
-	while ((c = getopt_long(argc, argv, "bcdt:", options, NULL)) != -1) {
+	while ((c = getopt_long(argc, argv, "bcdt:M:f:V", options, NULL)) != -1) {
 		switch (c) {
 		case 'b':
-			show_binary = 1;
+			fprintf(stderr, "-b/--binary option is not implemented\n");
 			break;
-
 		case 'c':
 			show_counters = 1;
 			break;
@@ -170,9 +154,34 @@
 		case 'M':
 			xtables_modprobe_program = optarg;
 			break;
+		case 'f':
+			file = fopen(optarg, "w");
+			if (file == NULL) {
+				fprintf(stderr, "Failed to open file, error: %s\n",
+					strerror(errno));
+				exit(1);
+			}
+			ret = dup2(fileno(file), STDOUT_FILENO);
+			if (ret == -1) {
+				fprintf(stderr, "Failed to redirect stdout, error: %s\n",
+					strerror(errno));
+				exit(1);
+			}
+			fclose(file);
+			break;
 		case 'd':
-			do_output(tablename);
+			do_output(cb, tablename);
 			exit(0);
+		case 'V':
+			printf("%s v%s (legacy)\n",
+			       xt_params->program_name,
+			       xt_params->program_version);
+			exit(0);
+		default:
+			fprintf(stderr,
+				"Look at manual page `%s.8' for more information.\n",
+				xt_params->program_name);
+			exit(1);
 		}
 	}
 
@@ -181,5 +190,97 @@
 		exit(1);
 	}
 
-	return !do_output(tablename);
+	return !do_output(cb, tablename);
 }
+
+#ifdef ENABLE_IPV4
+static void iptables_dump_rules(const char *chain, struct xtc_handle *h)
+{
+	const struct ipt_entry *e;
+
+	/* Dump out rules */
+	e = iptc_first_rule(chain, h);
+	while(e) {
+		print_rule4(e, h, chain, show_counters);
+		e = iptc_next_rule(e, h);
+	}
+}
+
+struct iptables_save_cb ipt_save_cb = {
+	.ops		= &iptc_ops,
+	.dump_rules	= iptables_dump_rules,
+};
+
+/* Format:
+ * :Chain name POLICY packets bytes
+ * rule
+ */
+int
+iptables_save_main(int argc, char *argv[])
+{
+	int ret;
+
+	iptables_globals.program_name = "iptables-save";
+	if (xtables_init_all(&iptables_globals, NFPROTO_IPV4) < 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
+
+	ret = do_iptables_save(&ipt_save_cb, argc, argv);
+
+	xtables_fini();
+	return ret;
+}
+#endif /* ENABLE_IPV4 */
+
+#ifdef ENABLE_IPV6
+static void ip6tables_dump_rules(const char *chain, struct xtc_handle *h)
+{
+	const struct ip6t_entry *e;
+
+	/* Dump out rules */
+	e = ip6tc_first_rule(chain, h);
+	while(e) {
+		print_rule6(e, h, chain, show_counters);
+		e = ip6tc_next_rule(e, h);
+	}
+}
+
+struct iptables_save_cb ip6t_save_cb = {
+	.ops		= &ip6tc_ops,
+	.dump_rules	= ip6tables_dump_rules,
+};
+
+/* Format:
+ * :Chain name POLICY packets bytes
+ * rule
+ */
+int
+ip6tables_save_main(int argc, char *argv[])
+{
+	int ret;
+
+	ip6tables_globals.program_name = "ip6tables-save";
+	if (xtables_init_all(&ip6tables_globals, NFPROTO_IPV6) < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
+				ip6tables_globals.program_name,
+				ip6tables_globals.program_version);
+		exit(1);
+	}
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+	init_extensions();
+	init_extensions6();
+#endif
+
+	ret = do_iptables_save(&ip6t_save_cb, argc, argv);
+
+	xtables_fini();
+	return ret;
+}
+#endif /* ENABLE_IPV6 */
diff --git a/iptables/iptables-standalone.c b/iptables/iptables-standalone.c
index 6cf8483..3c8af60 100644
--- a/iptables/iptables-standalone.c
+++ b/iptables/iptables-standalone.c
@@ -39,17 +39,12 @@
 #include <iptables.h>
 #include "iptables-multi.h"
 
-#ifdef IPTABLES_MULTI
 int
 iptables_main(int argc, char *argv[])
-#else
-int
-main(int argc, char *argv[])
-#endif
 {
 	int ret;
 	char *table = "filter";
-	struct iptc_handle *handle = NULL;
+	struct xtc_handle *handle = NULL;
 
 	signal(SIGPIPE, SIG_IGN);
 
@@ -66,12 +61,14 @@
 	init_extensions4();
 #endif
 
-	ret = do_command4(argc, argv, &table, &handle);
+	ret = do_command4(argc, argv, &table, &handle, false);
 	if (ret) {
 		ret = iptc_commit(handle);
 		iptc_free(handle);
 	}
 
+	xtables_fini();
+
 	if (!ret) {
 		if (errno == EINVAL) {
 			fprintf(stderr, "iptables: %s. "
@@ -81,9 +78,8 @@
 			fprintf(stderr, "iptables: %s.\n",
 				iptc_strerror(errno));
 		}
-		if (errno == EAGAIN) {
+		if (errno == EAGAIN)
 			exit(RESOURCE_PROBLEM);
-		}
 	}
 
 	exit(!ret);
diff --git a/iptables/iptables-xml.1 b/iptables/iptables-xml.1
deleted file mode 100644
index 048c2cb..0000000
--- a/iptables/iptables-xml.1
+++ /dev/null
@@ -1,87 +0,0 @@
-.TH IPTABLES-XML 8 "Jul 16, 2007" "" ""
-.\"
-.\" Man page written by Sam Liddicott <azez@ufomechanic.net>
-.\" It is based on the iptables-save man page.
-.\"
-.\"	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.
-.\"
-.\"
-.SH NAME
-iptables-xml \(em Convert iptables-save format to XML
-.SH SYNOPSIS
-\fBiptables\-xml\fP [\fB\-c\fP] [\fB\-v\fP]
-.SH DESCRIPTION
-.PP
-.B iptables-xml
-is used to convert the output of iptables-save into an easily manipulatable
-XML format to STDOUT.  Use I/O-redirection provided by your shell to write to 
-a file.
-.TP
-\fB\-c\fR, \fB\-\-combine\fR
-combine consecutive rules with the same matches but different targets. iptables
-does not currently support more than one target per match, so this simulates 
-that by collecting the targets from consecutive iptables rules into one action
-tag, but only when the rule matches are identical. Terminating actions like
-RETURN, DROP, ACCEPT and QUEUE are not combined with subsequent targets.
-.TP
-\fB\-v\fR, \fB\-\-verbose\fR
-Output xml comments containing the iptables line from which the XML is derived
-
-.PP
-iptables-xml does a mechanistic conversion to a very expressive xml
-format; the only semantic considerations are for \-g and \-j targets in
-order to discriminate between <call> <goto> and <nane-of-target> as it
-helps xml processing scripts if they can tell the difference between a
-target like SNAT and another chain.
-
-Some sample output is:
-
-<iptables-rules>
-  <table name="mangle">
-    <chain name="PREROUTING" policy="ACCEPT" packet-count="63436"
-byte-count="7137573">
-      <rule>
-       <conditions>
-        <match>
-          <p>tcp</p>
-        </match>
-        <tcp>
-          <sport>8443</sport>
-        </tcp>
-       </conditions>
-       <actions>
-        <call>
-          <check_ip/>
-        </call>
-        <ACCEPT/>
-       </actions>
-      </rule>
-    </chain>
-  </table>
-</iptables-rules>
-
-.PP
-Conversion from XML to iptables-save format may be done using the 
-iptables.xslt script and xsltproc, or a custom program using
-libxsltproc or similar; in this fashion:
-
-xsltproc iptables.xslt my-iptables.xml | iptables-restore
-
-.SH BUGS
-None known as of iptables-1.3.7 release
-.SH AUTHOR
-Sam Liddicott <azez@ufomechanic.net>
-.SH SEE ALSO
-\fBiptables\-save\fP(8), \fBiptables\-restore\fP(8), \fBiptables\fP(8)
diff --git a/iptables/iptables-xml.1.in b/iptables/iptables-xml.1.in
new file mode 100644
index 0000000..7b7878f
--- /dev/null
+++ b/iptables/iptables-xml.1.in
@@ -0,0 +1,87 @@
+.TH IPTABLES-XML 1 "" "@PACKAGE_STRING@" "@PACKAGE_STRING@"
+.\"
+.\" Man page written by Sam Liddicott <azez@ufomechanic.net>
+.\" It is based on the iptables-save man page.
+.\"
+.\"	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.
+.\"
+.\"
+.SH NAME
+iptables-xml \(em Convert iptables-save format to XML
+.SH SYNOPSIS
+\fBiptables\-xml\fP [\fB\-c\fP] [\fB\-v\fP]
+.SH DESCRIPTION
+.PP
+.B iptables-xml
+is used to convert the output of iptables-save into an easily manipulatable
+XML format to STDOUT.  Use I/O-redirection provided by your shell to write to 
+a file.
+.TP
+\fB\-c\fR, \fB\-\-combine\fR
+combine consecutive rules with the same matches but different targets. iptables
+does not currently support more than one target per match, so this simulates 
+that by collecting the targets from consecutive iptables rules into one action
+tag, but only when the rule matches are identical. Terminating actions like
+RETURN, DROP, ACCEPT and QUEUE are not combined with subsequent targets.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+Output xml comments containing the iptables line from which the XML is derived
+
+.PP
+iptables-xml does a mechanistic conversion to a very expressive xml
+format; the only semantic considerations are for \-g and \-j targets in
+order to discriminate between <call> <goto> and <nane-of-target> as it
+helps xml processing scripts if they can tell the difference between a
+target like SNAT and another chain.
+
+Some sample output is:
+
+<iptables-rules>
+  <table name="mangle">
+    <chain name="PREROUTING" policy="ACCEPT" packet-count="63436"
+byte-count="7137573">
+      <rule>
+       <conditions>
+        <match>
+          <p>tcp</p>
+        </match>
+        <tcp>
+          <sport>8443</sport>
+        </tcp>
+       </conditions>
+       <actions>
+        <call>
+          <check_ip/>
+        </call>
+        <ACCEPT/>
+       </actions>
+      </rule>
+    </chain>
+  </table>
+</iptables-rules>
+
+.PP
+Conversion from XML to iptables-save format may be done using the 
+iptables.xslt script and xsltproc, or a custom program using
+libxsltproc or similar; in this fashion:
+
+xsltproc iptables.xslt my-iptables.xml | iptables-restore
+
+.SH BUGS
+None known as of iptables-1.3.7 release
+.SH AUTHOR
+Sam Liddicott <azez@ufomechanic.net>
+.SH SEE ALSO
+\fBiptables\-save\fP(8), \fBiptables\-restore\fP(8), \fBiptables\fP(8)
diff --git a/iptables/iptables-xml.c b/iptables/iptables-xml.c
index 5aa638c..98d03dd 100644
--- a/iptables/iptables-xml.c
+++ b/iptables/iptables-xml.c
@@ -5,9 +5,9 @@
  *
  * This code is distributed under the terms of GNU GPL v2
  */
-
+#include "config.h"
 #include <getopt.h>
-#include <sys/errno.h>
+#include <errno.h>
 #include <string.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -16,20 +16,11 @@
 #include "libiptc/libiptc.h"
 #include "xtables-multi.h"
 #include <xtables.h>
-
-#ifdef DEBUG
-#define DEBUGP(x, args...) fprintf(stderr, x, ## args)
-#else
-#define DEBUGP(x, args...)
-#endif
-
-#ifndef IPTABLES_MULTI
-int line = 0;
-#endif
+#include "xshared.h"
 
 struct xtables_globals iptables_xml_globals = {
 	.option_offset = 0,
-	.program_version = IPTABLES_VERSION,
+	.program_version = PACKAGE_VERSION,
 	.program_name = "iptables-xml",
 };
 #define prog_name iptables_xml_globals.program_name
@@ -38,11 +29,11 @@
 static void print_usage(const char *name, const char *version)
 	    __attribute__ ((noreturn));
 
-static int verbose = 0;
+static int verbose;
 /* Whether to combine actions of sequential rules with identical conditions */
-static int combine = 0;
+static int combine;
 /* Keeping track of external matches and targets.  */
-static struct option options[] = {
+static const struct option options[] = {
 	{"verbose", 0, NULL, 'v'},
 	{"combine", 0, NULL, 'c'},
 	{"help", 0, NULL, 'h'},
@@ -59,98 +50,22 @@
 	exit(1);
 }
 
-static int
-parse_counters(char *string, struct ipt_counters *ctr)
-{
-	__u64 *pcnt, *bcnt;
-
-	if (string != NULL) {
-		pcnt = &ctr->pcnt;
-		bcnt = &ctr->bcnt;
-		return (sscanf
-			(string, "[%llu:%llu]",
-			 (unsigned long long *)pcnt,
-			 (unsigned long long *)bcnt) == 2);
-	} else
-		return (0 == 2);
-}
-
-/* global new argv and argc */
-static char *newargv[255];
-static unsigned int newargc = 0;
-
-static char *oldargv[255];
-static unsigned int oldargc = 0;
-
-/* arg meta data, were they quoted, frinstance */
-static int newargvattr[255];
-
-#define IPT_CHAIN_MAXNAMELEN IPT_TABLE_MAXNAMELEN
-static char closeActionTag[IPT_TABLE_MAXNAMELEN + 1];
-static char closeRuleTag[IPT_TABLE_MAXNAMELEN + 1];
-static char curTable[IPT_TABLE_MAXNAMELEN + 1];
-static char curChain[IPT_CHAIN_MAXNAMELEN + 1];
+#define XT_CHAIN_MAXNAMELEN XT_TABLE_MAXNAMELEN
+static char closeActionTag[XT_TABLE_MAXNAMELEN + 1];
+static char closeRuleTag[XT_TABLE_MAXNAMELEN + 1];
+static char curTable[XT_TABLE_MAXNAMELEN + 1];
+static char curChain[XT_CHAIN_MAXNAMELEN + 1];
 
 struct chain {
 	char *chain;
 	char *policy;
-	struct ipt_counters count;
+	struct xt_counters count;
 	int created;
 };
 
 #define maxChains 10240		/* max chains per table */
 static struct chain chains[maxChains];
-static int nextChain = 0;
-
-/* funCtion adding one argument to newargv, updating newargc 
- * returns true if argument added, false otherwise */
-static int
-add_argv(char *what, int quoted)
-{
-	DEBUGP("add_argv: %d %s\n", newargc, what);
-	if (what && newargc + 1 < ARRAY_SIZE(newargv)) {
-		newargv[newargc] = strdup(what);
-		newargvattr[newargc] = quoted;
-		newargc++;
-		return 1;
-	} else
-		return 0;
-}
-
-static void
-free_argv(void)
-{
-	unsigned int i;
-
-	for (i = 0; i < newargc; i++) {
-		free(newargv[i]);
-		newargv[i] = NULL;
-	}
-	newargc = 0;
-
-	for (i = 0; i < oldargc; i++) {
-		free(oldargv[i]);
-		oldargv[i] = NULL;
-	}
-	oldargc = 0;
-}
-
-/* save parsed rule for comparison with next rule 
-   to perform action agregation on duplicate conditions */
-static void
-save_argv(void)
-{
-	unsigned int i;
-
-	for (i = 0; i < oldargc; i++)
-		free(oldargv[i]);
-	oldargc = newargc;
-	newargc = 0;
-	for (i = 0; i < oldargc; i++) {
-		oldargv[i] = newargv[i];
-		newargv[i] = NULL;
-	}
-}
+static int nextChain;
 
 /* like puts but with xml encoding */
 static void
@@ -237,12 +152,12 @@
 }
 
 static void
-openChain(char *chain, char *policy, struct ipt_counters *ctr, char close)
+openChain(char *chain, char *policy, struct xt_counters *ctr, char close)
 {
 	closeChain();
 
-	strncpy(curChain, chain, IPT_CHAIN_MAXNAMELEN);
-	curChain[IPT_CHAIN_MAXNAMELEN] = '\0';
+	strncpy(curChain, chain, XT_CHAIN_MAXNAMELEN);
+	curChain[XT_CHAIN_MAXNAMELEN] = '\0';
 
 	printf("    <chain ");
 	xmlAttrS("name", curChain);
@@ -291,14 +206,13 @@
 }
 
 static void
-saveChain(char *chain, char *policy, struct ipt_counters *ctr)
+saveChain(char *chain, char *policy, struct xt_counters *ctr)
 {
-	if (nextChain >= maxChains) {
+	if (nextChain >= maxChains)
 		xtables_error(PARAMETER_PROBLEM,
 			   "%s: line %u chain name invalid\n",
 			   prog_name, line);
-		exit(1);
-	};
+
 	chains[nextChain].chain = strdup(chain);
 	chains[nextChain].policy = strdup(policy);
 	chains[nextChain].count = *ctr;
@@ -336,8 +250,8 @@
 {
 	closeTable();
 
-	strncpy(curTable, table, IPT_TABLE_MAXNAMELEN);
-	curTable[IPT_TABLE_MAXNAMELEN] = '\0';
+	strncpy(curTable, table, XT_TABLE_MAXNAMELEN);
+	curTable[XT_TABLE_MAXNAMELEN] = '\0';
 
 	printf("  <table ");
 	xmlAttrS("name", curTable);
@@ -371,7 +285,8 @@
 do_rule_part(char *leveltag1, char *leveltag2, int part, int argc,
 	     char *argv[], int argvattr[])
 {
-	int arg = 1;		// ignore leading -A
+	int i;
+	int arg = 2;		// ignore leading -A <chain>
 	char invert_next = 0;
 	char *spacer = "";	// space when needed to assemble arguments
 	char *level1 = NULL;
@@ -403,11 +318,17 @@
 			arg++;
 	}
 
-	/* Before we start, if the first arg is -[^-] and not -m or -j or -g 
-	   then start a dummy <match> tag for old style built-in matches.  
-	   We would do this in any case, but no need if it would be empty */
-	if (arg < argc && argv[arg][0] == '-' && !isTarget(argv[arg])
-	    && strcmp(argv[arg], "-m") != 0) {
+	/* Before we start, if the first arg is -[^-] and not -m or -j or -g
+	 * then start a dummy <match> tag for old style built-in matches.
+	 * We would do this in any case, but no need if it would be empty.
+	 * In the case of negation, we need to look at arg+1
+	 */
+	if (arg < argc && strcmp(argv[arg], "!") == 0)
+		i = arg + 1;
+	else
+		i = arg;
+	if (i < argc && argv[i][0] == '-' && !isTarget(argv[i])
+	    && strcmp(argv[i], "-m") != 0) {
 		OPEN_LEVEL(1, "match");
 		printf(">\n");
 	}
@@ -422,12 +343,9 @@
 			else
 				printf("%s%s", spacer, argv[arg]);
 			spacer = " ";
-		} else if (!argvattr[arg] && isTarget(argv[arg])
-			   && existsChain(argv[arg + 1])
-			   && (2 + arg >= argc)) {
-			if (!((1 + arg) < argc))
-				// no args to -j, -m or -g, ignore & finish loop
-				break;
+		} else if (!argvattr[arg] && isTarget(argv[arg]) &&
+			   (arg + 1 < argc) &&
+			   existsChain(argv[arg + 1])) {
 			CLOSE_LEVEL(2);
 			if (level1)
 				printf("%s", leveli1);
@@ -522,14 +440,15 @@
 }
 
 static int
-compareRules(void)
+compareRules(int newargc, char *newargv[], int oldargc, char *oldargv[])
 {
-	/* compare arguments up to -j or -g for match.
-	   NOTE: We don't want to combine actions if there were no criteria 
-	   in each rule, or rules didn't have an action 
-	   NOTE: Depends on arguments being in some kind of "normal" order which 
-	   is the case when processing the ACTUAL output of actual iptables-save 
-	   rather than a file merely in a compatable format */
+	/* Compare arguments up to -j or -g for match.
+	 * NOTE: We don't want to combine actions if there were no criteria
+	 * in each rule, or rules didn't have an action.
+	 * NOTE: Depends on arguments being in some kind of "normal" order which
+	 * is the case when processing the ACTUAL output of actual iptables-save
+	 * rather than a file merely in a compatible format.
+	 */
 
 	unsigned int old = 0;
 	unsigned int new = 0;
@@ -570,11 +489,13 @@
 
 /* has a nice parsed rule starting with -A */
 static void
-do_rule(char *pcnt, char *bcnt, int argc, char *argv[], int argvattr[])
+do_rule(char *pcnt, char *bcnt, int argc, char *argv[], int argvattr[],
+	int oldargc, char *oldargv[])
 {
 	/* are these conditions the same as the previous rule?
 	 * If so, skip arg straight to -j or -g */
-	if (combine && argc > 2 && !isTarget(argv[2]) && compareRules()) {
+	if (combine && argc > 2 && !isTarget(argv[2]) &&
+	    compareRules(argc, argv, oldargc, oldargv)) {
 		xmlComment("Combine action from next rule");
 	} else {
 
@@ -596,8 +517,8 @@
 			xmlAttrS("byte-count", bcnt);
 		printf(">\n");
 
-		strncpy(closeRuleTag, "      </rule>\n", IPT_TABLE_MAXNAMELEN);
-		closeRuleTag[IPT_TABLE_MAXNAMELEN] = '\0';
+		strncpy(closeRuleTag, "      </rule>\n", XT_TABLE_MAXNAMELEN);
+		closeRuleTag[XT_TABLE_MAXNAMELEN] = '\0';
 
 		/* no point in writing out condition if there isn't one */
 		if (argc >= 3 && !isTarget(argv[2])) {
@@ -611,20 +532,16 @@
 	if (!closeActionTag[0]) {
 		printf("       <actions>\n");
 		strncpy(closeActionTag, "       </actions>\n",
-			IPT_TABLE_MAXNAMELEN);
-		closeActionTag[IPT_TABLE_MAXNAMELEN] = '\0';
+			XT_TABLE_MAXNAMELEN);
+		closeActionTag[XT_TABLE_MAXNAMELEN] = '\0';
 	}
 	do_rule_part(NULL, NULL, 1, argc, argv, argvattr);
 }
 
-#ifdef IPTABLES_MULTI
 int
 iptables_xml_main(int argc, char *argv[])
-#else
-int
-main(int argc, char *argv[])
-#endif
 {
+	struct argv_store last_rule = {}, cur_rule = {};
 	char buffer[10240];
 	int c;
 	FILE *in;
@@ -642,7 +559,7 @@
 			verbose = 1;
 			break;
 		case 'h':
-			print_usage("iptables-xml", IPTABLES_VERSION);
+			print_usage("iptables-xml", PACKAGE_VERSION);
 			break;
 		}
 	}
@@ -691,40 +608,35 @@
 
 			table = strtok(buffer + 1, " \t\n");
 			DEBUGP("line %u, table '%s'\n", line, table);
-			if (!table) {
+			if (!table)
 				xtables_error(PARAMETER_PROBLEM,
 					   "%s: line %u table name invalid\n",
 					   prog_name, line);
-				exit(1);
-			}
+
 			openTable(table);
 
 			ret = 1;
 		} else if ((buffer[0] == ':') && (curTable[0])) {
 			/* New chain. */
 			char *policy, *chain;
-			struct ipt_counters count;
+			struct xt_counters count;
 			char *ctrs;
 
 			chain = strtok(buffer + 1, " \t\n");
 			DEBUGP("line %u, chain '%s'\n", line, chain);
-			if (!chain) {
+			if (!chain)
 				xtables_error(PARAMETER_PROBLEM,
 					   "%s: line %u chain name invalid\n",
 					   prog_name, line);
-				exit(1);
-			}
 
 			DEBUGP("Creating new chain '%s'\n", chain);
 
 			policy = strtok(NULL, " \t\n");
 			DEBUGP("line %u, policy '%s'\n", line, policy);
-			if (!policy) {
+			if (!policy)
 				xtables_error(PARAMETER_PROBLEM,
 					   "%s: line %u policy invalid\n",
 					   prog_name, line);
-				exit(1);
-			}
 
 			ctrs = strtok(NULL, " \t\n");
 			parse_counters(ctrs, &count);
@@ -733,124 +645,34 @@
 			ret = 1;
 		} else if (curTable[0]) {
 			unsigned int a;
-			char *ptr = buffer;
 			char *pcnt = NULL;
 			char *bcnt = NULL;
-			char *parsestart;
+			char *parsestart = buffer;
 			char *chain = NULL;
 
-			/* the parser */
-			char *param_start, *curchar;
-			int quote_open, quoted;
-
-			/* 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;
-			}
-
-
-			/* This is a 'real' parser crafted in artist mode
-			 * not hacker mode. If the author can live with that
-			 * then so can everyone else */
-
-			quote_open = 0;
-			/* We need to know which args were quoted so we 
-			   can preserve quote */
-			quoted = 0;
-			param_start = parsestart;
-
-			for (curchar = parsestart; *curchar; curchar++) {
-				if (*curchar == '"') {
-					/* quote_open cannot be true if there
-					 * was no previous character.  Thus, 
-					 * curchar-1 has to be within bounds */
-					if (quote_open &&
-					    *(curchar - 1) != '\\') {
-						quote_open = 0;
-						*curchar = ' ';
-					} else {
-						quote_open = 1;
-						quoted = 1;
-						param_start++;
-					}
-				}
-				if (*curchar == ' '
-				    || *curchar == '\t' || *curchar == '\n') {
-					char param_buffer[1024];
-					int param_len = curchar - param_start;
-
-					if (quote_open)
-						continue;
-
-					if (!param_len) {
-						/* two spaces? */
-						param_start++;
-						continue;
-					}
-
-					/* end of one parameter */
-					strncpy(param_buffer, param_start,
-						param_len);
-					*(param_buffer + param_len) = '\0';
-
-					/* check if table name specified */
-					if (!strncmp(param_buffer, "-t", 3)
-					    || !strncmp(param_buffer,
-							"--table", 8)) {
-						xtables_error(PARAMETER_PROBLEM,
-							   "Line %u seems to have a "
-							   "-t table option.\n",
-							   line);
-						exit(1);
-					}
-
-					add_argv(param_buffer, quoted);
-					if (newargc >= 2
-					    && 0 ==
-					    strcmp(newargv[newargc - 2], "-A"))
-						chain = newargv[newargc - 1];
-					quoted = 0;
-					param_start += param_len + 1;
-				} else {
-					/* regular character, skip */
-				}
-			}
+			tokenize_rule_counters(&parsestart, &pcnt, &bcnt, line);
+			add_param_to_argv(&cur_rule, parsestart, line);
 
 			DEBUGP("calling do_command4(%u, argv, &%s, handle):\n",
-			       newargc, curTable);
+			       cur_rule.argc, curTable);
+			debug_print_argv(&cur_rule);
 
-			for (a = 0; a < newargc; a++)
-				DEBUGP("argv[%u]: %s\n", a, newargv[a]);
-
+			for (a = 1; a < cur_rule.argc; a++) {
+				if (strcmp(cur_rule.argv[a - 1], "-A"))
+					continue;
+				chain = cur_rule.argv[a];
+				break;
+			}
+			if (!chain) {
+				fprintf(stderr, "%s: line %u failed - no chain found\n",
+					prog_name, line);
+				exit(1);
+			}
 			needChain(chain);// Should we explicitly look for -A
-			do_rule(pcnt, bcnt, newargc, newargv, newargvattr);
+			do_rule(pcnt, bcnt, cur_rule.argc, cur_rule.argv,
+				cur_rule.argvattr, last_rule.argc, last_rule.argv);
 
-			save_argv();
+			save_argv(&last_rule, &cur_rule);
 			ret = 1;
 		}
 		if (!ret) {
@@ -865,10 +687,9 @@
 		exit(1);
 	}
 
-	if (in != NULL)
-		fclose(in);
+	fclose(in);
 	printf("</iptables-rules>\n");
-	free_argv();
+	free_argv(&last_rule);
 
 	return 0;
 }
diff --git a/iptables/iptables.8.in b/iptables/iptables.8.in
index d09bf7a..999cf33 100644
--- a/iptables/iptables.8.in
+++ b/iptables/iptables.8.in
@@ -1,4 +1,4 @@
-.TH IPTABLES 8 "" "@PACKAGE_AND_VERSION@" "@PACKAGE_AND_VERSION@"
+.TH IPTABLES 8 "" "@PACKAGE_STRING@" "@PACKAGE_STRING@"
 .\"
 .\" Man page written by Herve Eychenne <rv@wallfire.org> (May 1999)
 .\" It is based on ipchains page.
@@ -23,10 +23,13 @@
 .\"
 .\"
 .SH NAME
-iptables \(em administration tool for IPv4 packet filtering and NAT
+iptables/ip6tables \(em administration tool for IPv4/IPv6 packet filtering and NAT
 .SH SYNOPSIS
 \fBiptables\fP [\fB\-t\fP \fItable\fP] {\fB\-A\fP|\fB\-C\fP|\fB\-D\fP}
 \fIchain\fP \fIrule-specification\fP
+.P
+\fBip6tables\fP [\fB\-t\fP \fItable\fP] {\fB\-A\fP|\fB\-C\fP|\fB\-D\fP}
+\fIchain rule-specification\fP
 .PP
 \fBiptables\fP [\fB\-t\fP \fItable\fP] \fB\-I\fP \fIchain\fP [\fIrulenum\fP] \fIrule-specification\fP
 .PP
@@ -52,8 +55,8 @@
 .PP
 target = \fB\-j\fP \fItargetname\fP [\fIper\-target\-options\fP]
 .SH DESCRIPTION
-\fBIptables\fP is used to set up, maintain, and inspect the
-tables of IPv4 packet
+\fBIptables\fP and \fBip6tables\fP are used to set up, maintain, and inspect the
+tables of IPv4 and IPv6 packet
 filter rules in the Linux kernel.  Several different tables
 may be defined.  Each table contains a number of built-in
 chains and may also contain user-defined chains.
@@ -64,21 +67,14 @@
 table.
 .SH TARGETS
 A firewall rule specifies criteria for a packet and a target.  If the
-packet does not match, the next rule in the chain is the examined; if
+packet does not match, the next rule in the chain is examined; if
 it does match, then the next rule is specified by the value of the
-target, which can be the name of a user-defined chain or one of the
-special values \fBACCEPT\fP, \fBDROP\fP, \fBQUEUE\fP or \fBRETURN\fP.
+target, which can be the name of a user-defined chain, one of the targets
+described in \fBiptables\-extensions\fP(8), or one of the
+special values \fBACCEPT\fP, \fBDROP\fP or \fBRETURN\fP.
 .PP
 \fBACCEPT\fP means to let the packet through.
 \fBDROP\fP means to drop the packet on the floor.
-\fBQUEUE\fP means to pass the packet to userspace.
-(How the packet can be received
-by a userspace process differs by the particular queue handler.  2.4.x
-and 2.6.x kernels up to 2.6.13 include the \fBip_queue\fP
-queue handler.  Kernels 2.6.14 and later additionally include the
-\fBnfnetlink_queue\fP queue handler.  Packets with a target of QUEUE will be
-sent to queue number '0' in this case. Please also see the \fBNFQUEUE\fP
-target as described later in this man page.)
 \fBRETURN\fP means stop traversing this chain and resume at the next
 rule in the
 previous (calling) chain.  If the end of a built-in chain is reached
@@ -86,7 +82,7 @@
 is matched, the target specified by the chain policy determines the
 fate of the packet.
 .SH TABLES
-There are currently three independent tables (which tables are present
+There are currently five independent tables (which tables are present
 at any time depends on the kernel configuration options and which
 modules are present).
 .TP
@@ -107,10 +103,12 @@
 .TP
 \fBnat\fP:
 This table is consulted when a packet that creates a new
-connection is encountered.  It consists of three built-ins: \fBPREROUTING\fP
-(for altering packets as soon as they come in), \fBOUTPUT\fP
+connection is encountered.  It consists of four built-ins: \fBPREROUTING\fP
+(for altering packets as soon as they come in), \fBINPUT\fP (for altering
+packets destined for local sockets), \fBOUTPUT\fP
 (for altering locally-generated packets before routing), and \fBPOSTROUTING\fP
 (for altering packets as they are about to go out).
+IPv6 NAT support is available since kernel 3.7.
 .TP
 \fBmangle\fP:
 This table is used for specialized packet alteration.  Until kernel
@@ -143,7 +141,7 @@
 .RE
 .SH OPTIONS
 The options that are recognized by
-\fBiptables\fP can be divided into several different groups.
+\fBiptables\fP and \fBip6tables\fP can be divided into several different groups.
 .SS COMMANDS
 These options specify the desired action to perform. Only one of them
 can be specified on the command line unless otherwise stated
@@ -197,6 +195,8 @@
 .nf
  iptables \-L \-v
 .fi
+or
+\fBiptables\-save\fP(8).
 .TP
 \fB\-S\fP, \fB\-\-list\-rules\fP [\fIchain\fP]
 Print all rules in the selected chain.  If no chain is selected, all
@@ -227,10 +227,8 @@
 non-builtin chain in the table.
 .TP
 \fB\-P\fP, \fB\-\-policy\fP \fIchain target\fP
-Set the policy for the chain to the given target.  See the section \fBTARGETS\fP
-for the legal targets.  Only built-in (non-user-defined) chains can have
-policies, and neither built-in nor user-defined chains can be policy
-targets.
+Set the policy for the built-in (non-user-defined) chain to the given target.
+The policy target must be either \fBACCEPT\fP or \fBDROP\fP.
 .TP
 \fB\-E\fP, \fB\-\-rename\-chain\fP \fIold\-chain new\-chain\fP
 Rename the user specified chain to the user supplied name.  This is
@@ -243,16 +241,37 @@
 The following parameters make up a rule specification (as used in the
 add, delete, insert, replace and append commands).
 .TP
+\fB\-4\fP, \fB\-\-ipv4\fP
+This option has no effect in iptables and iptables-restore.
+If a rule using the \fB\-4\fP option is inserted with (and only with)
+ip6tables-restore, it will be silently ignored. Any other uses will throw an
+error. This option allows IPv4 and IPv6 rules in a single rule file
+for use with both iptables-restore and ip6tables-restore.
+.TP
+\fB\-6\fP, \fB\-\-ipv6\fP
+If a rule using the \fB\-6\fP option is inserted with (and only with)
+iptables-restore, it will be silently ignored. Any other uses will throw an
+error. This option allows IPv4 and IPv6 rules in a single rule file
+for use with both iptables-restore and ip6tables-restore.
+This option has no effect in ip6tables and ip6tables-restore.
+.TP
 [\fB!\fP] \fB\-p\fP, \fB\-\-protocol\fP \fIprotocol\fP
 The protocol of the rule or of the packet to check.
 The specified protocol can be one of \fBtcp\fP, \fBudp\fP, \fBudplite\fP,
-\fBicmp\fP, \fBesp\fP, \fBah\fP, \fBsctp\fP or the special keyword "\fBall\fP",
+\fBicmp\fP, \fBicmpv6\fP,\fBesp\fP, \fBah\fP, \fBsctp\fP, \fBmh\fP or the special keyword "\fBall\fP",
 or it can be a numeric value, representing one of these protocols or a
 different one.  A protocol name from /etc/protocols is also allowed.
 A "!" argument before the protocol inverts the
 test.  The number zero is equivalent to \fBall\fP. "\fBall\fP"
 will match with all protocols and is taken as default when this
 option is omitted.
+Note that, in ip6tables, IPv6 extension headers except \fBesp\fP are not allowed.
+\fBesp\fP and \fBipv6\-nonext\fP
+can be used with Kernel version 2.6.11 or later.
+The number zero is equivalent to \fBall\fP, which means that you cannot
+test the protocol field for the value 0 directly. To match on a HBH header,
+even if it were the last, you cannot use \fB\-p 0\fP, but always need
+\fB\-m hbh\fP.
 .TP
 [\fB!\fP] \fB\-s\fP, \fB\-\-source\fP \fIaddress\fP[\fB/\fP\fImask\fP][\fB,\fP\fI...\fP]
 Source specification. \fIAddress\fP
@@ -262,9 +281,9 @@
 Please note that specifying any name to be resolved with a remote query such as
 DNS is a really bad idea.
 The \fImask\fP
-can be either a network mask or a plain number,
+can be either an ipv4 network mask (for iptables) or a plain number,
 specifying the number of 1's at the left side of the network mask.
-Thus, a mask of \fI24\fP is equivalent to \fI255.255.255.0\fP.
+Thus, an iptables mask of \fI24\fP is equivalent to \fI255.255.255.0\fP.
 A "!" argument before the address specification inverts the sense of
 the address. The flag \fB\-\-src\fP is an alias for this option.
 Multiple addresses can be specified, but this will \fBexpand to multiple
@@ -277,6 +296,13 @@
 (source) flag for a detailed description of the syntax.  The flag
 \fB\-\-dst\fP is an alias for this option.
 .TP
+\fB\-m\fP, \fB\-\-match\fP \fImatch\fP
+Specifies a match to use, that is, an extension module that tests for a
+specific property. The set of matches make up the condition under which a
+target is invoked. Matches are evaluated first to last as specified on the
+command line and work in short-circuit fashion, i.e. if one extension yields
+false, evaluation will stop.
+.TP
 \fB\-j\fP, \fB\-\-jump\fP \fItarget\fP
 This specifies the target of the rule; i.e., what to do if the packet
 matches it.  The target can be a user-defined chain (other than the
@@ -311,12 +337,13 @@
 omitted, any interface name will match.
 .TP
 [\fB!\fP] \fB\-f\fP, \fB\-\-fragment\fP
-This means that the rule only refers to second and further fragments
+This means that the rule only refers to second and further IPv4 fragments
 of fragmented packets.  Since there is no way to tell the source or
 destination ports of such a packet (or ICMP type), such a packet will
 not match any rules which specify them.  When the "!" argument
 precedes the "\-f" flag, the rule will only match head fragments, or
-unfragmented packets.
+unfragmented packets. This option is IPv4 specific, it is not available
+in ip6tables.
 .TP
 \fB\-c\fP, \fB\-\-set\-counters\fP \fIpackets bytes\fP
 This enables the administrator to initialize the packet and byte
@@ -332,7 +359,23 @@
 1000, 1,000,000 and 1,000,000,000 multipliers respectively (but see
 the \fB\-x\fP flag to change this).
 For appending, insertion, deletion and replacement, this causes
-detailed information on the rule or rules to be printed.
+detailed information on the rule or rules to be printed. \fB\-v\fP may be
+specified multiple times to possibly emit more detailed debug statements.
+.TP
+\fB\-w\fP, \fB\-\-wait\fP [\fIseconds\fP]
+Wait for the xtables lock.
+To prevent multiple instances of the program from running concurrently,
+an attempt will be made to obtain an exclusive lock at launch.  By default,
+the program will exit if the lock cannot be obtained.  This option will
+make the program wait (indefinitely or for optional \fIseconds\fP) until
+the exclusive lock can be obtained.
+.TP
+\fB\-W\fP, \fB\-\-wait-interval\fP \fImicroseconds\fP
+Interval to wait per each iteration.
+When running latency sensitive applications, waiting for the xtables lock
+for extended durations may not be acceptable. This option will make each
+iteration take the amount of time specified. The default interval is
+1 second. This option only works with \fB\-w\fP.
 .TP
 \fB\-n\fP, \fB\-\-numeric\fP
 Numeric output.
@@ -354,24 +397,18 @@
 \fB\-\-modprobe=\fP\fIcommand\fP
 When adding or inserting rules into a chain, use \fIcommand\fP
 to load any necessary modules (targets, match extensions, etc).
-.SH MATCH EXTENSIONS
-iptables can use extended packet matching modules.  These are loaded
-in two ways: implicitly, when \fB\-p\fP or \fB\-\-protocol\fP
-is specified, or with the \fB\-m\fP or \fB\-\-match\fP
-options, followed by the matching module name; after these, various
-extra command line options become available, depending on the specific
-module.  You can specify multiple extended match modules in one line,
-and you can use the \fB\-h\fP or \fB\-\-help\fP
-options after the module has been specified to receive help specific
-to that module.
+
+.SH LOCK FILE
+iptables uses the \fI@XT_LOCK_NAME@\fP file to take an exclusive lock at
+launch.
+
+The \fBXTABLES_LOCKFILE\fP environment variable can be used to override
+the default setting.
+
+.SH MATCH AND TARGET EXTENSIONS
 .PP
-The following are included in the base package, and most of these can
-be preceded by a "\fB!\fP" to invert the sense of the match.
-.\" @MATCH@
-.SH TARGET EXTENSIONS
-iptables can use extended target modules: the following are included
-in the standard distribution.
-.\" @TARGET@
+iptables can use extended packet matching and target modules.
+A list of these is available in the \fBiptables\-extensions\fP(8) manpage.
 .SH DIAGNOSTICS
 Various error messages are printed to standard error.  The exit code
 is 0 for correct functioning.  Errors which appear to be caused by
@@ -406,12 +443,10 @@
 .fi
 There are several other changes in iptables.
 .SH SEE ALSO
+\fBiptables\-apply\fP(8),
 \fBiptables\-save\fP(8),
 \fBiptables\-restore\fP(8),
-\fBip6tables\fP(8),
-\fBip6tables\-save\fP(8),
-\fBip6tables\-restore\fP(8),
-\fBlibipq\fP(3).
+\fBiptables\-extensions\fP(8),
 .PP
 The packet-filtering-HOWTO details iptables usage for
 packet filtering, the NAT-HOWTO details NAT,
@@ -435,9 +470,11 @@
 .PP
 Harald Welte wrote the ULOG and NFQUEUE target, the new libiptc, as well as the TTL, DSCP, ECN matches and targets.
 .PP
-The Netfilter Core Team is: Marc Boucher, Martin Josefsson, Yasuyuki Kozakai,
-Jozsef Kadlecsik, Patrick McHardy, James Morris, Pablo Neira Ayuso,
-Harald Welte and Rusty Russell.
+The Netfilter Core Team is: Jozsef Kadlecsik, Pablo Neira Ayuso,
+Eric Leblond, Florian Westphal and  Arturo Borrero Gonzalez.
+Emeritus Core Team members are: Marc
+Boucher, Martin Josefsson, Yasuyuki Kozakai, James Morris, Harald Welte and
+Rusty Russell.
 .PP
 Man page originally written by Herve Eychenne <rv@wallfire.org>.
 .\" .. and did I mention that we are incredibly cool people?
@@ -446,4 +483,4 @@
 .\" .. and most of all, modest ..
 .SH VERSION
 .PP
-This manual page applies to iptables @PACKAGE_VERSION@.
+This manual page applies to iptables/ip6tables @PACKAGE_VERSION@.
diff --git a/iptables/iptables.c b/iptables/iptables.c
index 4ae7541..7d61831 100644
--- a/iptables/iptables.c
+++ b/iptables/iptables.c
@@ -24,7 +24,7 @@
  *	along with this program; if not, write to the Free Software
  *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
-
+#include "config.h"
 #include <getopt.h>
 #include <string.h>
 #include <netdb.h>
@@ -39,55 +39,9 @@
 #include <iptables.h>
 #include <xtables.h>
 #include <fcntl.h>
-#include <sys/utsname.h>
 #include "xshared.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',
-				 'Z', 'N', 'X', 'P', 'E', 'S', '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 const char unsupported_rev[] = " [unsupported revision]";
 
 static struct option original_opts[] = {
 	{.name = "append",        .has_arg = 1, .val = 'A'},
@@ -115,6 +69,8 @@
 	{.name = "numeric",       .has_arg = 0, .val = 'n'},
 	{.name = "out-interface", .has_arg = 1, .val = 'o'},
 	{.name = "verbose",       .has_arg = 0, .val = 'v'},
+	{.name = "wait",          .has_arg = 2, .val = 'w'},
+	{.name = "wait-interval", .has_arg = 2, .val = 'W'},
 	{.name = "exact",         .has_arg = 0, .val = 'x'},
 	{.name = "fragments",     .has_arg = 0, .val = 'f'},
 	{.name = "version",       .has_arg = 0, .val = 'V'},
@@ -132,39 +88,10 @@
 
 struct xtables_globals iptables_globals = {
 	.option_offset = 0,
-	.program_version = IPTABLES_VERSION,
+	.program_version = PACKAGE_VERSION,
 	.orig_opts = original_opts,
 	.exit_err = iptables_exit_error,
-};
-
-/* 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',' '},
+	.compat_rev = xtables_compatible_revision,
 };
 
 static const int inverse_for_options[NUMBER_OF_OPT] =
@@ -172,39 +99,21 @@
 /* -n */ 0,
 /* -s */ IPT_INV_SRCIP,
 /* -d */ IPT_INV_DSTIP,
-/* -p */ IPT_INV_PROTO,
+/* -p */ XT_INV_PROTO,
 /* -j */ 0,
 /* -v */ 0,
 /* -x */ 0,
 /* -i */ IPT_INV_VIA_IN,
 /* -o */ IPT_INV_VIA_OUT,
-/* -f */ IPT_INV_FRAG,
 /*--line*/ 0,
 /* -c */ 0,
+/* -f */ IPT_INV_FRAG,
 };
 
 #define opts iptables_globals.opts
 #define prog_name iptables_globals.program_name
 #define prog_vers iptables_globals.program_version
 
-int kernel_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)
 {
@@ -265,7 +174,7 @@
 "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"
+"[!] --protocol	-p proto	protocol: by number or name, eg. `tcp'\n"
 "[!] --source	-s address[/mask][...]\n"
 "				source specification\n"
 "[!] --destination -d address[/mask][...]\n"
@@ -285,6 +194,9 @@
 "				network interface name ([+] for wildcard)\n"
 "  --table	-t table	table to manipulate (default: `filter')\n"
 "  --verbose	-v		verbose mode\n"
+"  --wait	-w [seconds]	maximum wait to acquire xtables lock before give up\n"
+"  --wait-interval -W [usecs]	wait time to try to acquire xtables lock\n"
+"				default is 1 second\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"
@@ -302,7 +214,7 @@
 	va_list args;
 
 	va_start(args, msg);
-	fprintf(stderr, "%s v%s: ", prog_name, prog_vers);
+	fprintf(stderr, "%s v%s (legacy): ", prog_name, prog_vers);
 	vfprintf(stderr, msg, args);
 	va_end(args);
 	fprintf(stderr, "\n");
@@ -316,72 +228,6 @@
 	exit(status);
 }
 
-static void
-generic_opt_check(int command, int options)
-{
-	int i, j, legal = 0;
-
-	/* Check that commands are valid with options.  Complicated by the
-	 * fact that if an option is legal with *any* command given, it is
-	 * legal overall (ie. -z and -l).
-	 */
-	for (i = 0; i < NUMBER_OF_OPT; i++) {
-		legal = 0; /* -1 => illegal, 1 => legal, 0 => undecided. */
-
-		for (j = 0; j < NUMBER_OF_CMD; j++) {
-			if (!(command & (1<<j)))
-				continue;
-
-			if (!(options & (1<<i))) {
-				if (commands_v_options[j][i] == '+')
-					xtables_error(PARAMETER_PROBLEM,
-						   "You need to supply the `-%c' "
-						   "option for this command\n",
-						   optflags[i]);
-			} else {
-				if (commands_v_options[j][i] != 'x')
-					legal = 1;
-				else if (legal == 0)
-					legal = -1;
-			}
-		}
-		if (legal == -1)
-			xtables_error(PARAMETER_PROBLEM,
-				   "Illegal option `-%c' with this command\n",
-				   optflags[i]);
-	}
-}
-
-static char
-opt2char(int option)
-{
-	const char *ptr;
-	for (ptr = optflags; option > 1; option >>= 1, ptr++);
-
-	return *ptr;
-}
-
-static char
-cmd2char(int option)
-{
-	const char *ptr;
-	for (ptr = cmdflags; option > 1; option >>= 1, ptr++);
-
-	return *ptr;
-}
-
-static void
-add_command(unsigned int *cmd, const int newcmd, const int othercmds, 
-	    int invert)
-{
-	if (invert)
-		xtables_error(PARAMETER_PROBLEM, "unexpected ! flag");
-	if (*cmd & (~othercmds))
-		xtables_error(PARAMETER_PROBLEM, "Cannot use -%c with -%c\n",
-			   cmd2char(newcmd), cmd2char(*cmd & (~othercmds)));
-	*cmd |= newcmd;
-}
-
 /*
  *	All functions starting with "parse" should succeed, otherwise
  *	the program fails.
@@ -392,38 +238,31 @@
 */
 
 /* 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)
+static void
+parse_chain(const char *chainname)
 {
 	const char *ptr;
 
-	if (strlen(targetname) < 1)
+	if (strlen(chainname) >= XT_EXTENSION_MAXNAMELEN)
 		xtables_error(PARAMETER_PROBLEM,
-			   "Invalid target name (too short)");
+			   "chain name `%s' too long (must be under %u chars)",
+			   chainname, XT_EXTENSION_MAXNAMELEN);
 
-	if (strlen(targetname) >= XT_EXTENSION_MAXNAMELEN)
+	if (*chainname == '-' || *chainname == '!')
 		xtables_error(PARAMETER_PROBLEM,
-			   "Invalid target name `%s' (%u chars max)",
-			   targetname, XT_EXTENSION_MAXNAMELEN - 1);
+			   "chain name not allowed to start "
+			   "with `%c'\n", *chainname);
 
-	for (ptr = targetname; *ptr; ptr++)
+	if (xtables_find_target(chainname, XTF_TRY_LOAD))
+		xtables_error(PARAMETER_PROBLEM,
+			   "chain name may not clash "
+			   "with target name\n");
+
+	for (ptr = chainname; *ptr; ptr++)
 		if (isspace(*ptr))
 			xtables_error(PARAMETER_PROBLEM,
-				   "Invalid target name `%s'", targetname);
-	return targetname;
+				   "Invalid chain name `%s'", chainname);
 }
 
 static void
@@ -448,44 +287,18 @@
 }
 
 static void
-print_num(uint64_t number, unsigned int format)
+print_header(unsigned int format, const char *chain, struct xtc_handle *handle)
 {
-	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, struct iptc_handle *handle)
-{
-	struct ipt_counters counters;
+	struct xt_counters counters;
 	const char *pol = iptc_get_policy(chain, &counters, handle);
 	printf("Chain %s", chain);
 	if (pol) {
 		printf(" (policy %s", pol);
 		if (!(format & FMT_NOCOUNTS)) {
 			fputc(' ', stdout);
-			print_num(counters.pcnt, (format|FMT_NOTABLE));
+			xtables_print_num(counters.pcnt, (format|FMT_NOTABLE));
 			fputs("packets, ", stdout);
-			print_num(counters.bcnt, (format|FMT_NOTABLE));
+			xtables_print_num(counters.bcnt, (format|FMT_NOTABLE));
 			fputs("bytes", stdout);
 		}
 		printf(")\n");
@@ -524,21 +337,27 @@
 
 
 static int
-print_match(const struct ipt_entry_match *m,
+print_match(const struct xt_entry_match *m,
 	    const struct ipt_ip *ip,
 	    int numeric)
 {
-	const struct xtables_match *match =
-		xtables_find_match(m->u.user.name, XTF_TRY_LOAD, NULL);
+	const char *name = m->u.user.name;
+	const int revision = m->u.user.revision;
+	struct xtables_match *match, *mt;
 
+	match = xtables_find_match(name, XTF_TRY_LOAD, NULL);
 	if (match) {
-		if (match->print)
-			match->print(ip, m, numeric);
+		mt = xtables_find_match_revision(name, XTF_TRY_LOAD,
+						 match, revision);
+		if (mt && mt->print)
+			mt->print(ip, m, numeric);
+		else if (match->print)
+			printf("%s%s ", match->name, unsupported_rev);
 		else
 			printf("%s ", match->name);
 	} else {
-		if (m->u.user.name[0])
-			printf("UNKNOWN match `%s' ", m->u.user.name);
+		if (name[0])
+			printf("UNKNOWN match `%s' ", name);
 	}
 	/* Don't stop iterating. */
 	return 0;
@@ -550,17 +369,16 @@
 	       const char *targname,
 	       unsigned int num,
 	       unsigned int format,
-	       struct iptc_handle *const handle)
+	       struct xtc_handle *const handle)
 {
-	const struct xtables_target *target = NULL;
-	const struct ipt_entry_target *t;
+	struct xtables_target *target, *tg;
+	const struct xt_entry_target *t;
 	uint8_t flags;
-	char buf[BUFSIZ];
 
 	if (!iptc_is_chain(targname, handle))
 		target = xtables_find_target(targname, XTF_TRY_LOAD);
 	else
-		target = xtables_find_target(IPT_STANDARD_TARGET,
+		target = xtables_find_target(XT_STANDARD_TARGET,
 		         XTF_LOAD_MUST_SUCCEED);
 
 	t = ipt_get_target((struct ipt_entry *)fw);
@@ -570,14 +388,14 @@
 		printf(FMT("%-4u ", "%u "), num);
 
 	if (!(format & FMT_NOCOUNTS)) {
-		print_num(fw->counters.pcnt, format);
-		print_num(fw->counters.bcnt, format);
+		xtables_print_num(fw->counters.pcnt, format);
+		xtables_print_num(fw->counters.bcnt, format);
 	}
 
 	if (!(format & FMT_NOTARGET))
 		printf(FMT("%-9s ", "%s "), targname);
 
-	fputc(fw->ip.invflags & IPT_INV_PROTO ? '!' : ' ', stdout);
+	fputc(fw->ip.invflags & XT_INV_PROTO ? '!' : ' ', stdout);
 	{
 		const char *pname = proto_to_name(fw->ip.proto, format&FMT_NUMERIC);
 		if (pname)
@@ -594,59 +412,9 @@
 		fputc(' ', stdout);
 	}
 
-	if (format & FMT_VIA) {
-		char iface[IFNAMSIZ+2];
+	print_ifaces(fw->ip.iniface, fw->ip.outiface, fw->ip.invflags, format);
 
-		if (fw->ip.invflags & IPT_INV_VIA_IN) {
-			iface[0] = '!';
-			iface[1] = '\0';
-		}
-		else iface[0] = '\0';
-
-		if (fw->ip.iniface[0] != '\0') {
-			strcat(iface, fw->ip.iniface);
-		}
-		else if (format & FMT_NUMERIC) strcat(iface, "*");
-		else strcat(iface, "any");
-		printf(FMT(" %-6s ","in %s "), iface);
-
-		if (fw->ip.invflags & IPT_INV_VIA_OUT) {
-			iface[0] = '!';
-			iface[1] = '\0';
-		}
-		else iface[0] = '\0';
-
-		if (fw->ip.outiface[0] != '\0') {
-			strcat(iface, fw->ip.outiface);
-		}
-		else if (format & FMT_NUMERIC) strcat(iface, "*");
-		else strcat(iface, "any");
-		printf(FMT("%-6s ","out %s "), iface);
-	}
-
-	fputc(fw->ip.invflags & IPT_INV_SRCIP ? '!' : ' ', stdout);
-	if (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(&fw->ip.src));
-		else
-			strcpy(buf, xtables_ipaddr_to_anyname(&fw->ip.src));
-		strcat(buf, xtables_ipmask_to_numeric(&fw->ip.smsk));
-		printf(FMT("%-19s ","%s "), buf);
-	}
-
-	fputc(fw->ip.invflags & IPT_INV_DSTIP ? '!' : ' ', stdout);
-	if (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(&fw->ip.dst));
-		else
-			strcpy(buf, xtables_ipaddr_to_anyname(&fw->ip.dst));
-		strcat(buf, xtables_ipmask_to_numeric(&fw->ip.dmsk));
-		printf(FMT("%-19s ","-> %s"), buf);
-	}
+	print_ipv4_addresses(fw, format);
 
 	if (format & FMT_NOTABLE)
 		fputs("  ", stdout);
@@ -659,9 +427,15 @@
 	IPT_MATCH_ITERATE(fw, print_match, &fw->ip, format & FMT_NUMERIC);
 
 	if (target) {
-		if (target->print)
+		const int revision = t->u.user.revision;
+
+		tg = xtables_find_target_revision(targname, XTF_TRY_LOAD,
+						  target, revision);
+		if (tg && tg->print)
 			/* Print the target information. */
-			target->print(&fw->ip, t, format & FMT_NUMERIC);
+			tg->print(&fw->ip, t, format & FMT_NUMERIC);
+		else if (target->print)
+			printf(" %s%s", target->name, unsupported_rev);
 	} else if (t->u.target_size != sizeof(*t))
 		printf("[%u bytes of unknown target data] ",
 		       (unsigned int)(t->u.target_size - sizeof(*t)));
@@ -672,16 +446,16 @@
 
 static void
 print_firewall_line(const struct ipt_entry *fw,
-		    struct iptc_handle *const h)
+		    struct xtc_handle *const h)
 {
-	struct ipt_entry_target *t;
+	struct xt_entry_target *t;
 
 	t = ipt_get_target((struct ipt_entry *)fw);
 	print_firewall(fw, t->u.user.name, 0, FMT_PRINT_RULE, h);
 }
 
 static int
-append_entry(const ipt_chainlabel chain,
+append_entry(const xt_chainlabel chain,
 	     struct ipt_entry *fw,
 	     unsigned int nsaddrs,
 	     const struct in_addr saddrs[],
@@ -690,7 +464,7 @@
 	     const struct in_addr daddrs[],
 	     const struct in_addr dmasks[],
 	     int verbose,
-	     struct iptc_handle *handle)
+	     struct xtc_handle *handle)
 {
 	unsigned int i, j;
 	int ret = 1;
@@ -711,13 +485,13 @@
 }
 
 static int
-replace_entry(const ipt_chainlabel chain,
+replace_entry(const xt_chainlabel chain,
 	      struct ipt_entry *fw,
 	      unsigned int rulenum,
 	      const struct in_addr *saddr, const struct in_addr *smask,
 	      const struct in_addr *daddr, const struct in_addr *dmask,
 	      int verbose,
-	      struct iptc_handle *handle)
+	      struct xtc_handle *handle)
 {
 	fw->ip.src.s_addr = saddr->s_addr;
 	fw->ip.dst.s_addr = daddr->s_addr;
@@ -730,7 +504,7 @@
 }
 
 static int
-insert_entry(const ipt_chainlabel chain,
+insert_entry(const xt_chainlabel chain,
 	     struct ipt_entry *fw,
 	     unsigned int rulenum,
 	     unsigned int nsaddrs,
@@ -740,7 +514,7 @@
 	     const struct in_addr daddrs[],
 	     const struct in_addr dmasks[],
 	     int verbose,
-	     struct iptc_handle *handle)
+	     struct xtc_handle *handle)
 {
 	unsigned int i, j;
 	int ret = 1;
@@ -771,10 +545,10 @@
 
 	size = sizeof(struct ipt_entry);
 	for (matchp = matches; matchp; matchp = matchp->next)
-		size += XT_ALIGN(sizeof(struct ipt_entry_match)) + matchp->match->size;
+		size += XT_ALIGN(sizeof(struct xt_entry_match)) + matchp->match->size;
 
 	mask = xtables_calloc(1, size
-			 + XT_ALIGN(sizeof(struct ipt_entry_target))
+			 + XT_ALIGN(sizeof(struct xt_entry_target))
 			 + target->size);
 
 	memset(mask, 0xFF, sizeof(struct ipt_entry));
@@ -782,20 +556,20 @@
 
 	for (matchp = matches; matchp; matchp = matchp->next) {
 		memset(mptr, 0xFF,
-		       XT_ALIGN(sizeof(struct ipt_entry_match))
+		       XT_ALIGN(sizeof(struct xt_entry_match))
 		       + matchp->match->userspacesize);
-		mptr += XT_ALIGN(sizeof(struct ipt_entry_match)) + matchp->match->size;
+		mptr += XT_ALIGN(sizeof(struct xt_entry_match)) + matchp->match->size;
 	}
 
 	memset(mptr, 0xFF,
-	       XT_ALIGN(sizeof(struct ipt_entry_target))
+	       XT_ALIGN(sizeof(struct xt_entry_target))
 	       + target->userspacesize);
 
 	return mask;
 }
 
 static int
-delete_entry(const ipt_chainlabel chain,
+delete_entry(const xt_chainlabel chain,
 	     struct ipt_entry *fw,
 	     unsigned int nsaddrs,
 	     const struct in_addr saddrs[],
@@ -804,7 +578,7 @@
 	     const struct in_addr daddrs[],
 	     const struct in_addr dmasks[],
 	     int verbose,
-	     struct iptc_handle *handle,
+	     struct xtc_handle *handle,
 	     struct xtables_rule_match *matches,
 	     const struct xtables_target *target)
 {
@@ -830,11 +604,11 @@
 }
 
 static int
-check_entry(const ipt_chainlabel chain, struct ipt_entry *fw,
+check_entry(const xt_chainlabel chain, struct ipt_entry *fw,
 	    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 iptc_handle *handle,
+	    bool verbose, struct xtc_handle *handle,
 	    struct xtables_rule_match *matches,
 	    const struct xtables_target *target)
 {
@@ -860,8 +634,8 @@
 }
 
 int
-for_each_chain4(int (*fn)(const ipt_chainlabel, int, struct iptc_handle *),
-	       int verbose, int builtinstoo, struct iptc_handle *handle)
+for_each_chain4(int (*fn)(const xt_chainlabel, int, struct xtc_handle *),
+	       int verbose, int builtinstoo, struct xtc_handle *handle)
 {
         int ret = 1;
 	const char *chain;
@@ -874,21 +648,21 @@
 		chain = iptc_next_chain(handle);
         }
 
-	chains = xtables_malloc(sizeof(ipt_chainlabel) * chaincount);
+	chains = xtables_malloc(sizeof(xt_chainlabel) * chaincount);
 	i = 0;
 	chain = iptc_first_chain(handle);
 	while (chain) {
-		strcpy(chains + i*sizeof(ipt_chainlabel), chain);
+		strcpy(chains + i*sizeof(xt_chainlabel), chain);
 		i++;
 		chain = iptc_next_chain(handle);
         }
 
 	for (i = 0; i < chaincount; i++) {
 		if (!builtinstoo
-		    && iptc_builtin(chains + i*sizeof(ipt_chainlabel),
+		    && iptc_builtin(chains + i*sizeof(xt_chainlabel),
 				    handle) == 1)
 			continue;
-	        ret &= fn(chains + i*sizeof(ipt_chainlabel), verbose, handle);
+	        ret &= fn(chains + i*sizeof(xt_chainlabel), verbose, handle);
 	}
 
 	free(chains);
@@ -896,8 +670,8 @@
 }
 
 int
-flush_entries4(const ipt_chainlabel chain, int verbose,
-	      struct iptc_handle *handle)
+flush_entries4(const xt_chainlabel chain, int verbose,
+	      struct xtc_handle *handle)
 {
 	if (!chain)
 		return for_each_chain4(flush_entries4, verbose, 1, handle);
@@ -908,8 +682,8 @@
 }
 
 static int
-zero_entries(const ipt_chainlabel chain, int verbose,
-	     struct iptc_handle *handle)
+zero_entries(const xt_chainlabel chain, int verbose,
+	     struct xtc_handle *handle)
 {
 	if (!chain)
 		return for_each_chain4(zero_entries, verbose, 1, handle);
@@ -920,8 +694,8 @@
 }
 
 int
-delete_chain4(const ipt_chainlabel chain, int verbose,
-	     struct iptc_handle *handle)
+delete_chain4(const xt_chainlabel chain, int verbose,
+	     struct xtc_handle *handle)
 {
 	if (!chain)
 		return for_each_chain4(delete_chain4, verbose, 0, handle);
@@ -932,8 +706,8 @@
 }
 
 static int
-list_entries(const ipt_chainlabel chain, int rulenum, int verbose, int numeric,
-	     int expanded, int linenumbers, struct iptc_handle *handle)
+list_entries(const xt_chainlabel chain, int rulenum, int verbose, int numeric,
+	     int expanded, int linenumbers, struct xtc_handle *handle)
 {
 	int found = 0;
 	unsigned int format;
@@ -1044,30 +818,38 @@
 	}
 }
 
-static int print_match_save(const struct ipt_entry_match *e,
+static int print_match_save(const struct xt_entry_match *e,
 			const struct ipt_ip *ip)
 {
-	const struct xtables_match *match =
-		xtables_find_match(e->u.user.name, XTF_TRY_LOAD, NULL);
+	const char *name = e->u.user.name;
+	const int revision = e->u.user.revision;
+	struct xtables_match *match, *mt, *mt2;
 
+	match = xtables_find_match(name, XTF_TRY_LOAD, NULL);
 	if (match) {
-		printf(" -m %s", e->u.user.name);
+		mt = mt2 = xtables_find_match_revision(name, XTF_TRY_LOAD,
+						       match, revision);
+		if (!mt2)
+			mt2 = match;
+		printf(" -m %s", mt2->alias ? mt2->alias(e) : name);
 
 		/* some matches don't provide a save function */
-		if (match->save)
-			match->save(ip, e);
+		if (mt && mt->save)
+			mt->save(ip, e);
+		else if (match->save)
+			printf(unsupported_rev);
 	} else {
 		if (e->u.match_size) {
 			fprintf(stderr,
 				"Can't find library for match `%s'\n",
-				e->u.user.name);
+				name);
 			exit(1);
 		}
 	}
 	return 0;
 }
 
-/* print a given ip including mask if neccessary */
+/* Print a given ip including mask if necessary. */
 static void print_ip(const char *prefix, uint32_t ip,
 		     uint32_t mask, int invert)
 {
@@ -1097,12 +879,13 @@
 		printf("/%u.%u.%u.%u", IP_PARTS(mask));
 }
 
-/* We want this to be readable, so only print out neccessary fields.
- * Because that's the kind of world I want to live in.  */
+/* We want this to be readable, so only print out necessary fields.
+ * Because that's the kind of world I want to live in.
+ */
 void print_rule4(const struct ipt_entry *e,
-		struct iptc_handle *h, const char *chain, int counters)
+		struct xtc_handle *h, const char *chain, int counters)
 {
-	const struct ipt_entry_target *t;
+	const struct xt_entry_target *t;
 	const char *target_name;
 
 	/* print counters for iptables-save */
@@ -1125,63 +908,70 @@
 	print_iface('o', e->ip.outiface, e->ip.outiface_mask,
 		    e->ip.invflags & IPT_INV_VIA_OUT);
 
-	print_proto(e->ip.proto, e->ip.invflags & IPT_INV_PROTO);
+	print_proto(e->ip.proto, e->ip.invflags & XT_INV_PROTO);
 
 	if (e->ip.flags & IPT_F_FRAG)
 		printf("%s -f",
 		       e->ip.invflags & IPT_INV_FRAG ? " !" : "");
 
 	/* Print matchinfo part */
-	if (e->target_offset) {
+	if (e->target_offset)
 		IPT_MATCH_ITERATE(e, print_match_save, &e->ip);
-	}
 
 	/* print counters for iptables -R */
 	if (counters < 0)
 		printf(" -c %llu %llu", (unsigned long long)e->counters.pcnt, (unsigned long long)e->counters.bcnt);
 
-	/* Print target name */
+	/* Print target name and targinfo part */
 	target_name = iptc_get_target(e, h);
-	if (target_name && (*target_name != '\0'))
+	t = ipt_get_target((struct ipt_entry *)e);
+	if (t->u.user.name[0]) {
+		const char *name = t->u.user.name;
+		const int revision = t->u.user.revision;
+		struct xtables_target *target, *tg, *tg2;
+
+		target = xtables_find_target(name, XTF_TRY_LOAD);
+		if (!target) {
+			fprintf(stderr, "Can't find library for target `%s'\n",
+				name);
+			exit(1);
+		}
+
+		tg = tg2 = xtables_find_target_revision(name, XTF_TRY_LOAD,
+							target, revision);
+		if (!tg2)
+			tg2 = target;
+		printf(" -j %s", tg2->alias ? tg2->alias(t) : target_name);
+
+		if (tg && tg->save)
+			tg->save(&e->ip, t);
+		else if (target->save)
+			printf(unsupported_rev);
+		else {
+			/* If the target size is greater than xt_entry_target
+			 * there is something to be saved, we just don't know
+			 * how to print it */
+			if (t->u.target_size !=
+			    sizeof(struct xt_entry_target)) {
+				fprintf(stderr, "Target `%s' is missing "
+						"save function\n",
+					name);
+				exit(1);
+			}
+		}
+	} else if (target_name && (*target_name != '\0'))
 #ifdef IPT_F_GOTO
 		printf(" -%c %s", e->ip.flags & IPT_F_GOTO ? 'g' : 'j', target_name);
 #else
 		printf(" -j %s", target_name);
 #endif
 
-	/* Print targinfo part */
-	t = ipt_get_target((struct ipt_entry *)e);
-	if (t->u.user.name[0]) {
-		const struct xtables_target *target =
-			xtables_find_target(t->u.user.name, XTF_TRY_LOAD);
-
-		if (!target) {
-			fprintf(stderr, "Can't find library for target `%s'\n",
-				t->u.user.name);
-			exit(1);
-		}
-
-		if (target->save)
-			target->save(&e->ip, t);
-		else {
-			/* If the target size is greater than ipt_entry_target
-			 * there is something to be saved, we just don't know
-			 * how to print it */
-			if (t->u.target_size !=
-			    sizeof(struct ipt_entry_target)) {
-				fprintf(stderr, "Target `%s' is missing "
-						"save function\n",
-					t->u.user.name);
-				exit(1);
-			}
-		}
-	}
 	printf("\n");
 }
 
 static int
-list_rules(const ipt_chainlabel chain, int rulenum, int counters,
-	     struct iptc_handle *handle)
+list_rules(const xt_chainlabel chain, int rulenum, int counters,
+	     struct xtc_handle *handle)
 {
 	const char *this = NULL;
 	int found = 0;
@@ -1198,7 +988,7 @@
 			continue;
 
 		if (iptc_builtin(this, handle)) {
-			struct ipt_counters count;
+			struct xt_counters count;
 			printf("-P %s %s", this, iptc_get_policy(this, &count, handle));
 			if (counters)
 			    printf(" -c %llu %llu", (unsigned long long)count.pcnt, (unsigned long long)count.bcnt);
@@ -1235,7 +1025,7 @@
 static struct ipt_entry *
 generate_entry(const struct ipt_entry *fw,
 	       struct xtables_rule_match *matches,
-	       struct ipt_entry_target *target)
+	       struct xt_entry_target *target)
 {
 	unsigned int size;
 	struct xtables_rule_match *matchp;
@@ -1260,114 +1050,23 @@
 	return e;
 }
 
-static void clear_rule_matches(struct xtables_rule_match **matches)
+int do_command4(int argc, char *argv[], char **table,
+		struct xtc_handle **handle, bool restore)
 {
-	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;
-}
-
-void
-get_kernel_version(void) {
-	static struct utsname uts;
-	int x = 0, y = 0, z = 0;
-
-	if (uname(&uts) == -1) {
-		fprintf(stderr, "Unable to retrieve kernel version.\n");
-		xtables_free_opts(1);
-		exit(1);
-	}
-
-	sscanf(uts.release, "%d.%d.%d", &x, &y, &z);
-	kernel_version = LINUX_VERSION(x, y, z);
-}
-
-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 ipt_entry_target))
-		+ cs->target->size;
-
-	cs->target->t = xtables_calloc(1, size);
-	cs->target->t->u.target_size = size;
-	strcpy(cs->target->t->u.user.name, cs->jumpto);
-	cs->target->t->u.user.revision = cs->target->revision;
-	if (cs->target->init != NULL)
-		cs->target->init(cs->target->t);
-	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 ipt_entry_match)) + m->size;
-	m->m = xtables_calloc(1, size);
-	m->m->u.match_size = size;
-	strcpy(m->m->u.user.name, m->name);
-	m->m->u.user.revision = m->revision;
-	if (m->init != NULL)
-		m->init(m->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_command4(int argc, char *argv[], char **table, struct iptc_handle **handle)
-{
-	struct iptables_command_state cs;
+	struct iptables_command_state cs = {
+		.jumpto	= "",
+		.argv	= argv,
+	};
 	struct ipt_entry *e = NULL;
 	unsigned int nsaddrs = 0, ndaddrs = 0;
 	struct in_addr *saddrs = NULL, *smasks = NULL;
 	struct in_addr *daddrs = NULL, *dmasks = NULL;
-
+	struct timeval wait_interval = {
+		.tv_sec = 1,
+	};
+	bool wait_interval_set = false;
 	int verbose = 0;
+	int wait = 0;
 	const char *chain = NULL;
 	const char *shostnetworkmask = NULL, *dhostnetworkmask = NULL;
 	const char *policy = NULL, *newname = NULL;
@@ -1378,10 +1077,7 @@
 	struct xtables_rule_match *matchp;
 	struct xtables_target *t;
 	unsigned long long cnt;
-
-	memset(&cs, 0, sizeof(cs));
-	cs.jumpto = "";
-	cs.argv = argv;
+	bool table_set = false;
 
 	/* re-set optind to 0 in case do_command4 gets called
 	 * a second time */
@@ -1400,10 +1096,9 @@
 	/* 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",
+	   "-:A:C:D:R:I:L::S::M:F::Z::N:X::E:P:Vh::o:p:s:d:j:i:fbvw::W::nt:m:xc:g:46",
 					   opts, NULL)) != -1) {
 		switch (cs.c) {
 			/*
@@ -1425,8 +1120,7 @@
 			add_command(&command, CMD_DELETE, CMD_NONE,
 				    cs.invert);
 			chain = optarg;
-			if (optind < argc && argv[optind][0] != '-'
-			    && argv[optind][0] != '!') {
+			if (xs_has_arg(argc, argv)) {
 				rulenum = parse_rulenumber(argv[optind++]);
 				command = CMD_DELETE_NUM;
 			}
@@ -1436,8 +1130,7 @@
 			add_command(&command, CMD_REPLACE, CMD_NONE,
 				    cs.invert);
 			chain = optarg;
-			if (optind < argc && argv[optind][0] != '-'
-			    && argv[optind][0] != '!')
+			if (xs_has_arg(argc, argv))
 				rulenum = parse_rulenumber(argv[optind++]);
 			else
 				xtables_error(PARAMETER_PROBLEM,
@@ -1449,8 +1142,7 @@
 			add_command(&command, CMD_INSERT, CMD_NONE,
 				    cs.invert);
 			chain = optarg;
-			if (optind < argc && argv[optind][0] != '-'
-			    && argv[optind][0] != '!')
+			if (xs_has_arg(argc, argv))
 				rulenum = parse_rulenumber(argv[optind++]);
 			else rulenum = 1;
 			break;
@@ -1459,11 +1151,9 @@
 			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] != '!')
+			else if (xs_has_arg(argc, argv))
 				chain = argv[optind++];
-			if (optind < argc && argv[optind][0] != '-'
-			    && argv[optind][0] != '!')
+			if (xs_has_arg(argc, argv))
 				rulenum = parse_rulenumber(argv[optind++]);
 			break;
 
@@ -1471,11 +1161,9 @@
 			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] != '!')
+			else if (xs_has_arg(argc, argv))
 				chain = argv[optind++];
-			if (optind < argc && argv[optind][0] != '-'
-			    && argv[optind][0] != '!')
+			if (xs_has_arg(argc, argv))
 				rulenum = parse_rulenumber(argv[optind++]);
 			break;
 
@@ -1483,8 +1171,7 @@
 			add_command(&command, CMD_FLUSH, CMD_NONE,
 				    cs.invert);
 			if (optarg) chain = optarg;
-			else if (optind < argc && argv[optind][0] != '-'
-				 && argv[optind][0] != '!')
+			else if (xs_has_arg(argc, argv))
 				chain = argv[optind++];
 			break;
 
@@ -1492,25 +1179,16 @@
 			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] != '!')
+			else if (xs_has_arg(argc, argv))
 				chain = argv[optind++];
-			if (optind < argc && argv[optind][0] != '-'
-				&& argv[optind][0] != '!') {
+			if (xs_has_arg(argc, argv)) {
 				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");
+			parse_chain(optarg);
 			add_command(&command, CMD_NEW_CHAIN, CMD_NONE,
 				    cs.invert);
 			chain = optarg;
@@ -1520,8 +1198,7 @@
 			add_command(&command, CMD_DELETE_CHAIN, CMD_NONE,
 				    cs.invert);
 			if (optarg) chain = optarg;
-			else if (optind < argc && argv[optind][0] != '-'
-				 && argv[optind][0] != '!')
+			else if (xs_has_arg(argc, argv))
 				chain = argv[optind++];
 			break;
 
@@ -1529,8 +1206,7 @@
 			add_command(&command, CMD_RENAME_CHAIN, CMD_NONE,
 				    cs.invert);
 			chain = optarg;
-			if (optind < argc && argv[optind][0] != '-'
-			    && argv[optind][0] != '!')
+			if (xs_has_arg(argc, argv))
 				newname = argv[optind++];
 			else
 				xtables_error(PARAMETER_PROBLEM,
@@ -1543,8 +1219,7 @@
 			add_command(&command, CMD_SET_POLICY, CMD_NONE,
 				    cs.invert);
 			chain = optarg;
-			if (optind < argc && argv[optind][0] != '-'
-			    && argv[optind][0] != '!')
+			if (xs_has_arg(argc, argv))
 				policy = argv[optind++];
 			else
 				xtables_error(PARAMETER_PROBLEM,
@@ -1567,7 +1242,6 @@
 			 * Option selection
 			 */
 		case 'p':
-			xtables_check_inverse(optarg, &cs.invert, &optind, argc, argv);
 			set_option(&cs.options, OPT_PROTOCOL, &cs.fw.ip.invflags,
 				   cs.invert);
 
@@ -1579,20 +1253,18 @@
 			cs.fw.ip.proto = xtables_parse_protocol(cs.protocol);
 
 			if (cs.fw.ip.proto == 0
-			    && (cs.fw.ip.invflags & IPT_INV_PROTO))
+			    && (cs.fw.ip.invflags & XT_INV_PROTO))
 				xtables_error(PARAMETER_PROBLEM,
 					   "rule would never match protocol");
 			break;
 
 		case 's':
-			xtables_check_inverse(optarg, &cs.invert, &optind, argc, argv);
 			set_option(&cs.options, OPT_SOURCE, &cs.fw.ip.invflags,
 				   cs.invert);
 			shostnetworkmask = optarg;
 			break;
 
 		case 'd':
-			xtables_check_inverse(optarg, &cs.invert, &optind, argc, argv);
 			set_option(&cs.options, OPT_DESTINATION, &cs.fw.ip.invflags,
 				   cs.invert);
 			dhostnetworkmask = optarg;
@@ -1603,12 +1275,14 @@
 			set_option(&cs.options, OPT_JUMP, &cs.fw.ip.invflags,
 				   cs.invert);
 			cs.fw.ip.flags |= IPT_F_GOTO;
-			cs.jumpto = parse_target(optarg);
+			cs.jumpto = xt_parse_target(optarg);
 			break;
 #endif
 
 		case 'j':
-			command_jump(&cs);
+			set_option(&cs.options, OPT_JUMP, &cs.fw.ip.invflags,
+				   cs.invert);
+			command_jump(&cs, optarg);
 			break;
 
 
@@ -1617,7 +1291,6 @@
 				xtables_error(PARAMETER_PROBLEM,
 					"Empty interface is likely to be "
 					"undesired");
-			xtables_check_inverse(optarg, &cs.invert, &optind, argc, argv);
 			set_option(&cs.options, OPT_VIANAMEIN, &cs.fw.ip.invflags,
 				   cs.invert);
 			xtables_parse_interface(optarg,
@@ -1630,7 +1303,6 @@
 				xtables_error(PARAMETER_PROBLEM,
 					"Empty interface is likely to be "
 					"undesired");
-			xtables_check_inverse(optarg, &cs.invert, &optind, argc, argv);
 			set_option(&cs.options, OPT_VIANAMEOUT, &cs.fw.ip.invflags,
 				   cs.invert);
 			xtables_parse_interface(optarg,
@@ -1651,6 +1323,25 @@
 			verbose++;
 			break;
 
+		case 'w':
+			if (restore) {
+				xtables_error(PARAMETER_PROBLEM,
+					      "You cannot use `-w' from "
+					      "iptables-restore");
+			}
+			wait = parse_wait_time(argc, argv);
+			break;
+
+		case 'W':
+			if (restore) {
+				xtables_error(PARAMETER_PROBLEM,
+					      "You cannot use `-W' from "
+					      "iptables-restore");
+			}
+			parse_wait_interval(argc, argv, &wait_interval);
+			wait_interval_set = true;
+			break;
+
 		case 'm':
 			command_match(&cs);
 			break;
@@ -1664,7 +1355,12 @@
 			if (cs.invert)
 				xtables_error(PARAMETER_PROBLEM,
 					   "unexpected ! flag before --table");
+			if (restore && table_set)
+				xtables_error(PARAMETER_PROBLEM,
+					      "The -t option (seen in line %u) cannot be used in %s.\n",
+					      line, xt_params->program_name);
 			*table = optarg;
+			table_set = true;
 			break;
 
 		case 'x':
@@ -1676,7 +1372,7 @@
 			if (cs.invert)
 				printf("Not %s ;-)\n", prog_vers);
 			else
-				printf("%s v%s\n",
+				printf("%s v%s (legacy)\n",
 				       prog_name, prog_vers);
 			exit(0);
 
@@ -1697,8 +1393,7 @@
 			bcnt = strchr(pcnt + 1, ',');
 			if (bcnt)
 			    bcnt++;
-			if (!bcnt && optind < argc && argv[optind][0] != '-'
-			    && argv[optind][0] != '!')
+			if (!bcnt && xs_has_arg(argc, argv))
 				bcnt = argv[optind++];
 			if (!bcnt)
 				xtables_error(PARAMETER_PROBLEM,
@@ -1735,7 +1430,7 @@
 					xtables_error(PARAMETER_PROBLEM,
 						   "multiple consecutive ! not"
 						   " allowed");
-				cs.invert = TRUE;
+				cs.invert = true;
 				optarg[0] = '\0';
 				continue;
 			}
@@ -1748,9 +1443,13 @@
 				continue;
 			break;
 		}
-		cs.invert = FALSE;
+		cs.invert = false;
 	}
 
+	if (!wait && wait_interval_set)
+		xtables_error(PARAMETER_PROBLEM,
+			      "--wait-interval only makes sense with --wait\n");
+
 	if (strcmp(*table, "nat") == 0 &&
 	    ((policy != NULL && strcmp(policy, "DROP") == 0) ||
 	    (cs.jumpto != NULL && strcmp(cs.jumpto, "DROP") == 0)))
@@ -1800,10 +1499,9 @@
 
 	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);
+	/* Attempt to acquire the xtables lock */
+	if (!restore)
+		xtables_lock_or_exit(wait, &wait_interval);
 
 	/* only allocate handle if we weren't called with a handle */
 	if (!*handle)
@@ -1861,25 +1559,25 @@
 			|| iptc_is_chain(cs.jumpto, *handle))) {
 			size_t size;
 
-			cs.target = xtables_find_target(IPT_STANDARD_TARGET,
+			cs.target = xtables_find_target(XT_STANDARD_TARGET,
 					 XTF_LOAD_MUST_SUCCEED);
 
-			size = sizeof(struct ipt_entry_target)
+			size = sizeof(struct xt_entry_target)
 				+ cs.target->size;
 			cs.target->t = xtables_calloc(1, size);
 			cs.target->t->u.target_size = size;
 			strcpy(cs.target->t->u.user.name, cs.jumpto);
 			if (!iptc_is_chain(cs.jumpto, *handle))
 				cs.target->t->u.user.revision = cs.target->revision;
-			if (cs.target->init != NULL)
-				cs.target->init(cs.target->t);
+			xs_init_target(cs.target);
 		}
 
 		if (!cs.target) {
-			/* it is no chain, and we can't load a plugin.
+			/* It is no chain, and we can't load a plugin.
 			 * We cannot know if the plugin is corrupt, non
-			 * existant OR if the user just misspelled a
-			 * chain. */
+			 * existent OR if the user just misspelled a
+			 * chain.
+			 */
 #ifdef IPT_F_GOTO
 			if (cs.fw.ip.flags & IPT_F_GOTO)
 				xtables_error(PARAMETER_PROBLEM,
@@ -1988,7 +1686,7 @@
 	if (verbose > 1)
 		dump_entries(*handle);
 
-	clear_rule_matches(&cs.matches);
+	xtables_rule_matches_free(&cs.matches);
 
 	if (e != NULL) {
 		free(e);
diff --git a/iptables/iptables.xslt b/iptables/iptables.xslt
index d6a432c..afe6d0d 100644
--- a/iptables/iptables.xslt
+++ b/iptables/iptables.xslt
@@ -13,7 +13,7 @@
 
   <!-- output conditions of a rule but not an action -->
   <xsl:template match="iptables-rules/table/chain/rule/conditions/*">
-    <!-- <match> is the psuedo module when a match module doesn't need to be loaded
+    <!-- <match> is the pseudo module when a match module doesn't need to be loaded
          and when -m does not need to be inserted -->
     <xsl:if test="name() != 'match'">
       <xsl:text> -m </xsl:text><xsl:value-of select="name()"/>
diff --git a/iptables/nft-arp.c b/iptables/nft-arp.c
new file mode 100644
index 0000000..c82ffdc
--- /dev/null
+++ b/iptables/nft-arp.c
@@ -0,0 +1,647 @@
+/*
+ * (C) 2013 by Pablo Neira Ayuso <pablo@netfilter.org>
+ * (C) 2013 by Giuseppe Longo <giuseppelng@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This code has been sponsored by Sophos Astaro <http://www.sophos.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netdb.h>
+#include <net/if_arp.h>
+
+#include <xtables.h>
+#include <libiptc/libxtc.h>
+#include <net/if_arp.h>
+#include <netinet/if_ether.h>
+
+#include <linux/netfilter_arp/arp_tables.h>
+#include <linux/netfilter/nf_tables.h>
+
+#include "nft-shared.h"
+#include "nft-arp.h"
+#include "nft.h"
+
+/* a few names */
+char *arp_opcodes[] =
+{
+	"Request",
+	"Reply",
+	"Request_Reverse",
+	"Reply_Reverse",
+	"DRARP_Request",
+	"DRARP_Reply",
+	"DRARP_Error",
+	"InARP_Request",
+	"ARP_NAK",
+};
+
+static char *
+addr_to_dotted(const struct in_addr *addrp)
+{
+	static char buf[20];
+	const unsigned char *bytep;
+
+	bytep = (const unsigned char *) &(addrp->s_addr);
+	sprintf(buf, "%d.%d.%d.%d", bytep[0], bytep[1], bytep[2], bytep[3]);
+	return buf;
+}
+
+static char *
+addr_to_host(const struct in_addr *addr)
+{
+	struct hostent *host;
+
+	if ((host = gethostbyaddr((char *) addr,
+					sizeof(struct in_addr), AF_INET)) != NULL)
+		return (char *) host->h_name;
+
+	return (char *) NULL;
+}
+
+static char *
+addr_to_network(const struct in_addr *addr)
+{
+	struct netent *net;
+
+	if ((net = getnetbyaddr((long) ntohl(addr->s_addr), AF_INET)) != NULL)
+		return (char *) net->n_name;
+
+	return (char *) NULL;
+}
+
+static char *
+addr_to_anyname(const struct in_addr *addr)
+{
+	char *name;
+
+	if ((name = addr_to_host(addr)) != NULL ||
+		(name = addr_to_network(addr)) != NULL)
+		return name;
+
+	return addr_to_dotted(addr);
+}
+
+static char *
+mask_to_dotted(const struct in_addr *mask)
+{
+	int i;
+	static char buf[22];
+	u_int32_t maskaddr, bits;
+
+	maskaddr = ntohl(mask->s_addr);
+
+	if (maskaddr == 0xFFFFFFFFL)
+		/* we don't want to see "/32" */
+		return "";
+
+	i = 32;
+	bits = 0xFFFFFFFEL;
+	while (--i >= 0 && maskaddr != bits)
+		bits <<= 1;
+	if (i >= 0)
+		sprintf(buf, "/%d", i);
+	else
+		/* mask was not a decent combination of 1's and 0's */
+		snprintf(buf, sizeof(buf), "/%s", addr_to_dotted(mask));
+
+	return buf;
+}
+
+static bool need_devaddr(struct arpt_devaddr_info *info)
+{
+	int i;
+
+	for (i = 0; i < ETH_ALEN; i++) {
+		if (info->addr[i] || info->mask[i])
+			return true;
+	}
+
+	return false;
+}
+
+static int nft_arp_add(struct nft_handle *h, struct nftnl_rule *r, void *data)
+{
+	struct iptables_command_state *cs = data;
+	struct arpt_entry *fw = &cs->arp;
+	uint32_t op;
+	int ret = 0;
+
+	if (fw->arp.iniface[0] != '\0') {
+		op = nft_invflags2cmp(fw->arp.invflags, IPT_INV_VIA_IN);
+		add_iniface(r, fw->arp.iniface, op);
+	}
+
+	if (fw->arp.outiface[0] != '\0') {
+		op = nft_invflags2cmp(fw->arp.invflags, IPT_INV_VIA_OUT);
+		add_outiface(r, fw->arp.outiface, op);
+	}
+
+	if (fw->arp.arhrd != 0 ||
+	    fw->arp.invflags & IPT_INV_ARPHRD) {
+		op = nft_invflags2cmp(fw->arp.invflags, IPT_INV_ARPHRD);
+		add_payload(r, offsetof(struct arphdr, ar_hrd), 2,
+			    NFT_PAYLOAD_NETWORK_HEADER);
+		add_cmp_u16(r, fw->arp.arhrd, op);
+	}
+
+	if (fw->arp.arpro != 0 ||
+	    fw->arp.invflags & IPT_INV_PROTO) {
+		op = nft_invflags2cmp(fw->arp.invflags, IPT_INV_PROTO);
+	        add_payload(r, offsetof(struct arphdr, ar_pro), 2,
+			    NFT_PAYLOAD_NETWORK_HEADER);
+		add_cmp_u16(r, fw->arp.arpro, op);
+	}
+
+	if (fw->arp.arhln != 0 ||
+	    fw->arp.invflags & IPT_INV_ARPHLN) {
+		op = nft_invflags2cmp(fw->arp.invflags, IPT_INV_ARPHLN);
+		add_proto(r, offsetof(struct arphdr, ar_hln), 1,
+			  fw->arp.arhln, op);
+	}
+
+	add_proto(r, offsetof(struct arphdr, ar_pln), 1, 4, NFT_CMP_EQ);
+
+	if (fw->arp.arpop != 0 ||
+	    fw->arp.invflags & IPT_INV_ARPOP) {
+		op = nft_invflags2cmp(fw->arp.invflags, IPT_INV_ARPOP);
+		add_payload(r, offsetof(struct arphdr, ar_op), 2,
+			    NFT_PAYLOAD_NETWORK_HEADER);
+		add_cmp_u16(r, fw->arp.arpop, op);
+	}
+
+	if (need_devaddr(&fw->arp.src_devaddr)) {
+		op = nft_invflags2cmp(fw->arp.invflags, IPT_INV_SRCDEVADDR);
+		add_addr(r, NFT_PAYLOAD_NETWORK_HEADER,
+			 sizeof(struct arphdr),
+			 &fw->arp.src_devaddr.addr,
+			 &fw->arp.src_devaddr.mask,
+			 fw->arp.arhln, op);
+
+	}
+
+	if (fw->arp.src.s_addr != 0 ||
+	    fw->arp.smsk.s_addr != 0 ||
+	    fw->arp.invflags & IPT_INV_SRCIP) {
+		op = nft_invflags2cmp(fw->arp.invflags, IPT_INV_SRCIP);
+		add_addr(r, NFT_PAYLOAD_NETWORK_HEADER,
+			 sizeof(struct arphdr) + fw->arp.arhln,
+			 &fw->arp.src.s_addr, &fw->arp.smsk.s_addr,
+			 sizeof(struct in_addr), op);
+	}
+
+
+	if (need_devaddr(&fw->arp.tgt_devaddr)) {
+		op = nft_invflags2cmp(fw->arp.invflags, IPT_INV_TGTDEVADDR);
+		add_addr(r, NFT_PAYLOAD_NETWORK_HEADER,
+			 sizeof(struct arphdr) + fw->arp.arhln + sizeof(struct in_addr),
+			 &fw->arp.tgt_devaddr.addr,
+			 &fw->arp.tgt_devaddr.mask,
+			 fw->arp.arhln, op);
+	}
+
+	if (fw->arp.tgt.s_addr != 0 ||
+	    fw->arp.tmsk.s_addr != 0 ||
+	    fw->arp.invflags & IPT_INV_DSTIP) {
+		op = nft_invflags2cmp(fw->arp.invflags, IPT_INV_DSTIP);
+		add_addr(r, NFT_PAYLOAD_NETWORK_HEADER,
+			 sizeof(struct arphdr) + fw->arp.arhln + sizeof(struct in_addr) + fw->arp.arhln,
+			 &fw->arp.tgt.s_addr, &fw->arp.tmsk.s_addr,
+			 sizeof(struct in_addr), op);
+	}
+
+	/* Counters need to me added before the target, otherwise they are
+	 * increased for each rule because of the way nf_tables works.
+	 */
+	if (add_counters(r, fw->counters.pcnt, fw->counters.bcnt) < 0)
+		return -1;
+
+	if (cs->target != NULL) {
+		/* Standard target? */
+		if (strcmp(cs->jumpto, XTC_LABEL_ACCEPT) == 0)
+			ret = add_verdict(r, NF_ACCEPT);
+		else if (strcmp(cs->jumpto, XTC_LABEL_DROP) == 0)
+			ret = add_verdict(r, NF_DROP);
+		else if (strcmp(cs->jumpto, XTC_LABEL_RETURN) == 0)
+			ret = add_verdict(r, NFT_RETURN);
+		else
+			ret = add_target(r, cs->target->t);
+	} else if (strlen(cs->jumpto) > 0) {
+		/* No goto in arptables */
+		ret = add_jumpto(r, cs->jumpto, NFT_JUMP);
+	}
+
+	return ret;
+}
+
+static void nft_arp_parse_meta(struct nft_xt_ctx *ctx, struct nftnl_expr *e,
+			       void *data)
+{
+	struct iptables_command_state *cs = data;
+	struct arpt_entry *fw = &cs->arp;
+	uint8_t flags = 0;
+
+	parse_meta(e, ctx->meta.key, fw->arp.iniface, fw->arp.iniface_mask,
+		   fw->arp.outiface, fw->arp.outiface_mask,
+		   &flags);
+
+	fw->arp.invflags |= flags;
+}
+
+static void nft_arp_parse_immediate(const char *jumpto, bool nft_goto,
+				    void *data)
+{
+	struct iptables_command_state *cs = data;
+
+	cs->jumpto = jumpto;
+}
+
+static void parse_mask_ipv4(struct nft_xt_ctx *ctx, struct in_addr *mask)
+{
+	mask->s_addr = ctx->bitwise.mask[0];
+}
+
+static bool nft_arp_parse_devaddr(struct nft_xt_ctx *ctx,
+				  struct nftnl_expr *e,
+				  struct arpt_devaddr_info *info)
+{
+	uint32_t hlen;
+	bool inv;
+
+	nftnl_expr_get(e, NFTNL_EXPR_CMP_DATA, &hlen);
+
+	if (hlen != ETH_ALEN)
+		return false;
+
+	get_cmp_data(e, info->addr, ETH_ALEN, &inv);
+
+	if (ctx->flags & NFT_XT_CTX_BITWISE) {
+		memcpy(info->mask, ctx->bitwise.mask, ETH_ALEN);
+		ctx->flags &= ~NFT_XT_CTX_BITWISE;
+	} else {
+		memset(info->mask, 0xff,
+		       min(ctx->payload.len, ETH_ALEN));
+	}
+
+	return inv;
+}
+
+static void nft_arp_parse_payload(struct nft_xt_ctx *ctx,
+				  struct nftnl_expr *e, void *data)
+{
+	struct iptables_command_state *cs = data;
+	struct arpt_entry *fw = &cs->arp;
+	struct in_addr addr;
+	uint16_t ar_hrd, ar_pro, ar_op;
+	uint8_t ar_hln;
+	bool inv;
+
+	switch (ctx->payload.offset) {
+	case offsetof(struct arphdr, ar_hrd):
+		get_cmp_data(e, &ar_hrd, sizeof(ar_hrd), &inv);
+		fw->arp.arhrd = ar_hrd;
+		fw->arp.arhrd_mask = 0xffff;
+		if (inv)
+			fw->arp.invflags |= IPT_INV_ARPHRD;
+		break;
+	case offsetof(struct arphdr, ar_pro):
+		get_cmp_data(e, &ar_pro, sizeof(ar_pro), &inv);
+		fw->arp.arpro = ar_pro;
+		fw->arp.arpro_mask = 0xffff;
+		if (inv)
+			fw->arp.invflags |= IPT_INV_PROTO;
+		break;
+	case offsetof(struct arphdr, ar_op):
+		get_cmp_data(e, &ar_op, sizeof(ar_op), &inv);
+		fw->arp.arpop = ar_op;
+		fw->arp.arpop_mask = 0xffff;
+		if (inv)
+			fw->arp.invflags |= IPT_INV_ARPOP;
+		break;
+	case offsetof(struct arphdr, ar_hln):
+		get_cmp_data(e, &ar_hln, sizeof(ar_hln), &inv);
+		fw->arp.arhln = ar_hln;
+		fw->arp.arhln_mask = 0xff;
+		if (inv)
+			fw->arp.invflags |= IPT_INV_ARPOP;
+		break;
+	default:
+		if (ctx->payload.offset == sizeof(struct arphdr)) {
+			if (nft_arp_parse_devaddr(ctx, e, &fw->arp.src_devaddr))
+				fw->arp.invflags |= IPT_INV_SRCDEVADDR;
+		} else if (ctx->payload.offset == sizeof(struct arphdr) +
+					   fw->arp.arhln) {
+			get_cmp_data(e, &addr, sizeof(addr), &inv);
+			fw->arp.src.s_addr = addr.s_addr;
+			if (ctx->flags & NFT_XT_CTX_BITWISE) {
+				parse_mask_ipv4(ctx, &fw->arp.smsk);
+				ctx->flags &= ~NFT_XT_CTX_BITWISE;
+			} else {
+				memset(&fw->arp.smsk, 0xff,
+				       min(ctx->payload.len,
+					   sizeof(struct in_addr)));
+			}
+
+			if (inv)
+				fw->arp.invflags |= IPT_INV_SRCIP;
+		} else if (ctx->payload.offset == sizeof(struct arphdr) +
+						  fw->arp.arhln +
+						  sizeof(struct in_addr)) {
+			if (nft_arp_parse_devaddr(ctx, e, &fw->arp.tgt_devaddr))
+				fw->arp.invflags |= IPT_INV_TGTDEVADDR;
+		} else if (ctx->payload.offset == sizeof(struct arphdr) +
+						  fw->arp.arhln +
+						  sizeof(struct in_addr) +
+						  fw->arp.arhln) {
+			get_cmp_data(e, &addr, sizeof(addr), &inv);
+			fw->arp.tgt.s_addr = addr.s_addr;
+			if (ctx->flags & NFT_XT_CTX_BITWISE) {
+				parse_mask_ipv4(ctx, &fw->arp.tmsk);
+				ctx->flags &= ~NFT_XT_CTX_BITWISE;
+			} else {
+				memset(&fw->arp.tmsk, 0xff,
+				       min(ctx->payload.len,
+					   sizeof(struct in_addr)));
+			}
+
+			if (inv)
+				fw->arp.invflags |= IPT_INV_DSTIP;
+		}
+		break;
+	}
+}
+
+static void nft_arp_print_header(unsigned int format, const char *chain,
+				 const char *pol,
+				 const struct xt_counters *counters,
+				 bool basechain, uint32_t refs,
+				 uint32_t entries)
+{
+	printf("Chain %s", chain);
+	if (basechain && pol) {
+		printf(" (policy %s", pol);
+		if (!(format & FMT_NOCOUNTS)) {
+			fputc(' ', stdout);
+			xtables_print_num(counters->pcnt, (format|FMT_NOTABLE));
+			fputs("packets, ", stdout);
+			xtables_print_num(counters->bcnt, (format|FMT_NOTABLE));
+			fputs("bytes", stdout);
+		}
+		printf(")\n");
+	} else {
+		printf(" (%u references)\n", refs);
+	}
+}
+
+static void nft_arp_print_rule_details(const struct iptables_command_state *cs,
+				       unsigned int format)
+{
+	const struct arpt_entry *fw = &cs->arp;
+	char buf[BUFSIZ];
+	char iface[IFNAMSIZ+2];
+	const char *sep = "";
+	int print_iface = 0;
+	int i;
+
+	if (strlen(cs->jumpto)) {
+		printf("%s-j %s", sep, cs->jumpto);
+		sep = " ";
+	}
+
+	iface[0] = '\0';
+
+	if (fw->arp.iniface[0] != '\0') {
+		strcat(iface, fw->arp.iniface);
+		print_iface = 1;
+	}
+	else if (format & FMT_VIA) {
+		print_iface = 1;
+		if (format & FMT_NUMERIC) strcat(iface, "*");
+		else strcat(iface, "any");
+	}
+	if (print_iface) {
+		printf("%s%s-i %s", sep, fw->arp.invflags & IPT_INV_VIA_IN ?
+				   "! " : "", iface);
+		sep = " ";
+	}
+
+	print_iface = 0;
+	iface[0] = '\0';
+
+	if (fw->arp.outiface[0] != '\0') {
+		strcat(iface, fw->arp.outiface);
+		print_iface = 1;
+	}
+	else if (format & FMT_VIA) {
+		print_iface = 1;
+		if (format & FMT_NUMERIC) strcat(iface, "*");
+		else strcat(iface, "any");
+	}
+	if (print_iface) {
+		printf("%s%s-o %s", sep, fw->arp.invflags & IPT_INV_VIA_OUT ?
+				   "! " : "", iface);
+		sep = " ";
+	}
+
+	if (fw->arp.smsk.s_addr != 0L) {
+		printf("%s%s", sep, fw->arp.invflags & IPT_INV_SRCIP
+			? "! " : "");
+		if (format & FMT_NUMERIC)
+			sprintf(buf, "%s", addr_to_dotted(&(fw->arp.src)));
+		else
+			sprintf(buf, "%s", addr_to_anyname(&(fw->arp.src)));
+		strncat(buf, mask_to_dotted(&(fw->arp.smsk)),
+			sizeof(buf) - strlen(buf) - 1);
+		printf("-s %s", buf);
+		sep = " ";
+	}
+
+	for (i = 0; i < ARPT_DEV_ADDR_LEN_MAX; i++)
+		if (fw->arp.src_devaddr.mask[i] != 0)
+			break;
+	if (i == ARPT_DEV_ADDR_LEN_MAX)
+		goto after_devsrc;
+	printf("%s%s", sep, fw->arp.invflags & IPT_INV_SRCDEVADDR
+		? "! " : "");
+	printf("--src-mac ");
+	xtables_print_mac_and_mask((unsigned char *)fw->arp.src_devaddr.addr,
+				   (unsigned char *)fw->arp.src_devaddr.mask);
+	sep = " ";
+after_devsrc:
+
+	if (fw->arp.tmsk.s_addr != 0L) {
+		printf("%s%s", sep, fw->arp.invflags & IPT_INV_DSTIP
+			? "! " : "");
+		if (format & FMT_NUMERIC)
+			sprintf(buf, "%s", addr_to_dotted(&(fw->arp.tgt)));
+		else
+			sprintf(buf, "%s", addr_to_anyname(&(fw->arp.tgt)));
+		strncat(buf, mask_to_dotted(&(fw->arp.tmsk)),
+			sizeof(buf) - strlen(buf) - 1);
+		printf("-d %s", buf);
+		sep = " ";
+	}
+
+	for (i = 0; i <ARPT_DEV_ADDR_LEN_MAX; i++)
+		if (fw->arp.tgt_devaddr.mask[i] != 0)
+			break;
+	if (i == ARPT_DEV_ADDR_LEN_MAX)
+		goto after_devdst;
+	printf("%s%s", sep, fw->arp.invflags & IPT_INV_TGTDEVADDR
+		? "! " : "");
+	printf("--dst-mac ");
+	xtables_print_mac_and_mask((unsigned char *)fw->arp.tgt_devaddr.addr,
+				   (unsigned char *)fw->arp.tgt_devaddr.mask);
+	sep = " ";
+
+after_devdst:
+
+	if (fw->arp.arhln_mask != 255 || fw->arp.arhln != 6) {
+		printf("%s%s", sep, fw->arp.invflags & IPT_INV_ARPHLN
+			? "! " : "");
+		printf("--h-length %d", fw->arp.arhln);
+		if (fw->arp.arhln_mask != 255)
+			printf("/%d", fw->arp.arhln_mask);
+		sep = " ";
+	}
+
+	if (fw->arp.arpop_mask != 0) {
+		int tmp = ntohs(fw->arp.arpop);
+
+		printf("%s%s", sep, fw->arp.invflags & IPT_INV_ARPOP
+			? "! " : "");
+		if (tmp <= NUMOPCODES && !(format & FMT_NUMERIC))
+			printf("--opcode %s", arp_opcodes[tmp-1]);
+		else
+			printf("--opcode %d", tmp);
+
+		if (fw->arp.arpop_mask != 65535)
+			printf("/%d", ntohs(fw->arp.arpop_mask));
+		sep = " ";
+	}
+
+	if (fw->arp.arhrd_mask != 65535 || fw->arp.arhrd != htons(1)) {
+		uint16_t tmp = ntohs(fw->arp.arhrd);
+
+		printf("%s%s", sep, fw->arp.invflags & IPT_INV_ARPHRD
+			? "! " : "");
+		if (tmp == 1 && !(format & FMT_NUMERIC))
+			printf("--h-type %s", "Ethernet");
+		else
+			printf("--h-type %u", tmp);
+		if (fw->arp.arhrd_mask != 65535)
+			printf("/%d", ntohs(fw->arp.arhrd_mask));
+		sep = " ";
+	}
+
+	if (fw->arp.arpro_mask != 0) {
+		int tmp = ntohs(fw->arp.arpro);
+
+		printf("%s%s", sep, fw->arp.invflags & IPT_INV_PROTO
+			? "! " : "");
+		if (tmp == 0x0800 && !(format & FMT_NUMERIC))
+			printf("--proto-type %s", "IPv4");
+		else
+			printf("--proto-type 0x%x", tmp);
+		if (fw->arp.arpro_mask != 65535)
+			printf("/%x", ntohs(fw->arp.arpro_mask));
+		sep = " ";
+	}
+}
+
+static void
+nft_arp_save_rule(const void *data, unsigned int format)
+{
+	const struct iptables_command_state *cs = data;
+
+	format |= FMT_NUMERIC;
+
+	nft_arp_print_rule_details(cs, format);
+	if (cs->target && cs->target->save)
+		cs->target->save(&cs->fw, cs->target->t);
+	printf("\n");
+}
+
+static void
+nft_arp_print_rule(struct nft_handle *h, struct nftnl_rule *r,
+		   unsigned int num, unsigned int format)
+{
+	struct iptables_command_state cs = {};
+
+	if (format & FMT_LINENUMBERS)
+		printf("%u ", num);
+
+	nft_rule_to_iptables_command_state(h, r, &cs);
+
+	nft_arp_print_rule_details(&cs, format);
+	print_matches_and_target(&cs, format);
+
+	if (!(format & FMT_NOCOUNTS)) {
+		printf(" , pcnt=");
+		xtables_print_num(cs.counters.pcnt, format | FMT_NOTABLE);
+		printf("-- bcnt=");
+		xtables_print_num(cs.counters.bcnt, format | FMT_NOTABLE);
+	}
+
+	if (!(format & FMT_NONEWLINE))
+		fputc('\n', stdout);
+
+	nft_clear_iptables_command_state(&cs);
+}
+
+static bool nft_arp_is_same(const void *data_a,
+			    const void *data_b)
+{
+	const struct arpt_entry *a = data_a;
+	const struct arpt_entry *b = data_b;
+
+	if (a->arp.src.s_addr != b->arp.src.s_addr
+	    || a->arp.tgt.s_addr != b->arp.tgt.s_addr
+	    || a->arp.smsk.s_addr != b->arp.smsk.s_addr
+	    || a->arp.tmsk.s_addr != b->arp.tmsk.s_addr
+	    || a->arp.arpro != b->arp.arpro
+	    || a->arp.flags != b->arp.flags
+	    || a->arp.invflags != b->arp.invflags) {
+		DEBUGP("different src/dst/proto/flags/invflags\n");
+		return false;
+	}
+
+	return is_same_interfaces(a->arp.iniface,
+				  a->arp.outiface,
+				  (unsigned char *)a->arp.iniface_mask,
+				  (unsigned char *)a->arp.outiface_mask,
+				  b->arp.iniface,
+				  b->arp.outiface,
+				  (unsigned char *)b->arp.iniface_mask,
+				  (unsigned char *)b->arp.outiface_mask);
+}
+
+static void nft_arp_save_chain(const struct nftnl_chain *c, const char *policy)
+{
+	const char *chain = nftnl_chain_get_str(c, NFTNL_CHAIN_NAME);
+
+	printf(":%s %s\n", chain, policy ?: "-");
+}
+
+struct nft_family_ops nft_family_ops_arp = {
+	.add			= nft_arp_add,
+	.is_same		= nft_arp_is_same,
+	.print_payload		= NULL,
+	.parse_meta		= nft_arp_parse_meta,
+	.parse_payload		= nft_arp_parse_payload,
+	.parse_immediate	= nft_arp_parse_immediate,
+	.print_header		= nft_arp_print_header,
+	.print_rule		= nft_arp_print_rule,
+	.save_rule		= nft_arp_save_rule,
+	.save_chain		= nft_arp_save_chain,
+	.post_parse		= NULL,
+	.rule_to_cs		= nft_rule_to_iptables_command_state,
+	.clear_cs		= nft_clear_iptables_command_state,
+	.parse_target		= nft_ipv46_parse_target,
+};
diff --git a/iptables/nft-arp.h b/iptables/nft-arp.h
new file mode 100644
index 0000000..0d93a31
--- /dev/null
+++ b/iptables/nft-arp.h
@@ -0,0 +1,14 @@
+#ifndef _NFT_ARP_H_
+#define _NFT_ARP_H_
+
+extern char *arp_opcodes[];
+#define NUMOPCODES 9
+
+/* define invflags which won't collide with IPT ones */
+#define IPT_INV_SRCDEVADDR	0x0080
+#define IPT_INV_TGTDEVADDR	0x0100
+#define IPT_INV_ARPHLN		0x0200
+#define IPT_INV_ARPOP		0x0400
+#define IPT_INV_ARPHRD		0x0800
+
+#endif
diff --git a/iptables/nft-bridge.c b/iptables/nft-bridge.c
new file mode 100644
index 0000000..d98fd52
--- /dev/null
+++ b/iptables/nft-bridge.c
@@ -0,0 +1,905 @@
+/*
+ * (C) 2014 by Giuseppe Longo <giuseppelng@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netinet/ether.h>
+#include <inttypes.h>
+
+#include <xtables.h>
+#include <libiptc/libxtc.h>
+#include <linux/netfilter/nf_tables.h>
+
+#include <libnftnl/set.h>
+
+#include "nft-shared.h"
+#include "nft-bridge.h"
+#include "nft-cache.h"
+#include "nft.h"
+
+void ebt_cs_clean(struct iptables_command_state *cs)
+{
+	struct ebt_match *m, *nm;
+
+	xtables_rule_matches_free(&cs->matches);
+
+	for (m = cs->match_list; m;) {
+		if (!m->ismatch) {
+			struct xtables_target *target = m->u.watcher;
+
+			if (target->t) {
+				free(target->t);
+				target->t = NULL;
+			}
+			if (target == target->next)
+				free(target);
+		}
+
+		nm = m->next;
+		free(m);
+		m = nm;
+	}
+
+	if (cs->target) {
+		free(cs->target->t);
+		cs->target->t = NULL;
+
+		if (cs->target == cs->target->next) {
+			free(cs->target);
+			cs->target = NULL;
+		}
+	}
+}
+
+/* Put the mac address into 6 (ETH_ALEN) bytes returns 0 on success. */
+static void ebt_print_mac_and_mask(const unsigned char *mac, const unsigned char *mask)
+{
+	if (xtables_print_well_known_mac_and_mask(mac, mask))
+		xtables_print_mac_and_mask(mac, mask);
+}
+
+static void add_logical_iniface(struct nftnl_rule *r, char *iface, uint32_t op)
+{
+	int iface_len;
+
+	iface_len = strlen(iface);
+
+	add_meta(r, NFT_META_BRI_IIFNAME);
+	if (iface[iface_len - 1] == '+')
+		add_cmp_ptr(r, op, iface, iface_len - 1);
+	else
+		add_cmp_ptr(r, op, iface, iface_len + 1);
+}
+
+static void add_logical_outiface(struct nftnl_rule *r, char *iface, uint32_t op)
+{
+	int iface_len;
+
+	iface_len = strlen(iface);
+
+	add_meta(r, NFT_META_BRI_OIFNAME);
+	if (iface[iface_len - 1] == '+')
+		add_cmp_ptr(r, op, iface, iface_len - 1);
+	else
+		add_cmp_ptr(r, op, iface, iface_len + 1);
+}
+
+static int _add_action(struct nftnl_rule *r, struct iptables_command_state *cs)
+{
+	return add_action(r, cs, false);
+}
+
+static int nft_bridge_add(struct nft_handle *h,
+			  struct nftnl_rule *r, void *data)
+{
+	struct iptables_command_state *cs = data;
+	struct ebt_match *iter;
+	struct ebt_entry *fw = &cs->eb;
+	uint32_t op;
+
+	if (fw->in[0] != '\0') {
+		op = nft_invflags2cmp(fw->invflags, EBT_IIN);
+		add_iniface(r, fw->in, op);
+	}
+
+	if (fw->out[0] != '\0') {
+		op = nft_invflags2cmp(fw->invflags, EBT_IOUT);
+		add_outiface(r, fw->out, op);
+	}
+
+	if (fw->logical_in[0] != '\0') {
+		op = nft_invflags2cmp(fw->invflags, EBT_ILOGICALIN);
+		add_logical_iniface(r, fw->logical_in, op);
+	}
+
+	if (fw->logical_out[0] != '\0') {
+		op = nft_invflags2cmp(fw->invflags, EBT_ILOGICALOUT);
+		add_logical_outiface(r, fw->logical_out, op);
+	}
+
+	if (fw->bitmask & EBT_ISOURCE) {
+		op = nft_invflags2cmp(fw->invflags, EBT_ISOURCE);
+		add_addr(r, NFT_PAYLOAD_LL_HEADER,
+			 offsetof(struct ethhdr, h_source),
+			 fw->sourcemac, fw->sourcemsk, ETH_ALEN, op);
+	}
+
+	if (fw->bitmask & EBT_IDEST) {
+		op = nft_invflags2cmp(fw->invflags, EBT_IDEST);
+		add_addr(r, NFT_PAYLOAD_LL_HEADER,
+			 offsetof(struct ethhdr, h_dest),
+			 fw->destmac, fw->destmsk, ETH_ALEN, op);
+	}
+
+	if ((fw->bitmask & EBT_NOPROTO) == 0) {
+		op = nft_invflags2cmp(fw->invflags, EBT_IPROTO);
+		add_payload(r, offsetof(struct ethhdr, h_proto), 2,
+			    NFT_PAYLOAD_LL_HEADER);
+		add_cmp_u16(r, fw->ethproto, op);
+	}
+
+	add_compat(r, fw->ethproto, fw->invflags & EBT_IPROTO);
+
+	for (iter = cs->match_list; iter; iter = iter->next) {
+		if (iter->ismatch) {
+			if (add_match(h, r, iter->u.match->m))
+				break;
+		} else {
+			if (add_target(r, iter->u.watcher->t))
+				break;
+		}
+	}
+
+	if (add_counters(r, cs->counters.pcnt, cs->counters.bcnt) < 0)
+		return -1;
+
+	return _add_action(r, cs);
+}
+
+static void nft_bridge_parse_meta(struct nft_xt_ctx *ctx,
+				  struct nftnl_expr *e, void *data)
+{
+	struct iptables_command_state *cs = data;
+	struct ebt_entry *fw = &cs->eb;
+	uint8_t invflags = 0;
+	char iifname[IFNAMSIZ] = {}, oifname[IFNAMSIZ] = {};
+
+	parse_meta(e, ctx->meta.key, iifname, NULL, oifname, NULL, &invflags);
+
+	switch (ctx->meta.key) {
+	case NFT_META_BRI_IIFNAME:
+		if (invflags & IPT_INV_VIA_IN)
+			cs->eb.invflags |= EBT_ILOGICALIN;
+		snprintf(fw->logical_in, sizeof(fw->logical_in), "%s", iifname);
+		break;
+	case NFT_META_IIFNAME:
+		if (invflags & IPT_INV_VIA_IN)
+			cs->eb.invflags |= EBT_IIN;
+		snprintf(fw->in, sizeof(fw->in), "%s", iifname);
+		break;
+	case NFT_META_BRI_OIFNAME:
+		if (invflags & IPT_INV_VIA_OUT)
+			cs->eb.invflags |= EBT_ILOGICALOUT;
+		snprintf(fw->logical_out, sizeof(fw->logical_out), "%s", oifname);
+		break;
+	case NFT_META_OIFNAME:
+		if (invflags & IPT_INV_VIA_OUT)
+			cs->eb.invflags |= EBT_IOUT;
+		snprintf(fw->out, sizeof(fw->out), "%s", oifname);
+		break;
+	default:
+		break;
+	}
+}
+
+static void nft_bridge_parse_payload(struct nft_xt_ctx *ctx,
+				     struct nftnl_expr *e, void *data)
+{
+	struct iptables_command_state *cs = data;
+	struct ebt_entry *fw = &cs->eb;
+	unsigned char addr[ETH_ALEN];
+	unsigned short int ethproto;
+	bool inv;
+	int i;
+
+	switch (ctx->payload.offset) {
+	case offsetof(struct ethhdr, h_dest):
+		get_cmp_data(e, addr, sizeof(addr), &inv);
+		for (i = 0; i < ETH_ALEN; i++)
+			fw->destmac[i] = addr[i];
+		if (inv)
+			fw->invflags |= EBT_IDEST;
+
+		if (ctx->flags & NFT_XT_CTX_BITWISE) {
+                        memcpy(fw->destmsk, ctx->bitwise.mask, ETH_ALEN);
+                        ctx->flags &= ~NFT_XT_CTX_BITWISE;
+                } else {
+			memset(&fw->destmsk, 0xff,
+			       min(ctx->payload.len, ETH_ALEN));
+                }
+		fw->bitmask |= EBT_IDEST;
+		break;
+	case offsetof(struct ethhdr, h_source):
+		get_cmp_data(e, addr, sizeof(addr), &inv);
+		for (i = 0; i < ETH_ALEN; i++)
+			fw->sourcemac[i] = addr[i];
+		if (inv)
+			fw->invflags |= EBT_ISOURCE;
+		if (ctx->flags & NFT_XT_CTX_BITWISE) {
+                        memcpy(fw->sourcemsk, ctx->bitwise.mask, ETH_ALEN);
+                        ctx->flags &= ~NFT_XT_CTX_BITWISE;
+                } else {
+			memset(&fw->sourcemsk, 0xff,
+			       min(ctx->payload.len, ETH_ALEN));
+                }
+		fw->bitmask |= EBT_ISOURCE;
+		break;
+	case offsetof(struct ethhdr, h_proto):
+		get_cmp_data(e, &ethproto, sizeof(ethproto), &inv);
+		fw->ethproto = ethproto;
+		if (inv)
+			fw->invflags |= EBT_IPROTO;
+		fw->bitmask &= ~EBT_NOPROTO;
+		break;
+	}
+}
+
+static void nft_bridge_parse_immediate(const char *jumpto, bool nft_goto,
+				       void *data)
+{
+	struct iptables_command_state *cs = data;
+
+	cs->jumpto = jumpto;
+}
+
+/* return 0 if saddr, 1 if daddr, -1 on error */
+static int
+lookup_check_ether_payload(uint32_t base, uint32_t offset, uint32_t len)
+{
+	if (base != 0 || len != ETH_ALEN)
+		return -1;
+
+	switch (offset) {
+	case offsetof(struct ether_header, ether_dhost):
+		return 1;
+	case offsetof(struct ether_header, ether_shost):
+		return 0;
+	default:
+		return -1;
+	}
+}
+
+/* return 0 if saddr, 1 if daddr, -1 on error */
+static int
+lookup_check_iphdr_payload(uint32_t base, uint32_t offset, uint32_t len)
+{
+	if (base != 1 || len != 4)
+		return -1;
+
+	switch (offset) {
+	case offsetof(struct iphdr, daddr):
+		return 1;
+	case offsetof(struct iphdr, saddr):
+		return 0;
+	default:
+		return -1;
+	}
+}
+
+/* Make sure previous payload expression(s) is/are consistent and extract if
+ * matching on source or destination address and if matching on MAC and IP or
+ * only MAC address. */
+static int lookup_analyze_payloads(const struct nft_xt_ctx *ctx,
+				   bool *dst, bool *ip)
+{
+	int val, val2 = -1;
+
+	if (ctx->flags & NFT_XT_CTX_PREV_PAYLOAD) {
+		val = lookup_check_ether_payload(ctx->prev_payload.base,
+						 ctx->prev_payload.offset,
+						 ctx->prev_payload.len);
+		if (val < 0) {
+			DEBUGP("unknown payload base/offset/len %d/%d/%d\n",
+			       ctx->prev_payload.base, ctx->prev_payload.offset,
+			       ctx->prev_payload.len);
+			return -1;
+		}
+		if (!(ctx->flags & NFT_XT_CTX_PAYLOAD)) {
+			DEBUGP("Previous but no current payload?\n");
+			return -1;
+		}
+		val2 = lookup_check_iphdr_payload(ctx->payload.base,
+						  ctx->payload.offset,
+						  ctx->payload.len);
+		if (val2 < 0) {
+			DEBUGP("unknown payload base/offset/len %d/%d/%d\n",
+			       ctx->payload.base, ctx->payload.offset,
+			       ctx->payload.len);
+			return -1;
+		} else if (val != val2) {
+			DEBUGP("mismatching payload match offsets\n");
+			return -1;
+		}
+	} else if (ctx->flags & NFT_XT_CTX_PAYLOAD) {
+		val = lookup_check_ether_payload(ctx->payload.base,
+						 ctx->payload.offset,
+						 ctx->payload.len);
+		if (val < 0) {
+			DEBUGP("unknown payload base/offset/len %d/%d/%d\n",
+			       ctx->payload.base, ctx->payload.offset,
+			       ctx->payload.len);
+			return -1;
+		}
+	} else {
+		DEBUGP("unknown LHS of lookup expression\n");
+		return -1;
+	}
+
+	if (dst)
+		*dst = (val == 1);
+	if (ip)
+		*ip = (val2 != -1);
+	return 0;
+}
+
+static int set_elems_to_among_pairs(struct nft_among_pair *pairs,
+				    const struct nftnl_set *s, int cnt)
+{
+	struct nftnl_set_elems_iter *iter = nftnl_set_elems_iter_create(s);
+	struct nftnl_set_elem *elem;
+	size_t tmpcnt = 0;
+	const void *data;
+	uint32_t datalen;
+	int ret = -1;
+
+	if (!iter) {
+		fprintf(stderr, "BUG: set elems iter allocation failed\n");
+		return ret;
+	}
+
+	while ((elem = nftnl_set_elems_iter_next(iter))) {
+		data = nftnl_set_elem_get(elem, NFTNL_SET_ELEM_KEY, &datalen);
+		if (!data) {
+			fprintf(stderr, "BUG: set elem without key\n");
+			goto err;
+		}
+		if (datalen > sizeof(*pairs)) {
+			fprintf(stderr, "BUG: overlong set elem\n");
+			goto err;
+		}
+		nft_among_insert_pair(pairs, &tmpcnt, data);
+	}
+	ret = 0;
+err:
+	nftnl_set_elems_iter_destroy(iter);
+	return ret;
+}
+
+static struct nftnl_set *set_from_lookup_expr(struct nft_xt_ctx *ctx,
+					      const struct nftnl_expr *e)
+{
+	const char *set_name = nftnl_expr_get_str(e, NFTNL_EXPR_LOOKUP_SET);
+	uint32_t set_id = nftnl_expr_get_u32(e, NFTNL_EXPR_LOOKUP_SET_ID);
+	struct nftnl_set_list *slist;
+	struct nftnl_set *set;
+
+	slist = nft_set_list_get(ctx->h, ctx->table, set_name);
+	if (slist) {
+		set = nftnl_set_list_lookup_byname(slist, set_name);
+		if (set)
+			return set;
+
+		set = nft_set_batch_lookup_byid(ctx->h, set_id);
+		if (set)
+			return set;
+	}
+
+	return NULL;
+}
+
+static void nft_bridge_parse_lookup(struct nft_xt_ctx *ctx,
+				    struct nftnl_expr *e, void *data)
+{
+	struct xtables_match *match = NULL;
+	struct nft_among_data *among_data;
+	bool is_dst, have_ip, inv;
+	struct ebt_match *ematch;
+	struct nftnl_set *s;
+	size_t poff, size;
+	uint32_t cnt;
+
+	if (lookup_analyze_payloads(ctx, &is_dst, &have_ip))
+		return;
+
+	s = set_from_lookup_expr(ctx, e);
+	if (!s)
+		xtables_error(OTHER_PROBLEM,
+			      "BUG: lookup expression references unknown set");
+
+	cnt = nftnl_set_get_u32(s, NFTNL_SET_DESC_SIZE);
+
+	for (ematch = ctx->cs->match_list; ematch; ematch = ematch->next) {
+		if (!ematch->ismatch || strcmp(ematch->u.match->name, "among"))
+			continue;
+
+		match = ematch->u.match;
+		among_data = (struct nft_among_data *)match->m->data;
+
+		size = cnt + among_data->src.cnt + among_data->dst.cnt;
+		size *= sizeof(struct nft_among_pair);
+
+		size += XT_ALIGN(sizeof(struct xt_entry_match)) +
+			sizeof(struct nft_among_data);
+
+		match->m = xtables_realloc(match->m, size);
+		break;
+	}
+	if (!match) {
+		match = xtables_find_match("among", XTF_TRY_LOAD,
+					   &ctx->cs->matches);
+
+		size = cnt * sizeof(struct nft_among_pair);
+		size += XT_ALIGN(sizeof(struct xt_entry_match)) +
+			sizeof(struct nft_among_data);
+
+		match->m = xtables_calloc(1, size);
+		strcpy(match->m->u.user.name, match->name);
+		match->m->u.user.revision = match->revision;
+		xs_init_match(match);
+
+		if (ctx->h->ops->parse_match != NULL)
+			ctx->h->ops->parse_match(match, ctx->cs);
+	}
+	if (!match)
+		return;
+
+	match->m->u.match_size = size;
+
+	inv = !!(nftnl_expr_get_u32(e, NFTNL_EXPR_LOOKUP_FLAGS) &
+				    NFT_LOOKUP_F_INV);
+
+	among_data = (struct nft_among_data *)match->m->data;
+	poff = nft_among_prepare_data(among_data, is_dst, cnt, inv, have_ip);
+	if (set_elems_to_among_pairs(among_data->pairs + poff, s, cnt))
+		xtables_error(OTHER_PROBLEM,
+			      "ebtables among pair parsing failed");
+
+	ctx->flags &= ~(NFT_XT_CTX_PAYLOAD | NFT_XT_CTX_PREV_PAYLOAD);
+}
+
+static void parse_watcher(void *object, struct ebt_match **match_list,
+			  bool ismatch)
+{
+	struct ebt_match *m;
+
+	m = calloc(1, sizeof(struct ebt_match));
+	if (m == NULL)
+		xtables_error(OTHER_PROBLEM, "Can't allocate memory");
+
+	if (ismatch)
+		m->u.match = object;
+	else
+		m->u.watcher = object;
+
+	m->ismatch = ismatch;
+	if (*match_list == NULL)
+		*match_list = m;
+	else
+		(*match_list)->next = m;
+}
+
+static void nft_bridge_parse_match(struct xtables_match *m, void *data)
+{
+	struct iptables_command_state *cs = data;
+
+	parse_watcher(m, &cs->match_list, true);
+}
+
+static void nft_bridge_parse_target(struct xtables_target *t, void *data)
+{
+	struct iptables_command_state *cs = data;
+
+	/* harcoded names :-( */
+	if (strcmp(t->name, "log") == 0 ||
+	    strcmp(t->name, "nflog") == 0) {
+		parse_watcher(t, &cs->match_list, false);
+		return;
+	}
+
+	cs->target = t;
+}
+
+static void nft_rule_to_ebtables_command_state(struct nft_handle *h,
+					       const struct nftnl_rule *r,
+					       struct iptables_command_state *cs)
+{
+	cs->eb.bitmask = EBT_NOPROTO;
+	nft_rule_to_iptables_command_state(h, r, cs);
+}
+
+static void print_iface(const char *option, const char *name, bool invert)
+{
+	if (*name)
+		printf("%s%s %s ", option, invert ? " !" : "", name);
+}
+
+static void nft_bridge_print_table_header(const char *tablename)
+{
+	printf("Bridge table: %s\n\n", tablename);
+}
+
+static void nft_bridge_print_header(unsigned int format, const char *chain,
+				    const char *pol,
+				    const struct xt_counters *counters,
+				    bool basechain, uint32_t refs, uint32_t entries)
+{
+	printf("Bridge chain: %s, entries: %u, policy: %s\n",
+	       chain, entries, pol ?: "RETURN");
+}
+
+static void print_matches_and_watchers(const struct iptables_command_state *cs,
+				       unsigned int format)
+{
+	struct xtables_target *watcherp;
+	struct xtables_match *matchp;
+	struct ebt_match *m;
+
+	for (m = cs->match_list; m; m = m->next) {
+		if (m->ismatch) {
+			matchp = m->u.match;
+			if (matchp->print != NULL) {
+				matchp->print(&cs->eb, matchp->m,
+					      format & FMT_NUMERIC);
+			}
+		} else {
+			watcherp = m->u.watcher;
+			if (watcherp->print != NULL) {
+				watcherp->print(&cs->eb, watcherp->t,
+						format & FMT_NUMERIC);
+			}
+		}
+	}
+}
+
+static void print_mac(char option, const unsigned char *mac,
+		      const unsigned char *mask,
+		      bool invert)
+{
+	printf("-%c ", option);
+	if (invert)
+		printf("! ");
+	ebt_print_mac_and_mask(mac, mask);
+	printf(" ");
+}
+
+
+static void print_protocol(uint16_t ethproto, bool invert, unsigned int bitmask)
+{
+	struct xt_ethertypeent *ent;
+
+	/* Dont print anything about the protocol if no protocol was
+	 * specified, obviously this means any protocol will do. */
+	if (bitmask & EBT_NOPROTO)
+		return;
+
+	printf("-p ");
+	if (invert)
+		printf("! ");
+
+	if (bitmask & EBT_802_3) {
+		printf("length ");
+		return;
+	}
+
+	ent = xtables_getethertypebynumber(ntohs(ethproto));
+	if (!ent)
+		printf("0x%x ", ntohs(ethproto));
+	else
+		printf("%s ", ent->e_name);
+}
+
+static void nft_bridge_save_rule(const void *data, unsigned int format)
+{
+	const struct iptables_command_state *cs = data;
+
+	if (cs->eb.ethproto)
+		print_protocol(cs->eb.ethproto, cs->eb.invflags & EBT_IPROTO,
+			       cs->eb.bitmask);
+	if (cs->eb.bitmask & EBT_ISOURCE)
+		print_mac('s', cs->eb.sourcemac, cs->eb.sourcemsk,
+		          cs->eb.invflags & EBT_ISOURCE);
+	if (cs->eb.bitmask & EBT_IDEST)
+		print_mac('d', cs->eb.destmac, cs->eb.destmsk,
+		          cs->eb.invflags & EBT_IDEST);
+
+	print_iface("-i", cs->eb.in, cs->eb.invflags & EBT_IIN);
+	print_iface("--logical-in", cs->eb.logical_in,
+		    cs->eb.invflags & EBT_ILOGICALIN);
+	print_iface("-o", cs->eb.out, cs->eb.invflags & EBT_IOUT);
+	print_iface("--logical-out", cs->eb.logical_out,
+		    cs->eb.invflags & EBT_ILOGICALOUT);
+
+	print_matches_and_watchers(cs, format);
+
+	printf("-j ");
+
+	if (cs->jumpto != NULL) {
+		if (strcmp(cs->jumpto, "") != 0)
+			printf("%s", cs->jumpto);
+		else
+			printf("CONTINUE");
+	}
+	if (cs->target != NULL && cs->target->print != NULL) {
+		printf(" ");
+		cs->target->print(&cs->fw, cs->target->t, format & FMT_NUMERIC);
+	}
+
+	if ((format & (FMT_NOCOUNTS | FMT_C_COUNTS)) == FMT_C_COUNTS) {
+		if (format & FMT_EBT_SAVE)
+			printf(" -c %"PRIu64" %"PRIu64"",
+			       (uint64_t)cs->counters.pcnt,
+			       (uint64_t)cs->counters.bcnt);
+		else
+			printf(" , pcnt = %"PRIu64" -- bcnt = %"PRIu64"",
+			       (uint64_t)cs->counters.pcnt,
+			       (uint64_t)cs->counters.bcnt);
+	}
+
+	if (!(format & FMT_NONEWLINE))
+		fputc('\n', stdout);
+}
+
+static void nft_bridge_print_rule(struct nft_handle *h, struct nftnl_rule *r,
+				  unsigned int num, unsigned int format)
+{
+	struct iptables_command_state cs = {};
+
+	if (format & FMT_LINENUMBERS)
+		printf("%d ", num);
+
+	nft_rule_to_ebtables_command_state(h, r, &cs);
+	nft_bridge_save_rule(&cs, format);
+	ebt_cs_clean(&cs);
+}
+
+static void nft_bridge_save_chain(const struct nftnl_chain *c,
+				  const char *policy)
+{
+	const char *chain = nftnl_chain_get_str(c, NFTNL_CHAIN_NAME);
+
+	printf(":%s %s\n", chain, policy ?: "ACCEPT");
+}
+
+static bool nft_bridge_is_same(const void *data_a, const void *data_b)
+{
+	const struct ebt_entry *a = data_a;
+	const struct ebt_entry *b = data_b;
+	int i;
+
+	if (a->ethproto != b->ethproto ||
+	    /* FIXME: a->flags != b->flags || */
+	    a->invflags != b->invflags) {
+		DEBUGP("different proto/flags/invflags\n");
+		return false;
+	}
+
+	for (i = 0; i < ETH_ALEN; i++) {
+		if (a->sourcemac[i] != b->sourcemac[i]) {
+			DEBUGP("different source mac %x, %x (%d)\n",
+			a->sourcemac[i] & 0xff, b->sourcemac[i] & 0xff, i);
+			return false;
+		}
+
+		if (a->destmac[i] != b->destmac[i]) {
+			DEBUGP("different destination mac %x, %x (%d)\n",
+			a->destmac[i] & 0xff, b->destmac[i] & 0xff, i);
+			return false;
+		}
+	}
+
+	for (i = 0; i < IFNAMSIZ; i++) {
+		if (a->logical_in[i] != b->logical_in[i]) {
+			DEBUGP("different logical iniface %x, %x (%d)\n",
+			a->logical_in[i] & 0xff, b->logical_in[i] & 0xff, i);
+			return false;
+		}
+
+		if (a->logical_out[i] != b->logical_out[i]) {
+			DEBUGP("different logical outiface %x, %x (%d)\n",
+			a->logical_out[i] & 0xff, b->logical_out[i] & 0xff, i);
+			return false;
+		}
+	}
+
+	return strcmp(a->in, b->in) == 0 && strcmp(a->out, b->out) == 0;
+}
+
+static int xlate_ebmatches(const struct iptables_command_state *cs, struct xt_xlate *xl)
+{
+	int ret = 1, numeric = cs->options & OPT_NUMERIC;
+	struct ebt_match *m;
+
+	for (m = cs->match_list; m; m = m->next) {
+		if (m->ismatch) {
+			struct xtables_match *matchp = m->u.match;
+			struct xt_xlate_mt_params mt_params = {
+				.ip		= (const void *)&cs->eb,
+				.numeric	= numeric,
+				.escape_quotes	= false,
+				.match		= matchp->m,
+			};
+
+			if (!matchp->xlate)
+				return 0;
+
+			ret = matchp->xlate(xl, &mt_params);
+		} else {
+			struct xtables_target *watcherp = m->u.watcher;
+			struct xt_xlate_tg_params wt_params = {
+				.ip		= (const void *)&cs->eb,
+				.numeric	= numeric,
+				.escape_quotes	= false,
+				.target		= watcherp->t,
+			};
+
+			if (!watcherp->xlate)
+				return 0;
+
+			ret = watcherp->xlate(xl, &wt_params);
+		}
+
+		if (!ret)
+			break;
+	}
+
+	return ret;
+}
+
+static int xlate_ebaction(const struct iptables_command_state *cs, struct xt_xlate *xl)
+{
+	int ret = 1, numeric = cs->options & OPT_NUMERIC;
+
+	/* If no target at all, add nothing (default to continue) */
+	if (cs->target != NULL) {
+		/* Standard target? */
+		if (strcmp(cs->jumpto, XTC_LABEL_ACCEPT) == 0)
+			xt_xlate_add(xl, " accept");
+		else if (strcmp(cs->jumpto, XTC_LABEL_DROP) == 0)
+			xt_xlate_add(xl, " drop");
+		else if (strcmp(cs->jumpto, XTC_LABEL_RETURN) == 0)
+			xt_xlate_add(xl, " return");
+		else if (cs->target->xlate) {
+			xt_xlate_add(xl, " ");
+			struct xt_xlate_tg_params params = {
+				.ip		= (const void *)&cs->eb,
+				.target		= cs->target->t,
+				.numeric	= numeric,
+			};
+			ret = cs->target->xlate(xl, &params);
+		}
+		else
+			return 0;
+	} else if (cs->jumpto == NULL) {
+	} else if (strlen(cs->jumpto) > 0)
+		xt_xlate_add(xl, " jump %s", cs->jumpto);
+
+	return ret;
+}
+
+static void xlate_mac(struct xt_xlate *xl, const unsigned char *mac)
+{
+	int i;
+
+	xt_xlate_add(xl, "%02x", mac[0]);
+
+	for (i=1; i < ETH_ALEN; i++)
+		xt_xlate_add(xl, ":%02x", mac[i]);
+}
+
+static void nft_bridge_xlate_mac(struct xt_xlate *xl, const char *type, bool invert,
+				 const unsigned char *mac, const unsigned char *mask)
+{
+	char one_msk[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+	xt_xlate_add(xl, "ether %s %s", type, invert ? "!= " : "");
+
+	xlate_mac(xl, mac);
+
+	if (memcmp(mask, one_msk, ETH_ALEN)) {
+		int i;
+		xt_xlate_add(xl, " and ");
+
+		xlate_mac(xl, mask);
+
+		xt_xlate_add(xl, " == %02x", mac[0] & mask[0]);
+		for (i=1; i < ETH_ALEN; i++)
+			xt_xlate_add(xl, ":%02x", mac[i] & mask[i]);
+	}
+
+	xt_xlate_add(xl, " ");
+}
+
+static int nft_bridge_xlate(const void *data, struct xt_xlate *xl)
+{
+	const struct iptables_command_state *cs = data;
+	int ret;
+
+	xlate_ifname(xl, "iifname", cs->eb.in,
+		     cs->eb.invflags & EBT_IIN);
+	xlate_ifname(xl, "meta ibrname", cs->eb.logical_in,
+		     cs->eb.invflags & EBT_ILOGICALIN);
+	xlate_ifname(xl, "oifname", cs->eb.out,
+		     cs->eb.invflags & EBT_IOUT);
+	xlate_ifname(xl, "meta obrname", cs->eb.logical_out,
+		     cs->eb.invflags & EBT_ILOGICALOUT);
+
+	if ((cs->eb.bitmask & EBT_NOPROTO) == 0) {
+		const char *implicit = NULL;
+
+		switch (ntohs(cs->eb.ethproto)) {
+		case ETH_P_IP:
+			implicit = "ip";
+			break;
+		case ETH_P_IPV6:
+			implicit = "ip6";
+			break;
+		case ETH_P_8021Q:
+			implicit = "vlan";
+			break;
+		default:
+			break;
+		}
+
+		if (!implicit || !xlate_find_match(cs, implicit))
+			xt_xlate_add(xl, "ether type %s0x%x ",
+				     cs->eb.invflags & EBT_IPROTO ? "!= " : "",
+				     ntohs(cs->eb.ethproto));
+	}
+
+	if (cs->eb.bitmask & EBT_802_3)
+		return 0;
+
+	if (cs->eb.bitmask & EBT_ISOURCE)
+		nft_bridge_xlate_mac(xl, "saddr", cs->eb.invflags & EBT_ISOURCE,
+				     cs->eb.sourcemac, cs->eb.sourcemsk);
+	if (cs->eb.bitmask & EBT_IDEST)
+		nft_bridge_xlate_mac(xl, "daddr", cs->eb.invflags & EBT_IDEST,
+				     cs->eb.destmac, cs->eb.destmsk);
+	ret = xlate_ebmatches(cs, xl);
+	if (ret == 0)
+		return ret;
+
+	/* Always add counters per rule, as in ebtables */
+	xt_xlate_add(xl, "counter");
+	ret = xlate_ebaction(cs, xl);
+
+	return ret;
+}
+
+struct nft_family_ops nft_family_ops_bridge = {
+	.add			= nft_bridge_add,
+	.is_same		= nft_bridge_is_same,
+	.print_payload		= NULL,
+	.parse_meta		= nft_bridge_parse_meta,
+	.parse_payload		= nft_bridge_parse_payload,
+	.parse_immediate	= nft_bridge_parse_immediate,
+	.parse_lookup		= nft_bridge_parse_lookup,
+	.parse_match		= nft_bridge_parse_match,
+	.parse_target		= nft_bridge_parse_target,
+	.print_table_header	= nft_bridge_print_table_header,
+	.print_header		= nft_bridge_print_header,
+	.print_rule		= nft_bridge_print_rule,
+	.save_rule		= nft_bridge_save_rule,
+	.save_chain		= nft_bridge_save_chain,
+	.post_parse		= NULL,
+	.rule_to_cs		= nft_rule_to_ebtables_command_state,
+	.clear_cs		= ebt_cs_clean,
+	.xlate			= nft_bridge_xlate,
+};
diff --git a/iptables/nft-bridge.h b/iptables/nft-bridge.h
new file mode 100644
index 0000000..eb1b392
--- /dev/null
+++ b/iptables/nft-bridge.h
@@ -0,0 +1,181 @@
+#ifndef _NFT_BRIDGE_H_
+#define _NFT_BRIDGE_H_
+
+#include <netinet/in.h>
+//#include <linux/netfilter_bridge/ebtables.h>
+#include <linux/netfilter/x_tables.h>
+#include <linux/netfilter/nf_tables.h>
+#include <net/ethernet.h>
+#include <libiptc/libxtc.h>
+
+/* We use replace->flags, so we can't use the following values:
+ * 0x01 == OPT_COMMAND, 0x02 == OPT_TABLE, 0x100 == OPT_ZERO */
+#define LIST_N	  0x04
+#define LIST_C	  0x08
+#define LIST_X	  0x10
+#define LIST_MAC2 0x20
+
+extern unsigned char eb_mac_type_unicast[ETH_ALEN];
+extern unsigned char eb_msk_type_unicast[ETH_ALEN];
+extern unsigned char eb_mac_type_multicast[ETH_ALEN];
+extern unsigned char eb_msk_type_multicast[ETH_ALEN];
+extern unsigned char eb_mac_type_broadcast[ETH_ALEN];
+extern unsigned char eb_msk_type_broadcast[ETH_ALEN];
+extern unsigned char eb_mac_type_bridge_group[ETH_ALEN];
+extern unsigned char eb_msk_type_bridge_group[ETH_ALEN];
+
+int ebt_get_mac_and_mask(const char *from, unsigned char *to, unsigned char *mask);
+
+/* From: include/linux/netfilter_bridge/ebtables.h
+ *
+ * Adapted for the need of the ebtables-compat.
+ */
+
+#define EBT_TABLE_MAXNAMELEN 32
+#define EBT_FUNCTION_MAXNAMELEN EBT_TABLE_MAXNAMELEN
+
+/* verdicts >0 are "branches" */
+#define EBT_ACCEPT   -1
+#define EBT_DROP     -2
+#define EBT_CONTINUE -3
+#define EBT_RETURN   -4
+#define NUM_STANDARD_TARGETS   4
+
+#define EBT_ENTRY_OR_ENTRIES 0x01
+/* these are the normal masks */
+#define EBT_NOPROTO 0x02
+#define EBT_802_3 0x04
+#define EBT_SOURCEMAC 0x08
+#define EBT_DESTMAC 0x10
+#define EBT_F_MASK (EBT_NOPROTO | EBT_802_3 | EBT_SOURCEMAC | EBT_DESTMAC \
+   | EBT_ENTRY_OR_ENTRIES)
+
+#define EBT_IPROTO 0x01
+#define EBT_IIN 0x02
+#define EBT_IOUT 0x04
+#define EBT_ISOURCE 0x8
+#define EBT_IDEST 0x10
+#define EBT_ILOGICALIN 0x20
+#define EBT_ILOGICALOUT 0x40
+#define EBT_INV_MASK (EBT_IPROTO | EBT_IIN | EBT_IOUT | EBT_ILOGICALIN \
+   | EBT_ILOGICALOUT | EBT_ISOURCE | EBT_IDEST)
+
+/* ebtables target modules store the verdict inside an int. We can
+ * reclaim a part of this int for backwards compatible extensions.
+ * The 4 lsb are more than enough to store the verdict.
+ */
+#define EBT_VERDICT_BITS 0x0000000F
+
+struct nftnl_rule;
+struct iptables_command_state;
+
+static const char *ebt_standard_targets[NUM_STANDARD_TARGETS] = {
+	"ACCEPT",
+	"DROP",
+	"CONTINUE",
+	"RETURN",
+};
+
+static inline const char *nft_ebt_standard_target(unsigned int num)
+{
+	if (num >= NUM_STANDARD_TARGETS)
+		return NULL;
+
+	return ebt_standard_targets[num];
+}
+
+static inline int ebt_fill_target(const char *str, unsigned int *verdict)
+{
+	int i, ret = 0;
+
+	for (i = 0; i < NUM_STANDARD_TARGETS; i++) {
+		if (!strcmp(str, nft_ebt_standard_target(i))) {
+			*verdict = -i - 1;
+			break;
+		}
+	}
+
+	if (i == NUM_STANDARD_TARGETS)
+		ret = 1;
+
+	return ret;
+}
+
+static inline const char *ebt_target_name(unsigned int verdict)
+{
+	return nft_ebt_standard_target(-verdict - 1);
+}
+
+#define EBT_CHECK_OPTION(flags, mask) ({			\
+	if (*flags & mask)					\
+		xtables_error(PARAMETER_PROBLEM,		\
+			      "Multiple use of same "		\
+			      "option not allowed");		\
+	*flags |= mask;						\
+})								\
+
+void ebt_cs_clean(struct iptables_command_state *cs);
+void ebt_load_match_extensions(void);
+void ebt_add_match(struct xtables_match *m,
+			  struct iptables_command_state *cs);
+void ebt_add_watcher(struct xtables_target *watcher,
+                     struct iptables_command_state *cs);
+int ebt_command_default(struct iptables_command_state *cs);
+
+struct nft_among_pair {
+	struct ether_addr ether;
+	struct in_addr in __attribute__((aligned (4)));
+};
+
+struct nft_among_data {
+	struct {
+		size_t cnt;
+		bool inv;
+		bool ip;
+	} src, dst;
+	/* first source, then dest pairs */
+	struct nft_among_pair pairs[0];
+};
+
+/* initialize fields, return offset into pairs array to write pairs to */
+static inline size_t
+nft_among_prepare_data(struct nft_among_data *data, bool dst,
+		       size_t cnt, bool inv, bool ip)
+{
+	size_t poff;
+
+	if (dst) {
+		data->dst.cnt = cnt;
+		data->dst.inv = inv;
+		data->dst.ip = ip;
+		poff = data->src.cnt;
+	} else {
+		data->src.cnt = cnt;
+		data->src.inv = inv;
+		data->src.ip = ip;
+		poff = 0;
+		memmove(data->pairs + cnt, data->pairs,
+			data->dst.cnt * sizeof(*data->pairs));
+	}
+	return poff;
+}
+
+static inline void
+nft_among_insert_pair(struct nft_among_pair *pairs,
+		      size_t *pcount, const struct nft_among_pair *new)
+{
+	int i;
+
+	/* nftables automatically sorts set elements from smallest to largest,
+	 * insert sorted so extension comparison works */
+
+	for (i = 0; i < *pcount; i++) {
+		if (memcmp(new, &pairs[i], sizeof(*new)) < 0)
+			break;
+	}
+	memmove(&pairs[i + 1], &pairs[i], sizeof(*pairs) * (*pcount - i));
+	memcpy(&pairs[i], new, sizeof(*new));
+	(*pcount)++;
+}
+
+#endif
diff --git a/iptables/nft-cache.c b/iptables/nft-cache.c
new file mode 100644
index 0000000..6b6e6da
--- /dev/null
+++ b/iptables/nft-cache.c
@@ -0,0 +1,772 @@
+/*
+ * (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 <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <xtables.h>
+
+#include <linux/netfilter/nf_tables.h>
+
+#include <libmnl/libmnl.h>
+#include <libnftnl/gen.h>
+#include <libnftnl/set.h>
+#include <libnftnl/table.h>
+
+#include "nft.h"
+#include "nft-cache.h"
+#include "nft-chain.h"
+
+static void cache_chain_list_insert(struct list_head *list, const char *name)
+{
+	struct cache_chain *pos = NULL, *new;
+
+	list_for_each_entry(pos, list, head) {
+		int cmp = strcmp(pos->name, name);
+
+		if (!cmp)
+			return;
+		if (cmp > 0)
+			break;
+	}
+
+	new = xtables_malloc(sizeof(*new));
+	new->name = strdup(name);
+	list_add_tail(&new->head, pos ? &pos->head : list);
+}
+
+void nft_cache_level_set(struct nft_handle *h, int level,
+			 const struct nft_cmd *cmd)
+{
+	struct nft_cache_req *req = &h->cache_req;
+
+	if (level > req->level)
+		req->level = level;
+
+	if (!cmd || !cmd->table || req->all_chains)
+		return;
+
+	if (!req->table)
+		req->table = strdup(cmd->table);
+	else
+		assert(!strcmp(req->table, cmd->table));
+
+	if (!cmd->chain) {
+		req->all_chains = true;
+		return;
+	}
+
+	cache_chain_list_insert(&req->chain_list, cmd->chain);
+	if (cmd->rename)
+		cache_chain_list_insert(&req->chain_list, cmd->rename);
+	if (cmd->jumpto)
+		cache_chain_list_insert(&req->chain_list, cmd->jumpto);
+}
+
+static int genid_cb(const struct nlmsghdr *nlh, void *data)
+{
+	uint32_t *genid = data;
+	struct nftnl_gen *gen;
+
+	gen = nftnl_gen_alloc();
+	if (!gen)
+		return MNL_CB_ERROR;
+
+	if (nftnl_gen_nlmsg_parse(nlh, gen) < 0)
+		goto out;
+
+	*genid = nftnl_gen_get_u32(gen, NFTNL_GEN_ID);
+
+	nftnl_gen_free(gen);
+	return MNL_CB_STOP;
+out:
+	nftnl_gen_free(gen);
+	return MNL_CB_ERROR;
+}
+
+static void mnl_genid_get(struct nft_handle *h, uint32_t *genid)
+{
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh;
+	int ret;
+
+	nlh = nftnl_nlmsg_build_hdr(buf, NFT_MSG_GETGEN, 0, 0, h->seq);
+	ret = mnl_talk(h, nlh, genid_cb, genid);
+	if (ret == 0)
+		return;
+
+	xtables_error(RESOURCE_PROBLEM,
+		      "Could not fetch rule set generation id: %s\n", nft_strerror(errno));
+}
+
+static int nftnl_table_list_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct nftnl_table *nftnl = nftnl_table_alloc();
+	const struct builtin_table *t;
+	struct nft_handle *h = data;
+	const char *name;
+
+	if (!nftnl)
+		return MNL_CB_OK;
+
+	if (nftnl_table_nlmsg_parse(nlh, nftnl) < 0)
+		goto out;
+
+	name = nftnl_table_get_str(nftnl, NFTNL_TABLE_NAME);
+	if (!name)
+		goto out;
+
+	t = nft_table_builtin_find(h, name);
+	if (!t)
+		goto out;
+
+	h->cache->table[t->type].exists = true;
+out:
+	nftnl_table_free(nftnl);
+	return MNL_CB_OK;
+}
+
+static int fetch_table_cache(struct nft_handle *h)
+{
+	struct nlmsghdr *nlh;
+	char buf[16536];
+	int i, ret;
+
+	nlh = nftnl_rule_nlmsg_build_hdr(buf, NFT_MSG_GETTABLE, h->family,
+					NLM_F_DUMP, h->seq);
+
+	ret = mnl_talk(h, nlh, nftnl_table_list_cb, h);
+	if (ret < 0 && errno == EINTR)
+		assert(nft_restart(h) >= 0);
+
+	for (i = 0; i < NFT_TABLE_MAX; i++) {
+		enum nft_table_type type = h->tables[i].type;
+
+		if (!h->tables[i].name)
+			continue;
+
+		h->cache->table[type].chains = nft_chain_list_alloc();
+
+		h->cache->table[type].sets = nftnl_set_list_alloc();
+		if (!h->cache->table[type].sets)
+			return 0;
+	}
+
+	return 1;
+}
+
+static uint32_t djb_hash(const char *key)
+{
+	uint32_t i, hash = 5381;
+
+	for (i = 0; i < strlen(key); i++)
+		hash = ((hash << 5) + hash) + key[i];
+
+	return hash;
+}
+
+static struct hlist_head *chain_name_hlist(struct nft_handle *h,
+					   const struct builtin_table *t,
+					   const char *chain)
+{
+	int key = djb_hash(chain) % CHAIN_NAME_HSIZE;
+
+	return &h->cache->table[t->type].chains->names[key];
+}
+
+struct nft_chain *
+nft_chain_find(struct nft_handle *h, const char *table, const char *chain)
+{
+	const struct builtin_table *t;
+	struct hlist_node *node;
+	struct nft_chain *c;
+
+	t = nft_table_builtin_find(h, table);
+	if (!t)
+		return NULL;
+
+	hlist_for_each_entry(c, node, chain_name_hlist(h, t, chain), hnode) {
+		if (!strcmp(nftnl_chain_get_str(c->nftnl, NFTNL_CHAIN_NAME),
+			    chain))
+			return c;
+	}
+	return NULL;
+}
+
+int nft_cache_add_chain(struct nft_handle *h, const struct builtin_table *t,
+			struct nftnl_chain *c)
+{
+	const char *cname = nftnl_chain_get_str(c, NFTNL_CHAIN_NAME);
+	struct nft_chain *nc = nft_chain_alloc(c);
+
+	if (nftnl_chain_is_set(c, NFTNL_CHAIN_HOOKNUM)) {
+		uint32_t hooknum = nftnl_chain_get_u32(c, NFTNL_CHAIN_HOOKNUM);
+
+		if (hooknum >= NF_INET_NUMHOOKS) {
+			nft_chain_free(nc);
+			return -EINVAL;
+		}
+
+		if (h->cache->table[t->type].base_chains[hooknum]) {
+			nft_chain_free(nc);
+			return -EEXIST;
+		}
+
+		h->cache->table[t->type].base_chains[hooknum] = nc;
+	} else {
+		struct nft_chain_list *clist = h->cache->table[t->type].chains;
+		struct list_head *pos = &clist->list;
+		struct nft_chain *cur;
+		const char *n;
+
+		list_for_each_entry(cur, &clist->list, head) {
+			n = nftnl_chain_get_str(cur->nftnl, NFTNL_CHAIN_NAME);
+			if (strcmp(cname, n) <= 0) {
+				pos = &cur->head;
+				break;
+			}
+		}
+		list_add_tail(&nc->head, pos);
+	}
+	hlist_add_head(&nc->hnode, chain_name_hlist(h, t, cname));
+	return 0;
+}
+
+struct nftnl_chain_list_cb_data {
+	struct nft_handle *h;
+	const struct builtin_table *t;
+};
+
+static int nftnl_chain_list_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct nftnl_chain_list_cb_data *d = data;
+	const struct builtin_table *t = d->t;
+	struct nft_handle *h = d->h;
+	struct nftnl_chain *c;
+	const char *tname;
+
+	c = nftnl_chain_alloc();
+	if (c == NULL)
+		goto err;
+
+	if (nftnl_chain_nlmsg_parse(nlh, c) < 0)
+		goto out;
+
+	tname = nftnl_chain_get_str(c, NFTNL_CHAIN_TABLE);
+
+	if (!t) {
+		t = nft_table_builtin_find(h, tname);
+		if (!t)
+			goto out;
+	} else if (strcmp(t->name, tname)) {
+		goto out;
+	}
+
+	if (nft_cache_add_chain(h, t, c))
+		goto out;
+
+	return MNL_CB_OK;
+out:
+	nftnl_chain_free(c);
+err:
+	return MNL_CB_OK;
+}
+
+struct nftnl_set_list_cb_data {
+	struct nft_handle *h;
+	const struct builtin_table *t;
+};
+
+static int nftnl_set_list_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct nftnl_set_list_cb_data *d = data;
+	const struct builtin_table *t = d->t;
+	struct nftnl_set_list *list;
+	struct nft_handle *h = d->h;
+	const char *tname, *sname;
+	struct nftnl_set *s;
+
+	s = nftnl_set_alloc();
+	if (s == NULL)
+		return MNL_CB_OK;
+
+	if (nftnl_set_nlmsg_parse(nlh, s) < 0)
+		goto out_free;
+
+	tname = nftnl_set_get_str(s, NFTNL_SET_TABLE);
+
+	if (!t)
+		t = nft_table_builtin_find(h, tname);
+	else if (strcmp(t->name, tname))
+		goto out_free;
+
+	if (!t)
+		goto out_free;
+
+	list = h->cache->table[t->type].sets;
+	sname = nftnl_set_get_str(s, NFTNL_SET_NAME);
+
+	if (nftnl_set_list_lookup_byname(list, sname))
+		goto out_free;
+
+	nftnl_set_list_add_tail(s, list);
+
+	return MNL_CB_OK;
+out_free:
+	nftnl_set_free(s);
+	return MNL_CB_OK;
+}
+
+static int set_elem_cb(const struct nlmsghdr *nlh, void *data)
+{
+	return nftnl_set_elems_nlmsg_parse(nlh, data) ? -1 : MNL_CB_OK;
+}
+
+static bool set_has_elements(struct nftnl_set *s)
+{
+	struct nftnl_set_elems_iter *iter;
+	bool ret = false;
+
+	iter = nftnl_set_elems_iter_create(s);
+	if (iter) {
+		ret = !!nftnl_set_elems_iter_cur(iter);
+		nftnl_set_elems_iter_destroy(iter);
+	}
+	return ret;
+}
+
+static int set_fetch_elem_cb(struct nftnl_set *s, void *data)
+{
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nft_handle *h = data;
+	struct nlmsghdr *nlh;
+
+	if (set_has_elements(s))
+		return 0;
+
+	nlh = nftnl_nlmsg_build_hdr(buf, NFT_MSG_GETSETELEM, h->family,
+				    NLM_F_DUMP, h->seq);
+	nftnl_set_elems_nlmsg_build_payload(nlh, s);
+
+	return mnl_talk(h, nlh, set_elem_cb, s);
+}
+
+static int fetch_set_cache(struct nft_handle *h,
+			   const struct builtin_table *t, const char *set)
+{
+	struct nftnl_set_list_cb_data d = {
+		.h = h,
+		.t = t,
+	};
+	uint16_t flags = NLM_F_DUMP;
+	struct nftnl_set *s = NULL;
+	struct nlmsghdr *nlh;
+	char buf[16536];
+	int i, ret;
+
+	if (t) {
+		s = nftnl_set_alloc();
+		if (!s)
+			return -1;
+
+		nftnl_set_set_str(s, NFTNL_SET_TABLE, t->name);
+
+		if (set) {
+			nftnl_set_set_str(s, NFTNL_SET_NAME, set);
+			flags = NLM_F_ACK;
+		}
+	}
+
+	nlh = nftnl_set_nlmsg_build_hdr(buf, NFT_MSG_GETSET,
+					h->family, flags, h->seq);
+
+	if (s) {
+		nftnl_set_nlmsg_build_payload(nlh, s);
+		nftnl_set_free(s);
+	}
+
+	ret = mnl_talk(h, nlh, nftnl_set_list_cb, &d);
+	if (ret < 0 && errno == EINTR) {
+		assert(nft_restart(h) >= 0);
+		return ret;
+	}
+
+	if (t) {
+		nftnl_set_list_foreach(h->cache->table[t->type].sets,
+				       set_fetch_elem_cb, h);
+	} else {
+		for (i = 0; i < NFT_TABLE_MAX; i++) {
+			enum nft_table_type type = h->tables[i].type;
+
+			if (!h->tables[i].name)
+				continue;
+
+			nftnl_set_list_foreach(h->cache->table[type].sets,
+					       set_fetch_elem_cb, h);
+		}
+	}
+	return ret;
+}
+
+static int __fetch_chain_cache(struct nft_handle *h,
+			       const struct builtin_table *t,
+			       const struct nftnl_chain *c)
+{
+	struct nftnl_chain_list_cb_data d = {
+		.h = h,
+		.t = t,
+	};
+	char buf[16536];
+	struct nlmsghdr *nlh;
+	int ret;
+
+	nlh = nftnl_chain_nlmsg_build_hdr(buf, NFT_MSG_GETCHAIN, h->family,
+					  c ? NLM_F_ACK : NLM_F_DUMP, h->seq);
+	if (c)
+		nftnl_chain_nlmsg_build_payload(nlh, c);
+
+	ret = mnl_talk(h, nlh, nftnl_chain_list_cb, &d);
+	if (ret < 0 && errno == EINTR)
+		assert(nft_restart(h) >= 0);
+
+	return ret;
+}
+
+static int fetch_chain_cache(struct nft_handle *h,
+			     const struct builtin_table *t,
+			     struct list_head *chains)
+{
+	struct cache_chain *cc;
+	struct nftnl_chain *c;
+	int rc, ret = 0;
+
+	if (!chains)
+		return __fetch_chain_cache(h, t, NULL);
+
+	assert(t);
+
+	c = nftnl_chain_alloc();
+	if (!c)
+		return -1;
+
+	nftnl_chain_set_str(c, NFTNL_CHAIN_TABLE, t->name);
+
+	list_for_each_entry(cc, chains, head) {
+		nftnl_chain_set_str(c, NFTNL_CHAIN_NAME, cc->name);
+		rc = __fetch_chain_cache(h, t, c);
+		if (rc)
+			ret = rc;
+	}
+
+	nftnl_chain_free(c);
+	return ret;
+}
+
+static int nftnl_rule_list_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct nftnl_chain *c = data;
+	struct nftnl_rule *r;
+
+	r = nftnl_rule_alloc();
+	if (r == NULL)
+		return MNL_CB_OK;
+
+	if (nftnl_rule_nlmsg_parse(nlh, r) < 0) {
+		nftnl_rule_free(r);
+		return MNL_CB_OK;
+	}
+
+	nftnl_chain_rule_add_tail(r, c);
+	return MNL_CB_OK;
+}
+
+static int nft_rule_list_update(struct nft_chain *nc, void *data)
+{
+	struct nftnl_chain *c = nc->nftnl;
+	struct nft_handle *h = data;
+	char buf[16536];
+	struct nlmsghdr *nlh;
+	struct nftnl_rule *rule;
+	int ret;
+
+	if (nftnl_rule_lookup_byindex(c, 0))
+		return 0;
+
+	rule = nftnl_rule_alloc();
+	if (!rule)
+		return -1;
+
+	nftnl_rule_set_str(rule, NFTNL_RULE_TABLE,
+			   nftnl_chain_get_str(c, NFTNL_CHAIN_TABLE));
+	nftnl_rule_set_str(rule, NFTNL_RULE_CHAIN,
+			   nftnl_chain_get_str(c, NFTNL_CHAIN_NAME));
+
+	nlh = nftnl_rule_nlmsg_build_hdr(buf, NFT_MSG_GETRULE, h->family,
+					NLM_F_DUMP, h->seq);
+	nftnl_rule_nlmsg_build_payload(nlh, rule);
+
+	ret = mnl_talk(h, nlh, nftnl_rule_list_cb, c);
+	if (ret < 0 && errno == EINTR)
+		assert(nft_restart(h) >= 0);
+
+	nftnl_rule_free(rule);
+
+	if (h->family == NFPROTO_BRIDGE)
+		nft_bridge_chain_postprocess(h, c);
+
+	return 0;
+}
+
+static int fetch_rule_cache(struct nft_handle *h,
+			    const struct builtin_table *t)
+{
+	int i;
+
+	if (t)
+		return nft_chain_foreach(h, t->name, nft_rule_list_update, h);
+
+	for (i = 0; i < NFT_TABLE_MAX; i++) {
+
+		if (!h->tables[i].name)
+			continue;
+
+		if (nft_chain_foreach(h, h->tables[i].name,
+				      nft_rule_list_update, h))
+			return -1;
+	}
+	return 0;
+}
+
+static int flush_cache(struct nft_handle *h, struct nft_cache *c,
+		       const char *tablename);
+
+static void
+__nft_build_cache(struct nft_handle *h)
+{
+	struct nft_cache_req *req = &h->cache_req;
+	const struct builtin_table *t = NULL;
+	struct list_head *chains = NULL;
+	uint32_t genid_check;
+
+	if (h->cache_init)
+		return;
+
+	if (req->table) {
+		t = nft_table_builtin_find(h, req->table);
+		if (!req->all_chains)
+			chains = &req->chain_list;
+	}
+
+	h->cache_init = true;
+retry:
+	mnl_genid_get(h, &h->nft_genid);
+
+	if (req->level >= NFT_CL_TABLES)
+		fetch_table_cache(h);
+	if (req->level == NFT_CL_FAKE)
+		goto genid_check;
+	if (req->level >= NFT_CL_CHAINS)
+		fetch_chain_cache(h, t, chains);
+	if (req->level >= NFT_CL_SETS)
+		fetch_set_cache(h, t, NULL);
+	if (req->level >= NFT_CL_RULES)
+		fetch_rule_cache(h, t);
+genid_check:
+	mnl_genid_get(h, &genid_check);
+	if (h->nft_genid != genid_check) {
+		flush_cache(h, h->cache, NULL);
+		goto retry;
+	}
+}
+
+static void __nft_flush_cache(struct nft_handle *h)
+{
+	if (!h->cache_index) {
+		h->cache_index++;
+		h->cache = &h->__cache[h->cache_index];
+	} else {
+		flush_chain_cache(h, NULL);
+	}
+}
+
+static int ____flush_rule_cache(struct nftnl_rule *r, void *data)
+{
+	nftnl_rule_list_del(r);
+	nftnl_rule_free(r);
+
+	return 0;
+}
+
+static int __flush_rule_cache(struct nft_chain *c, void *data)
+{
+	return nftnl_rule_foreach(c->nftnl, ____flush_rule_cache, NULL);
+}
+
+int flush_rule_cache(struct nft_handle *h, const char *table,
+		     struct nft_chain *c)
+{
+	if (c)
+		return __flush_rule_cache(c, NULL);
+
+	nft_chain_foreach(h, table, __flush_rule_cache, NULL);
+	return 0;
+}
+
+static int __flush_chain_cache(struct nft_chain *c, void *data)
+{
+	nft_chain_list_del(c);
+	nft_chain_free(c);
+
+	return 0;
+}
+
+static int __flush_set_cache(struct nftnl_set *s, void *data)
+{
+	nftnl_set_list_del(s);
+	nftnl_set_free(s);
+
+	return 0;
+}
+
+static void flush_base_chain_cache(struct nft_chain **base_chains)
+{
+	int i;
+
+	for (i = 0; i < NF_INET_NUMHOOKS; i++) {
+		if (!base_chains[i])
+			continue;
+		hlist_del(&base_chains[i]->hnode);
+		nft_chain_free(base_chains[i]);
+		base_chains[i] = NULL;
+	}
+}
+
+static int flush_cache(struct nft_handle *h, struct nft_cache *c,
+		       const char *tablename)
+{
+	const struct builtin_table *table;
+	int i;
+
+	if (tablename) {
+		table = nft_table_builtin_find(h, tablename);
+		if (!table)
+			return 0;
+
+		flush_base_chain_cache(c->table[table->type].base_chains);
+		nft_chain_foreach(h, tablename, __flush_chain_cache, NULL);
+
+		if (c->table[table->type].sets)
+			nftnl_set_list_foreach(c->table[table->type].sets,
+					       __flush_set_cache, NULL);
+		return 0;
+	}
+
+	for (i = 0; i < NFT_TABLE_MAX; i++) {
+		if (h->tables[i].name == NULL)
+			continue;
+
+		flush_base_chain_cache(c->table[i].base_chains);
+		if (c->table[i].chains) {
+			nft_chain_list_free(c->table[i].chains);
+			c->table[i].chains = NULL;
+		}
+
+		if (c->table[i].sets) {
+			nftnl_set_list_free(c->table[i].sets);
+			c->table[i].sets = NULL;
+		}
+
+		c->table[i].exists = false;
+	}
+
+	return 1;
+}
+
+void flush_chain_cache(struct nft_handle *h, const char *tablename)
+{
+	if (!h->cache_init)
+		return;
+
+	if (flush_cache(h, h->cache, tablename))
+		h->cache_init = false;
+}
+
+void nft_rebuild_cache(struct nft_handle *h)
+{
+	if (h->cache_init) {
+		__nft_flush_cache(h);
+		h->cache_init = false;
+	}
+
+	__nft_build_cache(h);
+}
+
+void nft_cache_build(struct nft_handle *h)
+{
+	struct nft_cache_req *req = &h->cache_req;
+	const struct builtin_table *t = NULL;
+	int i;
+
+	if (req->table)
+		t = nft_table_builtin_find(h, req->table);
+
+	/* fetch builtin chains as well (if existing) so nft_xt_builtin_init()
+	 * doesn't override policies by accident */
+	if (t && !req->all_chains) {
+		for (i = 0; i < NF_INET_NUMHOOKS; i++) {
+			const char *cname = t->chains[i].name;
+
+			if (!cname)
+				break;
+			cache_chain_list_insert(&req->chain_list, cname);
+		}
+	}
+
+	__nft_build_cache(h);
+}
+
+void nft_release_cache(struct nft_handle *h)
+{
+	struct nft_cache_req *req = &h->cache_req;
+	struct cache_chain *cc, *cc_tmp;
+
+	while (h->cache_index)
+		flush_cache(h, &h->__cache[h->cache_index--], NULL);
+	flush_cache(h, &h->__cache[0], NULL);
+	h->cache = &h->__cache[0];
+	h->cache_init = false;
+
+	if (req->level != NFT_CL_FAKE)
+		req->level = NFT_CL_TABLES;
+	if (req->table) {
+		free(req->table);
+		req->table = NULL;
+	}
+	req->all_chains = false;
+	list_for_each_entry_safe(cc, cc_tmp, &req->chain_list, head) {
+		list_del(&cc->head);
+		free(cc->name);
+		free(cc);
+	}
+}
+
+struct nftnl_set_list *
+nft_set_list_get(struct nft_handle *h, const char *table, const char *set)
+{
+	const struct builtin_table *t;
+
+	t = nft_table_builtin_find(h, table);
+	if (!t)
+		return NULL;
+
+	return h->cache->table[t->type].sets;
+}
diff --git a/iptables/nft-cache.h b/iptables/nft-cache.h
new file mode 100644
index 0000000..20d96be
--- /dev/null
+++ b/iptables/nft-cache.h
@@ -0,0 +1,26 @@
+#ifndef _NFT_CACHE_H_
+#define _NFT_CACHE_H_
+
+struct nft_handle;
+struct nft_chain;
+struct nft_cmd;
+struct builtin_table;
+
+void nft_cache_level_set(struct nft_handle *h, int level,
+			 const struct nft_cmd *cmd);
+void nft_rebuild_cache(struct nft_handle *h);
+void nft_release_cache(struct nft_handle *h);
+void flush_chain_cache(struct nft_handle *h, const char *tablename);
+int flush_rule_cache(struct nft_handle *h, const char *table,
+		     struct nft_chain *c);
+void nft_cache_build(struct nft_handle *h);
+int nft_cache_add_chain(struct nft_handle *h, const struct builtin_table *t,
+			struct nftnl_chain *c);
+
+struct nft_chain *
+nft_chain_find(struct nft_handle *h, const char *table, const char *chain);
+
+struct nftnl_set_list *
+nft_set_list_get(struct nft_handle *h, const char *table, const char *set);
+
+#endif /* _NFT_CACHE_H_ */
diff --git a/iptables/nft-chain.c b/iptables/nft-chain.c
new file mode 100644
index 0000000..e954170
--- /dev/null
+++ b/iptables/nft-chain.c
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2020  Red Hat GmbH.  Author: Phil Sutter <phil@nwl.cc>
+ *
+ * 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.
+ */
+
+#include <stdlib.h>
+#include <xtables.h>
+
+#include "nft-chain.h"
+
+struct nft_chain *nft_chain_alloc(struct nftnl_chain *nftnl)
+{
+	struct nft_chain *c = xtables_malloc(sizeof(*c));
+
+	INIT_LIST_HEAD(&c->head);
+	c->nftnl = nftnl;
+
+	return c;
+}
+
+void nft_chain_free(struct nft_chain *c)
+{
+	if (c->nftnl)
+		nftnl_chain_free(c->nftnl);
+	free(c);
+}
+
+struct nft_chain_list *nft_chain_list_alloc(void)
+{
+	struct nft_chain_list *list = xtables_malloc(sizeof(*list));
+	int i;
+
+	INIT_LIST_HEAD(&list->list);
+	for (i = 0; i < CHAIN_NAME_HSIZE; i++)
+		INIT_HLIST_HEAD(&list->names[i]);
+
+	return list;
+}
+
+void nft_chain_list_del(struct nft_chain *c)
+{
+	list_del(&c->head);
+	hlist_del(&c->hnode);
+}
+
+void nft_chain_list_free(struct nft_chain_list *list)
+{
+	struct nft_chain *c, *c2;
+
+	list_for_each_entry_safe(c, c2, &list->list, head) {
+		nft_chain_list_del(c);
+		nft_chain_free(c);
+	}
+	free(list);
+}
diff --git a/iptables/nft-chain.h b/iptables/nft-chain.h
new file mode 100644
index 0000000..137f4b7
--- /dev/null
+++ b/iptables/nft-chain.h
@@ -0,0 +1,29 @@
+#ifndef _NFT_CHAIN_H_
+#define _NFT_CHAIN_H_
+
+#include <libnftnl/chain.h>
+#include <libiptc/linux_list.h>
+
+struct nft_handle;
+
+struct nft_chain {
+	struct list_head	head;
+	struct hlist_node	hnode;
+	struct nftnl_chain	*nftnl;
+};
+
+#define CHAIN_NAME_HSIZE	512
+
+struct nft_chain_list {
+	struct list_head	list;
+	struct hlist_head	names[CHAIN_NAME_HSIZE];
+};
+
+struct nft_chain *nft_chain_alloc(struct nftnl_chain *nftnl);
+void nft_chain_free(struct nft_chain *c);
+
+struct nft_chain_list *nft_chain_list_alloc(void);
+void nft_chain_list_free(struct nft_chain_list *list);
+void nft_chain_list_del(struct nft_chain *c);
+
+#endif /* _NFT_CHAIN_H_ */
diff --git a/iptables/nft-cmd.c b/iptables/nft-cmd.c
new file mode 100644
index 0000000..5d33f1f
--- /dev/null
+++ b/iptables/nft-cmd.c
@@ -0,0 +1,395 @@
+/*
+ * (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 <stdlib.h>
+#include <string.h>
+#include "nft.h"
+#include "nft-cmd.h"
+
+struct nft_cmd *nft_cmd_new(struct nft_handle *h, int command,
+			    const char *table, const char *chain,
+			    struct iptables_command_state *state,
+			    int rulenum, bool verbose)
+{
+	struct nftnl_rule *rule;
+	struct nft_cmd *cmd;
+
+	cmd = calloc(1, sizeof(struct nft_cmd));
+	if (!cmd)
+		return NULL;
+
+	cmd->command = command;
+	cmd->table = strdup(table);
+	if (chain)
+		cmd->chain = strdup(chain);
+	cmd->rulenum = rulenum;
+	cmd->verbose = verbose;
+
+	if (state) {
+		rule = nft_rule_new(h, chain, table, state);
+		if (!rule)
+			return NULL;
+
+		cmd->obj.rule = rule;
+
+		if (!state->target && strlen(state->jumpto) > 0)
+			cmd->jumpto = strdup(state->jumpto);
+	}
+
+	list_add_tail(&cmd->head, &h->cmd_list);
+
+	return cmd;
+}
+
+void nft_cmd_free(struct nft_cmd *cmd)
+{
+	free((void *)cmd->table);
+	free((void *)cmd->chain);
+	free((void *)cmd->policy);
+	free((void *)cmd->rename);
+	free((void *)cmd->jumpto);
+
+	switch (cmd->command) {
+	case NFT_COMPAT_RULE_CHECK:
+	case NFT_COMPAT_RULE_DELETE:
+		if (cmd->obj.rule)
+			nftnl_rule_free(cmd->obj.rule);
+		break;
+	default:
+		break;
+	}
+
+	list_del(&cmd->head);
+	free(cmd);
+}
+
+static void nft_cmd_rule_bridge(struct nft_handle *h, const struct nft_cmd *cmd)
+{
+	const struct builtin_table *t;
+
+	t = nft_table_builtin_find(h, cmd->table);
+	if (!t)
+		return;
+
+	/* Since ebtables user-defined chain policies are implemented as last
+	 * rule in nftables, rule cache is required here to treat them right.
+	 */
+	if (h->family == NFPROTO_BRIDGE &&
+	    !nft_chain_builtin_find(t, cmd->chain))
+		nft_cache_level_set(h, NFT_CL_RULES, cmd);
+	else
+		nft_cache_level_set(h, NFT_CL_CHAINS, cmd);
+}
+
+int nft_cmd_rule_append(struct nft_handle *h, const char *chain,
+			const char *table, struct iptables_command_state *state,
+			void *ref, bool verbose)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_RULE_APPEND, table, chain, state, -1,
+			  verbose);
+	if (!cmd)
+		return 0;
+
+	nft_cmd_rule_bridge(h, cmd);
+
+	return 1;
+}
+
+int nft_cmd_rule_insert(struct nft_handle *h, const char *chain,
+			const char *table, struct iptables_command_state *state,
+			int rulenum, bool verbose)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_RULE_INSERT, table, chain, state,
+			  rulenum, verbose);
+	if (!cmd)
+		return 0;
+
+	nft_cmd_rule_bridge(h, cmd);
+
+	if (cmd->rulenum > 0)
+		nft_cache_level_set(h, NFT_CL_RULES, cmd);
+	else
+		nft_cache_level_set(h, NFT_CL_CHAINS, cmd);
+
+	return 1;
+}
+
+int nft_cmd_rule_delete(struct nft_handle *h, const char *chain,
+			const char *table, struct iptables_command_state *state,
+			bool verbose)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_RULE_DELETE, table, chain, state,
+			  -1, verbose);
+	if (!cmd)
+		return 0;
+
+	nft_cache_level_set(h, NFT_CL_RULES, cmd);
+
+	return 1;
+}
+
+int nft_cmd_rule_delete_num(struct nft_handle *h, const char *chain,
+			    const char *table, int rulenum, bool verbose)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_RULE_DELETE, table, chain, NULL,
+			  rulenum, verbose);
+	if (!cmd)
+		return 0;
+
+	nft_cache_level_set(h, NFT_CL_RULES, cmd);
+
+	return 1;
+}
+
+int nft_cmd_rule_flush(struct nft_handle *h, const char *chain,
+		       const char *table, bool verbose)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_RULE_FLUSH, table, chain, NULL, -1,
+			  verbose);
+	if (!cmd)
+		return 0;
+
+	if (chain || verbose)
+		nft_cache_level_set(h, NFT_CL_CHAINS, cmd);
+	else
+		nft_cache_level_set(h, NFT_CL_TABLES, cmd);
+
+	return 1;
+}
+
+int nft_cmd_chain_zero_counters(struct nft_handle *h, const char *chain,
+				const char *table, bool verbose)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_CHAIN_ZERO, table, chain, NULL, -1,
+			  verbose);
+	if (!cmd)
+		return 0;
+
+	nft_cache_level_set(h, NFT_CL_CHAINS, cmd);
+
+	return 1;
+}
+
+int nft_cmd_chain_user_add(struct nft_handle *h, const char *chain,
+			   const char *table)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_CHAIN_USER_ADD, table, chain, NULL, -1,
+			  false);
+	if (!cmd)
+		return 0;
+
+	nft_cache_level_set(h, NFT_CL_CHAINS, cmd);
+
+	return 1;
+}
+
+int nft_cmd_chain_user_del(struct nft_handle *h, const char *chain,
+			   const char *table, bool verbose)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_CHAIN_USER_DEL, table, chain, NULL, -1,
+			  verbose);
+	if (!cmd)
+		return 0;
+
+	/* This triggers nft_bridge_chain_postprocess() when fetching the
+	 * rule cache.
+	 */
+	if (h->family == NFPROTO_BRIDGE)
+		nft_cache_level_set(h, NFT_CL_RULES, cmd);
+	else
+		nft_cache_level_set(h, NFT_CL_CHAINS, cmd);
+
+	return 1;
+}
+
+int nft_cmd_chain_user_rename(struct nft_handle *h,const char *chain,
+			      const char *table, const char *newname)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_CHAIN_RENAME, table, chain, NULL, -1,
+			  false);
+	if (!cmd)
+		return 0;
+
+	cmd->rename = strdup(newname);
+
+	nft_cache_level_set(h, NFT_CL_CHAINS, cmd);
+
+	return 1;
+}
+
+int nft_cmd_rule_list(struct nft_handle *h, const char *chain,
+		      const char *table, int rulenum, unsigned int format)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_RULE_LIST, table, chain, NULL, rulenum,
+			  false);
+	if (!cmd)
+		return 0;
+
+	cmd->format = format;
+
+	nft_cache_level_set(h, NFT_CL_RULES, cmd);
+
+	return 1;
+}
+
+int nft_cmd_rule_replace(struct nft_handle *h, const char *chain,
+			 const char *table, void *data, int rulenum,
+			 bool verbose)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_RULE_REPLACE, table, chain, data,
+			  rulenum, verbose);
+	if (!cmd)
+		return 0;
+
+	nft_cache_level_set(h, NFT_CL_RULES, cmd);
+
+	return 1;
+}
+
+int nft_cmd_rule_check(struct nft_handle *h, const char *chain,
+		       const char *table, void *data, bool verbose)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_RULE_CHECK, table, chain, data, -1,
+			  verbose);
+	if (!cmd)
+		return 0;
+
+	nft_cache_level_set(h, NFT_CL_RULES, cmd);
+
+	return 1;
+}
+
+int nft_cmd_chain_set(struct nft_handle *h, const char *table,
+		      const char *chain, const char *policy,
+		      const struct xt_counters *counters)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_CHAIN_UPDATE, table, chain, NULL, -1,
+			  false);
+	if (!cmd)
+		return 0;
+
+	cmd->policy = strdup(policy);
+	if (counters)
+		cmd->counters = *counters;
+
+	nft_cache_level_set(h, NFT_CL_CHAINS, cmd);
+
+	return 1;
+}
+
+int nft_cmd_table_flush(struct nft_handle *h, const char *table, bool verbose)
+{
+	struct nft_cmd *cmd;
+
+	if (verbose) {
+		return nft_cmd_rule_flush(h, NULL, table, verbose) &&
+		       nft_cmd_chain_user_del(h, NULL, table, verbose);
+	}
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_TABLE_FLUSH, table, NULL, NULL, -1,
+			  false);
+	if (!cmd)
+		return 0;
+
+	nft_cache_level_set(h, NFT_CL_TABLES, cmd);
+
+	return 1;
+}
+
+int nft_cmd_chain_restore(struct nft_handle *h, const char *chain,
+			  const char *table)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_CHAIN_RESTORE, table, chain, NULL, -1,
+			  false);
+	if (!cmd)
+		return 0;
+
+	nft_cache_level_set(h, NFT_CL_CHAINS, cmd);
+
+	return 1;
+}
+
+int nft_cmd_rule_zero_counters(struct nft_handle *h, const char *chain,
+			       const char *table, int rulenum)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_RULE_ZERO, table, chain, NULL, rulenum,
+			  false);
+	if (!cmd)
+		return 0;
+
+	nft_cache_level_set(h, NFT_CL_RULES, cmd);
+
+	return 1;
+}
+
+int nft_cmd_rule_list_save(struct nft_handle *h, const char *chain,
+			   const char *table, int rulenum, int counters)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_RULE_SAVE, table, chain, NULL, rulenum,
+			  false);
+	if (!cmd)
+		return 0;
+
+	cmd->counters_save = counters;
+
+	nft_cache_level_set(h, NFT_CL_RULES, cmd);
+
+	return 1;
+}
+
+int ebt_cmd_user_chain_policy(struct nft_handle *h, const char *table,
+                              const char *chain, const char *policy)
+{
+	struct nft_cmd *cmd;
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_BRIDGE_USER_CHAIN_UPDATE, table, chain,
+			  NULL, -1, false);
+	if (!cmd)
+		return 0;
+
+	cmd->policy = strdup(policy);
+
+	nft_cache_level_set(h, NFT_CL_RULES, cmd);
+
+	return 1;
+}
diff --git a/iptables/nft-cmd.h b/iptables/nft-cmd.h
new file mode 100644
index 0000000..ecf7655
--- /dev/null
+++ b/iptables/nft-cmd.h
@@ -0,0 +1,79 @@
+#ifndef _NFT_CMD_H_
+#define _NFT_CMD_H_
+
+#include <libiptc/linux_list.h>
+#include <stdbool.h>
+#include "nft.h"
+
+struct nftnl_rule;
+
+struct nft_cmd {
+	struct list_head		head;
+	int				command;
+	const char			*table;
+	const char			*chain;
+	const char			*jumpto;
+	int				rulenum;
+	bool				verbose;
+	unsigned int			format;
+	struct {
+		struct nftnl_rule	*rule;
+		struct nftnl_set	*set;
+	} obj;
+	const char			*policy;
+	struct xt_counters		counters;
+	const char			*rename;
+	int				counters_save;
+};
+
+struct nft_cmd *nft_cmd_new(struct nft_handle *h, int command,
+			    const char *table, const char *chain,
+			    struct iptables_command_state *state,
+			    int rulenum, bool verbose);
+void nft_cmd_free(struct nft_cmd *cmd);
+
+int nft_cmd_rule_append(struct nft_handle *h, const char *chain,
+			const char *table, struct iptables_command_state *state,
+                        void *ref, bool verbose);
+int nft_cmd_rule_insert(struct nft_handle *h, const char *chain,
+			const char *table, struct iptables_command_state *state,
+			int rulenum, bool verbose);
+int nft_cmd_rule_delete(struct nft_handle *h, const char *chain,
+                        const char *table, struct iptables_command_state *state,
+			bool verbose);
+int nft_cmd_rule_delete_num(struct nft_handle *h, const char *chain,
+			    const char *table, int rulenum, bool verbose);
+int nft_cmd_rule_flush(struct nft_handle *h, const char *chain,
+		       const char *table, bool verbose);
+int nft_cmd_zero_counters(struct nft_handle *h, const char *chain,
+			  const char *table, bool verbose);
+int nft_cmd_chain_user_add(struct nft_handle *h, const char *chain,
+			   const char *table);
+int nft_cmd_chain_user_del(struct nft_handle *h, const char *chain,
+			   const char *table, bool verbose);
+int nft_cmd_chain_zero_counters(struct nft_handle *h, const char *chain,
+				const char *table, bool verbose);
+int nft_cmd_rule_list(struct nft_handle *h, const char *chain,
+		      const char *table, int rulenum, unsigned int format);
+int nft_cmd_rule_check(struct nft_handle *h, const char *chain,
+                       const char *table, void *data, bool verbose);
+int nft_cmd_chain_set(struct nft_handle *h, const char *table,
+		      const char *chain, const char *policy,
+		      const struct xt_counters *counters);
+int nft_cmd_chain_user_rename(struct nft_handle *h,const char *chain,
+			      const char *table, const char *newname);
+int nft_cmd_rule_replace(struct nft_handle *h, const char *chain,
+			 const char *table, void *data, int rulenum,
+			 bool verbose);
+int nft_cmd_table_flush(struct nft_handle *h, const char *table, bool verbose);
+int nft_cmd_chain_restore(struct nft_handle *h, const char *chain,
+			  const char *table);
+int nft_cmd_rule_zero_counters(struct nft_handle *h, const char *chain,
+			       const char *table, int rulenum);
+int nft_cmd_rule_list_save(struct nft_handle *h, const char *chain,
+			   const char *table, int rulenum, int counters);
+int ebt_cmd_user_chain_policy(struct nft_handle *h, const char *table,
+			      const char *chain, const char *policy);
+void nft_cmd_table_new(struct nft_handle *h, const char *table);
+
+#endif /* _NFT_CMD_H_ */
diff --git a/iptables/nft-ipv4.c b/iptables/nft-ipv4.c
new file mode 100644
index 0000000..fdc15c6
--- /dev/null
+++ b/iptables/nft-ipv4.c
@@ -0,0 +1,464 @@
+/*
+ * (C) 2012-2014 by Pablo Neira Ayuso <pablo@netfilter.org>
+ * (C) 2013 by Tomasz Bursztyka <tomasz.bursztyka@linux.intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This code has been sponsored by Sophos Astaro <http://www.sophos.com>
+ */
+
+#include <string.h>
+#include <stdio.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netinet/ip.h>
+#include <netdb.h>
+
+#include <xtables.h>
+
+#include <linux/netfilter/nf_tables.h>
+
+#include "nft.h"
+#include "nft-shared.h"
+
+static int nft_ipv4_add(struct nft_handle *h, struct nftnl_rule *r, void *data)
+{
+	struct iptables_command_state *cs = data;
+	struct xtables_rule_match *matchp;
+	uint32_t op;
+	int ret;
+
+	if (cs->fw.ip.iniface[0] != '\0') {
+		op = nft_invflags2cmp(cs->fw.ip.invflags, IPT_INV_VIA_IN);
+		add_iniface(r, cs->fw.ip.iniface, op);
+	}
+
+	if (cs->fw.ip.outiface[0] != '\0') {
+		op = nft_invflags2cmp(cs->fw.ip.invflags, IPT_INV_VIA_OUT);
+		add_outiface(r, cs->fw.ip.outiface, op);
+	}
+
+	if (cs->fw.ip.proto != 0) {
+		op = nft_invflags2cmp(cs->fw.ip.invflags, XT_INV_PROTO);
+		add_l4proto(r, cs->fw.ip.proto, op);
+	}
+
+	if (cs->fw.ip.src.s_addr || cs->fw.ip.smsk.s_addr || cs->fw.ip.invflags & IPT_INV_SRCIP) {
+		op = nft_invflags2cmp(cs->fw.ip.invflags, IPT_INV_SRCIP);
+		add_addr(r, NFT_PAYLOAD_NETWORK_HEADER,
+			 offsetof(struct iphdr, saddr),
+			 &cs->fw.ip.src.s_addr, &cs->fw.ip.smsk.s_addr,
+			 sizeof(struct in_addr), op);
+	}
+	if (cs->fw.ip.dst.s_addr || cs->fw.ip.dmsk.s_addr || cs->fw.ip.invflags & IPT_INV_DSTIP) {
+		op = nft_invflags2cmp(cs->fw.ip.invflags, IPT_INV_DSTIP);
+		add_addr(r, NFT_PAYLOAD_NETWORK_HEADER,
+			 offsetof(struct iphdr, daddr),
+			 &cs->fw.ip.dst.s_addr, &cs->fw.ip.dmsk.s_addr,
+			 sizeof(struct in_addr), op);
+	}
+	if (cs->fw.ip.flags & IPT_F_FRAG) {
+		add_payload(r, offsetof(struct iphdr, frag_off), 2,
+			    NFT_PAYLOAD_NETWORK_HEADER);
+		/* get the 13 bits that contain the fragment offset */
+		add_bitwise_u16(r, htons(0x1fff), 0);
+
+		/* if offset is non-zero, this is a fragment */
+		op = NFT_CMP_NEQ;
+		if (cs->fw.ip.invflags & IPT_INV_FRAG)
+			op = NFT_CMP_EQ;
+
+		add_cmp_u16(r, 0, op);
+	}
+
+	add_compat(r, cs->fw.ip.proto, cs->fw.ip.invflags & XT_INV_PROTO);
+
+	for (matchp = cs->matches; matchp; matchp = matchp->next) {
+		ret = add_match(h, r, matchp->match->m);
+		if (ret < 0)
+			return ret;
+	}
+
+	/* Counters need to me added before the target, otherwise they are
+	 * increased for each rule because of the way nf_tables works.
+	 */
+	if (add_counters(r, cs->counters.pcnt, cs->counters.bcnt) < 0)
+		return -1;
+
+	return add_action(r, cs, !!(cs->fw.ip.flags & IPT_F_GOTO));
+}
+
+static bool nft_ipv4_is_same(const void *data_a,
+			     const void *data_b)
+{
+	const struct iptables_command_state *a = data_a;
+	const struct iptables_command_state *b = data_b;
+
+	if (a->fw.ip.src.s_addr != b->fw.ip.src.s_addr
+	    || a->fw.ip.dst.s_addr != b->fw.ip.dst.s_addr
+	    || a->fw.ip.smsk.s_addr != b->fw.ip.smsk.s_addr
+	    || a->fw.ip.dmsk.s_addr != b->fw.ip.dmsk.s_addr
+	    || a->fw.ip.proto != b->fw.ip.proto
+	    || a->fw.ip.flags != b->fw.ip.flags
+	    || a->fw.ip.invflags != b->fw.ip.invflags) {
+		DEBUGP("different src/dst/proto/flags/invflags\n");
+		return false;
+	}
+
+	return is_same_interfaces(a->fw.ip.iniface, a->fw.ip.outiface,
+				  a->fw.ip.iniface_mask, a->fw.ip.outiface_mask,
+				  b->fw.ip.iniface, b->fw.ip.outiface,
+				  b->fw.ip.iniface_mask, b->fw.ip.outiface_mask);
+}
+
+static void get_frag(struct nft_xt_ctx *ctx, struct nftnl_expr *e, bool *inv)
+{
+	uint8_t op;
+
+	/* we assume correct mask and xor */
+	if (!(ctx->flags & NFT_XT_CTX_BITWISE))
+		return;
+
+	/* we assume correct data */
+	op = nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP);
+	if (op == NFT_CMP_EQ)
+		*inv = true;
+	else
+		*inv = false;
+
+	ctx->flags &= ~NFT_XT_CTX_BITWISE;
+}
+
+static const char *mask_to_str(uint32_t mask)
+{
+	static char mask_str[sizeof("255.255.255.255")];
+	uint32_t bits, hmask = ntohl(mask);
+	struct in_addr mask_addr = {
+		.s_addr = mask,
+	};
+	int i;
+
+	if (mask == 0xFFFFFFFFU) {
+		sprintf(mask_str, "32");
+		return mask_str;
+	}
+
+	i    = 32;
+	bits = 0xFFFFFFFEU;
+	while (--i >= 0 && hmask != bits)
+		bits <<= 1;
+	if (i >= 0)
+		sprintf(mask_str, "%u", i);
+	else
+		sprintf(mask_str, "%s", inet_ntoa(mask_addr));
+
+	return mask_str;
+}
+
+static void nft_ipv4_parse_meta(struct nft_xt_ctx *ctx, struct nftnl_expr *e,
+				void *data)
+{
+	struct iptables_command_state *cs = data;
+
+	switch (ctx->meta.key) {
+	case NFT_META_L4PROTO:
+		cs->fw.ip.proto = nftnl_expr_get_u8(e, NFTNL_EXPR_CMP_DATA);
+		if (nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP) == NFT_CMP_NEQ)
+			cs->fw.ip.invflags |= XT_INV_PROTO;
+		return;
+	default:
+		break;
+	}
+
+	parse_meta(e, ctx->meta.key, cs->fw.ip.iniface, cs->fw.ip.iniface_mask,
+		   cs->fw.ip.outiface, cs->fw.ip.outiface_mask,
+		   &cs->fw.ip.invflags);
+}
+
+static void parse_mask_ipv4(struct nft_xt_ctx *ctx, struct in_addr *mask)
+{
+	mask->s_addr = ctx->bitwise.mask[0];
+}
+
+static void nft_ipv4_parse_payload(struct nft_xt_ctx *ctx,
+				   struct nftnl_expr *e, void *data)
+{
+	struct iptables_command_state *cs = data;
+	struct in_addr addr;
+	uint8_t proto;
+	bool inv;
+
+	switch(ctx->payload.offset) {
+	case offsetof(struct iphdr, saddr):
+		get_cmp_data(e, &addr, sizeof(addr), &inv);
+		cs->fw.ip.src.s_addr = addr.s_addr;
+		if (ctx->flags & NFT_XT_CTX_BITWISE) {
+			parse_mask_ipv4(ctx, &cs->fw.ip.smsk);
+			ctx->flags &= ~NFT_XT_CTX_BITWISE;
+		} else {
+			memset(&cs->fw.ip.smsk, 0xff,
+			       min(ctx->payload.len, sizeof(struct in_addr)));
+		}
+
+		if (inv)
+			cs->fw.ip.invflags |= IPT_INV_SRCIP;
+		break;
+	case offsetof(struct iphdr, daddr):
+		get_cmp_data(e, &addr, sizeof(addr), &inv);
+		cs->fw.ip.dst.s_addr = addr.s_addr;
+		if (ctx->flags & NFT_XT_CTX_BITWISE) {
+			parse_mask_ipv4(ctx, &cs->fw.ip.dmsk);
+			ctx->flags &= ~NFT_XT_CTX_BITWISE;
+		} else {
+			memset(&cs->fw.ip.dmsk, 0xff,
+			       min(ctx->payload.len, sizeof(struct in_addr)));
+		}
+
+		if (inv)
+			cs->fw.ip.invflags |= IPT_INV_DSTIP;
+		break;
+	case offsetof(struct iphdr, protocol):
+		get_cmp_data(e, &proto, sizeof(proto), &inv);
+		cs->fw.ip.proto = proto;
+		if (inv)
+			cs->fw.ip.invflags |= IPT_INV_PROTO;
+		break;
+	case offsetof(struct iphdr, frag_off):
+		cs->fw.ip.flags |= IPT_F_FRAG;
+		inv = false;
+		get_frag(ctx, e, &inv);
+		if (inv)
+			cs->fw.ip.invflags |= IPT_INV_FRAG;
+		break;
+	default:
+		DEBUGP("unknown payload offset %d\n", ctx->payload.offset);
+		break;
+	}
+}
+
+static void nft_ipv4_parse_immediate(const char *jumpto, bool nft_goto,
+				     void *data)
+{
+	struct iptables_command_state *cs = data;
+
+	cs->jumpto = jumpto;
+
+	if (nft_goto)
+		cs->fw.ip.flags |= IPT_F_GOTO;
+}
+
+static void print_fragment(unsigned int flags, unsigned int invflags,
+			   unsigned int format)
+{
+	if (!(format & FMT_OPTIONS))
+		return;
+
+	if (format & FMT_NOTABLE)
+		fputs("opt ", stdout);
+	fputc(invflags & IPT_INV_FRAG ? '!' : '-', stdout);
+	fputc(flags & IPT_F_FRAG ? 'f' : '-', stdout);
+	fputc(' ', stdout);
+}
+
+static void nft_ipv4_print_rule(struct nft_handle *h, struct nftnl_rule *r,
+				unsigned int num, unsigned int format)
+{
+	struct iptables_command_state cs = {};
+
+	nft_rule_to_iptables_command_state(h, r, &cs);
+
+	print_rule_details(&cs, cs.jumpto, cs.fw.ip.flags,
+			   cs.fw.ip.invflags, cs.fw.ip.proto, num, format);
+	print_fragment(cs.fw.ip.flags, cs.fw.ip.invflags, format);
+	print_ifaces(cs.fw.ip.iniface, cs.fw.ip.outiface, cs.fw.ip.invflags,
+		     format);
+	print_ipv4_addresses(&cs.fw, format);
+
+	if (format & FMT_NOTABLE)
+		fputs("  ", stdout);
+
+#ifdef IPT_F_GOTO
+	if (cs.fw.ip.flags & IPT_F_GOTO)
+		printf("[goto] ");
+#endif
+
+	print_matches_and_target(&cs, format);
+
+	if (!(format & FMT_NONEWLINE))
+		fputc('\n', stdout);
+
+	nft_clear_iptables_command_state(&cs);
+}
+
+static void save_ipv4_addr(char letter, const struct in_addr *addr,
+			   uint32_t mask, int invert)
+{
+	if (!mask && !invert && !addr->s_addr)
+		return;
+
+	printf("%s-%c %s/%s ", invert ? "! " : "", letter, inet_ntoa(*addr),
+	       mask_to_str(mask));
+}
+
+static void nft_ipv4_save_rule(const void *data, unsigned int format)
+{
+	const struct iptables_command_state *cs = data;
+
+	save_ipv4_addr('s', &cs->fw.ip.src, cs->fw.ip.smsk.s_addr,
+		       cs->fw.ip.invflags & IPT_INV_SRCIP);
+	save_ipv4_addr('d', &cs->fw.ip.dst, cs->fw.ip.dmsk.s_addr,
+		       cs->fw.ip.invflags & IPT_INV_DSTIP);
+
+	save_rule_details(cs, cs->fw.ip.invflags, cs->fw.ip.proto,
+			  cs->fw.ip.iniface, cs->fw.ip.iniface_mask,
+			  cs->fw.ip.outiface, cs->fw.ip.outiface_mask);
+
+	if (cs->fw.ip.flags & IPT_F_FRAG) {
+		if (cs->fw.ip.invflags & IPT_INV_FRAG)
+			printf("! ");
+		printf("-f ");
+	}
+
+	save_matches_and_target(cs, cs->fw.ip.flags & IPT_F_GOTO,
+				&cs->fw, format);
+}
+
+static void nft_ipv4_proto_parse(struct iptables_command_state *cs,
+				 struct xtables_args *args)
+{
+	cs->fw.ip.proto = args->proto;
+	cs->fw.ip.invflags = args->invflags;
+}
+
+static void nft_ipv4_post_parse(int command,
+				struct iptables_command_state *cs,
+				struct xtables_args *args)
+{
+	cs->fw.ip.flags = args->flags;
+	/* We already set invflags in proto_parse, but we need to refresh it
+	 * to include new parsed options.
+	 */
+	cs->fw.ip.invflags = args->invflags;
+
+	strncpy(cs->fw.ip.iniface, args->iniface, IFNAMSIZ);
+	memcpy(cs->fw.ip.iniface_mask,
+	       args->iniface_mask, IFNAMSIZ*sizeof(unsigned char));
+
+	strncpy(cs->fw.ip.outiface, args->outiface, IFNAMSIZ);
+	memcpy(cs->fw.ip.outiface_mask,
+	       args->outiface_mask, IFNAMSIZ*sizeof(unsigned char));
+
+	if (args->goto_set)
+		cs->fw.ip.flags |= IPT_F_GOTO;
+
+	cs->counters.pcnt = args->pcnt_cnt;
+	cs->counters.bcnt = args->bcnt_cnt;
+
+	if (command & (CMD_REPLACE | CMD_INSERT |
+			CMD_DELETE | CMD_APPEND | CMD_CHECK)) {
+		if (!(cs->options & OPT_DESTINATION))
+			args->dhostnetworkmask = "0.0.0.0/0";
+		if (!(cs->options & OPT_SOURCE))
+			args->shostnetworkmask = "0.0.0.0/0";
+	}
+
+	if (args->shostnetworkmask)
+		xtables_ipparse_multiple(args->shostnetworkmask,
+					 &args->s.addr.v4, &args->s.mask.v4,
+					 &args->s.naddrs);
+	if (args->dhostnetworkmask)
+		xtables_ipparse_multiple(args->dhostnetworkmask,
+					 &args->d.addr.v4, &args->d.mask.v4,
+					 &args->d.naddrs);
+
+	if ((args->s.naddrs > 1 || args->d.naddrs > 1) &&
+	    (cs->fw.ip.invflags & (IPT_INV_SRCIP | IPT_INV_DSTIP)))
+		xtables_error(PARAMETER_PROBLEM,
+			      "! not allowed with multiple"
+			      " source or destination IP addresses");
+}
+
+static int nft_ipv4_xlate(const void *data, struct xt_xlate *xl)
+{
+	const struct iptables_command_state *cs = data;
+	const char *comment;
+	int ret;
+
+	xlate_ifname(xl, "iifname", cs->fw.ip.iniface,
+		     cs->fw.ip.invflags & IPT_INV_VIA_IN);
+	xlate_ifname(xl, "oifname", cs->fw.ip.outiface,
+		     cs->fw.ip.invflags & IPT_INV_VIA_OUT);
+
+	if (cs->fw.ip.flags & IPT_F_FRAG) {
+		xt_xlate_add(xl, "ip frag-off & 0x1fff %s%x ",
+			   cs->fw.ip.invflags & IPT_INV_FRAG? "" : "!= ", 0);
+	}
+
+	if (cs->fw.ip.proto != 0) {
+		const struct protoent *pent =
+			getprotobynumber(cs->fw.ip.proto);
+		char protonum[sizeof("65535")];
+		const char *name = protonum;
+
+		snprintf(protonum, sizeof(protonum), "%u",
+			 cs->fw.ip.proto);
+
+		if (!pent || !xlate_find_match(cs, pent->p_name)) {
+			if (pent)
+				name = pent->p_name;
+			xt_xlate_add(xl, "ip protocol %s%s ",
+				   cs->fw.ip.invflags & IPT_INV_PROTO ?
+					"!= " : "", name);
+		}
+	}
+
+	if (cs->fw.ip.src.s_addr != 0) {
+		xt_xlate_add(xl, "ip saddr %s%s%s ",
+			   cs->fw.ip.invflags & IPT_INV_SRCIP ? "!= " : "",
+			   inet_ntoa(cs->fw.ip.src),
+			   xtables_ipmask_to_numeric(&cs->fw.ip.smsk));
+	}
+	if (cs->fw.ip.dst.s_addr != 0) {
+		xt_xlate_add(xl, "ip daddr %s%s%s ",
+			   cs->fw.ip.invflags & IPT_INV_DSTIP ? "!= " : "",
+			   inet_ntoa(cs->fw.ip.dst),
+			   xtables_ipmask_to_numeric(&cs->fw.ip.dmsk));
+	}
+
+	ret = xlate_matches(cs, xl);
+	if (!ret)
+		return ret;
+
+	/* Always add counters per rule, as in iptables */
+	xt_xlate_add(xl, "counter");
+	ret = xlate_action(cs, !!(cs->fw.ip.flags & IPT_F_GOTO), xl);
+
+	comment = xt_xlate_get_comment(xl);
+	if (comment)
+		xt_xlate_add(xl, " comment %s", comment);
+
+	return ret;
+}
+
+struct nft_family_ops nft_family_ops_ipv4 = {
+	.add			= nft_ipv4_add,
+	.is_same		= nft_ipv4_is_same,
+	.parse_meta		= nft_ipv4_parse_meta,
+	.parse_payload		= nft_ipv4_parse_payload,
+	.parse_immediate	= nft_ipv4_parse_immediate,
+	.print_header		= print_header,
+	.print_rule		= nft_ipv4_print_rule,
+	.save_rule		= nft_ipv4_save_rule,
+	.save_chain		= nft_ipv46_save_chain,
+	.proto_parse		= nft_ipv4_proto_parse,
+	.post_parse		= nft_ipv4_post_parse,
+	.parse_target		= nft_ipv46_parse_target,
+	.rule_to_cs		= nft_rule_to_iptables_command_state,
+	.clear_cs		= nft_clear_iptables_command_state,
+	.xlate			= nft_ipv4_xlate,
+};
diff --git a/iptables/nft-ipv6.c b/iptables/nft-ipv6.c
new file mode 100644
index 0000000..130ad3e
--- /dev/null
+++ b/iptables/nft-ipv6.c
@@ -0,0 +1,416 @@
+/*
+ * (C) 2012-2014 by Pablo Neira Ayuso <pablo@netfilter.org>
+ * (C) 2013 by Tomasz Bursztyka <tomasz.bursztyka@linux.intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This code has been sponsored by Sophos Astaro <http://www.sophos.com>
+ */
+
+#include <string.h>
+#include <stdio.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/ip6.h>
+#include <netdb.h>
+
+#include <xtables.h>
+
+#include <linux/netfilter/nf_tables.h>
+#include "nft.h"
+#include "nft-shared.h"
+
+static int nft_ipv6_add(struct nft_handle *h, struct nftnl_rule *r, void *data)
+{
+	struct iptables_command_state *cs = data;
+	struct xtables_rule_match *matchp;
+	uint32_t op;
+	int ret;
+
+	if (cs->fw6.ipv6.iniface[0] != '\0') {
+		op = nft_invflags2cmp(cs->fw6.ipv6.invflags, IPT_INV_VIA_IN);
+		add_iniface(r, cs->fw6.ipv6.iniface, op);
+	}
+
+	if (cs->fw6.ipv6.outiface[0] != '\0') {
+		op = nft_invflags2cmp(cs->fw6.ipv6.invflags, IPT_INV_VIA_OUT);
+		add_outiface(r, cs->fw6.ipv6.outiface, op);
+	}
+
+	if (cs->fw6.ipv6.proto != 0) {
+		op = nft_invflags2cmp(cs->fw6.ipv6.invflags, XT_INV_PROTO);
+		add_l4proto(r, cs->fw6.ipv6.proto, op);
+	}
+
+	if (!IN6_IS_ADDR_UNSPECIFIED(&cs->fw6.ipv6.src) ||
+	    !IN6_IS_ADDR_UNSPECIFIED(&cs->fw6.ipv6.smsk) ||
+	    (cs->fw6.ipv6.invflags & IPT_INV_SRCIP)) {
+		op = nft_invflags2cmp(cs->fw6.ipv6.invflags, IPT_INV_SRCIP);
+		add_addr(r, NFT_PAYLOAD_NETWORK_HEADER,
+			 offsetof(struct ip6_hdr, ip6_src),
+			 &cs->fw6.ipv6.src, &cs->fw6.ipv6.smsk,
+			 sizeof(struct in6_addr), op);
+	}
+	if (!IN6_IS_ADDR_UNSPECIFIED(&cs->fw6.ipv6.dst) ||
+	    !IN6_IS_ADDR_UNSPECIFIED(&cs->fw6.ipv6.dmsk) ||
+	    (cs->fw6.ipv6.invflags & IPT_INV_DSTIP)) {
+		op = nft_invflags2cmp(cs->fw6.ipv6.invflags, IPT_INV_DSTIP);
+		add_addr(r, NFT_PAYLOAD_NETWORK_HEADER,
+			 offsetof(struct ip6_hdr, ip6_dst),
+			 &cs->fw6.ipv6.dst, &cs->fw6.ipv6.dmsk,
+			 sizeof(struct in6_addr), op);
+	}
+	add_compat(r, cs->fw6.ipv6.proto, cs->fw6.ipv6.invflags & XT_INV_PROTO);
+
+	for (matchp = cs->matches; matchp; matchp = matchp->next) {
+		ret = add_match(h, r, matchp->match->m);
+		if (ret < 0)
+			return ret;
+	}
+
+	/* Counters need to me added before the target, otherwise they are
+	 * increased for each rule because of the way nf_tables works.
+	 */
+	if (add_counters(r, cs->counters.pcnt, cs->counters.bcnt) < 0)
+		return -1;
+
+	return add_action(r, cs, !!(cs->fw6.ipv6.flags & IP6T_F_GOTO));
+}
+
+static bool nft_ipv6_is_same(const void *data_a,
+			     const void *data_b)
+{
+	const struct iptables_command_state *a = data_a;
+	const struct iptables_command_state *b = data_b;
+
+	if (memcmp(a->fw6.ipv6.src.s6_addr, b->fw6.ipv6.src.s6_addr,
+		   sizeof(struct in6_addr)) != 0
+	    || memcmp(a->fw6.ipv6.dst.s6_addr, b->fw6.ipv6.dst.s6_addr,
+		    sizeof(struct in6_addr)) != 0
+	    || a->fw6.ipv6.proto != b->fw6.ipv6.proto
+	    || a->fw6.ipv6.flags != b->fw6.ipv6.flags
+	    || a->fw6.ipv6.invflags != b->fw6.ipv6.invflags) {
+		DEBUGP("different src/dst/proto/flags/invflags\n");
+		return false;
+	}
+
+	return is_same_interfaces(a->fw6.ipv6.iniface, a->fw6.ipv6.outiface,
+				  a->fw6.ipv6.iniface_mask,
+				  a->fw6.ipv6.outiface_mask,
+				  b->fw6.ipv6.iniface, b->fw6.ipv6.outiface,
+				  b->fw6.ipv6.iniface_mask,
+				  b->fw6.ipv6.outiface_mask);
+}
+
+static void nft_ipv6_parse_meta(struct nft_xt_ctx *ctx, struct nftnl_expr *e,
+				void *data)
+{
+	struct iptables_command_state *cs = data;
+
+	switch (ctx->meta.key) {
+	case NFT_META_L4PROTO:
+		cs->fw6.ipv6.proto = nftnl_expr_get_u8(e, NFTNL_EXPR_CMP_DATA);
+		if (nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP) == NFT_CMP_NEQ)
+			cs->fw6.ipv6.invflags |= XT_INV_PROTO;
+		return;
+	default:
+		break;
+	}
+
+	parse_meta(e, ctx->meta.key, cs->fw6.ipv6.iniface,
+		   cs->fw6.ipv6.iniface_mask, cs->fw6.ipv6.outiface,
+		   cs->fw6.ipv6.outiface_mask, &cs->fw6.ipv6.invflags);
+}
+
+static void parse_mask_ipv6(struct nft_xt_ctx *ctx, struct in6_addr *mask)
+{
+	memcpy(mask, ctx->bitwise.mask, sizeof(struct in6_addr));
+}
+
+static void nft_ipv6_parse_payload(struct nft_xt_ctx *ctx,
+				   struct nftnl_expr *e, void *data)
+{
+	struct iptables_command_state *cs = data;
+	struct in6_addr addr;
+	uint8_t proto;
+	bool inv;
+
+	switch (ctx->payload.offset) {
+	case offsetof(struct ip6_hdr, ip6_src):
+		get_cmp_data(e, &addr, sizeof(addr), &inv);
+		memcpy(cs->fw6.ipv6.src.s6_addr, &addr, sizeof(addr));
+		if (ctx->flags & NFT_XT_CTX_BITWISE) {
+			parse_mask_ipv6(ctx, &cs->fw6.ipv6.smsk);
+			ctx->flags &= ~NFT_XT_CTX_BITWISE;
+		} else {
+			memset(&cs->fw6.ipv6.smsk, 0xff,
+			       min(ctx->payload.len, sizeof(struct in6_addr)));
+		}
+
+		if (inv)
+			cs->fw6.ipv6.invflags |= IP6T_INV_SRCIP;
+		break;
+	case offsetof(struct ip6_hdr, ip6_dst):
+		get_cmp_data(e, &addr, sizeof(addr), &inv);
+		memcpy(cs->fw6.ipv6.dst.s6_addr, &addr, sizeof(addr));
+		if (ctx->flags & NFT_XT_CTX_BITWISE) {
+			parse_mask_ipv6(ctx, &cs->fw6.ipv6.dmsk);
+			ctx->flags &= ~NFT_XT_CTX_BITWISE;
+		} else {
+			memset(&cs->fw6.ipv6.dmsk, 0xff,
+			       min(ctx->payload.len, sizeof(struct in6_addr)));
+		}
+
+		if (inv)
+			cs->fw6.ipv6.invflags |= IP6T_INV_DSTIP;
+		break;
+	case offsetof(struct ip6_hdr, ip6_nxt):
+		get_cmp_data(e, &proto, sizeof(proto), &inv);
+		cs->fw6.ipv6.proto = proto;
+		if (inv)
+			cs->fw6.ipv6.invflags |= IP6T_INV_PROTO;
+	default:
+		DEBUGP("unknown payload offset %d\n", ctx->payload.offset);
+		break;
+	}
+}
+
+static void nft_ipv6_parse_immediate(const char *jumpto, bool nft_goto,
+				     void *data)
+{
+	struct iptables_command_state *cs = data;
+
+	cs->jumpto = jumpto;
+
+	if (nft_goto)
+		cs->fw6.ipv6.flags |= IP6T_F_GOTO;
+}
+
+static void nft_ipv6_print_rule(struct nft_handle *h, struct nftnl_rule *r,
+				unsigned int num, unsigned int format)
+{
+	struct iptables_command_state cs = {};
+
+	nft_rule_to_iptables_command_state(h, r, &cs);
+
+	print_rule_details(&cs, cs.jumpto, cs.fw6.ipv6.flags,
+			   cs.fw6.ipv6.invflags, cs.fw6.ipv6.proto,
+			   num, format);
+	if (format & FMT_OPTIONS) {
+		if (format & FMT_NOTABLE)
+			fputs("opt ", stdout);
+		fputs("   ", stdout);
+	}
+	print_ifaces(cs.fw6.ipv6.iniface, cs.fw6.ipv6.outiface,
+		     cs.fw6.ipv6.invflags, format);
+	print_ipv6_addresses(&cs.fw6, format);
+
+	if (format & FMT_NOTABLE)
+		fputs("  ", stdout);
+
+	if (cs.fw6.ipv6.flags & IP6T_F_GOTO)
+		printf("[goto] ");
+
+	print_matches_and_target(&cs, format);
+
+	if (!(format & FMT_NONEWLINE))
+		fputc('\n', stdout);
+
+	nft_clear_iptables_command_state(&cs);
+}
+
+static void save_ipv6_addr(char letter, const struct in6_addr *addr,
+			   const struct in6_addr *mask,
+			   int invert)
+{
+	char addr_str[INET6_ADDRSTRLEN];
+	int l = xtables_ip6mask_to_cidr(mask);
+
+	if (!invert && l == 0)
+		return;
+
+	printf("%s-%c %s",
+		invert ? "! " : "", letter,
+		inet_ntop(AF_INET6, addr, addr_str, sizeof(addr_str)));
+
+	if (l == -1)
+		printf("/%s ", inet_ntop(AF_INET6, mask, addr_str, sizeof(addr_str)));
+	else
+		printf("/%d ", l);
+}
+
+static void nft_ipv6_save_rule(const void *data, unsigned int format)
+{
+	const struct iptables_command_state *cs = data;
+
+	save_ipv6_addr('s', &cs->fw6.ipv6.src, &cs->fw6.ipv6.smsk,
+		       cs->fw6.ipv6.invflags & IP6T_INV_SRCIP);
+	save_ipv6_addr('d', &cs->fw6.ipv6.dst, &cs->fw6.ipv6.dmsk,
+		       cs->fw6.ipv6.invflags & IP6T_INV_DSTIP);
+
+	save_rule_details(cs, cs->fw6.ipv6.invflags, cs->fw6.ipv6.proto,
+			  cs->fw6.ipv6.iniface, cs->fw6.ipv6.iniface_mask,
+			  cs->fw6.ipv6.outiface, cs->fw6.ipv6.outiface_mask);
+
+	save_matches_and_target(cs, cs->fw6.ipv6.flags & IP6T_F_GOTO,
+				&cs->fw6, format);
+}
+
+/* These are invalid numbers as upper layer protocol */
+static int is_exthdr(uint16_t proto)
+{
+	return (proto == IPPROTO_ROUTING ||
+		proto == IPPROTO_FRAGMENT ||
+		proto == IPPROTO_AH ||
+		proto == IPPROTO_DSTOPTS);
+}
+
+static void nft_ipv6_proto_parse(struct iptables_command_state *cs,
+				 struct xtables_args *args)
+{
+	cs->fw6.ipv6.proto = args->proto;
+	cs->fw6.ipv6.invflags = args->invflags;
+
+	if (is_exthdr(cs->fw6.ipv6.proto)
+	    && (cs->fw6.ipv6.invflags & XT_INV_PROTO) == 0)
+		fprintf(stderr,
+			"Warning: never matched protocol: %s. "
+			"use extension match instead.\n",
+			cs->protocol);
+}
+
+static void nft_ipv6_post_parse(int command, struct iptables_command_state *cs,
+				struct xtables_args *args)
+{
+	cs->fw6.ipv6.flags = args->flags;
+	/* We already set invflags in proto_parse, but we need to refresh it
+	 * to include new parsed options.
+	 */
+	cs->fw6.ipv6.invflags = args->invflags;
+
+	strncpy(cs->fw6.ipv6.iniface, args->iniface, IFNAMSIZ);
+	memcpy(cs->fw6.ipv6.iniface_mask,
+	       args->iniface_mask, IFNAMSIZ*sizeof(unsigned char));
+
+	strncpy(cs->fw6.ipv6.outiface, args->outiface, IFNAMSIZ);
+	memcpy(cs->fw6.ipv6.outiface_mask,
+	       args->outiface_mask, IFNAMSIZ*sizeof(unsigned char));
+
+	if (args->goto_set)
+		cs->fw6.ipv6.flags |= IP6T_F_GOTO;
+
+	cs->fw6.counters.pcnt = args->pcnt_cnt;
+	cs->fw6.counters.bcnt = args->bcnt_cnt;
+
+	if (command & (CMD_REPLACE | CMD_INSERT |
+			CMD_DELETE | CMD_APPEND | CMD_CHECK)) {
+		if (!(cs->options & OPT_DESTINATION))
+			args->dhostnetworkmask = "::0/0";
+		if (!(cs->options & OPT_SOURCE))
+			args->shostnetworkmask = "::0/0";
+	}
+
+	if (args->shostnetworkmask)
+		xtables_ip6parse_multiple(args->shostnetworkmask,
+					  &args->s.addr.v6,
+					  &args->s.mask.v6,
+					  &args->s.naddrs);
+	if (args->dhostnetworkmask)
+		xtables_ip6parse_multiple(args->dhostnetworkmask,
+					  &args->d.addr.v6,
+					  &args->d.mask.v6,
+					  &args->d.naddrs);
+
+	if ((args->s.naddrs > 1 || args->d.naddrs > 1) &&
+	    (cs->fw6.ipv6.invflags & (IP6T_INV_SRCIP | IP6T_INV_DSTIP)))
+		xtables_error(PARAMETER_PROBLEM,
+			      "! not allowed with multiple"
+			      " source or destination IP addresses");
+}
+
+static void xlate_ipv6_addr(const char *selector, const struct in6_addr *addr,
+			    const struct in6_addr *mask,
+			    int invert, struct xt_xlate *xl)
+{
+	char addr_str[INET6_ADDRSTRLEN];
+
+	if (!invert && IN6_IS_ADDR_UNSPECIFIED(addr))
+		return;
+
+	inet_ntop(AF_INET6, addr, addr_str, INET6_ADDRSTRLEN);
+	xt_xlate_add(xl, "%s %s%s%s ", selector, invert ? "!= " : "", addr_str,
+			xtables_ip6mask_to_numeric(mask));
+}
+
+static int nft_ipv6_xlate(const void *data, struct xt_xlate *xl)
+{
+	const struct iptables_command_state *cs = data;
+	const char *comment;
+	int ret;
+
+	xlate_ifname(xl, "iifname", cs->fw6.ipv6.iniface,
+		     cs->fw6.ipv6.invflags & IP6T_INV_VIA_IN);
+	xlate_ifname(xl, "oifname", cs->fw6.ipv6.outiface,
+		     cs->fw6.ipv6.invflags & IP6T_INV_VIA_OUT);
+
+	if (cs->fw6.ipv6.proto != 0) {
+		const struct protoent *pent =
+			getprotobynumber(cs->fw6.ipv6.proto);
+		char protonum[sizeof("65535")];
+		const char *name = protonum;
+
+		snprintf(protonum, sizeof(protonum), "%u",
+			 cs->fw6.ipv6.proto);
+
+		if (!pent || !xlate_find_match(cs, pent->p_name)) {
+			if (pent)
+				name = pent->p_name;
+			xt_xlate_add(xl, "meta l4proto %s%s ",
+				   cs->fw6.ipv6.invflags & IP6T_INV_PROTO ?
+					"!= " : "", name);
+		}
+
+	}
+
+	xlate_ipv6_addr("ip6 saddr", &cs->fw6.ipv6.src, &cs->fw6.ipv6.smsk,
+			cs->fw6.ipv6.invflags & IP6T_INV_SRCIP, xl);
+	xlate_ipv6_addr("ip6 daddr", &cs->fw6.ipv6.dst, &cs->fw6.ipv6.dmsk,
+			cs->fw6.ipv6.invflags & IP6T_INV_DSTIP, xl);
+
+	ret = xlate_matches(cs, xl);
+	if (!ret)
+		return ret;
+
+	/* Always add counters per rule, as in iptables */
+	xt_xlate_add(xl, "counter");
+	ret = xlate_action(cs, !!(cs->fw6.ipv6.flags & IP6T_F_GOTO), xl);
+
+	comment = xt_xlate_get_comment(xl);
+	if (comment)
+		xt_xlate_add(xl, " comment %s", comment);
+
+	return ret;
+}
+
+struct nft_family_ops nft_family_ops_ipv6 = {
+	.add			= nft_ipv6_add,
+	.is_same		= nft_ipv6_is_same,
+	.parse_meta		= nft_ipv6_parse_meta,
+	.parse_payload		= nft_ipv6_parse_payload,
+	.parse_immediate	= nft_ipv6_parse_immediate,
+	.print_header		= print_header,
+	.print_rule		= nft_ipv6_print_rule,
+	.save_rule		= nft_ipv6_save_rule,
+	.save_chain		= nft_ipv46_save_chain,
+	.proto_parse		= nft_ipv6_proto_parse,
+	.post_parse		= nft_ipv6_post_parse,
+	.parse_target		= nft_ipv46_parse_target,
+	.rule_to_cs		= nft_rule_to_iptables_command_state,
+	.clear_cs		= nft_clear_iptables_command_state,
+	.xlate			= nft_ipv6_xlate,
+};
diff --git a/iptables/nft-shared.c b/iptables/nft-shared.c
new file mode 100644
index 0000000..10553ab
--- /dev/null
+++ b/iptables/nft-shared.c
@@ -0,0 +1,1016 @@
+/*
+ * (C) 2012-2013 by Pablo Neira Ayuso <pablo@netfilter.org>
+ * (C) 2013 by Tomasz Bursztyka <tomasz.bursztyka@linux.intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This code has been sponsored by Sophos Astaro <http://www.sophos.com>
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <netdb.h>
+#include <errno.h>
+#include <inttypes.h>
+
+#include <xtables.h>
+
+#include <linux/netfilter/xt_comment.h>
+#include <linux/netfilter/xt_limit.h>
+
+#include <libmnl/libmnl.h>
+#include <libnftnl/rule.h>
+#include <libnftnl/expr.h>
+
+#include "nft-shared.h"
+#include "nft-bridge.h"
+#include "xshared.h"
+#include "nft.h"
+
+extern struct nft_family_ops nft_family_ops_ipv4;
+extern struct nft_family_ops nft_family_ops_ipv6;
+extern struct nft_family_ops nft_family_ops_arp;
+extern struct nft_family_ops nft_family_ops_bridge;
+
+void add_meta(struct nftnl_rule *r, uint32_t key)
+{
+	struct nftnl_expr *expr;
+
+	expr = nftnl_expr_alloc("meta");
+	if (expr == NULL)
+		return;
+
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_META_KEY, key);
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_META_DREG, NFT_REG_1);
+
+	nftnl_rule_add_expr(r, expr);
+}
+
+void add_payload(struct nftnl_rule *r, int offset, int len, uint32_t base)
+{
+	struct nftnl_expr *expr;
+
+	expr = nftnl_expr_alloc("payload");
+	if (expr == NULL)
+		return;
+
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_PAYLOAD_BASE, base);
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_PAYLOAD_DREG, NFT_REG_1);
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_PAYLOAD_OFFSET, offset);
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_PAYLOAD_LEN, len);
+
+	nftnl_rule_add_expr(r, expr);
+}
+
+/* bitwise operation is = sreg & mask ^ xor */
+void add_bitwise_u16(struct nftnl_rule *r, uint16_t mask, uint16_t xor)
+{
+	struct nftnl_expr *expr;
+
+	expr = nftnl_expr_alloc("bitwise");
+	if (expr == NULL)
+		return;
+
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_BITWISE_SREG, NFT_REG_1);
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_BITWISE_DREG, NFT_REG_1);
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_BITWISE_LEN, sizeof(uint16_t));
+	nftnl_expr_set(expr, NFTNL_EXPR_BITWISE_MASK, &mask, sizeof(uint16_t));
+	nftnl_expr_set(expr, NFTNL_EXPR_BITWISE_XOR, &xor, sizeof(uint16_t));
+
+	nftnl_rule_add_expr(r, expr);
+}
+
+void add_bitwise(struct nftnl_rule *r, uint8_t *mask, size_t len)
+{
+	struct nftnl_expr *expr;
+	uint32_t xor[4] = { 0 };
+
+	expr = nftnl_expr_alloc("bitwise");
+	if (expr == NULL)
+		return;
+
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_BITWISE_SREG, NFT_REG_1);
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_BITWISE_DREG, NFT_REG_1);
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_BITWISE_LEN, len);
+	nftnl_expr_set(expr, NFTNL_EXPR_BITWISE_MASK, mask, len);
+	nftnl_expr_set(expr, NFTNL_EXPR_BITWISE_XOR, &xor, len);
+
+	nftnl_rule_add_expr(r, expr);
+}
+
+void add_cmp_ptr(struct nftnl_rule *r, uint32_t op, void *data, size_t len)
+{
+	struct nftnl_expr *expr;
+
+	expr = nftnl_expr_alloc("cmp");
+	if (expr == NULL)
+		return;
+
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_CMP_SREG, NFT_REG_1);
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_CMP_OP, op);
+	nftnl_expr_set(expr, NFTNL_EXPR_CMP_DATA, data, len);
+
+	nftnl_rule_add_expr(r, expr);
+}
+
+void add_cmp_u8(struct nftnl_rule *r, uint8_t val, uint32_t op)
+{
+	add_cmp_ptr(r, op, &val, sizeof(val));
+}
+
+void add_cmp_u16(struct nftnl_rule *r, uint16_t val, uint32_t op)
+{
+	add_cmp_ptr(r, op, &val, sizeof(val));
+}
+
+void add_cmp_u32(struct nftnl_rule *r, uint32_t val, uint32_t op)
+{
+	add_cmp_ptr(r, op, &val, sizeof(val));
+}
+
+void add_iniface(struct nftnl_rule *r, char *iface, uint32_t op)
+{
+	int iface_len;
+
+	iface_len = strlen(iface);
+
+	add_meta(r, NFT_META_IIFNAME);
+	if (iface[iface_len - 1] == '+') {
+		if (iface_len > 1)
+			add_cmp_ptr(r, op, iface, iface_len - 1);
+	} else
+		add_cmp_ptr(r, op, iface, iface_len + 1);
+}
+
+void add_outiface(struct nftnl_rule *r, char *iface, uint32_t op)
+{
+	int iface_len;
+
+	iface_len = strlen(iface);
+
+	add_meta(r, NFT_META_OIFNAME);
+	if (iface[iface_len - 1] == '+') {
+		if (iface_len > 1)
+			add_cmp_ptr(r, op, iface, iface_len - 1);
+	} else
+		add_cmp_ptr(r, op, iface, iface_len + 1);
+}
+
+void add_addr(struct nftnl_rule *r, enum nft_payload_bases base, int offset,
+	      void *data, void *mask, size_t len, uint32_t op)
+{
+	const unsigned char *m = mask;
+	bool bitwise = false;
+	int i;
+
+	for (i = 0; i < len; i++) {
+		if (m[i] != 0xff) {
+			bitwise = m[i] != 0;
+			break;
+		}
+	}
+
+	if (!bitwise)
+		len = i;
+
+	add_payload(r, offset, len, base);
+
+	if (bitwise)
+		add_bitwise(r, mask, len);
+
+	add_cmp_ptr(r, op, data, len);
+}
+
+void add_proto(struct nftnl_rule *r, int offset, size_t len,
+	       uint8_t proto, uint32_t op)
+{
+	add_payload(r, offset, len, NFT_PAYLOAD_NETWORK_HEADER);
+	add_cmp_u8(r, proto, op);
+}
+
+void add_l4proto(struct nftnl_rule *r, uint8_t proto, uint32_t op)
+{
+	add_meta(r, NFT_META_L4PROTO);
+	add_cmp_u8(r, proto, op);
+}
+
+bool is_same_interfaces(const char *a_iniface, const char *a_outiface,
+			unsigned const char *a_iniface_mask,
+			unsigned const char *a_outiface_mask,
+			const char *b_iniface, const char *b_outiface,
+			unsigned const char *b_iniface_mask,
+			unsigned const char *b_outiface_mask)
+{
+	int i;
+
+	for (i = 0; i < IFNAMSIZ; i++) {
+		if (a_iniface_mask[i] != b_iniface_mask[i]) {
+			DEBUGP("different iniface mask %x, %x (%d)\n",
+			a_iniface_mask[i] & 0xff, b_iniface_mask[i] & 0xff, i);
+			return false;
+		}
+		if ((a_iniface[i] & a_iniface_mask[i])
+		    != (b_iniface[i] & b_iniface_mask[i])) {
+			DEBUGP("different iniface\n");
+			return false;
+		}
+		if (a_outiface_mask[i] != b_outiface_mask[i]) {
+			DEBUGP("different outiface mask\n");
+			return false;
+		}
+		if ((a_outiface[i] & a_outiface_mask[i])
+		    != (b_outiface[i] & b_outiface_mask[i])) {
+			DEBUGP("different outiface\n");
+			return false;
+		}
+	}
+
+	return true;
+}
+
+static void parse_ifname(const char *name, unsigned int len, char *dst, unsigned char *mask)
+{
+	if (len == 0)
+		return;
+
+	memcpy(dst, name, len);
+	if (name[len - 1] == '\0') {
+		if (mask)
+			memset(mask, 0xff, len);
+		return;
+	}
+
+	if (len >= IFNAMSIZ)
+		return;
+
+	/* wildcard */
+	dst[len++] = '+';
+	if (len >= IFNAMSIZ)
+		return;
+	dst[len++] = 0;
+	if (mask)
+		memset(mask, 0xff, len - 2);
+}
+
+int parse_meta(struct nftnl_expr *e, uint8_t key, char *iniface,
+		unsigned char *iniface_mask, char *outiface,
+		unsigned char *outiface_mask, uint8_t *invflags)
+{
+	uint32_t value;
+	const void *ifname;
+	uint32_t len;
+
+	switch(key) {
+	case NFT_META_IIF:
+		value = nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_DATA);
+		if (nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP) == NFT_CMP_NEQ)
+			*invflags |= IPT_INV_VIA_IN;
+
+		if_indextoname(value, iniface);
+
+		memset(iniface_mask, 0xff, strlen(iniface)+1);
+		break;
+	case NFT_META_OIF:
+		value = nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_DATA);
+		if (nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP) == NFT_CMP_NEQ)
+			*invflags |= IPT_INV_VIA_OUT;
+
+		if_indextoname(value, outiface);
+
+		memset(outiface_mask, 0xff, strlen(outiface)+1);
+		break;
+	case NFT_META_BRI_IIFNAME:
+	case NFT_META_IIFNAME:
+		ifname = nftnl_expr_get(e, NFTNL_EXPR_CMP_DATA, &len);
+		if (nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP) == NFT_CMP_NEQ)
+			*invflags |= IPT_INV_VIA_IN;
+
+		parse_ifname(ifname, len, iniface, iniface_mask);
+		break;
+	case NFT_META_BRI_OIFNAME:
+	case NFT_META_OIFNAME:
+		ifname = nftnl_expr_get(e, NFTNL_EXPR_CMP_DATA, &len);
+		if (nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP) == NFT_CMP_NEQ)
+			*invflags |= IPT_INV_VIA_OUT;
+
+		parse_ifname(ifname, len, outiface, outiface_mask);
+		break;
+	default:
+		return -1;
+	}
+
+	return 0;
+}
+
+static void nft_parse_target(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
+{
+	uint32_t tg_len;
+	const char *targname = nftnl_expr_get_str(e, NFTNL_EXPR_TG_NAME);
+	const void *targinfo = nftnl_expr_get(e, NFTNL_EXPR_TG_INFO, &tg_len);
+	struct xtables_target *target;
+	struct xt_entry_target *t;
+	size_t size;
+	void *data = ctx->cs;
+
+	target = xtables_find_target(targname, XTF_TRY_LOAD);
+	if (target == NULL)
+		return;
+
+	size = XT_ALIGN(sizeof(struct xt_entry_target)) + tg_len;
+
+	t = xtables_calloc(1, size);
+	memcpy(&t->data, targinfo, tg_len);
+	t->u.target_size = size;
+	t->u.user.revision = nftnl_expr_get_u32(e, NFTNL_EXPR_TG_REV);
+	strcpy(t->u.user.name, target->name);
+
+	target->t = t;
+
+	ctx->h->ops->parse_target(target, data);
+}
+
+static void nft_parse_match(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
+{
+	uint32_t mt_len;
+	const char *mt_name = nftnl_expr_get_str(e, NFTNL_EXPR_MT_NAME);
+	const void *mt_info = nftnl_expr_get(e, NFTNL_EXPR_MT_INFO, &mt_len);
+	struct xtables_match *match;
+	struct xtables_rule_match **matches;
+	struct xt_entry_match *m;
+
+	switch (ctx->h->family) {
+	case NFPROTO_IPV4:
+	case NFPROTO_IPV6:
+	case NFPROTO_BRIDGE:
+		matches = &ctx->cs->matches;
+		break;
+	default:
+		fprintf(stderr, "BUG: nft_parse_match() unknown family %d\n",
+			ctx->h->family);
+		exit(EXIT_FAILURE);
+	}
+
+	match = xtables_find_match(mt_name, XTF_TRY_LOAD, matches);
+	if (match == NULL)
+		return;
+
+	m = xtables_calloc(1, sizeof(struct xt_entry_match) + mt_len);
+	memcpy(&m->data, mt_info, mt_len);
+	m->u.match_size = mt_len + XT_ALIGN(sizeof(struct xt_entry_match));
+	m->u.user.revision = nftnl_expr_get_u32(e, NFTNL_EXPR_TG_REV);
+	strcpy(m->u.user.name, match->name);
+
+	match->m = m;
+
+	if (ctx->h->ops->parse_match != NULL)
+		ctx->h->ops->parse_match(match, ctx->cs);
+}
+
+void print_proto(uint16_t proto, int invert)
+{
+	const struct protoent *pent = getprotobynumber(proto);
+
+	if (invert)
+		printf("! ");
+
+	if (pent) {
+		printf("-p %s ", pent->p_name);
+		return;
+	}
+
+	printf("-p %u ", proto);
+}
+
+void get_cmp_data(struct nftnl_expr *e, void *data, size_t dlen, bool *inv)
+{
+	uint32_t len;
+	uint8_t op;
+
+	memcpy(data, nftnl_expr_get(e, NFTNL_EXPR_CMP_DATA, &len), dlen);
+	op = nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP);
+	if (op == NFT_CMP_NEQ)
+		*inv = true;
+	else
+		*inv = false;
+}
+
+static void nft_meta_set_to_target(struct nft_xt_ctx *ctx)
+{
+	struct xtables_target *target;
+	struct xt_entry_target *t;
+	unsigned int size;
+	const char *targname;
+
+	switch (ctx->meta.key) {
+	case NFT_META_NFTRACE:
+		if (ctx->immediate.data[0] == 0)
+			return;
+		targname = "TRACE";
+		break;
+	default:
+		return;
+	}
+
+	target = xtables_find_target(targname, XTF_TRY_LOAD);
+	if (target == NULL)
+		return;
+
+	size = XT_ALIGN(sizeof(struct xt_entry_target)) + target->size;
+
+	t = xtables_calloc(1, size);
+	t->u.target_size = size;
+	t->u.user.revision = target->revision;
+	strcpy(t->u.user.name, targname);
+
+	target->t = t;
+
+	ctx->h->ops->parse_target(target, ctx->cs);
+}
+
+static void nft_parse_meta(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
+{
+	ctx->meta.key = nftnl_expr_get_u32(e, NFTNL_EXPR_META_KEY);
+
+	if (nftnl_expr_is_set(e, NFTNL_EXPR_META_SREG) &&
+	    (ctx->flags & NFT_XT_CTX_IMMEDIATE) &&
+	     nftnl_expr_get_u32(e, NFTNL_EXPR_META_SREG) == ctx->immediate.reg) {
+		ctx->flags &= ~NFT_XT_CTX_IMMEDIATE;
+		nft_meta_set_to_target(ctx);
+		return;
+	}
+
+	ctx->reg = nftnl_expr_get_u32(e, NFTNL_EXPR_META_DREG);
+	ctx->flags |= NFT_XT_CTX_META;
+}
+
+static void nft_parse_payload(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
+{
+	if (ctx->flags & NFT_XT_CTX_PAYLOAD) {
+		memcpy(&ctx->prev_payload, &ctx->payload,
+		       sizeof(ctx->prev_payload));
+		ctx->flags |= NFT_XT_CTX_PREV_PAYLOAD;
+	}
+
+	ctx->reg = nftnl_expr_get_u32(e, NFTNL_EXPR_META_DREG);
+	ctx->payload.base = nftnl_expr_get_u32(e, NFTNL_EXPR_PAYLOAD_BASE);
+	ctx->payload.offset = nftnl_expr_get_u32(e, NFTNL_EXPR_PAYLOAD_OFFSET);
+	ctx->payload.len = nftnl_expr_get_u32(e, NFTNL_EXPR_PAYLOAD_LEN);
+	ctx->flags |= NFT_XT_CTX_PAYLOAD;
+}
+
+static void nft_parse_bitwise(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
+{
+	uint32_t reg, len;
+	const void *data;
+
+	reg = nftnl_expr_get_u32(e, NFTNL_EXPR_BITWISE_SREG);
+	if (ctx->reg && reg != ctx->reg)
+		return;
+
+	data = nftnl_expr_get(e, NFTNL_EXPR_BITWISE_XOR, &len);
+	memcpy(ctx->bitwise.xor, data, len);
+	data = nftnl_expr_get(e, NFTNL_EXPR_BITWISE_MASK, &len);
+	memcpy(ctx->bitwise.mask, data, len);
+	ctx->flags |= NFT_XT_CTX_BITWISE;
+}
+
+static void nft_parse_cmp(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
+{
+	void *data = ctx->cs;
+	uint32_t reg;
+
+	reg = nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_SREG);
+	if (ctx->reg && reg != ctx->reg)
+		return;
+
+	if (ctx->flags & NFT_XT_CTX_META) {
+		ctx->h->ops->parse_meta(ctx, e, data);
+		ctx->flags &= ~NFT_XT_CTX_META;
+	}
+	/* bitwise context is interpreted from payload */
+	if (ctx->flags & NFT_XT_CTX_PAYLOAD) {
+		ctx->h->ops->parse_payload(ctx, e, data);
+		ctx->flags &= ~NFT_XT_CTX_PAYLOAD;
+	}
+}
+
+static void nft_parse_counter(struct nftnl_expr *e, struct xt_counters *counters)
+{
+	counters->pcnt = nftnl_expr_get_u64(e, NFTNL_EXPR_CTR_PACKETS);
+	counters->bcnt = nftnl_expr_get_u64(e, NFTNL_EXPR_CTR_BYTES);
+}
+
+static void nft_parse_immediate(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
+{
+	const char *chain = nftnl_expr_get_str(e, NFTNL_EXPR_IMM_CHAIN);
+	const char *jumpto = NULL;
+	bool nft_goto = false;
+	void *data = ctx->cs;
+	int verdict;
+
+	if (nftnl_expr_is_set(e, NFTNL_EXPR_IMM_DATA)) {
+		const void *imm_data;
+		uint32_t len;
+
+		imm_data = nftnl_expr_get_data(e, NFTNL_EXPR_IMM_DATA, &len);
+
+		if (len > sizeof(ctx->immediate.data))
+			return;
+
+		memcpy(ctx->immediate.data, imm_data, len);
+		ctx->immediate.len = len;
+		ctx->immediate.reg = nftnl_expr_get_u32(e, NFTNL_EXPR_IMM_DREG);
+		ctx->flags |= NFT_XT_CTX_IMMEDIATE;
+		return;
+	}
+
+	verdict = nftnl_expr_get_u32(e, NFTNL_EXPR_IMM_VERDICT);
+	/* Standard target? */
+	switch(verdict) {
+	case NF_ACCEPT:
+		jumpto = "ACCEPT";
+		break;
+	case NF_DROP:
+		jumpto = "DROP";
+		break;
+	case NFT_RETURN:
+		jumpto = "RETURN";
+		break;;
+	case NFT_GOTO:
+		nft_goto = true;
+		/* fall through */
+	case NFT_JUMP:
+		jumpto = chain;
+		break;
+	}
+
+	ctx->h->ops->parse_immediate(jumpto, nft_goto, data);
+}
+
+static void nft_parse_limit(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
+{
+	__u32 burst = nftnl_expr_get_u32(e, NFTNL_EXPR_LIMIT_BURST);
+	__u64 unit = nftnl_expr_get_u64(e, NFTNL_EXPR_LIMIT_UNIT);
+	__u64 rate = nftnl_expr_get_u64(e, NFTNL_EXPR_LIMIT_RATE);
+	struct xtables_rule_match **matches;
+	struct xtables_match *match;
+	struct xt_rateinfo *rinfo;
+	size_t size;
+
+	switch (ctx->h->family) {
+	case NFPROTO_IPV4:
+	case NFPROTO_IPV6:
+	case NFPROTO_BRIDGE:
+		matches = &ctx->cs->matches;
+		break;
+	default:
+		fprintf(stderr, "BUG: nft_parse_limit() unknown family %d\n",
+			ctx->h->family);
+		exit(EXIT_FAILURE);
+	}
+
+	match = xtables_find_match("limit", XTF_TRY_LOAD, matches);
+	if (match == NULL)
+		return;
+
+	size = XT_ALIGN(sizeof(struct xt_entry_match)) + match->size;
+	match->m = xtables_calloc(1, size);
+	match->m->u.match_size = size;
+	strcpy(match->m->u.user.name, match->name);
+	match->m->u.user.revision = match->revision;
+	xs_init_match(match);
+
+	rinfo = (void *)match->m->data;
+	rinfo->avg = XT_LIMIT_SCALE * unit / rate;
+	rinfo->burst = burst;
+
+	if (ctx->h->ops->parse_match != NULL)
+		ctx->h->ops->parse_match(match, ctx->cs);
+}
+
+static void nft_parse_lookup(struct nft_xt_ctx *ctx, struct nft_handle *h,
+			     struct nftnl_expr *e)
+{
+	if (ctx->h->ops->parse_lookup)
+		ctx->h->ops->parse_lookup(ctx, e, NULL);
+}
+
+void nft_rule_to_iptables_command_state(struct nft_handle *h,
+					const struct nftnl_rule *r,
+					struct iptables_command_state *cs)
+{
+	struct nftnl_expr_iter *iter;
+	struct nftnl_expr *expr;
+	struct nft_xt_ctx ctx = {
+		.cs = cs,
+		.h = h,
+		.table = nftnl_rule_get_str(r, NFTNL_RULE_TABLE),
+	};
+
+	iter = nftnl_expr_iter_create(r);
+	if (iter == NULL)
+		return;
+
+	ctx.iter = iter;
+	expr = nftnl_expr_iter_next(iter);
+	while (expr != NULL) {
+		const char *name =
+			nftnl_expr_get_str(expr, NFTNL_EXPR_NAME);
+
+		if (strcmp(name, "counter") == 0)
+			nft_parse_counter(expr, &ctx.cs->counters);
+		else if (strcmp(name, "payload") == 0)
+			nft_parse_payload(&ctx, expr);
+		else if (strcmp(name, "meta") == 0)
+			nft_parse_meta(&ctx, expr);
+		else if (strcmp(name, "bitwise") == 0)
+			nft_parse_bitwise(&ctx, expr);
+		else if (strcmp(name, "cmp") == 0)
+			nft_parse_cmp(&ctx, expr);
+		else if (strcmp(name, "immediate") == 0)
+			nft_parse_immediate(&ctx, expr);
+		else if (strcmp(name, "match") == 0)
+			nft_parse_match(&ctx, expr);
+		else if (strcmp(name, "target") == 0)
+			nft_parse_target(&ctx, expr);
+		else if (strcmp(name, "limit") == 0)
+			nft_parse_limit(&ctx, expr);
+		else if (strcmp(name, "lookup") == 0)
+			nft_parse_lookup(&ctx, h, expr);
+
+		expr = nftnl_expr_iter_next(iter);
+	}
+
+	nftnl_expr_iter_destroy(iter);
+
+	if (nftnl_rule_is_set(r, NFTNL_RULE_USERDATA)) {
+		const void *data;
+		uint32_t len, size;
+		const char *comment;
+
+		data = nftnl_rule_get_data(r, NFTNL_RULE_USERDATA, &len);
+		comment = get_comment(data, len);
+		if (comment) {
+			struct xtables_match *match;
+			struct xt_entry_match *m;
+
+			match = xtables_find_match("comment", XTF_TRY_LOAD,
+						   &cs->matches);
+			if (match == NULL)
+				return;
+
+			size = XT_ALIGN(sizeof(struct xt_entry_match))
+				+ match->size;
+			m = xtables_calloc(1, size);
+
+			strncpy((char *)m->data, comment, match->size - 1);
+			m->u.match_size = size;
+			m->u.user.revision = 0;
+			strcpy(m->u.user.name, match->name);
+
+			match->m = m;
+		}
+	}
+
+	if (cs->target != NULL) {
+		cs->jumpto = cs->target->name;
+	} else if (cs->jumpto != NULL) {
+		struct xt_entry_target *t;
+		uint32_t size;
+
+		cs->target = xtables_find_target(cs->jumpto, XTF_TRY_LOAD);
+		if (!cs->target)
+			return;
+
+		size = XT_ALIGN(sizeof(struct xt_entry_target)) + cs->target->size;
+		t = xtables_calloc(1, size);
+		t->u.target_size = size;
+		t->u.user.revision = cs->target->revision;
+		strcpy(t->u.user.name, cs->jumpto);
+		cs->target->t = t;
+	} else {
+		cs->jumpto = "";
+	}
+}
+
+void nft_clear_iptables_command_state(struct iptables_command_state *cs)
+{
+	xtables_rule_matches_free(&cs->matches);
+	if (cs->target) {
+		free(cs->target->t);
+		cs->target->t = NULL;
+
+		if (cs->target == cs->target->next) {
+			free(cs->target);
+			cs->target = NULL;
+		}
+	}
+}
+
+void print_header(unsigned int format, const char *chain, const char *pol,
+		  const struct xt_counters *counters, bool basechain,
+		  uint32_t refs, uint32_t entries)
+{
+	printf("Chain %s", chain);
+	if (basechain) {
+		printf(" (policy %s", pol);
+		if (!(format & FMT_NOCOUNTS)) {
+			fputc(' ', stdout);
+			xtables_print_num(counters->pcnt, (format|FMT_NOTABLE));
+			fputs("packets, ", stdout);
+			xtables_print_num(counters->bcnt, (format|FMT_NOTABLE));
+			fputs("bytes", stdout);
+		}
+		printf(")\n");
+	} else {
+		printf(" (%u references)\n", refs);
+	}
+
+	if (format & FMT_LINENUMBERS)
+		printf(FMT("%-4s ", "%s "), "num");
+	if (!(format & FMT_NOCOUNTS)) {
+		if (format & FMT_KILOMEGAGIGA) {
+			printf(FMT("%5s ","%s "), "pkts");
+			printf(FMT("%5s ","%s "), "bytes");
+		} else {
+			printf(FMT("%8s ","%s "), "pkts");
+			printf(FMT("%10s ","%s "), "bytes");
+		}
+	}
+	if (!(format & FMT_NOTARGET))
+		printf(FMT("%-9s ","%s "), "target");
+	fputs(" prot ", stdout);
+	if (format & FMT_OPTIONS)
+		fputs("opt", stdout);
+	if (format & FMT_VIA) {
+		printf(FMT(" %-6s ","%s "), "in");
+		printf(FMT("%-6s ","%s "), "out");
+	}
+	printf(FMT(" %-19s ","%s "), "source");
+	printf(FMT(" %-19s "," %s "), "destination");
+	printf("\n");
+}
+
+void print_rule_details(const struct iptables_command_state *cs,
+			const char *targname, uint8_t flags,
+			uint8_t invflags, uint8_t proto,
+			unsigned int num, unsigned int format)
+{
+	if (format & FMT_LINENUMBERS)
+		printf(FMT("%-4u ", "%u "), num);
+
+	if (!(format & FMT_NOCOUNTS)) {
+		xtables_print_num(cs->counters.pcnt, format);
+		xtables_print_num(cs->counters.bcnt, format);
+	}
+
+	if (!(format & FMT_NOTARGET))
+		printf(FMT("%-9s ", "%s "), targname ? targname : "");
+
+	fputc(invflags & XT_INV_PROTO ? '!' : ' ', stdout);
+	{
+		const char *pname =
+			proto_to_name(proto, format&FMT_NUMERIC);
+		if (pname)
+			printf(FMT("%-5s", "%s "), pname);
+		else
+			printf(FMT("%-5hu", "%hu "), proto);
+	}
+}
+
+static void
+print_iface(char letter, const char *iface, const unsigned char *mask, int inv)
+{
+	unsigned int i;
+
+	if (mask[0] == 0)
+		return;
+
+	printf("%s-%c ", inv ? "! " : "", letter);
+
+	for (i = 0; i < IFNAMSIZ; i++) {
+		if (mask[i] != 0) {
+			if (iface[i] != '\0')
+				printf("%c", iface[i]);
+			} else {
+				if (iface[i-1] != '\0')
+					printf("+");
+				break;
+		}
+	}
+
+	printf(" ");
+}
+
+void save_rule_details(const struct iptables_command_state *cs,
+		       uint8_t invflags, uint16_t proto,
+		       const char *iniface,
+		       unsigned const char *iniface_mask,
+		       const char *outiface,
+		       unsigned const char *outiface_mask)
+{
+	if (iniface != NULL) {
+		print_iface('i', iniface, iniface_mask,
+			    invflags & IPT_INV_VIA_IN);
+	}
+	if (outiface != NULL) {
+		print_iface('o', outiface, outiface_mask,
+			    invflags & IPT_INV_VIA_OUT);
+	}
+
+	if (proto > 0) {
+		const struct protoent *pent = getprotobynumber(proto);
+
+		if (invflags & XT_INV_PROTO)
+			printf("! ");
+
+		if (pent)
+			printf("-p %s ", pent->p_name);
+		else
+			printf("-p %u ", proto);
+	}
+}
+
+void nft_ipv46_save_chain(const struct nftnl_chain *c, const char *policy)
+{
+	const char *chain = nftnl_chain_get_str(c, NFTNL_CHAIN_NAME);
+	uint64_t pkts = nftnl_chain_get_u64(c, NFTNL_CHAIN_PACKETS);
+	uint64_t bytes = nftnl_chain_get_u64(c, NFTNL_CHAIN_BYTES);
+
+	printf(":%s %s [%"PRIu64":%"PRIu64"]\n",
+	       chain, policy ?: "-", pkts, bytes);
+}
+
+void save_matches_and_target(const struct iptables_command_state *cs,
+			     bool goto_flag, const void *fw,
+			     unsigned int format)
+{
+	struct xtables_rule_match *matchp;
+
+	for (matchp = cs->matches; matchp; matchp = matchp->next) {
+		if (matchp->match->alias) {
+			printf("-m %s",
+			       matchp->match->alias(matchp->match->m));
+		} else
+			printf("-m %s", matchp->match->name);
+
+		if (matchp->match->save != NULL) {
+			/* cs->fw union makes the trick */
+			matchp->match->save(fw, matchp->match->m);
+		}
+		printf(" ");
+	}
+
+	if ((format & (FMT_NOCOUNTS | FMT_C_COUNTS)) == FMT_C_COUNTS)
+		printf("-c %llu %llu ",
+		       (unsigned long long)cs->counters.pcnt,
+		       (unsigned long long)cs->counters.bcnt);
+
+	if (cs->target != NULL) {
+		if (cs->target->alias) {
+			printf("-j %s", cs->target->alias(cs->target->t));
+		} else
+			printf("-j %s", cs->jumpto);
+
+		if (cs->target->save != NULL)
+			cs->target->save(fw, cs->target->t);
+	} else if (strlen(cs->jumpto) > 0) {
+		printf("-%c %s", goto_flag ? 'g' : 'j', cs->jumpto);
+	}
+
+	printf("\n");
+}
+
+void print_matches_and_target(struct iptables_command_state *cs,
+			      unsigned int format)
+{
+	struct xtables_rule_match *matchp;
+
+	for (matchp = cs->matches; matchp; matchp = matchp->next) {
+		if (matchp->match->print != NULL) {
+			matchp->match->print(&cs->fw, matchp->match->m,
+					     format & FMT_NUMERIC);
+		}
+	}
+
+	if (cs->target != NULL) {
+		if (cs->target->print != NULL) {
+			cs->target->print(&cs->fw, cs->target->t,
+					  format & FMT_NUMERIC);
+		}
+	}
+}
+
+struct nft_family_ops *nft_family_ops_lookup(int family)
+{
+	switch (family) {
+	case AF_INET:
+		return &nft_family_ops_ipv4;
+	case AF_INET6:
+		return &nft_family_ops_ipv6;
+	case NFPROTO_ARP:
+		return &nft_family_ops_arp;
+	case NFPROTO_BRIDGE:
+		return &nft_family_ops_bridge;
+	default:
+		break;
+	}
+
+	return NULL;
+}
+
+bool compare_matches(struct xtables_rule_match *mt1,
+		     struct xtables_rule_match *mt2)
+{
+	struct xtables_rule_match *mp1;
+	struct xtables_rule_match *mp2;
+
+	for (mp1 = mt1, mp2 = mt2; mp1 && mp2; mp1 = mp1->next, mp2 = mp2->next) {
+		struct xt_entry_match *m1 = mp1->match->m;
+		struct xt_entry_match *m2 = mp2->match->m;
+
+		if (strcmp(m1->u.user.name, m2->u.user.name) != 0) {
+			DEBUGP("mismatching match name\n");
+			return false;
+		}
+
+		if (m1->u.user.match_size != m2->u.user.match_size) {
+			DEBUGP("mismatching match size\n");
+			return false;
+		}
+
+		if (memcmp(m1->data, m2->data,
+			   mp1->match->userspacesize) != 0) {
+			DEBUGP("mismatch match data\n");
+			return false;
+		}
+	}
+
+	/* Both cursors should be NULL */
+	if (mp1 != mp2) {
+		DEBUGP("mismatch matches amount\n");
+		return false;
+	}
+
+	return true;
+}
+
+bool compare_targets(struct xtables_target *tg1, struct xtables_target *tg2)
+{
+	if (tg1 == NULL && tg2 == NULL)
+		return true;
+
+	if (tg1 == NULL || tg2 == NULL)
+		return false;
+	if (tg1->userspacesize != tg2->userspacesize)
+		return false;
+
+	if (strcmp(tg1->t->u.user.name, tg2->t->u.user.name) != 0)
+		return false;
+
+	if (memcmp(tg1->t->data, tg2->t->data, tg1->userspacesize) != 0)
+		return false;
+
+	return true;
+}
+
+void nft_ipv46_parse_target(struct xtables_target *t, void *data)
+{
+	struct iptables_command_state *cs = data;
+
+	cs->target = t;
+}
+
+void nft_check_xt_legacy(int family, bool is_ipt_save)
+{
+	static const char tables6[] = "/proc/net/ip6_tables_names";
+	static const char tables4[] = "/proc/net/ip_tables_names";
+	const char *prefix = "ip";
+	FILE *fp = NULL;
+	char buf[1024];
+
+	switch (family) {
+	case NFPROTO_IPV4:
+		fp = fopen(tables4, "r");
+		break;
+	case NFPROTO_IPV6:
+		fp = fopen(tables6, "r");
+		prefix = "ip6";
+		break;
+	default:
+		break;
+	}
+
+	if (!fp)
+		return;
+
+	if (fgets(buf, sizeof(buf), fp))
+		fprintf(stderr, "# Warning: %stables-legacy tables present, use %stables-legacy%s to see them\n",
+			prefix, prefix, is_ipt_save ? "-save" : "");
+	fclose(fp);
+}
diff --git a/iptables/nft-shared.h b/iptables/nft-shared.h
new file mode 100644
index 0000000..da4ba9d
--- /dev/null
+++ b/iptables/nft-shared.h
@@ -0,0 +1,255 @@
+#ifndef _NFT_SHARED_H_
+#define _NFT_SHARED_H_
+
+#include <stdbool.h>
+
+#include <libnftnl/rule.h>
+#include <libnftnl/expr.h>
+#include <libnftnl/chain.h>
+
+#include <linux/netfilter_arp/arp_tables.h>
+#include <linux/netfilter/nf_tables.h>
+
+#include "xshared.h"
+
+#ifdef DEBUG
+#define NLDEBUG
+#define DEBUG_DEL
+#endif
+
+/*
+ * iptables print output emulation
+ */
+
+#define FMT_NUMERIC	0x0001
+#define FMT_NOCOUNTS	0x0002
+#define FMT_KILOMEGAGIGA 0x0004
+#define FMT_OPTIONS	0x0008
+#define FMT_NOTABLE	0x0010
+#define FMT_NOTARGET	0x0020
+#define FMT_VIA		0x0040
+#define FMT_NONEWLINE	0x0080
+#define FMT_LINENUMBERS 0x0100
+
+#define FMT_PRINT_RULE (FMT_NOCOUNTS | FMT_OPTIONS | FMT_VIA \
+			| FMT_NUMERIC | FMT_NOTABLE)
+#define FMT(tab,notab) ((format) & FMT_NOTABLE ? (notab) : (tab))
+
+struct xtables_args;
+struct nft_handle;
+struct xt_xlate;
+
+enum {
+	NFT_XT_CTX_PAYLOAD	= (1 << 0),
+	NFT_XT_CTX_META		= (1 << 1),
+	NFT_XT_CTX_BITWISE	= (1 << 2),
+	NFT_XT_CTX_IMMEDIATE	= (1 << 3),
+	NFT_XT_CTX_PREV_PAYLOAD	= (1 << 4),
+};
+
+struct nft_xt_ctx {
+	struct iptables_command_state *cs;
+	struct nftnl_expr_iter *iter;
+	struct nft_handle *h;
+	uint32_t flags;
+	const char *table;
+
+	uint32_t reg;
+	struct {
+		uint32_t base;
+		uint32_t offset;
+		uint32_t len;
+	} payload, prev_payload;
+	struct {
+		uint32_t key;
+	} meta;
+	struct {
+		uint32_t data[4];
+		uint32_t len, reg;
+	} immediate;
+	struct {
+		uint32_t mask[4];
+		uint32_t xor[4];
+	} bitwise;
+};
+
+struct nft_family_ops {
+	int (*add)(struct nft_handle *h, struct nftnl_rule *r, void *data);
+	bool (*is_same)(const void *data_a,
+			const void *data_b);
+	void (*print_payload)(struct nftnl_expr *e,
+			      struct nftnl_expr_iter *iter);
+	void (*parse_meta)(struct nft_xt_ctx *ctx, struct nftnl_expr *e,
+			   void *data);
+	void (*parse_payload)(struct nft_xt_ctx *ctx, struct nftnl_expr *e,
+			      void *data);
+	void (*parse_bitwise)(struct nft_xt_ctx *ctx, struct nftnl_expr *e,
+			      void *data);
+	void (*parse_cmp)(struct nft_xt_ctx *ctx, struct nftnl_expr *e,
+			  void *data);
+	void (*parse_lookup)(struct nft_xt_ctx *ctx, struct nftnl_expr *e,
+			     void *data);
+	void (*parse_immediate)(const char *jumpto, bool nft_goto, void *data);
+
+	void (*print_table_header)(const char *tablename);
+	void (*print_header)(unsigned int format, const char *chain,
+			     const char *pol,
+			     const struct xt_counters *counters, bool basechain,
+			     uint32_t refs, uint32_t entries);
+	void (*print_rule)(struct nft_handle *h, struct nftnl_rule *r,
+			   unsigned int num, unsigned int format);
+	void (*save_rule)(const void *data, unsigned int format);
+	void (*save_chain)(const struct nftnl_chain *c, const char *policy);
+	void (*proto_parse)(struct iptables_command_state *cs,
+			    struct xtables_args *args);
+	void (*post_parse)(int command, struct iptables_command_state *cs,
+			   struct xtables_args *args);
+	void (*parse_match)(struct xtables_match *m, void *data);
+	void (*parse_target)(struct xtables_target *t, void *data);
+	void (*rule_to_cs)(struct nft_handle *h, const struct nftnl_rule *r,
+			   struct iptables_command_state *cs);
+	void (*clear_cs)(struct iptables_command_state *cs);
+	int (*xlate)(const void *data, struct xt_xlate *xl);
+};
+
+void add_meta(struct nftnl_rule *r, uint32_t key);
+void add_payload(struct nftnl_rule *r, int offset, int len, uint32_t base);
+void add_bitwise(struct nftnl_rule *r, uint8_t *mask, size_t len);
+void add_bitwise_u16(struct nftnl_rule *r, uint16_t mask, uint16_t xor);
+void add_cmp_ptr(struct nftnl_rule *r, uint32_t op, void *data, size_t len);
+void add_cmp_u8(struct nftnl_rule *r, uint8_t val, uint32_t op);
+void add_cmp_u16(struct nftnl_rule *r, uint16_t val, uint32_t op);
+void add_cmp_u32(struct nftnl_rule *r, uint32_t val, uint32_t op);
+void add_iniface(struct nftnl_rule *r, char *iface, uint32_t op);
+void add_outiface(struct nftnl_rule *r, char *iface, uint32_t op);
+void add_addr(struct nftnl_rule *r, enum nft_payload_bases base, int offset,
+	      void *data, void *mask, size_t len, uint32_t op);
+void add_proto(struct nftnl_rule *r, int offset, size_t len,
+	       uint8_t proto, uint32_t op);
+void add_l4proto(struct nftnl_rule *r, uint8_t proto, uint32_t op);
+void add_compat(struct nftnl_rule *r, uint32_t proto, bool inv);
+
+bool is_same_interfaces(const char *a_iniface, const char *a_outiface,
+			unsigned const char *a_iniface_mask,
+			unsigned const char *a_outiface_mask,
+			const char *b_iniface, const char *b_outiface,
+			unsigned const char *b_iniface_mask,
+			unsigned const char *b_outiface_mask);
+
+int parse_meta(struct nftnl_expr *e, uint8_t key, char *iniface,
+		unsigned char *iniface_mask, char *outiface,
+		unsigned char *outiface_mask, uint8_t *invflags);
+void print_proto(uint16_t proto, int invert);
+void get_cmp_data(struct nftnl_expr *e, void *data, size_t dlen, bool *inv);
+void nft_rule_to_iptables_command_state(struct nft_handle *h,
+					const struct nftnl_rule *r,
+					struct iptables_command_state *cs);
+void nft_clear_iptables_command_state(struct iptables_command_state *cs);
+void print_header(unsigned int format, const char *chain, const char *pol,
+		  const struct xt_counters *counters, bool basechain,
+		  uint32_t refs, uint32_t entries);
+void print_rule_details(const struct iptables_command_state *cs,
+			const char *targname, uint8_t flags,
+			uint8_t invflags, uint8_t proto,
+			unsigned int num, unsigned int format);
+void print_matches_and_target(struct iptables_command_state *cs,
+			      unsigned int format);
+void save_rule_details(const struct iptables_command_state *cs,
+		       uint8_t invflags, uint16_t proto,
+		       const char *iniface,
+		       unsigned const char *iniface_mask,
+		       const char *outiface,
+		       unsigned const char *outiface_mask);
+void nft_ipv46_save_chain(const struct nftnl_chain *c, const char *policy);
+void save_matches_and_target(const struct iptables_command_state *cs,
+			     bool goto_flag, const void *fw,
+			     unsigned int format);
+
+struct nft_family_ops *nft_family_ops_lookup(int family);
+
+void nft_ipv46_parse_target(struct xtables_target *t, void *data);
+
+bool compare_matches(struct xtables_rule_match *mt1, struct xtables_rule_match *mt2);
+bool compare_targets(struct xtables_target *tg1, struct xtables_target *tg2);
+
+struct addr_mask {
+	union {
+		struct in_addr	*v4;
+		struct in6_addr *v6;
+	} addr;
+
+	unsigned int naddrs;
+
+	union {
+		struct in_addr	*v4;
+		struct in6_addr *v6;
+	} mask;
+};
+
+struct xtables_args {
+	int		family;
+	uint16_t	proto;
+	uint8_t		flags;
+	uint8_t		invflags;
+	char		iniface[IFNAMSIZ], outiface[IFNAMSIZ];
+	unsigned char	iniface_mask[IFNAMSIZ], outiface_mask[IFNAMSIZ];
+	bool		goto_set;
+	const char	*shostnetworkmask, *dhostnetworkmask;
+	const char	*pcnt, *bcnt;
+	struct addr_mask s, d;
+	unsigned long long pcnt_cnt, bcnt_cnt;
+};
+
+struct nft_xt_cmd_parse {
+	unsigned int			command;
+	unsigned int			rulenum;
+	char				*table;
+	const char			*chain;
+	const char			*newname;
+	const char			*policy;
+	bool				restore;
+	int				verbose;
+	bool				xlate;
+};
+
+void do_parse(struct nft_handle *h, int argc, char *argv[],
+	      struct nft_xt_cmd_parse *p, struct iptables_command_state *cs,
+	      struct xtables_args *args);
+
+struct nftnl_chain_list;
+
+struct nft_xt_restore_cb {
+	void (*table_new)(struct nft_handle *h, const char *table);
+	int (*chain_set)(struct nft_handle *h, const char *table,
+			 const char *chain, const char *policy,
+			 const struct xt_counters *counters);
+	int (*chain_restore)(struct nft_handle *h, const char *chain,
+			     const char *table);
+
+	int (*table_flush)(struct nft_handle *h, const char *table,
+			   bool verbose);
+
+	int (*do_command)(struct nft_handle *h, int argc, char *argv[],
+			  char **table, bool restore);
+
+	int (*commit)(struct nft_handle *h);
+	int (*abort)(struct nft_handle *h);
+};
+
+struct nft_xt_restore_parse {
+	FILE				*in;
+	int				testing;
+	const char			*tablename;
+	bool				commit;
+	const struct nft_xt_restore_cb	*cb;
+};
+
+void xtables_restore_parse(struct nft_handle *h,
+			   const struct nft_xt_restore_parse *p);
+
+void nft_check_xt_legacy(int family, bool is_ipt_save);
+
+#define min(x, y) ((x) < (y) ? (x) : (y))
+#define max(x, y) ((x) > (y) ? (x) : (y))
+
+#endif
diff --git a/iptables/nft.c b/iptables/nft.c
new file mode 100644
index 0000000..bde4ca7
--- /dev/null
+++ b/iptables/nft.c
@@ -0,0 +1,3550 @@
+/*
+ * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This code has been sponsored by Sophos Astaro <http://www.sophos.com>
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <netdb.h>	/* getprotobynumber */
+#include <time.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <assert.h>
+
+#include <xtables.h>
+#include <libiptc/libxtc.h>
+#include <libiptc/xtcshared.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <linux/netfilter/x_tables.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <netinet/ip6.h>
+
+#include <linux/netlink.h>
+#include <linux/netfilter/nfnetlink.h>
+#include <linux/netfilter/nf_tables.h>
+#include <linux/netfilter/nf_tables_compat.h>
+
+#include <linux/netfilter/xt_limit.h>
+
+#include <libmnl/libmnl.h>
+#include <libnftnl/gen.h>
+#include <libnftnl/table.h>
+#include <libnftnl/chain.h>
+#include <libnftnl/rule.h>
+#include <libnftnl/expr.h>
+#include <libnftnl/set.h>
+#include <libnftnl/udata.h>
+#include <libnftnl/batch.h>
+
+#include <netinet/in.h>	/* inet_ntoa */
+#include <arpa/inet.h>
+
+#include "nft.h"
+#include "xshared.h" /* proto_to_name */
+#include "nft-cache.h"
+#include "nft-shared.h"
+#include "nft-bridge.h" /* EBT_NOPROTO */
+
+static void *nft_fn;
+
+int mnl_talk(struct nft_handle *h, struct nlmsghdr *nlh,
+	     int (*cb)(const struct nlmsghdr *nlh, void *data),
+	     void *data)
+{
+	int ret;
+	char buf[32768];
+
+	if (mnl_socket_sendto(h->nl, nlh, nlh->nlmsg_len) < 0)
+		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;
+}
+
+#define NFT_NLMSG_MAXSIZE (UINT16_MAX + getpagesize())
+
+/* selected batch page is 256 Kbytes long to load ruleset of
+ * half a million rules without hitting -EMSGSIZE due to large
+ * iovec.
+ */
+#define BATCH_PAGE_SIZE getpagesize() * 32
+
+static struct nftnl_batch *mnl_batch_init(void)
+{
+	struct nftnl_batch *batch;
+
+	batch = nftnl_batch_alloc(BATCH_PAGE_SIZE, NFT_NLMSG_MAXSIZE);
+	if (batch == NULL)
+		return NULL;
+
+	return batch;
+}
+
+static void mnl_nft_batch_continue(struct nftnl_batch *batch)
+{
+	assert(nftnl_batch_update(batch) >= 0);
+}
+
+static uint32_t mnl_batch_begin(struct nftnl_batch *batch, uint32_t genid, uint32_t seqnum)
+{
+	struct nlmsghdr *nlh;
+
+	nlh = nftnl_batch_begin(nftnl_batch_buffer(batch), seqnum);
+
+	mnl_attr_put_u32(nlh, NFTA_GEN_ID, htonl(genid));
+
+	mnl_nft_batch_continue(batch);
+
+	return seqnum;
+}
+
+static void mnl_batch_end(struct nftnl_batch *batch, uint32_t seqnum)
+{
+	nftnl_batch_end(nftnl_batch_buffer(batch), seqnum);
+	mnl_nft_batch_continue(batch);
+}
+
+static void mnl_batch_reset(struct nftnl_batch *batch)
+{
+	nftnl_batch_free(batch);
+}
+
+struct mnl_err {
+	struct list_head	head;
+	int			err;
+	uint32_t		seqnum;
+};
+
+static void mnl_err_list_node_add(struct list_head *err_list, int error,
+				  int seqnum)
+{
+	struct mnl_err *err = malloc(sizeof(struct mnl_err));
+
+	err->seqnum = seqnum;
+	err->err = error;
+	list_add_tail(&err->head, err_list);
+}
+
+static void mnl_err_list_free(struct mnl_err *err)
+{
+	list_del(&err->head);
+	free(err);
+}
+
+static void mnl_set_sndbuffer(struct nft_handle *h)
+{
+	int newbuffsiz = nftnl_batch_iovec_len(h->batch) * BATCH_PAGE_SIZE;
+
+	if (newbuffsiz <= h->nlsndbuffsiz)
+		return;
+
+	/* Rise sender buffer length to avoid hitting -EMSGSIZE */
+	if (setsockopt(mnl_socket_get_fd(h->nl), SOL_SOCKET, SO_SNDBUFFORCE,
+		       &newbuffsiz, sizeof(socklen_t)) < 0)
+		return;
+
+	h->nlsndbuffsiz = newbuffsiz;
+}
+
+static void mnl_set_rcvbuffer(struct nft_handle *h, int numcmds)
+{
+	int newbuffsiz = getpagesize() * numcmds;
+
+	if (newbuffsiz <= h->nlrcvbuffsiz)
+		return;
+
+	/* Rise receiver buffer length to avoid hitting -ENOBUFS */
+	if (setsockopt(mnl_socket_get_fd(h->nl), SOL_SOCKET, SO_RCVBUFFORCE,
+		       &newbuffsiz, sizeof(socklen_t)) < 0)
+		return;
+
+	h->nlrcvbuffsiz = newbuffsiz;
+}
+
+static ssize_t mnl_nft_socket_sendmsg(struct nft_handle *h, int numcmds)
+{
+	static const struct sockaddr_nl snl = {
+		.nl_family = AF_NETLINK
+	};
+	uint32_t iov_len = nftnl_batch_iovec_len(h->batch);
+	struct iovec iov[iov_len];
+	struct msghdr msg = {
+		.msg_name	= (struct sockaddr *) &snl,
+		.msg_namelen	= sizeof(snl),
+		.msg_iov	= iov,
+		.msg_iovlen	= iov_len,
+	};
+
+	mnl_set_sndbuffer(h);
+	mnl_set_rcvbuffer(h, numcmds);
+	nftnl_batch_iovec(h->batch, iov, iov_len);
+
+	return sendmsg(mnl_socket_get_fd(h->nl), &msg, 0);
+}
+
+static int mnl_batch_talk(struct nft_handle *h, int numcmds)
+{
+	const struct mnl_socket *nl = h->nl;
+	int ret, fd = mnl_socket_get_fd(nl), portid = mnl_socket_get_portid(nl);
+	char rcv_buf[MNL_SOCKET_BUFFER_SIZE];
+	fd_set readfds;
+	struct timeval tv = {
+		.tv_sec		= 0,
+		.tv_usec	= 0
+	};
+	int err = 0;
+
+	ret = mnl_nft_socket_sendmsg(h, numcmds);
+	if (ret == -1)
+		return -1;
+
+	FD_ZERO(&readfds);
+	FD_SET(fd, &readfds);
+
+	/* receive and digest all the acknowledgments from the kernel. */
+	ret = select(fd+1, &readfds, NULL, NULL, &tv);
+	if (ret == -1)
+		return -1;
+
+	while (ret > 0 && FD_ISSET(fd, &readfds)) {
+		struct nlmsghdr *nlh = (struct nlmsghdr *)rcv_buf;
+
+		ret = mnl_socket_recvfrom(nl, rcv_buf, sizeof(rcv_buf));
+		if (ret == -1)
+			return -1;
+
+		ret = mnl_cb_run(rcv_buf, ret, 0, portid, NULL, NULL);
+		/* Continue on error, make sure we get all acknowledgments */
+		if (ret == -1) {
+			mnl_err_list_node_add(&h->err_list, errno,
+					      nlh->nlmsg_seq);
+			err = -1;
+		}
+
+		ret = select(fd+1, &readfds, NULL, NULL, &tv);
+		if (ret == -1)
+			return -1;
+
+		FD_ZERO(&readfds);
+		FD_SET(fd, &readfds);
+	}
+	return err;
+}
+
+enum obj_action {
+	NFT_COMPAT_COMMIT,
+	NFT_COMPAT_ABORT,
+};
+
+struct obj_update {
+	struct list_head	head;
+	enum obj_update_type	type:8;
+	uint8_t			skip:1;
+	unsigned int		seq;
+	union {
+		struct nftnl_table	*table;
+		struct nftnl_chain	*chain;
+		struct nftnl_rule	*rule;
+		struct nftnl_set	*set;
+		void			*ptr;
+	};
+	struct {
+		unsigned int		lineno;
+	} error;
+};
+
+static int mnl_append_error(const struct nft_handle *h,
+			    const struct obj_update *o,
+			    const struct mnl_err *err,
+			    char *buf, unsigned int len)
+{
+	static const char *type_name[] = {
+		[NFT_COMPAT_TABLE_ADD] = "TABLE_ADD",
+		[NFT_COMPAT_TABLE_FLUSH] = "TABLE_FLUSH",
+		[NFT_COMPAT_CHAIN_ADD] = "CHAIN_ADD",
+		[NFT_COMPAT_CHAIN_USER_ADD] = "CHAIN_USER_ADD",
+		[NFT_COMPAT_CHAIN_USER_DEL] = "CHAIN_USER_DEL",
+		[NFT_COMPAT_CHAIN_USER_FLUSH] = "CHAIN_USER_FLUSH",
+		[NFT_COMPAT_CHAIN_UPDATE] = "CHAIN_UPDATE",
+		[NFT_COMPAT_CHAIN_RENAME] = "CHAIN_RENAME",
+		[NFT_COMPAT_CHAIN_ZERO] = "CHAIN_ZERO",
+		[NFT_COMPAT_RULE_APPEND] = "RULE_APPEND",
+		[NFT_COMPAT_RULE_INSERT] = "RULE_INSERT",
+		[NFT_COMPAT_RULE_REPLACE] = "RULE_REPLACE",
+		[NFT_COMPAT_RULE_DELETE] = "RULE_DELETE",
+		[NFT_COMPAT_RULE_FLUSH] = "RULE_FLUSH",
+		[NFT_COMPAT_SET_ADD] = "SET_ADD",
+	};
+	char errmsg[256];
+	char tcr[128];
+
+	if (o->error.lineno)
+		snprintf(errmsg, sizeof(errmsg), "\nline %u: %s failed (%s)",
+			 o->error.lineno, type_name[o->type], strerror(err->err));
+	else
+		snprintf(errmsg, sizeof(errmsg), " %s failed (%s)",
+			 type_name[o->type], strerror(err->err));
+
+	switch (o->type) {
+	case NFT_COMPAT_TABLE_ADD:
+	case NFT_COMPAT_TABLE_FLUSH:
+		snprintf(tcr, sizeof(tcr), "table %s",
+			 nftnl_table_get_str(o->table, NFTNL_TABLE_NAME));
+		break;
+	case NFT_COMPAT_CHAIN_ADD:
+	case NFT_COMPAT_CHAIN_ZERO:
+	case NFT_COMPAT_CHAIN_USER_ADD:
+	case NFT_COMPAT_CHAIN_USER_DEL:
+	case NFT_COMPAT_CHAIN_USER_FLUSH:
+	case NFT_COMPAT_CHAIN_UPDATE:
+	case NFT_COMPAT_CHAIN_RENAME:
+		snprintf(tcr, sizeof(tcr), "chain %s",
+			 nftnl_chain_get_str(o->chain, NFTNL_CHAIN_NAME));
+		break;
+	case NFT_COMPAT_RULE_APPEND:
+	case NFT_COMPAT_RULE_INSERT:
+	case NFT_COMPAT_RULE_REPLACE:
+	case NFT_COMPAT_RULE_DELETE:
+	case NFT_COMPAT_RULE_FLUSH:
+		snprintf(tcr, sizeof(tcr), "rule in chain %s",
+			 nftnl_rule_get_str(o->rule, NFTNL_RULE_CHAIN));
+#if 0
+		{
+			nft_rule_print_save(h, o->rule, NFT_RULE_APPEND, FMT_NOCOUNTS);
+		}
+#endif
+		break;
+	case NFT_COMPAT_SET_ADD:
+		snprintf(tcr, sizeof(tcr), "set %s",
+			 nftnl_set_get_str(o->set, NFTNL_SET_NAME));
+		break;
+	case NFT_COMPAT_RULE_LIST:
+	case NFT_COMPAT_RULE_CHECK:
+	case NFT_COMPAT_CHAIN_RESTORE:
+	case NFT_COMPAT_RULE_SAVE:
+	case NFT_COMPAT_RULE_ZERO:
+	case NFT_COMPAT_BRIDGE_USER_CHAIN_UPDATE:
+		assert(0);
+		break;
+	}
+
+	return snprintf(buf, len, "%s: %s", errmsg, tcr);
+}
+
+static struct obj_update *batch_add(struct nft_handle *h, enum obj_update_type type, void *ptr)
+{
+	struct obj_update *obj;
+
+	obj = calloc(1, sizeof(struct obj_update));
+	if (obj == NULL)
+		return NULL;
+
+	obj->ptr = ptr;
+	obj->error.lineno = h->error.lineno;
+	obj->type = type;
+	list_add_tail(&obj->head, &h->obj_list);
+	h->obj_list_num++;
+
+	return obj;
+}
+
+static struct obj_update *
+batch_table_add(struct nft_handle *h, enum obj_update_type type,
+		struct nftnl_table *t)
+{
+	return batch_add(h, type, t);
+}
+
+static struct obj_update *
+batch_set_add(struct nft_handle *h, enum obj_update_type type,
+	      struct nftnl_set *s)
+{
+	return batch_add(h, type, s);
+}
+
+static struct obj_update *
+batch_chain_add(struct nft_handle *h, enum obj_update_type type,
+			   struct nftnl_chain *c)
+{
+	return batch_add(h, type, c);
+}
+
+static struct obj_update *
+batch_rule_add(struct nft_handle *h, enum obj_update_type type,
+			  struct nftnl_rule *r)
+{
+	return batch_add(h, type, r);
+}
+
+static void batch_obj_del(struct nft_handle *h, struct obj_update *o);
+
+static void batch_chain_flush(struct nft_handle *h,
+			      const char *table, const char *chain)
+{
+	struct obj_update *obj, *tmp;
+
+	list_for_each_entry_safe(obj, tmp, &h->obj_list, head) {
+		struct nftnl_rule *r = obj->ptr;
+
+		switch (obj->type) {
+		case NFT_COMPAT_RULE_APPEND:
+		case NFT_COMPAT_RULE_INSERT:
+		case NFT_COMPAT_RULE_REPLACE:
+		case NFT_COMPAT_RULE_DELETE:
+			break;
+		default:
+			continue;
+		}
+
+		if (table &&
+		    strcmp(table, nftnl_rule_get_str(r, NFTNL_RULE_TABLE)))
+			continue;
+
+		if (chain &&
+		    strcmp(chain, nftnl_rule_get_str(r, NFTNL_RULE_CHAIN)))
+			continue;
+
+		batch_obj_del(h, obj);
+	}
+}
+
+const struct builtin_table xtables_ipv4[NFT_TABLE_MAX] = {
+	[NFT_TABLE_RAW] = {
+		.name	= "raw",
+		.type	= NFT_TABLE_RAW,
+		.chains = {
+			{
+				.name	= "PREROUTING",
+				.type	= "filter",
+				.prio	= -300,	/* NF_IP_PRI_RAW */
+				.hook	= NF_INET_PRE_ROUTING,
+			},
+			{
+				.name	= "OUTPUT",
+				.type	= "filter",
+				.prio	= -300,	/* NF_IP_PRI_RAW */
+				.hook	= NF_INET_LOCAL_OUT,
+			},
+		},
+	},
+	[NFT_TABLE_MANGLE] = {
+		.name	= "mangle",
+		.type	= NFT_TABLE_MANGLE,
+		.chains = {
+			{
+				.name	= "PREROUTING",
+				.type	= "filter",
+				.prio	= -150,	/* NF_IP_PRI_MANGLE */
+				.hook	= NF_INET_PRE_ROUTING,
+			},
+			{
+				.name	= "INPUT",
+				.type	= "filter",
+				.prio	= -150,	/* NF_IP_PRI_MANGLE */
+				.hook	= NF_INET_LOCAL_IN,
+			},
+			{
+				.name	= "FORWARD",
+				.type	= "filter",
+				.prio	= -150,	/* NF_IP_PRI_MANGLE */
+				.hook	= NF_INET_FORWARD,
+			},
+			{
+				.name	= "OUTPUT",
+				.type	= "route",
+				.prio	= -150,	/* NF_IP_PRI_MANGLE */
+				.hook	= NF_INET_LOCAL_OUT,
+			},
+			{
+				.name	= "POSTROUTING",
+				.type	= "filter",
+				.prio	= -150,	/* NF_IP_PRI_MANGLE */
+				.hook	= NF_INET_POST_ROUTING,
+			},
+		},
+	},
+	[NFT_TABLE_FILTER] = {
+		.name	= "filter",
+		.type	= NFT_TABLE_FILTER,
+		.chains = {
+			{
+				.name	= "INPUT",
+				.type	= "filter",
+				.prio	= 0,	/* NF_IP_PRI_FILTER */
+				.hook	= NF_INET_LOCAL_IN,
+			},
+			{
+				.name	= "FORWARD",
+				.type	= "filter",
+				.prio	= 0,	/* NF_IP_PRI_FILTER */
+				.hook	= NF_INET_FORWARD,
+			},
+			{
+				.name	= "OUTPUT",
+				.type	= "filter",
+				.prio	= 0,	/* NF_IP_PRI_FILTER */
+				.hook	= NF_INET_LOCAL_OUT,
+			},
+		},
+	},
+	[NFT_TABLE_SECURITY] = {
+		.name	= "security",
+		.type	= NFT_TABLE_SECURITY,
+		.chains = {
+			{
+				.name	= "INPUT",
+				.type	= "filter",
+				.prio	= 150,	/* NF_IP_PRI_SECURITY */
+				.hook	= NF_INET_LOCAL_IN,
+			},
+			{
+				.name	= "FORWARD",
+				.type	= "filter",
+				.prio	= 150,	/* NF_IP_PRI_SECURITY */
+				.hook	= NF_INET_FORWARD,
+			},
+			{
+				.name	= "OUTPUT",
+				.type	= "filter",
+				.prio	= 150,	/* NF_IP_PRI_SECURITY */
+				.hook	= NF_INET_LOCAL_OUT,
+			},
+		},
+	},
+	[NFT_TABLE_NAT] = {
+		.name	= "nat",
+		.type	= NFT_TABLE_NAT,
+		.chains = {
+			{
+				.name	= "PREROUTING",
+				.type	= "nat",
+				.prio	= -100, /* NF_IP_PRI_NAT_DST */
+				.hook	= NF_INET_PRE_ROUTING,
+			},
+			{
+				.name	= "INPUT",
+				.type	= "nat",
+				.prio	= 100, /* NF_IP_PRI_NAT_SRC */
+				.hook	= NF_INET_LOCAL_IN,
+			},
+			{
+				.name	= "POSTROUTING",
+				.type	= "nat",
+				.prio	= 100, /* NF_IP_PRI_NAT_SRC */
+				.hook	= NF_INET_POST_ROUTING,
+			},
+			{
+				.name	= "OUTPUT",
+				.type	= "nat",
+				.prio	= -100, /* NF_IP_PRI_NAT_DST */
+				.hook	= NF_INET_LOCAL_OUT,
+			},
+		},
+	},
+};
+
+#include <linux/netfilter_arp.h>
+
+const struct builtin_table xtables_arp[NFT_TABLE_MAX] = {
+	[NFT_TABLE_FILTER] = {
+	.name   = "filter",
+	.type	= NFT_TABLE_FILTER,
+	.chains = {
+			{
+				.name   = "INPUT",
+				.type   = "filter",
+				.prio   = NF_IP_PRI_FILTER,
+				.hook   = NF_ARP_IN,
+			},
+			{
+				.name   = "OUTPUT",
+				.type   = "filter",
+				.prio   = NF_IP_PRI_FILTER,
+				.hook   = NF_ARP_OUT,
+			},
+		},
+	},
+};
+
+#include <linux/netfilter_bridge.h>
+
+const struct builtin_table xtables_bridge[NFT_TABLE_MAX] = {
+	[NFT_TABLE_FILTER] = {
+		.name = "filter",
+		.type	= NFT_TABLE_FILTER,
+		.chains = {
+			{
+				.name   = "INPUT",
+				.type   = "filter",
+				.prio   = NF_BR_PRI_FILTER_BRIDGED,
+				.hook   = NF_BR_LOCAL_IN,
+			},
+			{
+				.name   = "FORWARD",
+				.type   = "filter",
+				.prio   = NF_BR_PRI_FILTER_BRIDGED,
+				.hook   = NF_BR_FORWARD,
+			},
+			{
+				.name   = "OUTPUT",
+				.type   = "filter",
+				.prio   = NF_BR_PRI_FILTER_BRIDGED,
+				.hook   = NF_BR_LOCAL_OUT,
+			},
+		},
+	},
+	[NFT_TABLE_NAT] = {
+		.name = "nat",
+		.type	= NFT_TABLE_NAT,
+		.chains = {
+			{
+				.name   = "PREROUTING",
+				.type   = "filter",
+				.prio   = NF_BR_PRI_NAT_DST_BRIDGED,
+				.hook   = NF_BR_PRE_ROUTING,
+			},
+			{
+				.name   = "OUTPUT",
+				.type   = "filter",
+				.prio   = NF_BR_PRI_NAT_DST_OTHER,
+				.hook   = NF_BR_LOCAL_OUT,
+			},
+			{
+				.name   = "POSTROUTING",
+				.type   = "filter",
+				.prio   = NF_BR_PRI_NAT_SRC,
+				.hook   = NF_BR_POST_ROUTING,
+			},
+		},
+	},
+};
+
+static int nft_table_builtin_add(struct nft_handle *h,
+				 const struct builtin_table *_t)
+{
+	struct nftnl_table *t;
+	int ret;
+
+	if (h->cache->table[_t->type].exists)
+		return 0;
+
+	t = nftnl_table_alloc();
+	if (t == NULL)
+		return -1;
+
+	nftnl_table_set_str(t, NFTNL_TABLE_NAME, _t->name);
+
+	ret = batch_table_add(h, NFT_COMPAT_TABLE_ADD, t) ? 0 : - 1;
+
+	return ret;
+}
+
+static struct nftnl_chain *
+nft_chain_builtin_alloc(const struct builtin_table *table,
+			const struct builtin_chain *chain, int policy)
+{
+	struct nftnl_chain *c;
+
+	c = nftnl_chain_alloc();
+	if (c == NULL)
+		return NULL;
+
+	nftnl_chain_set_str(c, NFTNL_CHAIN_TABLE, table->name);
+	nftnl_chain_set_str(c, NFTNL_CHAIN_NAME, chain->name);
+	nftnl_chain_set_u32(c, NFTNL_CHAIN_HOOKNUM, chain->hook);
+	nftnl_chain_set_u32(c, NFTNL_CHAIN_PRIO, chain->prio);
+	if (policy >= 0)
+		nftnl_chain_set_u32(c, NFTNL_CHAIN_POLICY, policy);
+
+	nftnl_chain_set_str(c, NFTNL_CHAIN_TYPE, chain->type);
+
+	return c;
+}
+
+static void nft_chain_builtin_add(struct nft_handle *h,
+				  const struct builtin_table *table,
+				  const struct builtin_chain *chain,
+				  bool fake)
+{
+	struct nftnl_chain *c;
+
+	c = nft_chain_builtin_alloc(table, chain, NF_ACCEPT);
+	if (c == NULL)
+		return;
+
+	if (!fake)
+		batch_chain_add(h, NFT_COMPAT_CHAIN_ADD, c);
+	nft_cache_add_chain(h, table, c);
+}
+
+/* find if built-in table already exists */
+const struct builtin_table *
+nft_table_builtin_find(struct nft_handle *h, const char *table)
+{
+	int i;
+	bool found = false;
+
+	for (i = 0; i < NFT_TABLE_MAX; i++) {
+		if (h->tables[i].name == NULL)
+			continue;
+
+		if (strcmp(h->tables[i].name, table) != 0)
+			continue;
+
+		found = true;
+		break;
+	}
+
+	return found ? &h->tables[i] : NULL;
+}
+
+/* find if built-in chain already exists */
+const struct builtin_chain *
+nft_chain_builtin_find(const struct builtin_table *t, const char *chain)
+{
+	int i;
+	bool found = false;
+
+	for (i=0; i<NF_IP_NUMHOOKS && t->chains[i].name != NULL; i++) {
+		if (strcmp(t->chains[i].name, chain) != 0)
+			continue;
+
+		found = true;
+		break;
+	}
+	return found ? &t->chains[i] : NULL;
+}
+
+static void nft_chain_builtin_init(struct nft_handle *h,
+				   const struct builtin_table *table)
+{
+	int i;
+
+	/* Initialize built-in chains if they don't exist yet */
+	for (i=0; i < NF_INET_NUMHOOKS && table->chains[i].name != NULL; i++) {
+		if (nft_chain_find(h, table->name, table->chains[i].name))
+			continue;
+
+		nft_chain_builtin_add(h, table, &table->chains[i], false);
+	}
+}
+
+static const struct builtin_table *
+nft_xt_builtin_table_init(struct nft_handle *h, const char *table)
+{
+	const struct builtin_table *t;
+
+	if (!h->cache_init)
+		return NULL;
+
+	t = nft_table_builtin_find(h, table);
+	if (t == NULL)
+		return NULL;
+
+	if (nft_table_builtin_add(h, t) < 0)
+		return NULL;
+
+	return t;
+}
+
+static int nft_xt_builtin_init(struct nft_handle *h, const char *table,
+			       const char *chain)
+{
+	const struct builtin_table *t;
+	const struct builtin_chain *c;
+
+	if (!h->cache_init)
+		return 0;
+
+	t = nft_xt_builtin_table_init(h, table);
+	if (!t)
+		return -1;
+
+	if (h->cache_req.level < NFT_CL_CHAINS)
+		return 0;
+
+	if (!chain) {
+		nft_chain_builtin_init(h, t);
+		return 0;
+	}
+
+	c = nft_chain_builtin_find(t, chain);
+	if (!c)
+		return -1;
+
+	if (h->cache->table[t->type].base_chains[c->hook])
+		return 0;
+
+	nft_chain_builtin_add(h, t, c, false);
+	return 0;
+}
+
+static bool nft_chain_builtin(struct nftnl_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 nftnl_chain_get(c, NFTNL_CHAIN_HOOKNUM) != NULL;
+}
+
+static int __nft_xt_fake_builtin_chains(struct nft_handle *h,
+				        const char *table, void *data)
+{
+	const char *chain = data ? *(const char **)data : NULL;
+	const struct builtin_table *t;
+	struct nft_chain **bcp;
+	int i;
+
+	t = nft_table_builtin_find(h, table);
+	if (!t)
+		return -1;
+
+	bcp = h->cache->table[t->type].base_chains;
+	for (i = 0; i < NF_INET_NUMHOOKS && t->chains[i].name; i++) {
+		if (bcp[t->chains[i].hook])
+			continue;
+
+		if (chain && strcmp(chain, t->chains[i].name))
+			continue;
+
+		nft_chain_builtin_add(h, t, &t->chains[i], true);
+	}
+	return 0;
+}
+
+int nft_xt_fake_builtin_chains(struct nft_handle *h,
+			       const char *table, const char *chain)
+{
+	if (table)
+		return __nft_xt_fake_builtin_chains(h, table, &chain);
+
+	return nft_for_each_table(h, __nft_xt_fake_builtin_chains, &chain);
+}
+
+int nft_restart(struct nft_handle *h)
+{
+	mnl_socket_close(h->nl);
+
+	h->nl = mnl_socket_open(NETLINK_NETFILTER);
+	if (h->nl == NULL)
+		return -1;
+
+	if (mnl_socket_bind(h->nl, 0, MNL_SOCKET_AUTOPID) < 0)
+		return -1;
+
+	h->portid = mnl_socket_get_portid(h->nl);
+	h->nlsndbuffsiz = 0;
+	h->nlrcvbuffsiz = 0;
+
+	return 0;
+}
+
+int nft_init(struct nft_handle *h, int family, const struct builtin_table *t)
+{
+	memset(h, 0, sizeof(*h));
+
+	h->nl = mnl_socket_open(NETLINK_NETFILTER);
+	if (h->nl == NULL)
+		return -1;
+
+	if (mnl_socket_bind(h->nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+		mnl_socket_close(h->nl);
+		return -1;
+	}
+
+	h->ops = nft_family_ops_lookup(family);
+	if (!h->ops)
+		xtables_error(PARAMETER_PROBLEM, "Unknown family");
+
+	h->portid = mnl_socket_get_portid(h->nl);
+	h->tables = t;
+	h->cache = &h->__cache[0];
+	h->family = family;
+
+	INIT_LIST_HEAD(&h->obj_list);
+	INIT_LIST_HEAD(&h->err_list);
+	INIT_LIST_HEAD(&h->cmd_list);
+	INIT_LIST_HEAD(&h->cache_req.chain_list);
+
+	return 0;
+}
+
+void nft_fini(struct nft_handle *h)
+{
+	struct list_head *pos, *n;
+
+	list_for_each_safe(pos, n, &h->cmd_list)
+		nft_cmd_free(list_entry(pos, struct nft_cmd, head));
+
+	list_for_each_safe(pos, n, &h->obj_list)
+		batch_obj_del(h, list_entry(pos, struct obj_update, head));
+
+	list_for_each_safe(pos, n, &h->err_list)
+		mnl_err_list_free(list_entry(pos, struct mnl_err, head));
+
+	nft_release_cache(h);
+	mnl_socket_close(h->nl);
+}
+
+static void nft_chain_print_debug(struct nftnl_chain *c, struct nlmsghdr *nlh)
+{
+#ifdef NLDEBUG
+	char tmp[1024];
+
+	nftnl_chain_snprintf(tmp, sizeof(tmp), c, 0, 0);
+	printf("DEBUG: chain: %s\n", tmp);
+	mnl_nlmsg_fprintf(stdout, nlh, nlh->nlmsg_len, sizeof(struct nfgenmsg));
+#endif
+}
+
+static struct nftnl_chain *nft_chain_new(struct nft_handle *h,
+				       const char *table, const char *chain,
+				       int policy,
+				       const struct xt_counters *counters)
+{
+	struct nftnl_chain *c;
+	const struct builtin_table *_t;
+	const struct builtin_chain *_c;
+
+	_t = nft_table_builtin_find(h, table);
+	if (!_t) {
+		errno = ENXIO;
+		return NULL;
+	}
+
+	/* if this built-in table does not exists, create it */
+	nft_xt_builtin_init(h, table, chain);
+
+	_c = nft_chain_builtin_find(_t, chain);
+	if (_c != NULL) {
+		/* This is a built-in chain */
+		c = nft_chain_builtin_alloc(_t, _c, policy);
+		if (c == NULL)
+			return NULL;
+	} else {
+		errno = ENOENT;
+		return NULL;
+	}
+
+	if (counters) {
+		nftnl_chain_set_u64(c, NFTNL_CHAIN_BYTES,
+					counters->bcnt);
+		nftnl_chain_set_u64(c, NFTNL_CHAIN_PACKETS,
+					counters->pcnt);
+	}
+
+	return c;
+}
+
+int nft_chain_set(struct nft_handle *h, const char *table,
+		  const char *chain, const char *policy,
+		  const struct xt_counters *counters)
+{
+	struct nftnl_chain *c = NULL;
+
+	nft_fn = nft_chain_set;
+
+	if (strcmp(policy, "DROP") == 0)
+		c = nft_chain_new(h, table, chain, NF_DROP, counters);
+	else if (strcmp(policy, "ACCEPT") == 0)
+		c = nft_chain_new(h, table, chain, NF_ACCEPT, counters);
+	else if (strcmp(policy, "-") == 0)
+		c = nft_chain_new(h, table, chain, -1, counters);
+	else
+		errno = EINVAL;
+
+	if (c == NULL)
+		return 0;
+
+	if (!batch_chain_add(h, NFT_COMPAT_CHAIN_UPDATE, c))
+		return 0;
+
+	/* the core expects 1 for success and 0 for error */
+	return 1;
+}
+
+static int __add_match(struct nftnl_expr *e, struct xt_entry_match *m)
+{
+	void *info;
+
+	nftnl_expr_set(e, NFTNL_EXPR_MT_NAME, m->u.user.name, strlen(m->u.user.name));
+	nftnl_expr_set_u32(e, NFTNL_EXPR_MT_REV, m->u.user.revision);
+
+	info = calloc(1, m->u.match_size);
+	if (info == NULL)
+		return -ENOMEM;
+
+	memcpy(info, m->data, m->u.match_size - sizeof(*m));
+	nftnl_expr_set(e, NFTNL_EXPR_MT_INFO, info, m->u.match_size - sizeof(*m));
+
+	return 0;
+}
+
+static int add_nft_limit(struct nftnl_rule *r, struct xt_entry_match *m)
+{
+	struct xt_rateinfo *rinfo = (void *)m->data;
+	static const uint32_t mult[] = {
+		XT_LIMIT_SCALE*24*60*60,	/* day */
+		XT_LIMIT_SCALE*60*60,		/* hour */
+		XT_LIMIT_SCALE*60,		/* min */
+		XT_LIMIT_SCALE,			/* sec */
+	};
+	struct nftnl_expr *expr;
+	int i;
+
+	expr = nftnl_expr_alloc("limit");
+	if (!expr)
+		return -ENOMEM;
+
+	for (i = 1; i < ARRAY_SIZE(mult); i++) {
+		if (rinfo->avg > mult[i] ||
+		    mult[i] / rinfo->avg < mult[i] % rinfo->avg)
+			break;
+	}
+
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_LIMIT_TYPE, NFT_LIMIT_PKTS);
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_LIMIT_FLAGS, 0);
+
+	nftnl_expr_set_u64(expr, NFTNL_EXPR_LIMIT_RATE,
+			   mult[i - 1] / rinfo->avg);
+        nftnl_expr_set_u64(expr, NFTNL_EXPR_LIMIT_UNIT,
+			   mult[i - 1] / XT_LIMIT_SCALE);
+
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_LIMIT_BURST, rinfo->burst);
+
+	nftnl_rule_add_expr(r, expr);
+	return 0;
+}
+
+static struct nftnl_set *add_anon_set(struct nft_handle *h, const char *table,
+				      uint32_t flags, uint32_t key_type,
+				      uint32_t key_len, uint32_t size)
+{
+	static uint32_t set_id = 0;
+	struct nftnl_set *s;
+	struct nft_cmd *cmd;
+
+	s = nftnl_set_alloc();
+	if (!s)
+		return NULL;
+
+	nftnl_set_set_u32(s, NFTNL_SET_FAMILY, h->family);
+	nftnl_set_set_str(s, NFTNL_SET_TABLE, table);
+	nftnl_set_set_str(s, NFTNL_SET_NAME, "__set%d");
+	nftnl_set_set_u32(s, NFTNL_SET_ID, ++set_id);
+	nftnl_set_set_u32(s, NFTNL_SET_FLAGS,
+			  NFT_SET_ANONYMOUS | NFT_SET_CONSTANT | flags);
+	nftnl_set_set_u32(s, NFTNL_SET_KEY_TYPE, key_type);
+	nftnl_set_set_u32(s, NFTNL_SET_KEY_LEN, key_len);
+	nftnl_set_set_u32(s, NFTNL_SET_DESC_SIZE, size);
+
+	cmd = nft_cmd_new(h, NFT_COMPAT_SET_ADD, table, NULL, NULL, -1, false);
+	if (!cmd) {
+		nftnl_set_free(s);
+		return NULL;
+	}
+	cmd->obj.set = s;
+
+	return s;
+}
+
+static struct nftnl_expr *
+gen_payload(uint32_t base, uint32_t offset, uint32_t len, uint32_t dreg)
+{
+	struct nftnl_expr *e = nftnl_expr_alloc("payload");
+
+	if (!e)
+		return NULL;
+	nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_BASE, base);
+	nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_OFFSET, offset);
+	nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_LEN, len);
+	nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_DREG, dreg);
+	return e;
+}
+
+static struct nftnl_expr *
+gen_lookup(uint32_t sreg, const char *set_name, uint32_t set_id, uint32_t flags)
+{
+	struct nftnl_expr *e = nftnl_expr_alloc("lookup");
+
+	if (!e)
+		return NULL;
+	nftnl_expr_set_u32(e, NFTNL_EXPR_LOOKUP_SREG, sreg);
+	nftnl_expr_set_str(e, NFTNL_EXPR_LOOKUP_SET, set_name);
+	nftnl_expr_set_u32(e, NFTNL_EXPR_LOOKUP_SET_ID, set_id);
+	nftnl_expr_set_u32(e, NFTNL_EXPR_LOOKUP_FLAGS, flags);
+	return e;
+}
+
+/* simplified nftables:include/netlink.h, netlink_padded_len() */
+#define NETLINK_ALIGN		4
+
+/* from nftables:include/datatype.h, TYPE_BITS */
+#define CONCAT_TYPE_BITS	6
+
+/* from nftables:include/datatype.h, enum datatypes */
+#define NFT_DATATYPE_IPADDR	7
+#define NFT_DATATYPE_ETHERADDR	9
+
+static int __add_nft_among(struct nft_handle *h, const char *table,
+			   struct nftnl_rule *r, struct nft_among_pair *pairs,
+			   int cnt, bool dst, bool inv, bool ip)
+{
+	uint32_t set_id, type = NFT_DATATYPE_ETHERADDR, len = ETH_ALEN;
+	/* { !dst, dst } */
+	static const int eth_addr_off[] = {
+		offsetof(struct ether_header, ether_shost),
+		offsetof(struct ether_header, ether_dhost)
+	};
+	static const int ip_addr_off[] = {
+		offsetof(struct iphdr, saddr),
+		offsetof(struct iphdr, daddr)
+	};
+	struct nftnl_expr *e;
+	struct nftnl_set *s;
+	uint32_t flags = 0;
+	int idx = 0;
+
+	if (ip) {
+		type = type << CONCAT_TYPE_BITS | NFT_DATATYPE_IPADDR;
+		len += sizeof(struct in_addr) + NETLINK_ALIGN - 1;
+		len &= ~(NETLINK_ALIGN - 1);
+		flags = NFT_SET_INTERVAL;
+	}
+
+	s = add_anon_set(h, table, flags, type, len, cnt);
+	if (!s)
+		return -ENOMEM;
+	set_id = nftnl_set_get_u32(s, NFTNL_SET_ID);
+
+	if (ip) {
+		uint8_t field_len[2] = { ETH_ALEN, sizeof(struct in_addr) };
+
+		nftnl_set_set_data(s, NFTNL_SET_DESC_CONCAT,
+				   field_len, sizeof(field_len));
+	}
+
+	for (idx = 0; idx < cnt; idx++) {
+		struct nftnl_set_elem *elem = nftnl_set_elem_alloc();
+
+		if (!elem)
+			return -ENOMEM;
+		nftnl_set_elem_set(elem, NFTNL_SET_ELEM_KEY,
+				   &pairs[idx], len);
+		if (ip) {
+			struct in_addr tmp = pairs[idx].in;
+
+			if (tmp.s_addr == INADDR_ANY)
+				pairs[idx].in.s_addr = INADDR_BROADCAST;
+			nftnl_set_elem_set(elem, NFTNL_SET_ELEM_KEY_END,
+					   &pairs[idx], len);
+			pairs[idx].in = tmp;
+		}
+		nftnl_set_elem_add(s, elem);
+	}
+
+	e = gen_payload(NFT_PAYLOAD_LL_HEADER,
+			eth_addr_off[dst], ETH_ALEN, NFT_REG_1);
+	if (!e)
+		return -ENOMEM;
+	nftnl_rule_add_expr(r, e);
+
+	if (ip) {
+		e = gen_payload(NFT_PAYLOAD_NETWORK_HEADER, ip_addr_off[dst],
+				sizeof(struct in_addr), NFT_REG32_02);
+		if (!e)
+			return -ENOMEM;
+		nftnl_rule_add_expr(r, e);
+	}
+
+	e = gen_lookup(NFT_REG_1, "__set%d", set_id, inv);
+	if (!e)
+		return -ENOMEM;
+	nftnl_rule_add_expr(r, e);
+
+	return 0;
+}
+
+static int add_nft_among(struct nft_handle *h,
+			 struct nftnl_rule *r, struct xt_entry_match *m)
+{
+	struct nft_among_data *data = (struct nft_among_data *)m->data;
+	const char *table = nftnl_rule_get(r, NFTNL_RULE_TABLE);
+
+	if ((data->src.cnt && data->src.ip) ||
+	    (data->dst.cnt && data->dst.ip)) {
+		uint16_t eth_p_ip = htons(ETH_P_IP);
+
+		add_meta(r, NFT_META_PROTOCOL);
+		add_cmp_ptr(r, NFT_CMP_EQ, &eth_p_ip, 2);
+	}
+
+	if (data->src.cnt)
+		__add_nft_among(h, table, r, data->pairs, data->src.cnt,
+				false, data->src.inv, data->src.ip);
+	if (data->dst.cnt)
+		__add_nft_among(h, table, r, data->pairs + data->src.cnt,
+				data->dst.cnt, true, data->dst.inv,
+				data->dst.ip);
+	return 0;
+}
+
+int add_match(struct nft_handle *h,
+	      struct nftnl_rule *r, struct xt_entry_match *m)
+{
+	struct nftnl_expr *expr;
+	int ret;
+
+	if (!strcmp(m->u.user.name, "limit"))
+		return add_nft_limit(r, m);
+	else if (!strcmp(m->u.user.name, "among"))
+		return add_nft_among(h, r, m);
+
+	expr = nftnl_expr_alloc("match");
+	if (expr == NULL)
+		return -ENOMEM;
+
+	ret = __add_match(expr, m);
+	nftnl_rule_add_expr(r, expr);
+
+	return ret;
+}
+
+static int __add_target(struct nftnl_expr *e, struct xt_entry_target *t)
+{
+	void *info;
+
+	nftnl_expr_set(e, NFTNL_EXPR_TG_NAME, t->u.user.name,
+			  strlen(t->u.user.name));
+	nftnl_expr_set_u32(e, NFTNL_EXPR_TG_REV, t->u.user.revision);
+
+	info = calloc(1, t->u.target_size);
+	if (info == NULL)
+		return -ENOMEM;
+
+	memcpy(info, t->data, t->u.target_size - sizeof(*t));
+	nftnl_expr_set(e, NFTNL_EXPR_TG_INFO, info, t->u.target_size - sizeof(*t));
+
+	return 0;
+}
+
+static int add_meta_nftrace(struct nftnl_rule *r)
+{
+	struct nftnl_expr *expr;
+
+	expr = nftnl_expr_alloc("immediate");
+	if (expr == NULL)
+		return -ENOMEM;
+
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_IMM_DREG, NFT_REG32_01);
+	nftnl_expr_set_u8(expr, NFTNL_EXPR_IMM_DATA, 1);
+	nftnl_rule_add_expr(r, expr);
+
+	expr = nftnl_expr_alloc("meta");
+	if (expr == NULL)
+		return -ENOMEM;
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_META_KEY, NFT_META_NFTRACE);
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_META_SREG, NFT_REG32_01);
+
+	nftnl_rule_add_expr(r, expr);
+	return 0;
+}
+
+int add_target(struct nftnl_rule *r, struct xt_entry_target *t)
+{
+	struct nftnl_expr *expr;
+	int ret;
+
+	if (strcmp(t->u.user.name, "TRACE") == 0)
+		return add_meta_nftrace(r);
+
+	expr = nftnl_expr_alloc("target");
+	if (expr == NULL)
+		return -ENOMEM;
+
+	ret = __add_target(expr, t);
+	nftnl_rule_add_expr(r, expr);
+
+	return ret;
+}
+
+int add_jumpto(struct nftnl_rule *r, const char *name, int verdict)
+{
+	struct nftnl_expr *expr;
+
+	expr = nftnl_expr_alloc("immediate");
+	if (expr == NULL)
+		return -ENOMEM;
+
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_IMM_DREG, NFT_REG_VERDICT);
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_IMM_VERDICT, verdict);
+	nftnl_expr_set_str(expr, NFTNL_EXPR_IMM_CHAIN, (char *)name);
+	nftnl_rule_add_expr(r, expr);
+
+	return 0;
+}
+
+int add_verdict(struct nftnl_rule *r, int verdict)
+{
+	struct nftnl_expr *expr;
+
+	expr = nftnl_expr_alloc("immediate");
+	if (expr == NULL)
+		return -ENOMEM;
+
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_IMM_DREG, NFT_REG_VERDICT);
+	nftnl_expr_set_u32(expr, NFTNL_EXPR_IMM_VERDICT, verdict);
+	nftnl_rule_add_expr(r, expr);
+
+	return 0;
+}
+
+int add_action(struct nftnl_rule *r, struct iptables_command_state *cs,
+	       bool goto_set)
+{
+       int ret = 0;
+
+       /* If no target at all, add nothing (default to continue) */
+       if (cs->target != NULL) {
+	       /* Standard target? */
+	       if (strcmp(cs->jumpto, XTC_LABEL_ACCEPT) == 0)
+		       ret = add_verdict(r, NF_ACCEPT);
+	       else if (strcmp(cs->jumpto, XTC_LABEL_DROP) == 0)
+		       ret = add_verdict(r, NF_DROP);
+	       else if (strcmp(cs->jumpto, XTC_LABEL_RETURN) == 0)
+		       ret = add_verdict(r, NFT_RETURN);
+	       else
+		       ret = add_target(r, cs->target->t);
+       } else if (strlen(cs->jumpto) > 0) {
+	       /* Not standard, then it's a go / jump to chain */
+	       if (goto_set)
+		       ret = add_jumpto(r, cs->jumpto, NFT_GOTO);
+	       else
+		       ret = add_jumpto(r, cs->jumpto, NFT_JUMP);
+       }
+       return ret;
+}
+
+static void nft_rule_print_debug(struct nftnl_rule *r, struct nlmsghdr *nlh)
+{
+#ifdef NLDEBUG
+	char tmp[1024];
+
+	nftnl_rule_snprintf(tmp, sizeof(tmp), r, 0, 0);
+	printf("DEBUG: rule: %s\n", tmp);
+	mnl_nlmsg_fprintf(stdout, nlh, nlh->nlmsg_len, sizeof(struct nfgenmsg));
+#endif
+}
+
+int add_counters(struct nftnl_rule *r, uint64_t packets, uint64_t bytes)
+{
+	struct nftnl_expr *expr;
+
+	expr = nftnl_expr_alloc("counter");
+	if (expr == NULL)
+		return -ENOMEM;
+
+	nftnl_expr_set_u64(expr, NFTNL_EXPR_CTR_PACKETS, packets);
+	nftnl_expr_set_u64(expr, NFTNL_EXPR_CTR_BYTES, bytes);
+
+	nftnl_rule_add_expr(r, expr);
+
+	return 0;
+}
+
+enum udata_type {
+	UDATA_TYPE_COMMENT,
+	UDATA_TYPE_EBTABLES_POLICY,
+	__UDATA_TYPE_MAX,
+};
+#define UDATA_TYPE_MAX (__UDATA_TYPE_MAX - 1)
+
+static int parse_udata_cb(const struct nftnl_udata *attr, void *data)
+{
+	unsigned char *value = nftnl_udata_get(attr);
+	uint8_t type = nftnl_udata_type(attr);
+	uint8_t len = nftnl_udata_len(attr);
+	const struct nftnl_udata **tb = data;
+
+	switch (type) {
+	case UDATA_TYPE_COMMENT:
+		if (value[len - 1] != '\0')
+			return -1;
+		break;
+	case UDATA_TYPE_EBTABLES_POLICY:
+		break;
+	default:
+		return 0;
+	}
+	tb[type] = attr;
+	return 0;
+}
+
+char *get_comment(const void *data, uint32_t data_len)
+{
+	const struct nftnl_udata *tb[UDATA_TYPE_MAX + 1] = {};
+
+	if (nftnl_udata_parse(data, data_len, parse_udata_cb, tb) < 0)
+		return NULL;
+
+	if (!tb[UDATA_TYPE_COMMENT])
+		return NULL;
+
+	return nftnl_udata_get(tb[UDATA_TYPE_COMMENT]);
+}
+
+void add_compat(struct nftnl_rule *r, uint32_t proto, bool inv)
+{
+	nftnl_rule_set_u32(r, NFTNL_RULE_COMPAT_PROTO, proto);
+	nftnl_rule_set_u32(r, NFTNL_RULE_COMPAT_FLAGS,
+			      inv ? NFT_RULE_COMPAT_F_INV : 0);
+}
+
+struct nftnl_rule *
+nft_rule_new(struct nft_handle *h, const char *chain, const char *table,
+	     void *data)
+{
+	struct nftnl_rule *r;
+
+	r = nftnl_rule_alloc();
+	if (r == NULL)
+		return NULL;
+
+	nftnl_rule_set_u32(r, NFTNL_RULE_FAMILY, h->family);
+	nftnl_rule_set_str(r, NFTNL_RULE_TABLE, table);
+	nftnl_rule_set_str(r, NFTNL_RULE_CHAIN, chain);
+
+	if (h->ops->add(h, r, data) < 0)
+		goto err;
+
+	return r;
+err:
+	nftnl_rule_free(r);
+	return NULL;
+}
+
+int
+nft_rule_append(struct nft_handle *h, const char *chain, const char *table,
+		struct nftnl_rule *r, struct nftnl_rule *ref, bool verbose)
+{
+	struct nft_chain *c;
+	int type;
+
+	nft_xt_builtin_init(h, table, chain);
+
+	nft_fn = nft_rule_append;
+
+	if (ref) {
+		nftnl_rule_set_u64(r, NFTNL_RULE_HANDLE,
+				   nftnl_rule_get_u64(ref, NFTNL_RULE_HANDLE));
+		type = NFT_COMPAT_RULE_REPLACE;
+	} else
+		type = NFT_COMPAT_RULE_APPEND;
+
+	if (batch_rule_add(h, type, r) == NULL)
+		return 0;
+
+	if (verbose)
+		h->ops->print_rule(h, r, 0, FMT_PRINT_RULE);
+
+	if (ref) {
+		nftnl_chain_rule_insert_at(r, ref);
+		nftnl_chain_rule_del(ref);
+		nftnl_rule_free(ref);
+	} else {
+		c = nft_chain_find(h, table, chain);
+		if (!c) {
+			errno = ENOENT;
+			return 0;
+		}
+		nftnl_chain_rule_add_tail(r, c->nftnl);
+	}
+
+	return 1;
+}
+
+void
+nft_rule_print_save(struct nft_handle *h, const struct nftnl_rule *r,
+		    enum nft_rule_print type, unsigned int format)
+{
+	const char *chain = nftnl_rule_get_str(r, NFTNL_RULE_CHAIN);
+	struct iptables_command_state cs = {};
+	struct nft_family_ops *ops = h->ops;
+
+	ops->rule_to_cs(h, r, &cs);
+
+	if (!(format & (FMT_NOCOUNTS | FMT_C_COUNTS)))
+		printf("[%llu:%llu] ", (unsigned long long)cs.counters.pcnt,
+				       (unsigned long long)cs.counters.bcnt);
+
+	/* print chain name */
+	switch(type) {
+	case NFT_RULE_APPEND:
+		printf("-A %s ", chain);
+		break;
+	case NFT_RULE_DEL:
+		printf("-D %s ", chain);
+		break;
+	}
+
+	if (ops->save_rule)
+		ops->save_rule(&cs, format);
+
+	if (ops->clear_cs)
+		ops->clear_cs(&cs);
+}
+
+static bool nft_rule_is_policy_rule(struct nftnl_rule *r)
+{
+	const struct nftnl_udata *tb[UDATA_TYPE_MAX + 1] = {};
+	const void *data;
+	uint32_t len;
+
+	if (!nftnl_rule_is_set(r, NFTNL_RULE_USERDATA))
+		return false;
+
+	data = nftnl_rule_get_data(r, NFTNL_RULE_USERDATA, &len);
+	if (nftnl_udata_parse(data, len, parse_udata_cb, tb) < 0)
+		return NULL;
+
+	if (!tb[UDATA_TYPE_EBTABLES_POLICY] ||
+	    nftnl_udata_get_u32(tb[UDATA_TYPE_EBTABLES_POLICY]) != 1)
+		return false;
+
+	return true;
+}
+
+static struct nftnl_rule *nft_chain_last_rule(struct nftnl_chain *c)
+{
+	struct nftnl_rule *r = NULL, *last;
+	struct nftnl_rule_iter *iter;
+
+	iter = nftnl_rule_iter_create(c);
+	if (!iter)
+		return NULL;
+
+	do {
+		last = r;
+		r = nftnl_rule_iter_next(iter);
+	} while (r);
+	nftnl_rule_iter_destroy(iter);
+
+	return last;
+}
+
+void nft_bridge_chain_postprocess(struct nft_handle *h,
+				  struct nftnl_chain *c)
+{
+	struct nftnl_rule *last = nft_chain_last_rule(c);
+	struct nftnl_expr_iter *iter;
+	struct nftnl_expr *expr;
+	int verdict;
+
+	if (!last || !nft_rule_is_policy_rule(last))
+		return;
+
+	iter = nftnl_expr_iter_create(last);
+	if (!iter)
+		return;
+
+	expr = nftnl_expr_iter_next(iter);
+	if (!expr ||
+	    strcmp("counter", nftnl_expr_get_str(expr, NFTNL_EXPR_NAME)))
+		goto out_iter;
+
+	expr = nftnl_expr_iter_next(iter);
+	if (!expr ||
+	    strcmp("immediate", nftnl_expr_get_str(expr, NFTNL_EXPR_NAME)) ||
+	    !nftnl_expr_is_set(expr, NFTNL_EXPR_IMM_VERDICT))
+		goto out_iter;
+
+	verdict = nftnl_expr_get_u32(expr, NFTNL_EXPR_IMM_VERDICT);
+	switch (verdict) {
+	case NF_ACCEPT:
+	case NF_DROP:
+		break;
+	default:
+		goto out_iter;
+	}
+
+	nftnl_chain_set_u32(c, NFTNL_CHAIN_POLICY, verdict);
+	if (batch_rule_add(h, NFT_COMPAT_RULE_DELETE, last) == NULL)
+		fprintf(stderr, "Failed to delete old policy rule\n");
+	nftnl_chain_rule_del(last);
+out_iter:
+	nftnl_expr_iter_destroy(iter);
+}
+static const char *policy_name[NF_ACCEPT+1] = {
+	[NF_DROP] = "DROP",
+	[NF_ACCEPT] = "ACCEPT",
+};
+
+int nft_chain_save(struct nft_chain *nc, void *data)
+{
+	struct nftnl_chain *c = nc->nftnl;
+	struct nft_handle *h = data;
+	const char *policy = NULL;
+
+	if (nftnl_chain_is_set(c, NFTNL_CHAIN_POLICY)) {
+		policy = policy_name[nftnl_chain_get_u32(c, NFTNL_CHAIN_POLICY)];
+	} else if (nft_chain_builtin(c)) {
+		policy = "ACCEPT";
+	} else if (h->family == NFPROTO_BRIDGE) {
+		policy = "RETURN";
+	}
+
+	if (h->ops->save_chain)
+		h->ops->save_chain(c, policy);
+
+	return 0;
+}
+
+struct nft_rule_save_data {
+	struct nft_handle *h;
+	unsigned int format;
+};
+
+static int nft_rule_save_cb(struct nft_chain *c, void *data)
+{
+	struct nft_rule_save_data *d = data;
+	struct nftnl_rule_iter *iter;
+	struct nftnl_rule *r;
+
+	iter = nftnl_rule_iter_create(c->nftnl);
+	if (iter == NULL)
+		return 1;
+
+	r = nftnl_rule_iter_next(iter);
+	while (r != NULL) {
+		nft_rule_print_save(d->h, r, NFT_RULE_APPEND, d->format);
+		r = nftnl_rule_iter_next(iter);
+	}
+
+	nftnl_rule_iter_destroy(iter);
+	return 0;
+}
+
+int nft_rule_save(struct nft_handle *h, const char *table, unsigned int format)
+{
+	struct nft_rule_save_data d = {
+		.h = h,
+		.format = format,
+	};
+	int ret;
+
+	ret = nft_chain_foreach(h, table, nft_rule_save_cb, &d);
+
+	/* the core expects 1 for success and 0 for error */
+	return ret == 0 ? 1 : 0;
+}
+
+struct nftnl_set *nft_set_batch_lookup_byid(struct nft_handle *h,
+					    uint32_t set_id)
+{
+	struct obj_update *n;
+
+	list_for_each_entry(n, &h->obj_list, head) {
+		if (n->type == NFT_COMPAT_SET_ADD &&
+		    nftnl_set_get_u32(n->set, NFTNL_SET_ID) == set_id)
+			return n->set;
+	}
+
+	return NULL;
+}
+
+static void
+__nft_rule_flush(struct nft_handle *h, const char *table,
+		 const char *chain, bool verbose, bool skip)
+{
+	struct obj_update *obj;
+	struct nftnl_rule *r;
+
+	if (verbose && chain)
+		fprintf(stdout, "Flushing chain `%s'\n", chain);
+
+	r = nftnl_rule_alloc();
+	if (r == NULL)
+		return;
+
+	nftnl_rule_set_str(r, NFTNL_RULE_TABLE, table);
+	if (chain)
+		nftnl_rule_set_str(r, NFTNL_RULE_CHAIN, chain);
+
+	obj = batch_rule_add(h, NFT_COMPAT_RULE_FLUSH, r);
+	if (!obj) {
+		nftnl_rule_free(r);
+		return;
+	}
+
+	obj->skip = skip;
+}
+
+struct nft_rule_flush_data {
+	struct nft_handle *h;
+	const char *table;
+	bool verbose;
+};
+
+static int nft_rule_flush_cb(struct nft_chain *c, void *data)
+{
+	const char *chain = nftnl_chain_get_str(c->nftnl, NFTNL_CHAIN_NAME);
+	struct nft_rule_flush_data *d = data;
+
+	batch_chain_flush(d->h, d->table, chain);
+	__nft_rule_flush(d->h, d->table, chain, d->verbose, false);
+	flush_rule_cache(d->h, d->table, c);
+	return 0;
+}
+
+int nft_rule_flush(struct nft_handle *h, const char *chain, const char *table,
+		   bool verbose)
+{
+	struct nft_rule_flush_data d = {
+		.h = h,
+		.table = table,
+		.verbose = verbose,
+	};
+	struct nft_chain *c = NULL;
+	int ret = 0;
+
+	nft_fn = nft_rule_flush;
+
+	if (chain || verbose)
+		nft_xt_builtin_init(h, table, chain);
+	else if (!nft_table_find(h, table))
+		return 1;
+
+	if (chain) {
+		c = nft_chain_find(h, table, chain);
+		if (!c) {
+			errno = ENOENT;
+			return 0;
+		}
+	}
+
+	if (chain || !verbose) {
+		batch_chain_flush(h, table, chain);
+		__nft_rule_flush(h, table, chain, verbose, false);
+		flush_rule_cache(h, table, c);
+		return 1;
+	}
+
+	ret = nft_chain_foreach(h, table, nft_rule_flush_cb, &d);
+
+	/* 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)
+{
+	const struct builtin_table *t;
+	struct nftnl_chain *c;
+
+	nft_fn = nft_chain_user_add;
+
+	t = nft_xt_builtin_table_init(h, table);
+
+	if (nft_chain_exists(h, table, chain)) {
+		errno = EEXIST;
+		return 0;
+	}
+
+	c = nftnl_chain_alloc();
+	if (c == NULL)
+		return 0;
+
+	nftnl_chain_set_str(c, NFTNL_CHAIN_TABLE, table);
+	nftnl_chain_set_str(c, NFTNL_CHAIN_NAME, chain);
+	if (h->family == NFPROTO_BRIDGE)
+		nftnl_chain_set_u32(c, NFTNL_CHAIN_POLICY, NF_ACCEPT);
+
+	if (!batch_chain_add(h, NFT_COMPAT_CHAIN_USER_ADD, c))
+		return 0;
+
+	nft_cache_add_chain(h, t, c);
+
+	/* the core expects 1 for success and 0 for error */
+	return 1;
+}
+
+int nft_chain_restore(struct nft_handle *h, const char *chain, const char *table)
+{
+	const struct builtin_table *t;
+	struct obj_update *obj;
+	struct nftnl_chain *c;
+	struct nft_chain *nc;
+	bool created = false;
+
+	t = nft_xt_builtin_table_init(h, table);
+
+	nc = nft_chain_find(h, table, chain);
+	if (!nc) {
+		c = nftnl_chain_alloc();
+		if (!c)
+			return 0;
+
+		nftnl_chain_set_str(c, NFTNL_CHAIN_TABLE, table);
+		nftnl_chain_set_str(c, NFTNL_CHAIN_NAME, chain);
+		created = true;
+
+		nft_cache_add_chain(h, t, c);
+	} else {
+		c = nc->nftnl;
+
+		/* If the chain should vanish meanwhile, kernel genid changes
+		 * and the transaction is refreshed enabling the chain add
+		 * object. With the handle still set, kernel interprets it as a
+		 * chain replace job and errors since it is not found anymore.
+		 */
+		nftnl_chain_unset(c, NFTNL_CHAIN_HANDLE);
+	}
+
+	__nft_rule_flush(h, table, chain, false, created);
+
+	obj = batch_chain_add(h, NFT_COMPAT_CHAIN_USER_ADD, c);
+	if (!obj)
+		return 0;
+
+	obj->skip = !created;
+
+	/* the core expects 1 for success and 0 for error */
+	return 1;
+}
+
+/* From linux/netlink.h */
+#ifndef NLM_F_NONREC
+#define NLM_F_NONREC	0x100	/* Do not delete recursively    */
+#endif
+
+struct chain_user_del_data {
+	struct nft_handle	*handle;
+	bool			verbose;
+	int			builtin_err;
+};
+
+static int __nft_chain_user_del(struct nft_chain *nc, void *data)
+{
+	struct chain_user_del_data *d = data;
+	struct nftnl_chain *c = nc->nftnl;
+	struct nft_handle *h = d->handle;
+
+	/* don't delete built-in chain */
+	if (nft_chain_builtin(c))
+		return d->builtin_err;
+
+	if (d->verbose)
+		fprintf(stdout, "Deleting chain `%s'\n",
+			nftnl_chain_get_str(c, NFTNL_CHAIN_NAME));
+
+
+	/* XXX This triggers a fast lookup from the kernel. */
+	nftnl_chain_unset(c, NFTNL_CHAIN_HANDLE);
+	if (!batch_chain_add(h, NFT_COMPAT_CHAIN_USER_DEL, c))
+		return -1;
+
+	/* nftnl_chain is freed when deleting the batch object */
+	nc->nftnl = NULL;
+
+	nft_chain_list_del(nc);
+	nft_chain_free(nc);
+	return 0;
+}
+
+int nft_chain_user_del(struct nft_handle *h, const char *chain,
+		       const char *table, bool verbose)
+{
+	struct chain_user_del_data d = {
+		.handle = h,
+		.verbose = verbose,
+	};
+	struct nft_chain *c;
+	int ret = 0;
+
+	nft_fn = nft_chain_user_del;
+
+	if (chain) {
+		c = nft_chain_find(h, table, chain);
+		if (!c) {
+			errno = ENOENT;
+			return 0;
+		}
+		d.builtin_err = -2;
+		ret = __nft_chain_user_del(c, &d);
+		if (ret == -2)
+			errno = EINVAL;
+		goto out;
+	}
+
+	ret = nft_chain_foreach(h, table, __nft_chain_user_del, &d);
+out:
+	/* the core expects 1 for success and 0 for error */
+	return ret == 0 ? 1 : 0;
+}
+
+bool nft_chain_exists(struct nft_handle *h,
+		      const char *table, const char *chain)
+{
+	const struct builtin_table *t = nft_table_builtin_find(h, table);
+
+	/* xtables does not support custom tables */
+	if (!t)
+		return false;
+
+	if (nft_chain_builtin_find(t, chain))
+		return true;
+
+	return !!nft_chain_find(h, table, chain);
+}
+
+int nft_chain_user_rename(struct nft_handle *h,const char *chain,
+			  const char *table, const char *newname)
+{
+	struct nftnl_chain *c;
+	struct nft_chain *nc;
+	uint64_t handle;
+
+	nft_fn = nft_chain_user_rename;
+
+	if (nft_chain_exists(h, table, newname)) {
+		errno = EEXIST;
+		return 0;
+	}
+
+	/* Find the old chain to be renamed */
+	nc = nft_chain_find(h, table, chain);
+	if (nc == NULL) {
+		errno = ENOENT;
+		return 0;
+	}
+	handle = nftnl_chain_get_u64(nc->nftnl, NFTNL_CHAIN_HANDLE);
+
+	/* Now prepare the new name for the chain */
+	c = nftnl_chain_alloc();
+	if (c == NULL)
+		return 0;
+
+	nftnl_chain_set_str(c, NFTNL_CHAIN_TABLE, table);
+	nftnl_chain_set_str(c, NFTNL_CHAIN_NAME, newname);
+	nftnl_chain_set_u64(c, NFTNL_CHAIN_HANDLE, handle);
+
+	if (!batch_chain_add(h, NFT_COMPAT_CHAIN_RENAME, c))
+		return 0;
+
+	/* the core expects 1 for success and 0 for error */
+	return 1;
+}
+
+bool nft_table_find(struct nft_handle *h, const char *tablename)
+{
+	const struct builtin_table *t;
+
+	t = nft_table_builtin_find(h, tablename);
+	return t ? h->cache->table[t->type].exists : false;
+}
+
+int nft_for_each_table(struct nft_handle *h,
+		       int (*func)(struct nft_handle *h, const char *tablename, void *data),
+		       void *data)
+{
+	int i;
+
+	for (i = 0; i < NFT_TABLE_MAX; i++) {
+		if (h->tables[i].name == NULL)
+			continue;
+
+		if (!h->cache->table[h->tables[i].type].exists)
+			continue;
+
+		func(h, h->tables[i].name, data);
+	}
+
+	return 0;
+}
+
+static int __nft_table_flush(struct nft_handle *h, const char *table, bool exists)
+{
+	const struct builtin_table *_t;
+	struct obj_update *obj;
+	struct nftnl_table *t;
+
+	t = nftnl_table_alloc();
+	if (t == NULL)
+		return -1;
+
+	nftnl_table_set_str(t, NFTNL_TABLE_NAME, table);
+
+	obj = batch_table_add(h, NFT_COMPAT_TABLE_FLUSH, t);
+	if (!obj) {
+		nftnl_table_free(t);
+		return -1;
+	}
+
+	if (!exists)
+		obj->skip = 1;
+
+	_t = nft_table_builtin_find(h, table);
+	assert(_t);
+	h->cache->table[_t->type].exists = false;
+
+	flush_chain_cache(h, table);
+
+	return 0;
+}
+
+int nft_table_flush(struct nft_handle *h, const char *table)
+{
+	const struct builtin_table *t;
+	int ret = 0;
+
+	nft_fn = nft_table_flush;
+
+	t = nft_table_builtin_find(h, table);
+	if (!t)
+		return 0;
+
+	ret = __nft_table_flush(h, table, h->cache->table[t->type].exists);
+
+	/* the core expects 1 for success and 0 for error */
+	return ret == 0 ? 1 : 0;
+}
+
+static int __nft_rule_del(struct nft_handle *h, struct nftnl_rule *r)
+{
+	struct obj_update *obj;
+
+	nftnl_rule_list_del(r);
+
+	if (!nftnl_rule_get_u64(r, NFTNL_RULE_HANDLE))
+		nftnl_rule_set_u32(r, NFTNL_RULE_ID, ++h->rule_id);
+
+	obj = batch_rule_add(h, NFT_COMPAT_RULE_DELETE, r);
+	if (!obj) {
+		nftnl_rule_free(r);
+		return -1;
+	}
+	return 1;
+}
+
+static bool nft_rule_cmp(struct nft_handle *h, struct nftnl_rule *r,
+			 struct nftnl_rule *rule)
+{
+	struct iptables_command_state _cs = {}, this = {}, *cs = &_cs;
+	bool ret = false;
+
+	h->ops->rule_to_cs(h, r, &this);
+	h->ops->rule_to_cs(h, rule, cs);
+
+	DEBUGP("comparing with... ");
+#ifdef DEBUG_DEL
+	nft_rule_print_save(h, r, NFT_RULE_APPEND, 0);
+#endif
+	if (!h->ops->is_same(cs, &this))
+		goto out;
+
+	if (!compare_matches(cs->matches, this.matches)) {
+		DEBUGP("Different matches\n");
+		goto out;
+	}
+
+	if (!compare_targets(cs->target, this.target)) {
+		DEBUGP("Different target\n");
+		goto out;
+	}
+
+	if ((!cs->target || !this.target) &&
+	    strcmp(cs->jumpto, this.jumpto) != 0) {
+		DEBUGP("Different verdict\n");
+		goto out;
+	}
+
+	ret = true;
+out:
+	h->ops->clear_cs(&this);
+	h->ops->clear_cs(cs);
+	return ret;
+}
+
+static struct nftnl_rule *
+nft_rule_find(struct nft_handle *h, struct nft_chain *nc,
+	      struct nftnl_rule *rule, int rulenum)
+{
+	struct nftnl_chain *c = nc->nftnl;
+	struct nftnl_rule *r;
+	struct nftnl_rule_iter *iter;
+	bool found = false;
+
+	if (rulenum >= 0)
+		/* Delete by rule number case */
+		return nftnl_rule_lookup_byindex(c, rulenum);
+
+	iter = nftnl_rule_iter_create(c);
+	if (iter == NULL)
+		return 0;
+
+	r = nftnl_rule_iter_next(iter);
+	while (r != NULL) {
+		found = nft_rule_cmp(h, r, rule);
+		if (found)
+			break;
+		r = nftnl_rule_iter_next(iter);
+	}
+
+	nftnl_rule_iter_destroy(iter);
+
+	return found ? r : NULL;
+}
+
+int nft_rule_check(struct nft_handle *h, const char *chain,
+		   const char *table, struct nftnl_rule *rule, bool verbose)
+{
+	struct nftnl_rule *r;
+	struct nft_chain *c;
+
+	nft_fn = nft_rule_check;
+
+	c = nft_chain_find(h, table, chain);
+	if (!c)
+		goto fail_enoent;
+
+	r = nft_rule_find(h, c, rule, -1);
+	if (r == NULL)
+		goto fail_enoent;
+
+	if (verbose)
+		h->ops->print_rule(h, r, 0, FMT_PRINT_RULE);
+
+	return 1;
+fail_enoent:
+	errno = ENOENT;
+	return 0;
+}
+
+int nft_rule_delete(struct nft_handle *h, const char *chain,
+		    const char *table, struct nftnl_rule *rule, bool verbose)
+{
+	int ret = 0;
+	struct nftnl_rule *r;
+	struct nft_chain *c;
+
+	nft_fn = nft_rule_delete;
+
+	c = nft_chain_find(h, table, chain);
+	if (!c) {
+		errno = ENOENT;
+		return 0;
+	}
+
+	r = nft_rule_find(h, c, rule, -1);
+	if (r != NULL) {
+		ret =__nft_rule_del(h, r);
+		if (ret < 0)
+			errno = ENOMEM;
+		if (verbose)
+			h->ops->print_rule(h, r, 0, FMT_PRINT_RULE);
+	} else
+		errno = ENOENT;
+
+	return ret;
+}
+
+static struct nftnl_rule *
+nft_rule_add(struct nft_handle *h, const char *chain,
+	     const char *table, struct nftnl_rule *r,
+	     struct nftnl_rule *ref, bool verbose)
+{
+	uint64_t ref_id;
+
+	if (ref) {
+		ref_id = nftnl_rule_get_u64(ref, NFTNL_RULE_HANDLE);
+		if (ref_id > 0) {
+			nftnl_rule_set_u64(r, NFTNL_RULE_POSITION, ref_id);
+			DEBUGP("adding after rule handle %"PRIu64"\n", ref_id);
+		} else {
+			ref_id = nftnl_rule_get_u32(ref, NFTNL_RULE_ID);
+			if (!ref_id) {
+				ref_id = ++h->rule_id;
+				nftnl_rule_set_u32(ref, NFTNL_RULE_ID, ref_id);
+			}
+			nftnl_rule_set_u32(r, NFTNL_RULE_POSITION_ID, ref_id);
+			DEBUGP("adding after rule ID %"PRIu64"\n", ref_id);
+		}
+	}
+
+	if (!batch_rule_add(h, NFT_COMPAT_RULE_INSERT, r))
+		return NULL;
+
+	if (verbose)
+		h->ops->print_rule(h, r, 0, FMT_PRINT_RULE);
+
+	return r;
+}
+
+int nft_rule_insert(struct nft_handle *h, const char *chain,
+		    const char *table, struct nftnl_rule *new_rule, int rulenum,
+		    bool verbose)
+{
+	struct nftnl_rule *r = NULL;
+	struct nft_chain *c;
+
+	nft_xt_builtin_init(h, table, chain);
+
+	nft_fn = nft_rule_insert;
+
+	c = nft_chain_find(h, table, chain);
+	if (!c) {
+		errno = ENOENT;
+		goto err;
+	}
+
+	if (rulenum > 0) {
+		r = nft_rule_find(h, c, new_rule, rulenum);
+		if (r == NULL) {
+			/* special case: iptables allows to insert into
+			 * rule_count + 1 position.
+			 */
+			r = nft_rule_find(h, c, new_rule, rulenum - 1);
+			if (r != NULL)
+				return nft_rule_append(h, chain, table,
+						       new_rule, NULL, verbose);
+
+			errno = E2BIG;
+			goto err;
+		}
+	}
+
+	new_rule = nft_rule_add(h, chain, table, new_rule, r, verbose);
+	if (!new_rule)
+		goto err;
+
+	if (r)
+		nftnl_chain_rule_insert_at(new_rule, r);
+	else
+		nftnl_chain_rule_add(new_rule, c->nftnl);
+
+	return 1;
+err:
+	return 0;
+}
+
+int nft_rule_delete_num(struct nft_handle *h, const char *chain,
+			const char *table, int rulenum, bool verbose)
+{
+	int ret = 0;
+	struct nftnl_rule *r;
+	struct nft_chain *c;
+
+	nft_fn = nft_rule_delete_num;
+
+	c = nft_chain_find(h, table, chain);
+	if (!c) {
+		errno = ENOENT;
+		return 0;
+	}
+
+	r = nft_rule_find(h, c, NULL, rulenum);
+	if (r != NULL) {
+		DEBUGP("deleting rule by number %d\n", rulenum);
+		ret = __nft_rule_del(h, r);
+		if (ret < 0)
+			errno = ENOMEM;
+	} else
+		errno = E2BIG;
+
+	return ret;
+}
+
+int nft_rule_replace(struct nft_handle *h, const char *chain,
+		     const char *table, struct nftnl_rule *rule,
+		     int rulenum, bool verbose)
+{
+	int ret = 0;
+	struct nftnl_rule *r;
+	struct nft_chain *c;
+
+	nft_fn = nft_rule_replace;
+
+	c = nft_chain_find(h, table, chain);
+	if (!c) {
+		errno = ENOENT;
+		return 0;
+	}
+
+	r = nft_rule_find(h, c, rule, rulenum);
+	if (r != NULL) {
+		DEBUGP("replacing rule with handle=%llu\n",
+			(unsigned long long)
+			nftnl_rule_get_u64(r, NFTNL_RULE_HANDLE));
+
+		ret = nft_rule_append(h, chain, table, rule, r, verbose);
+	} else
+		errno = E2BIG;
+
+	return ret;
+}
+
+static int
+__nft_rule_list(struct nft_handle *h, struct nftnl_chain *c,
+		int rulenum, unsigned int format,
+		void (*cb)(struct nft_handle *h, struct nftnl_rule *r,
+			   unsigned int num, unsigned int format))
+{
+	struct nftnl_rule_iter *iter;
+	struct nftnl_rule *r;
+	int rule_ctr = 0;
+
+	if (rulenum > 0) {
+		r = nftnl_rule_lookup_byindex(c, rulenum - 1);
+		if (!r)
+			/* iptables-legacy returns 0 when listing for
+			 * valid chain but invalid rule number
+			 */
+			return 1;
+		cb(h, r, rulenum, format);
+		return 1;
+	}
+
+	iter = nftnl_rule_iter_create(c);
+	if (iter == NULL)
+		return 0;
+
+	r = nftnl_rule_iter_next(iter);
+	while (r != NULL) {
+		cb(h, r, ++rule_ctr, format);
+		r = nftnl_rule_iter_next(iter);
+	}
+
+	nftnl_rule_iter_destroy(iter);
+	return 1;
+}
+
+static int nft_rule_count(struct nft_handle *h, struct nftnl_chain *c)
+{
+	struct nftnl_rule_iter *iter;
+	struct nftnl_rule *r;
+	int rule_ctr = 0;
+
+	iter = nftnl_rule_iter_create(c);
+	if (iter == NULL)
+		return 0;
+
+	r = nftnl_rule_iter_next(iter);
+	while (r != NULL) {
+		rule_ctr++;
+		r = nftnl_rule_iter_next(iter);
+	}
+
+	nftnl_rule_iter_destroy(iter);
+	return rule_ctr;
+}
+
+static void __nft_print_header(struct nft_handle *h,
+			       struct nft_chain *nc, unsigned int format)
+{
+	struct nftnl_chain *c = nc->nftnl;
+	const char *chain_name = nftnl_chain_get_str(c, NFTNL_CHAIN_NAME);
+	bool basechain = !!nftnl_chain_get(c, NFTNL_CHAIN_HOOKNUM);
+	uint32_t refs = nftnl_chain_get_u32(c, NFTNL_CHAIN_USE);
+	uint32_t entries = nft_rule_count(h, c);
+	struct xt_counters ctrs = {
+		.pcnt = nftnl_chain_get_u64(c, NFTNL_CHAIN_PACKETS),
+		.bcnt = nftnl_chain_get_u64(c, NFTNL_CHAIN_BYTES),
+	};
+	const char *pname = NULL;
+
+	if (nftnl_chain_is_set(c, NFTNL_CHAIN_POLICY))
+		pname = policy_name[nftnl_chain_get_u32(c, NFTNL_CHAIN_POLICY)];
+
+	h->ops->print_header(format, chain_name, pname,
+			&ctrs, basechain, refs - entries, entries);
+}
+
+struct nft_rule_list_cb_data {
+	struct nft_handle *h;
+	unsigned int format;
+	int rulenum;
+	bool found;
+	bool save_fmt;
+	void (*cb)(struct nft_handle *h, struct nftnl_rule *r,
+		   unsigned int num, unsigned int format);
+};
+
+static int nft_rule_list_cb(struct nft_chain *c, void *data)
+{
+	struct nft_rule_list_cb_data *d = data;
+
+	if (!d->save_fmt) {
+		if (d->found)
+			printf("\n");
+		d->found = true;
+
+		__nft_print_header(d->h, c, d->format);
+	}
+
+	return __nft_rule_list(d->h, c->nftnl, d->rulenum, d->format, d->cb);
+}
+
+int nft_rule_list(struct nft_handle *h, const char *chain, const char *table,
+		  int rulenum, unsigned int format)
+{
+	const struct nft_family_ops *ops = h->ops;
+	struct nft_rule_list_cb_data d = {
+		.h = h,
+		.format = format,
+		.rulenum = rulenum,
+		.cb = ops->print_rule,
+	};
+	struct nft_chain *c;
+
+	nft_xt_fake_builtin_chains(h, table, chain);
+	nft_assert_table_compatible(h, table, chain);
+
+	if (chain) {
+		c = nft_chain_find(h, table, chain);
+		if (!c)
+			return 0;
+
+		if (rulenum)
+			d.save_fmt = true;	/* skip header printing */
+		else if (ops->print_table_header)
+			ops->print_table_header(table);
+
+		nft_rule_list_cb(c, &d);
+		return 1;
+	}
+
+	if (ops->print_table_header)
+		ops->print_table_header(table);
+
+	nft_chain_foreach(h, table, nft_rule_list_cb, &d);
+	return 1;
+}
+
+static void
+list_save(struct nft_handle *h, struct nftnl_rule *r,
+	  unsigned int num, unsigned int format)
+{
+	nft_rule_print_save(h, r, NFT_RULE_APPEND, format);
+}
+
+int nft_chain_foreach(struct nft_handle *h, const char *table,
+		      int (*cb)(struct nft_chain *c, void *data),
+		      void *data)
+{
+	const struct builtin_table *t;
+	struct nft_chain_list *list;
+	struct nft_chain *c, *c_bak;
+	int i, ret;
+
+	t = nft_table_builtin_find(h, table);
+	if (!t)
+		return -1;
+
+	for (i = 0; i < NF_INET_NUMHOOKS; i++) {
+		c = h->cache->table[t->type].base_chains[i];
+		if (!c)
+			continue;
+
+		ret = cb(c, data);
+		if (ret < 0)
+			return ret;
+	}
+
+	list = h->cache->table[t->type].chains;
+	if (!list)
+		return -1;
+
+	list_for_each_entry_safe(c, c_bak, &list->list, head) {
+		ret = cb(c, data);
+		if (ret < 0)
+			return ret;
+	}
+	return 0;
+}
+
+static int nft_rule_list_chain_save(struct nft_chain *nc, void *data)
+{
+	struct nftnl_chain *c = nc->nftnl;
+	const char *chain_name = nftnl_chain_get_str(c, NFTNL_CHAIN_NAME);
+	uint32_t policy = nftnl_chain_get_u32(c, NFTNL_CHAIN_POLICY);
+	int *counters = data;
+
+	if (!nft_chain_builtin(c)) {
+		printf("-N %s\n", chain_name);
+		return 0;
+	}
+
+	/* this is a base chain */
+
+	printf("-P %s %s", chain_name, policy_name[policy]);
+	if (*counters)
+		printf(" -c %"PRIu64" %"PRIu64,
+		       nftnl_chain_get_u64(c, NFTNL_CHAIN_PACKETS),
+		       nftnl_chain_get_u64(c, NFTNL_CHAIN_BYTES));
+	printf("\n");
+	return 0;
+}
+
+int nft_rule_list_save(struct nft_handle *h, const char *chain,
+		       const char *table, int rulenum, int counters)
+{
+	struct nft_rule_list_cb_data d = {
+		.h = h,
+		.rulenum = rulenum,
+		.save_fmt = true,
+		.cb = list_save,
+	};
+	struct nft_chain *c;
+	int ret = 0;
+
+	nft_xt_fake_builtin_chains(h, table, chain);
+	nft_assert_table_compatible(h, table, chain);
+
+	if (counters < 0)
+		d.format = FMT_C_COUNTS;
+	else if (counters == 0)
+		d.format = FMT_NOCOUNTS;
+
+	if (chain) {
+		c = nft_chain_find(h, table, chain);
+		if (!c)
+			return 0;
+
+		if (!rulenum)
+			nft_rule_list_chain_save(c, &counters);
+
+		return nft_rule_list_cb(c, &d);
+	}
+
+	/* Dump policies and custom chains first */
+	nft_chain_foreach(h, table, nft_rule_list_chain_save, &counters);
+
+	/* Now dump out rules in this table */
+	ret = nft_chain_foreach(h, table, nft_rule_list_cb, &d);
+	return ret == 0 ? 1 : 0;
+}
+
+int nft_rule_zero_counters(struct nft_handle *h, const char *chain,
+			   const char *table, int rulenum)
+{
+	struct iptables_command_state cs = {};
+	struct nftnl_rule *r, *new_rule;
+	struct nft_chain *c;
+	int ret = 0;
+
+	nft_fn = nft_rule_delete;
+
+	c = nft_chain_find(h, table, chain);
+	if (!c)
+		return 0;
+
+	r = nft_rule_find(h, c, NULL, rulenum);
+	if (r == NULL) {
+		errno = ENOENT;
+		ret = 1;
+		goto error;
+	}
+
+	nft_rule_to_iptables_command_state(h, r, &cs);
+
+	cs.counters.pcnt = cs.counters.bcnt = 0;
+	new_rule = nft_rule_new(h, chain, table, &cs);
+	if (!new_rule)
+		return 1;
+
+	ret = nft_rule_append(h, chain, table, new_rule, r, false);
+
+error:
+	return ret;
+}
+
+static void nft_compat_table_batch_add(struct nft_handle *h, uint16_t type,
+				       uint16_t flags, uint32_t seq,
+				       struct nftnl_table *table)
+{
+	struct nlmsghdr *nlh;
+
+	nlh = nftnl_table_nlmsg_build_hdr(nftnl_batch_buffer(h->batch),
+					type, h->family, flags, seq);
+	nftnl_table_nlmsg_build_payload(nlh, table);
+}
+
+static void nft_compat_set_batch_add(struct nft_handle *h, uint16_t type,
+				     uint16_t flags, uint32_t seq,
+				     struct nftnl_set *set)
+{
+	struct nlmsghdr *nlh;
+
+	nlh = nftnl_nlmsg_build_hdr(nftnl_batch_buffer(h->batch),
+					type, h->family, flags, seq);
+	nftnl_set_nlmsg_build_payload(nlh, set);
+}
+
+static void nft_compat_setelem_batch_add(struct nft_handle *h, uint16_t type,
+					 uint16_t flags, uint32_t *seq,
+					 struct nftnl_set *set)
+{
+	struct nftnl_set_elems_iter *iter;
+	struct nlmsghdr *nlh;
+
+	iter = nftnl_set_elems_iter_create(set);
+	if (!iter)
+		return;
+
+	while (nftnl_set_elems_iter_cur(iter)) {
+		(*seq)++;
+		mnl_nft_batch_continue(h->batch);
+		nlh = nftnl_nlmsg_build_hdr(nftnl_batch_buffer(h->batch),
+					    type, h->family, flags, *seq);
+		if (nftnl_set_elems_nlmsg_build_payload_iter(nlh, iter) <= 0)
+			break;
+	}
+	nftnl_set_elems_iter_destroy(iter);
+}
+
+static void nft_compat_chain_batch_add(struct nft_handle *h, uint16_t type,
+				       uint16_t flags, uint32_t seq,
+				       struct nftnl_chain *chain)
+{
+	struct nlmsghdr *nlh;
+
+	nlh = nftnl_chain_nlmsg_build_hdr(nftnl_batch_buffer(h->batch),
+					type, h->family, flags, seq);
+	nftnl_chain_nlmsg_build_payload(nlh, chain);
+	nft_chain_print_debug(chain, nlh);
+}
+
+static void nft_compat_rule_batch_add(struct nft_handle *h, uint16_t type,
+				      uint16_t flags, uint32_t seq,
+				      struct nftnl_rule *rule)
+{
+	struct nlmsghdr *nlh;
+
+	nlh = nftnl_rule_nlmsg_build_hdr(nftnl_batch_buffer(h->batch),
+				       type, h->family, flags, seq);
+	nftnl_rule_nlmsg_build_payload(nlh, rule);
+	nft_rule_print_debug(rule, nlh);
+}
+
+static void batch_obj_del(struct nft_handle *h, struct obj_update *o)
+{
+	switch (o->type) {
+	case NFT_COMPAT_TABLE_ADD:
+	case NFT_COMPAT_TABLE_FLUSH:
+		nftnl_table_free(o->table);
+		break;
+	case NFT_COMPAT_CHAIN_ZERO:
+	case NFT_COMPAT_CHAIN_USER_ADD:
+	case NFT_COMPAT_CHAIN_ADD:
+		break;
+	case NFT_COMPAT_CHAIN_USER_DEL:
+	case NFT_COMPAT_CHAIN_USER_FLUSH:
+	case NFT_COMPAT_CHAIN_UPDATE:
+	case NFT_COMPAT_CHAIN_RENAME:
+		nftnl_chain_free(o->chain);
+		break;
+	case NFT_COMPAT_RULE_APPEND:
+	case NFT_COMPAT_RULE_INSERT:
+	case NFT_COMPAT_RULE_REPLACE:
+		break;
+	case NFT_COMPAT_RULE_DELETE:
+	case NFT_COMPAT_RULE_FLUSH:
+		nftnl_rule_free(o->rule);
+		break;
+	case NFT_COMPAT_SET_ADD:
+		nftnl_set_free(o->set);
+		break;
+	case NFT_COMPAT_RULE_LIST:
+	case NFT_COMPAT_RULE_CHECK:
+	case NFT_COMPAT_CHAIN_RESTORE:
+	case NFT_COMPAT_RULE_SAVE:
+	case NFT_COMPAT_RULE_ZERO:
+	case NFT_COMPAT_BRIDGE_USER_CHAIN_UPDATE:
+		assert(0);
+		break;
+	}
+	h->obj_list_num--;
+	list_del(&o->head);
+	free(o);
+}
+
+static void nft_refresh_transaction(struct nft_handle *h)
+{
+	const char *tablename, *chainname;
+	const struct nft_chain *c;
+	struct obj_update *n, *tmp;
+	bool exists;
+
+	h->error.lineno = 0;
+
+	list_for_each_entry_safe(n, tmp, &h->obj_list, head) {
+		switch (n->type) {
+		case NFT_COMPAT_TABLE_FLUSH:
+			tablename = nftnl_table_get_str(n->table, NFTNL_TABLE_NAME);
+			if (!tablename)
+				continue;
+			exists = nft_table_find(h, tablename);
+			if (exists)
+				n->skip = 0;
+			else
+				n->skip = 1;
+			break;
+		case NFT_COMPAT_CHAIN_USER_ADD:
+			tablename = nftnl_chain_get_str(n->chain, NFTNL_CHAIN_TABLE);
+			if (!tablename)
+				continue;
+
+			chainname = nftnl_chain_get_str(n->chain, NFTNL_CHAIN_NAME);
+			if (!chainname)
+				continue;
+
+			if (!h->noflush)
+				break;
+
+			c = nft_chain_find(h, tablename, chainname);
+			if (c) {
+				n->skip = 1;
+			} else if (!c) {
+				n->skip = 0;
+			}
+			break;
+		case NFT_COMPAT_RULE_FLUSH:
+			tablename = nftnl_rule_get_str(n->rule, NFTNL_RULE_TABLE);
+			if (!tablename)
+				continue;
+
+			chainname = nftnl_rule_get_str(n->rule, NFTNL_RULE_CHAIN);
+			if (!chainname)
+				continue;
+
+			n->skip = !nft_chain_find(h, tablename, chainname);
+			break;
+		case NFT_COMPAT_TABLE_ADD:
+		case NFT_COMPAT_CHAIN_ADD:
+		case NFT_COMPAT_CHAIN_ZERO:
+		case NFT_COMPAT_CHAIN_USER_DEL:
+		case NFT_COMPAT_CHAIN_USER_FLUSH:
+		case NFT_COMPAT_CHAIN_UPDATE:
+		case NFT_COMPAT_CHAIN_RENAME:
+		case NFT_COMPAT_RULE_APPEND:
+		case NFT_COMPAT_RULE_INSERT:
+		case NFT_COMPAT_RULE_REPLACE:
+		case NFT_COMPAT_RULE_DELETE:
+		case NFT_COMPAT_SET_ADD:
+		case NFT_COMPAT_RULE_LIST:
+		case NFT_COMPAT_RULE_CHECK:
+		case NFT_COMPAT_CHAIN_RESTORE:
+		case NFT_COMPAT_RULE_SAVE:
+		case NFT_COMPAT_RULE_ZERO:
+		case NFT_COMPAT_BRIDGE_USER_CHAIN_UPDATE:
+			break;
+		}
+	}
+}
+
+static int nft_action(struct nft_handle *h, int action)
+{
+	struct obj_update *n, *tmp;
+	struct mnl_err *err, *ne;
+	unsigned int buflen, i, len;
+	bool show_errors = true;
+	char errmsg[1024];
+	uint32_t seq;
+	int ret = 0;
+
+retry:
+	seq = 1;
+	h->batch = mnl_batch_init();
+
+	mnl_batch_begin(h->batch, h->nft_genid, seq++);
+	h->nft_genid++;
+
+	list_for_each_entry(n, &h->obj_list, head) {
+		if (n->skip) {
+			n->seq = 0;
+			continue;
+		}
+
+		n->seq = seq++;
+		switch (n->type) {
+		case NFT_COMPAT_TABLE_ADD:
+			nft_compat_table_batch_add(h, NFT_MSG_NEWTABLE,
+						   NLM_F_CREATE, n->seq,
+						   n->table);
+			break;
+		case NFT_COMPAT_TABLE_FLUSH:
+			nft_compat_table_batch_add(h, NFT_MSG_DELTABLE,
+						   0,
+						   n->seq, n->table);
+			break;
+		case NFT_COMPAT_CHAIN_ADD:
+		case NFT_COMPAT_CHAIN_ZERO:
+			nft_compat_chain_batch_add(h, NFT_MSG_NEWCHAIN,
+						   NLM_F_CREATE, n->seq,
+						   n->chain);
+			break;
+		case NFT_COMPAT_CHAIN_USER_ADD:
+			nft_compat_chain_batch_add(h, NFT_MSG_NEWCHAIN,
+						   NLM_F_EXCL, n->seq,
+						   n->chain);
+			break;
+		case NFT_COMPAT_CHAIN_USER_DEL:
+			nft_compat_chain_batch_add(h, NFT_MSG_DELCHAIN,
+						   NLM_F_NONREC, n->seq,
+						   n->chain);
+			break;
+		case NFT_COMPAT_CHAIN_USER_FLUSH:
+			nft_compat_chain_batch_add(h, NFT_MSG_DELCHAIN,
+						   0, n->seq,
+						   n->chain);
+			break;
+		case NFT_COMPAT_CHAIN_UPDATE:
+			nft_compat_chain_batch_add(h, NFT_MSG_NEWCHAIN,
+						   h->restore ?
+						     NLM_F_CREATE : 0,
+						   n->seq, n->chain);
+			break;
+		case NFT_COMPAT_CHAIN_RENAME:
+			nft_compat_chain_batch_add(h, NFT_MSG_NEWCHAIN, 0,
+						   n->seq, n->chain);
+			break;
+		case NFT_COMPAT_RULE_APPEND:
+			nft_compat_rule_batch_add(h, NFT_MSG_NEWRULE,
+						  NLM_F_CREATE | NLM_F_APPEND,
+						  n->seq, n->rule);
+			break;
+		case NFT_COMPAT_RULE_INSERT:
+			nft_compat_rule_batch_add(h, NFT_MSG_NEWRULE,
+						  NLM_F_CREATE, n->seq,
+						  n->rule);
+			break;
+		case NFT_COMPAT_RULE_REPLACE:
+			nft_compat_rule_batch_add(h, NFT_MSG_NEWRULE,
+						  NLM_F_CREATE | NLM_F_REPLACE,
+						  n->seq, n->rule);
+			break;
+		case NFT_COMPAT_RULE_DELETE:
+		case NFT_COMPAT_RULE_FLUSH:
+			nft_compat_rule_batch_add(h, NFT_MSG_DELRULE, 0,
+						  n->seq, n->rule);
+			break;
+		case NFT_COMPAT_SET_ADD:
+			nft_compat_set_batch_add(h, NFT_MSG_NEWSET,
+						 NLM_F_CREATE, n->seq, n->set);
+			nft_compat_setelem_batch_add(h, NFT_MSG_NEWSETELEM,
+						     NLM_F_CREATE, &n->seq, n->set);
+			seq = n->seq;
+			break;
+		case NFT_COMPAT_RULE_LIST:
+		case NFT_COMPAT_RULE_CHECK:
+		case NFT_COMPAT_CHAIN_RESTORE:
+		case NFT_COMPAT_RULE_SAVE:
+		case NFT_COMPAT_RULE_ZERO:
+		case NFT_COMPAT_BRIDGE_USER_CHAIN_UPDATE:
+			assert(0);
+		}
+
+		mnl_nft_batch_continue(h->batch);
+	}
+
+	switch (action) {
+	case NFT_COMPAT_COMMIT:
+		mnl_batch_end(h->batch, seq++);
+		break;
+	case NFT_COMPAT_ABORT:
+		break;
+	}
+
+	errno = 0;
+	ret = mnl_batch_talk(h, seq);
+	if (ret && errno == ERESTART) {
+		nft_rebuild_cache(h);
+
+		nft_refresh_transaction(h);
+
+		list_for_each_entry_safe(err, ne, &h->err_list, head)
+			mnl_err_list_free(err);
+
+		mnl_batch_reset(h->batch);
+		goto retry;
+	}
+
+	i = 0;
+	buflen = sizeof(errmsg);
+
+	list_for_each_entry_safe(n, tmp, &h->obj_list, head) {
+		list_for_each_entry_safe(err, ne, &h->err_list, head) {
+			if (err->seqnum > n->seq)
+				break;
+
+			if (err->seqnum == n->seq && show_errors) {
+				if (n->error.lineno == 0)
+					show_errors = false;
+				len = mnl_append_error(h, n, err, errmsg + i, buflen);
+				if (len > 0 && len <= buflen) {
+					buflen -= len;
+					i += len;
+				}
+			}
+			mnl_err_list_free(err);
+		}
+		batch_obj_del(h, n);
+	}
+
+	nft_release_cache(h);
+	mnl_batch_reset(h->batch);
+
+	if (i)
+		xtables_error(RESOURCE_PROBLEM, "%s", errmsg);
+
+	return ret == 0 ? 1 : 0;
+}
+
+static int ebt_add_policy_rule(struct nftnl_chain *c, void *data)
+{
+	uint32_t policy = nftnl_chain_get_u32(c, NFTNL_CHAIN_POLICY);
+	struct iptables_command_state cs = {
+		.eb.bitmask = EBT_NOPROTO,
+	};
+	struct nftnl_udata_buf *udata;
+	struct nft_handle *h = data;
+	struct nftnl_rule *r;
+	const char *pname;
+
+	if (nftnl_chain_get(c, NFTNL_CHAIN_HOOKNUM))
+		return 0; /* ignore base chains */
+
+	if (!nftnl_chain_is_set(c, NFTNL_CHAIN_POLICY))
+		return 0;
+
+	nftnl_chain_unset(c, NFTNL_CHAIN_POLICY);
+
+	switch (policy) {
+	case NFT_RETURN:
+		return 0; /* return policy is default for nft chains */
+	case NF_ACCEPT:
+		pname = "ACCEPT";
+		break;
+	case NF_DROP:
+		pname = "DROP";
+		break;
+	default:
+		return -1;
+	}
+
+	command_jump(&cs, pname);
+
+	r = nft_rule_new(h, nftnl_chain_get_str(c, NFTNL_CHAIN_NAME),
+			 nftnl_chain_get_str(c, NFTNL_CHAIN_TABLE), &cs);
+	ebt_cs_clean(&cs);
+
+	if (!r)
+		return -1;
+
+	udata = nftnl_udata_buf_alloc(NFT_USERDATA_MAXLEN);
+	if (!udata)
+		goto err_free_rule;
+
+	if (!nftnl_udata_put_u32(udata, UDATA_TYPE_EBTABLES_POLICY, 1))
+		goto err_free_rule;
+
+	nftnl_rule_set_data(r, NFTNL_RULE_USERDATA,
+			    nftnl_udata_buf_data(udata),
+			    nftnl_udata_buf_len(udata));
+	nftnl_udata_buf_free(udata);
+
+	if (!batch_rule_add(h, NFT_COMPAT_RULE_APPEND, r))
+		goto err_free_rule;
+
+	/* add the rule to chain so it is freed later */
+	nftnl_chain_rule_add_tail(r, c);
+
+	return 0;
+err_free_rule:
+	nftnl_rule_free(r);
+	return -1;
+}
+
+int ebt_set_user_chain_policy(struct nft_handle *h, const char *table,
+			      const char *chain, const char *policy)
+{
+	struct nft_chain *c = nft_chain_find(h, table, chain);
+	int pval;
+
+	if (!c)
+		return 0;
+
+	if (!strcmp(policy, "DROP"))
+		pval = NF_DROP;
+	else if (!strcmp(policy, "ACCEPT"))
+		pval = NF_ACCEPT;
+	else if (!strcmp(policy, "RETURN"))
+		pval = NFT_RETURN;
+	else
+		return 0;
+
+	nftnl_chain_set_u32(c->nftnl, NFTNL_CHAIN_POLICY, pval);
+	return 1;
+}
+
+static void nft_bridge_commit_prepare(struct nft_handle *h)
+{
+	const struct builtin_table *t;
+	struct nft_chain_list *list;
+	struct nft_chain *c;
+	int i;
+
+	for (i = 0; i < NFT_TABLE_MAX; i++) {
+		t = &h->tables[i];
+
+		if (!t->name)
+			continue;
+
+		list = h->cache->table[t->type].chains;
+		if (!list)
+			continue;
+
+		list_for_each_entry(c, &list->list, head) {
+			ebt_add_policy_rule(c->nftnl, h);
+		}
+	}
+}
+
+static void assert_chain_exists(struct nft_handle *h,
+				const char *table, const char *chain)
+{
+	if (chain && !nft_chain_exists(h, table, chain))
+		xtables_error(PARAMETER_PROBLEM,
+			      "Chain '%s' does not exist", chain);
+}
+
+static int nft_prepare(struct nft_handle *h)
+{
+	struct nft_cmd *cmd, *next;
+	int ret = 1;
+
+	nft_cache_build(h);
+
+	list_for_each_entry_safe(cmd, next, &h->cmd_list, head) {
+		switch (cmd->command) {
+		case NFT_COMPAT_TABLE_FLUSH:
+			ret = nft_table_flush(h, cmd->table);
+			break;
+		case NFT_COMPAT_CHAIN_USER_ADD:
+			ret = nft_chain_user_add(h, cmd->chain, cmd->table);
+			break;
+		case NFT_COMPAT_CHAIN_USER_DEL:
+			ret = nft_chain_user_del(h, cmd->chain, cmd->table,
+						 cmd->verbose);
+			break;
+		case NFT_COMPAT_CHAIN_RESTORE:
+			ret = nft_chain_restore(h, cmd->chain, cmd->table);
+			break;
+		case NFT_COMPAT_CHAIN_UPDATE:
+			ret = nft_chain_set(h, cmd->table, cmd->chain,
+					    cmd->policy, &cmd->counters);
+			break;
+		case NFT_COMPAT_CHAIN_RENAME:
+			ret = nft_chain_user_rename(h, cmd->chain, cmd->table,
+						    cmd->rename);
+			break;
+		case NFT_COMPAT_CHAIN_ZERO:
+			ret = nft_chain_zero_counters(h, cmd->chain, cmd->table,
+						      cmd->verbose);
+			break;
+		case NFT_COMPAT_RULE_APPEND:
+			assert_chain_exists(h, cmd->table, cmd->jumpto);
+			ret = nft_rule_append(h, cmd->chain, cmd->table,
+					      cmd->obj.rule, NULL, cmd->verbose);
+			break;
+		case NFT_COMPAT_RULE_INSERT:
+			assert_chain_exists(h, cmd->table, cmd->jumpto);
+			ret = nft_rule_insert(h, cmd->chain, cmd->table,
+					      cmd->obj.rule, cmd->rulenum,
+					      cmd->verbose);
+			break;
+		case NFT_COMPAT_RULE_REPLACE:
+			assert_chain_exists(h, cmd->table, cmd->jumpto);
+			ret = nft_rule_replace(h, cmd->chain, cmd->table,
+					      cmd->obj.rule, cmd->rulenum,
+					      cmd->verbose);
+			break;
+		case NFT_COMPAT_RULE_DELETE:
+			assert_chain_exists(h, cmd->table, cmd->jumpto);
+			if (cmd->rulenum >= 0)
+				ret = nft_rule_delete_num(h, cmd->chain,
+							  cmd->table,
+							  cmd->rulenum,
+							  cmd->verbose);
+			else
+				ret = nft_rule_delete(h, cmd->chain, cmd->table,
+						      cmd->obj.rule, cmd->verbose);
+			break;
+		case NFT_COMPAT_RULE_FLUSH:
+			ret = nft_rule_flush(h, cmd->chain, cmd->table,
+					     cmd->verbose);
+			break;
+		case NFT_COMPAT_RULE_LIST:
+			ret = nft_rule_list(h, cmd->chain, cmd->table,
+					    cmd->rulenum, cmd->format);
+			break;
+		case NFT_COMPAT_RULE_CHECK:
+			assert_chain_exists(h, cmd->table, cmd->jumpto);
+			ret = nft_rule_check(h, cmd->chain, cmd->table,
+					     cmd->obj.rule, cmd->rulenum);
+			break;
+		case NFT_COMPAT_RULE_ZERO:
+			ret = nft_rule_zero_counters(h, cmd->chain, cmd->table,
+                                                     cmd->rulenum);
+			break;
+		case NFT_COMPAT_RULE_SAVE:
+			ret = nft_rule_list_save(h, cmd->chain, cmd->table,
+						 cmd->rulenum,
+						 cmd->counters_save);
+			break;
+		case NFT_COMPAT_BRIDGE_USER_CHAIN_UPDATE:
+			ret = ebt_set_user_chain_policy(h, cmd->table,
+							cmd->chain, cmd->policy);
+			break;
+		case NFT_COMPAT_SET_ADD:
+			nft_xt_builtin_table_init(h, cmd->table);
+			batch_set_add(h, NFT_COMPAT_SET_ADD, cmd->obj.set);
+			ret = 1;
+			break;
+		case NFT_COMPAT_TABLE_ADD:
+		case NFT_COMPAT_CHAIN_ADD:
+			assert(0);
+			break;
+		}
+
+		nft_cmd_free(cmd);
+
+		if (ret == 0)
+			return 0;
+	}
+
+	return 1;
+}
+
+int nft_commit(struct nft_handle *h)
+{
+	if (!nft_prepare(h))
+		return 0;
+
+	return nft_action(h, NFT_COMPAT_COMMIT);
+}
+
+int nft_bridge_commit(struct nft_handle *h)
+{
+	if (!nft_prepare(h))
+		return 0;
+
+	nft_bridge_commit_prepare(h);
+
+	return nft_action(h, NFT_COMPAT_COMMIT);
+}
+
+int nft_abort(struct nft_handle *h)
+{
+	struct nft_cmd *cmd, *next;
+
+	list_for_each_entry_safe(cmd, next, &h->cmd_list, head)
+		nft_cmd_free(cmd);
+
+	return nft_action(h, NFT_COMPAT_ABORT);
+}
+
+int nft_compatible_revision(const char *name, uint8_t rev, int opt)
+{
+	struct mnl_socket *nl;
+	char buf[16536];
+	struct nlmsghdr *nlh;
+	uint32_t portid, seq, type = 0;
+	uint32_t pf = AF_INET;
+	int ret = 0;
+
+	switch (opt) {
+	case IPT_SO_GET_REVISION_MATCH:
+		break;
+	case IP6T_SO_GET_REVISION_MATCH:
+		pf = AF_INET6;
+		break;
+	case IPT_SO_GET_REVISION_TARGET:
+		type = 1;
+		break;
+	case IP6T_SO_GET_REVISION_TARGET:
+		type = 1;
+		pf = AF_INET6;
+		break;
+	default:
+		/* No revision support (arp, ebtables), assume latest version ok */
+		return 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 = pf;
+	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)
+		return 0;
+
+	if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0)
+		goto err;
+
+	portid = mnl_socket_get_portid(nl);
+
+	if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0)
+		goto err;
+
+	ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+	if (ret == -1)
+		goto err;
+
+	ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL);
+	if (ret == -1)
+		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, EBUSY, "Directory not empty" },
+	    { nft_chain_user_del, EMLINK,
+	      "Can't delete chain with references left" },
+	    { nft_chain_user_add, EEXIST, "Chain already exists" },
+	    { nft_chain_user_rename, EEXIST, "File exists" },
+	    { nft_rule_insert, E2BIG, "Index of insertion too big" },
+	    { nft_rule_check, ENOENT, "Bad rule (does a matching rule exist in that chain?)" },
+	    { 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" }, */
+	    /* 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" },
+	    { nft_chain_set, ENXIO, "Bad table name" },
+	    { NULL, ELOOP, "Loop found in table" },
+	    { 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 < ARRAY_SIZE(table); i++) {
+		if ((!table[i].fn || table[i].fn == nft_fn)
+		    && table[i].err == err)
+			return table[i].message;
+	}
+
+	return strerror(err);
+}
+
+static int recover_rule_compat(struct nftnl_rule *r)
+{
+	struct nftnl_expr_iter *iter;
+	struct nftnl_expr *e;
+	uint32_t reg;
+	int ret = -1;
+
+	iter = nftnl_expr_iter_create(r);
+	if (!iter)
+		return -1;
+
+next_expr:
+	e = nftnl_expr_iter_next(iter);
+	if (!e)
+		goto out;
+
+	if (strcmp("meta", nftnl_expr_get_str(e, NFTNL_EXPR_NAME)) ||
+	    nftnl_expr_get_u32(e, NFTNL_EXPR_META_KEY) != NFT_META_L4PROTO)
+		goto next_expr;
+
+	reg = nftnl_expr_get_u32(e, NFTNL_EXPR_META_DREG);
+
+	e = nftnl_expr_iter_next(iter);
+	if (!e)
+		goto out;
+
+	if (strcmp("cmp", nftnl_expr_get_str(e, NFTNL_EXPR_NAME)) ||
+	    reg != nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_SREG))
+		goto next_expr;
+
+	add_compat(r, nftnl_expr_get_u8(e, NFTNL_EXPR_CMP_DATA),
+		   nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP) == NFT_CMP_NEQ);
+	ret = 0;
+out:
+	nftnl_expr_iter_destroy(iter);
+	return ret;
+}
+
+struct chain_zero_data {
+	struct nft_handle	*handle;
+	bool			verbose;
+};
+
+static int __nft_chain_zero_counters(struct nft_chain *nc, void *data)
+{
+	struct nftnl_chain *c = nc->nftnl;
+	struct chain_zero_data *d = data;
+	struct nft_handle *h = d->handle;
+	struct nftnl_rule_iter *iter;
+	struct nftnl_rule *r;
+
+	if (d->verbose)
+		fprintf(stdout, "Zeroing chain `%s'\n",
+		        nftnl_chain_get_str(c, NFTNL_CHAIN_NAME));
+
+	if (nftnl_chain_is_set(c, NFTNL_CHAIN_HOOKNUM)) {
+		/* zero base chain counters. */
+		nftnl_chain_set_u64(c, NFTNL_CHAIN_PACKETS, 0);
+		nftnl_chain_set_u64(c, NFTNL_CHAIN_BYTES, 0);
+		nftnl_chain_unset(c, NFTNL_CHAIN_HANDLE);
+		if (!batch_chain_add(h, NFT_COMPAT_CHAIN_ZERO, c))
+			return -1;
+	}
+
+	iter = nftnl_rule_iter_create(c);
+	if (iter == NULL)
+		return -1;
+
+	r = nftnl_rule_iter_next(iter);
+	while (r != NULL) {
+		struct nftnl_expr_iter *ei;
+		struct nftnl_expr *e;
+		bool zero_needed;
+
+		ei = nftnl_expr_iter_create(r);
+		if (!ei)
+			break;
+
+		e = nftnl_expr_iter_next(ei);
+	        zero_needed = false;
+		while (e != NULL) {
+			const char *en = nftnl_expr_get_str(e, NFTNL_EXPR_NAME);
+
+			if (strcmp(en, "counter") == 0 && (
+			    nftnl_expr_get_u64(e, NFTNL_EXPR_CTR_PACKETS) ||
+			    nftnl_expr_get_u64(e, NFTNL_EXPR_CTR_BYTES))) {
+				nftnl_expr_set_u64(e, NFTNL_EXPR_CTR_PACKETS, 0);
+				nftnl_expr_set_u64(e, NFTNL_EXPR_CTR_BYTES, 0);
+				zero_needed = true;
+			}
+
+			e = nftnl_expr_iter_next(ei);
+		}
+
+		nftnl_expr_iter_destroy(ei);
+
+		if (zero_needed) {
+			/*
+			 * Unset RULE_POSITION for older kernels, we want to replace
+			 * rule based on its handle only.
+			 */
+			recover_rule_compat(r);
+			nftnl_rule_unset(r, NFTNL_RULE_POSITION);
+			if (!batch_rule_add(h, NFT_COMPAT_RULE_REPLACE, r)) {
+				nftnl_rule_iter_destroy(iter);
+				return -1;
+			}
+		}
+		r = nftnl_rule_iter_next(iter);
+	}
+
+	nftnl_rule_iter_destroy(iter);
+	return 0;
+}
+
+int nft_chain_zero_counters(struct nft_handle *h, const char *chain,
+			    const char *table, bool verbose)
+{
+	struct chain_zero_data d = {
+		.handle = h,
+		.verbose = verbose,
+	};
+	struct nft_chain *c;
+	int ret = 0;
+
+	if (chain) {
+		c = nft_chain_find(h, table, chain);
+		if (!c) {
+			errno = ENOENT;
+			return 0;
+		}
+
+		ret = __nft_chain_zero_counters(c, &d);
+		goto err;
+	}
+
+	ret = nft_chain_foreach(h, table, __nft_chain_zero_counters, &d);
+err:
+	/* the core expects 1 for success and 0 for error */
+	return ret == 0 ? 1 : 0;
+}
+
+uint32_t nft_invflags2cmp(uint32_t invflags, uint32_t flag)
+{
+	if (invflags & flag)
+		return NFT_CMP_NEQ;
+
+	return NFT_CMP_EQ;
+}
+
+static const char *supported_exprs[] = {
+	"match",
+	"target",
+	"payload",
+	"meta",
+	"cmp",
+	"bitwise",
+	"counter",
+	"immediate",
+	"lookup",
+};
+
+
+static int nft_is_expr_compatible(struct nftnl_expr *expr, void *data)
+{
+	const char *name = nftnl_expr_get_str(expr, NFTNL_EXPR_NAME);
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(supported_exprs); i++) {
+		if (strcmp(supported_exprs[i], name) == 0)
+			return 0;
+	}
+
+	if (!strcmp(name, "limit") &&
+	    nftnl_expr_get_u32(expr, NFTNL_EXPR_LIMIT_TYPE) == NFT_LIMIT_PKTS &&
+	    nftnl_expr_get_u32(expr, NFTNL_EXPR_LIMIT_FLAGS) == 0)
+		return 0;
+
+	return -1;
+}
+
+static int nft_is_rule_compatible(struct nftnl_rule *rule, void *data)
+{
+	return nftnl_expr_foreach(rule, nft_is_expr_compatible, NULL);
+}
+
+static int nft_is_chain_compatible(struct nft_chain *nc, void *data)
+{
+	struct nftnl_chain *c = nc->nftnl;
+	const struct builtin_table *table;
+	const struct builtin_chain *chain;
+	const char *tname, *cname, *type;
+	struct nft_handle *h = data;
+	enum nf_inet_hooks hook;
+	int prio;
+
+	if (nftnl_rule_foreach(c, nft_is_rule_compatible, NULL))
+		return -1;
+
+	if (!nft_chain_builtin(c))
+		return 0;
+
+	tname = nftnl_chain_get_str(c, NFTNL_CHAIN_TABLE);
+	table = nft_table_builtin_find(h, tname);
+	if (!table)
+		return -1;
+
+	cname = nftnl_chain_get_str(c, NFTNL_CHAIN_NAME);
+	chain = nft_chain_builtin_find(table, cname);
+	if (!chain)
+		return -1;
+
+	type = nftnl_chain_get_str(c, NFTNL_CHAIN_TYPE);
+	prio = nftnl_chain_get_u32(c, NFTNL_CHAIN_PRIO);
+	hook = nftnl_chain_get_u32(c, NFTNL_CHAIN_HOOKNUM);
+	if (strcmp(type, chain->type) ||
+	    prio != chain->prio ||
+	    hook != chain->hook)
+		return -1;
+
+	return 0;
+}
+
+bool nft_is_table_compatible(struct nft_handle *h,
+			     const char *table, const char *chain)
+{
+	if (chain) {
+		struct nft_chain *c = nft_chain_find(h, table, chain);
+
+		return c && !nft_is_chain_compatible(c, h);
+	}
+
+	return !nft_chain_foreach(h, table, nft_is_chain_compatible, h);
+}
+
+void nft_assert_table_compatible(struct nft_handle *h,
+				 const char *table, const char *chain)
+{
+	const char *pfx = "", *sfx = "";
+
+	if (nft_is_table_compatible(h, table, chain))
+		return;
+
+	if (chain) {
+		pfx = "chain `";
+		sfx = "' in ";
+	} else {
+		chain = "";
+	}
+	xtables_error(OTHER_PROBLEM,
+		      "%s%s%stable `%s' is incompatible, use 'nft' tool.\n",
+		      pfx, chain, sfx, table);
+}
diff --git a/iptables/nft.h b/iptables/nft.h
new file mode 100644
index 0000000..0910f82
--- /dev/null
+++ b/iptables/nft.h
@@ -0,0 +1,270 @@
+#ifndef _NFT_H_
+#define _NFT_H_
+
+#include "xshared.h"
+#include "nft-shared.h"
+#include "nft-cache.h"
+#include "nft-chain.h"
+#include "nft-cmd.h"
+#include <libiptc/linux_list.h>
+
+enum nft_table_type {
+	NFT_TABLE_MANGLE	= 0,
+	NFT_TABLE_SECURITY,
+	NFT_TABLE_RAW,
+	NFT_TABLE_FILTER,
+	NFT_TABLE_NAT,
+};
+#define NFT_TABLE_MAX	(NFT_TABLE_NAT + 1)
+
+struct builtin_chain {
+	const char *name;
+	const char *type;
+	uint32_t prio;
+	uint32_t hook;
+};
+
+struct builtin_table {
+	const char *name;
+	enum nft_table_type type;
+	struct builtin_chain chains[NF_INET_NUMHOOKS];
+};
+
+enum nft_cache_level {
+	NFT_CL_TABLES,
+	NFT_CL_CHAINS,
+	NFT_CL_SETS,
+	NFT_CL_RULES,
+	NFT_CL_FAKE	/* must be last entry */
+};
+
+struct nft_cache {
+	struct {
+		struct nft_chain	*base_chains[NF_INET_NUMHOOKS];
+		struct nft_chain_list	*chains;
+		struct nftnl_set_list	*sets;
+		bool			exists;
+	} table[NFT_TABLE_MAX];
+};
+
+enum obj_update_type {
+	NFT_COMPAT_TABLE_ADD,
+	NFT_COMPAT_TABLE_FLUSH,
+	NFT_COMPAT_CHAIN_ADD,
+	NFT_COMPAT_CHAIN_USER_ADD,
+	NFT_COMPAT_CHAIN_USER_DEL,
+	NFT_COMPAT_CHAIN_USER_FLUSH,
+	NFT_COMPAT_CHAIN_UPDATE,
+	NFT_COMPAT_CHAIN_RENAME,
+	NFT_COMPAT_CHAIN_ZERO,
+	NFT_COMPAT_RULE_APPEND,
+	NFT_COMPAT_RULE_INSERT,
+	NFT_COMPAT_RULE_REPLACE,
+	NFT_COMPAT_RULE_DELETE,
+	NFT_COMPAT_RULE_FLUSH,
+	NFT_COMPAT_SET_ADD,
+	NFT_COMPAT_RULE_LIST,
+	NFT_COMPAT_RULE_CHECK,
+	NFT_COMPAT_CHAIN_RESTORE,
+	NFT_COMPAT_RULE_SAVE,
+	NFT_COMPAT_RULE_ZERO,
+	NFT_COMPAT_BRIDGE_USER_CHAIN_UPDATE,
+};
+
+struct cache_chain {
+	struct list_head head;
+	char *name;
+};
+
+struct nft_cache_req {
+	enum nft_cache_level	level;
+	char			*table;
+	bool			all_chains;
+	struct list_head	chain_list;
+};
+
+struct nft_handle {
+	int			family;
+	struct mnl_socket	*nl;
+	int			nlsndbuffsiz;
+	int			nlrcvbuffsiz;
+	uint32_t		portid;
+	uint32_t		seq;
+	uint32_t		nft_genid;
+	uint32_t		rule_id;
+	struct list_head	obj_list;
+	int			obj_list_num;
+	struct nftnl_batch	*batch;
+	struct list_head	err_list;
+	struct nft_family_ops	*ops;
+	const struct builtin_table *tables;
+	unsigned int		cache_index;
+	struct nft_cache	__cache[2];
+	struct nft_cache	*cache;
+	struct nft_cache_req	cache_req;
+	bool			restore;
+	bool			noflush;
+	int8_t			config_done;
+	struct list_head	cmd_list;
+	bool			cache_init;
+
+	/* meta data, for error reporting */
+	struct {
+		unsigned int	lineno;
+	} error;
+};
+
+extern const struct builtin_table xtables_ipv4[NFT_TABLE_MAX];
+extern const struct builtin_table xtables_arp[NFT_TABLE_MAX];
+extern const struct builtin_table xtables_bridge[NFT_TABLE_MAX];
+
+int mnl_talk(struct nft_handle *h, struct nlmsghdr *nlh,
+	     int (*cb)(const struct nlmsghdr *nlh, void *data),
+	     void *data);
+int nft_init(struct nft_handle *h, int family, const struct builtin_table *t);
+void nft_fini(struct nft_handle *h);
+int nft_restart(struct nft_handle *h);
+
+/*
+ * Operations with tables.
+ */
+struct nftnl_table;
+struct nftnl_chain_list;
+
+int nft_for_each_table(struct nft_handle *h, int (*func)(struct nft_handle *h, const char *tablename, void *data), void *data);
+bool nft_table_find(struct nft_handle *h, const char *tablename);
+int nft_table_purge_chains(struct nft_handle *h, const char *table, struct nftnl_chain_list *list);
+int nft_table_flush(struct nft_handle *h, const char *table);
+const struct builtin_table *nft_table_builtin_find(struct nft_handle *h, const char *table);
+int nft_xt_fake_builtin_chains(struct nft_handle *h, const char *table, const char *chain);
+
+/*
+ * Operations with chains.
+ */
+struct nftnl_chain;
+
+int nft_chain_set(struct nft_handle *h, const char *table, const char *chain, const char *policy, const struct xt_counters *counters);
+int nft_chain_save(struct nft_chain *c, void *data);
+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, bool verbose);
+int nft_chain_restore(struct nft_handle *h, const char *chain, const char *table);
+int nft_chain_user_rename(struct nft_handle *h, const char *chain, const char *table, const char *newname);
+int nft_chain_zero_counters(struct nft_handle *h, const char *chain, const char *table, bool verbose);
+const struct builtin_chain *nft_chain_builtin_find(const struct builtin_table *t, const char *chain);
+bool nft_chain_exists(struct nft_handle *h, const char *table, const char *chain);
+void nft_bridge_chain_postprocess(struct nft_handle *h,
+				  struct nftnl_chain *c);
+int nft_chain_foreach(struct nft_handle *h, const char *table,
+		      int (*cb)(struct nft_chain *c, void *data),
+		      void *data);
+
+
+/*
+ * Operations with sets.
+ */
+struct nftnl_set *nft_set_batch_lookup_byid(struct nft_handle *h,
+					    uint32_t set_id);
+
+/*
+ * Operations with rule-set.
+ */
+struct nftnl_rule;
+
+struct nftnl_rule *nft_rule_new(struct nft_handle *h, const char *chain, const char *table, void *data);
+int nft_rule_append(struct nft_handle *h, const char *chain, const char *table, struct nftnl_rule *r, struct nftnl_rule *ref, bool verbose);
+int nft_rule_insert(struct nft_handle *h, const char *chain, const char *table, struct nftnl_rule *r, int rulenum, bool verbose);
+int nft_rule_check(struct nft_handle *h, const char *chain, const char *table, struct nftnl_rule *r, bool verbose);
+int nft_rule_delete(struct nft_handle *h, const char *chain, const char *table, struct nftnl_rule *r, 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 nftnl_rule *r, 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, unsigned int format);
+int nft_rule_flush(struct nft_handle *h, const char *chain, const char *table, bool verbose);
+int nft_rule_zero_counters(struct nft_handle *h, const char *chain, const char *table, int rulenum);
+
+/*
+ * Operations used in userspace tools
+ */
+int add_counters(struct nftnl_rule *r, uint64_t packets, uint64_t bytes);
+int add_verdict(struct nftnl_rule *r, int verdict);
+int add_match(struct nft_handle *h, struct nftnl_rule *r, struct xt_entry_match *m);
+int add_target(struct nftnl_rule *r, struct xt_entry_target *t);
+int add_jumpto(struct nftnl_rule *r, const char *name, int verdict);
+int add_action(struct nftnl_rule *r, struct iptables_command_state *cs, bool goto_set);
+char *get_comment(const void *data, uint32_t data_len);
+
+enum nft_rule_print {
+	NFT_RULE_APPEND,
+	NFT_RULE_DEL,
+};
+
+void nft_rule_print_save(struct nft_handle *h, const struct nftnl_rule *r,
+			 enum nft_rule_print type, unsigned int format);
+
+uint32_t nft_invflags2cmp(uint32_t invflags, uint32_t flag);
+
+/*
+ * global commit and abort
+ */
+int nft_commit(struct nft_handle *h);
+int nft_bridge_commit(struct nft_handle *h);
+int nft_abort(struct nft_handle *h);
+
+/*
+ * revision compatibility.
+ */
+int nft_compatible_revision(const char *name, uint8_t rev, int opt);
+
+/*
+ * Error reporting.
+ */
+const char *nft_strerror(int err);
+
+/* For xtables.c */
+int do_commandx(struct nft_handle *h, int argc, char *argv[], char **table, bool restore);
+/* For xtables-arptables.c */
+int nft_init_arp(struct nft_handle *h, const char *pname);
+int do_commandarp(struct nft_handle *h, int argc, char *argv[], char **table, bool restore);
+/* For xtables-eb.c */
+int nft_init_eb(struct nft_handle *h, const char *pname);
+void nft_fini_eb(struct nft_handle *h);
+int ebt_get_current_chain(const char *chain);
+int do_commandeb(struct nft_handle *h, int argc, char *argv[], char **table, bool restore);
+
+/*
+ * Translation from iptables to nft
+ */
+struct xt_buf;
+
+bool xlate_find_match(const struct iptables_command_state *cs, const char *p_name);
+int xlate_matches(const struct iptables_command_state *cs, struct xt_xlate *xl);
+int xlate_action(const struct iptables_command_state *cs, bool goto_set,
+		 struct xt_xlate *xl);
+void xlate_ifname(struct xt_xlate *xl, const char *nftmeta, const char *ifname,
+		  bool invert);
+
+/*
+ * ARP
+ */
+
+struct arpt_entry;
+
+int nft_arp_rule_append(struct nft_handle *h, const char *chain,
+			const char *table, struct arpt_entry *fw,
+			bool verbose);
+int nft_arp_rule_insert(struct nft_handle *h, const char *chain,
+			const char *table, struct arpt_entry *fw,
+			int rulenum, bool verbose);
+
+void nft_rule_to_arpt_entry(struct nftnl_rule *r, struct arpt_entry *fw);
+
+bool nft_is_table_compatible(struct nft_handle *h,
+			     const char *table, const char *chain);
+void nft_assert_table_compatible(struct nft_handle *h,
+				 const char *table, const char *chain);
+
+int ebt_set_user_chain_policy(struct nft_handle *h, const char *table,
+			      const char *chain, const char *policy);
+
+#endif
diff --git a/iptables/tests/shell/README b/iptables/tests/shell/README
new file mode 100644
index 0000000..08da486
--- /dev/null
+++ b/iptables/tests/shell/README
@@ -0,0 +1,17 @@
+To run the test suite (as root):
+ $ cd iptables/tests/shell
+ # ./run-tests.sh
+
+Test files are executable files with the pattern <<name_N>> , where N is the
+expected return code of the executable. Since they are located with `find',
+test-files can be spreaded in any sub-directories.
+
+You can turn on a verbose execution by calling:
+ # ./run-tests.sh -v
+
+And to run test suite for pariticular test files:
+ # ./run-tests.sh <PATH_OF_TESTFILES>
+
+Also, test-files will receive the environment variable $XT_MULTI which contains
+the path to the old iptables (xtables-legacy-multi) or new iptables (xtables-nft-multi)
+binary being tested.
diff --git a/iptables/tests/shell/run-tests.sh b/iptables/tests/shell/run-tests.sh
new file mode 100755
index 0000000..65c37ad
--- /dev/null
+++ b/iptables/tests/shell/run-tests.sh
@@ -0,0 +1,198 @@
+#!/bin/bash
+
+#configuration
+TESTDIR="./$(dirname $0)/"
+RETURNCODE_SEPARATOR="_"
+
+usage() {
+	cat <<EOF
+Usage: $(basename $0) [-v|--verbose] [-H|--host] [-V|--valgrind]
+		      [[-l|--legacy]|[-n|--nft]] [testscript ...]
+
+-v | --verbose		Enable verbose mode (do not drop testscript output).
+-H | --host		Run tests against installed binaries in \$PATH,
+			not those built in this source tree.
+-V | --valgrind		Enable leak checking via valgrind.
+-l | --legacy		Test legacy variant only. Conflicts with --nft.
+-n | --nft		Test nft variant only. Conflicts with --legacy.
+testscript		Run only specific test(s). Implies --verbose.
+EOF
+}
+
+msg_error() {
+        echo "E: $1 ..." >&2
+        exit 1
+}
+
+msg_warn() {
+        echo "W: $1" >&2
+}
+
+msg_info() {
+        echo "I: $1"
+}
+
+if [ "$(id -u)" != "0" ] ; then
+        msg_error "this requires root!"
+fi
+
+if [ ! -d "$TESTDIR" ] ; then
+        msg_error "missing testdir $TESTDIR"
+fi
+
+# support matching repeated pattern in SINGLE check below
+shopt -s extglob
+
+while [ -n "$1" ]; do
+	case "$1" in
+	-v|--verbose)
+		VERBOSE=y
+		shift
+		;;
+	-H|--host)
+		HOST=y
+		shift
+		;;
+	-l|--legacy)
+		LEGACY_ONLY=y
+		shift
+		;;
+	-n|--nft)
+		NFT_ONLY=y
+		shift
+		;;
+	-V|--valgrind)
+		VALGRIND=y
+		shift
+		;;
+	-h|--help)
+		usage
+		exit 0
+		;;
+	*${RETURNCODE_SEPARATOR}+([0-9]))
+		SINGLE+=" $1"
+		VERBOSE=y
+		shift
+		;;
+	*)
+		msg_error "unknown parameter '$1'"
+		;;
+	esac
+done
+
+if [ "$HOST" != "y" ]; then
+	XTABLES_NFT_MULTI="$(dirname $0)/../../xtables-nft-multi"
+	XTABLES_LEGACY_MULTI="$(dirname $0)/../../xtables-legacy-multi"
+
+	export XTABLES_LIBDIR=${TESTDIR}/../../../extensions
+else
+	XTABLES_NFT_MULTI="xtables-nft-multi"
+	XTABLES_LEGACY_MULTI="xtables-legacy-multi"
+fi
+
+printscript() { # (cmd, tmpd)
+	cat <<EOF
+#!/bin/bash
+
+CMD="$1"
+
+# note: valgrind man page warns about --log-file with --trace-children, the
+# last child executed overwrites previous reports unless %p or %q is used.
+# Since libtool wrapper calls exec but none of the iptables tools do, this is
+# perfect for us as it effectively hides bash-related errors
+
+valgrind --log-file=$2/valgrind.log --trace-children=yes \
+	 --leak-check=full --show-leak-kinds=all \$CMD "\$@"
+RC=\$?
+
+# don't keep uninteresting logs
+if grep -q 'no leaks are possible' $2/valgrind.log; then
+	rm $2/valgrind.log
+else
+	mv $2/valgrind.log $2/valgrind_\$\$.log
+fi
+
+# drop logs for failing commands for now
+[ \$RC -eq 0 ] || rm $2/valgrind_\$\$.log
+
+exit \$RC
+EOF
+}
+
+if [ "$VALGRIND" == "y" ]; then
+	tmpd=$(mktemp -d)
+	msg_info "writing valgrind logs to $tmpd"
+	chmod a+rx $tmpd
+	printscript "$XTABLES_NFT_MULTI" "$tmpd" >${tmpd}/xtables-nft-multi
+	printscript "$XTABLES_LEGACY_MULTI" "$tmpd" >${tmpd}/xtables-legacy-multi
+	trap "rm ${tmpd}/xtables-*-multi" EXIT
+	chmod a+x ${tmpd}/xtables-nft-multi ${tmpd}/xtables-legacy-multi
+
+	XTABLES_NFT_MULTI="${tmpd}/xtables-nft-multi"
+	XTABLES_LEGACY_MULTI="${tmpd}/xtables-legacy-multi"
+
+fi
+
+find_tests() {
+        if [ ! -z "$SINGLE" ] ; then
+                echo $SINGLE
+                return
+        fi
+        find ${TESTDIR} -executable -regex \
+                .*${RETURNCODE_SEPARATOR}[0-9]+ | sort
+}
+
+ok=0
+failed=0
+
+do_test() {
+	testfile="$1"
+	xtables_multi="$2"
+
+	rc_spec=`echo $(basename ${testfile}) | cut -d _ -f2-`
+
+	msg_info "[EXECUTING]   $testfile"
+
+	if [ "$VERBOSE" = "y" ]; then
+		XT_MULTI=$xtables_multi unshare -n ${testfile}
+		rc_got=$?
+	else
+		XT_MULTI=$xtables_multi unshare -n ${testfile} > /dev/null 2>&1
+		rc_got=$?
+		echo -en "\033[1A\033[K" # clean the [EXECUTING] foobar line
+	fi
+
+	if [ "$rc_got" == "$rc_spec" ] ; then
+		msg_info "[OK]          $testfile"
+		((ok++))
+	else
+		((failed++))
+		msg_warn "[FAILED]      $testfile: expected $rc_spec but got $rc_got"
+	fi
+}
+
+echo ""
+if [ "$NFT_ONLY" != "y" ]; then
+	for testfile in $(find_tests);do
+		do_test "$testfile" "$XTABLES_LEGACY_MULTI"
+	done
+	msg_info "legacy results: [OK] $ok [FAILED] $failed [TOTAL] $((ok+failed))"
+
+fi
+legacy_ok=$ok
+legacy_fail=$failed
+ok=0
+failed=0
+if [ "$LEGACY_ONLY" != "y" ]; then
+	for testfile in $(find_tests);do
+		do_test "$testfile" "$XTABLES_NFT_MULTI"
+	done
+	msg_info "nft results: [OK] $ok [FAILED] $failed [TOTAL] $((ok+failed))"
+fi
+
+ok=$((legacy_ok+ok))
+failed=$((legacy_fail+failed))
+
+msg_info "combined results: [OK] $ok [FAILED] $failed [TOTAL] $((ok+failed))"
+
+exit 0
diff --git a/iptables/tests/shell/testcases/arptables/0001-arptables-save-restore_0 b/iptables/tests/shell/testcases/arptables/0001-arptables-save-restore_0
new file mode 100755
index 0000000..e64e914
--- /dev/null
+++ b/iptables/tests/shell/testcases/arptables/0001-arptables-save-restore_0
@@ -0,0 +1,61 @@
+#!/bin/bash
+
+set -e
+#set -x
+
+# there is no legacy backend to test
+[[ $XT_MULTI == *xtables-nft-multi ]] || { echo "skip $XT_MULTI"; exit 0; }
+
+# fill arptables manually
+
+$XT_MULTI arptables -F
+$XT_MULTI arptables -A INPUT -s 10.0.0.0/8 -j ACCEPT
+$XT_MULTI arptables -A INPUT -d 192.168.123.1 -j ACCEPT
+$XT_MULTI arptables -A INPUT --source-mac fe:ed:ba:be:00:01 -j ACCEPT
+$XT_MULTI arptables -A INPUT --destination-mac fe:ed:ba:be:00:01 -j ACCEPT
+$XT_MULTI arptables -N foo
+$XT_MULTI arptables -A foo -i lo -j ACCEPT
+$XT_MULTI arptables -A foo -l 6 -j ACCEPT
+$XT_MULTI arptables -A foo -j MARK --set-mark 12345
+$XT_MULTI arptables -A foo --opcode Request -j ACCEPT
+$XT_MULTI arptables -A foo --h-type 1 --proto-type 0x800 -j ACCEPT
+$XT_MULTI arptables -A foo -l 6 --h-type 1 --proto-type 0x800 -i lo --opcode Request -j ACCEPT
+$XT_MULTI arptables -A INPUT -j foo
+$XT_MULTI arptables -A INPUT
+
+$XT_MULTI arptables -A OUTPUT -o lo -j ACCEPT
+$XT_MULTI arptables -A OUTPUT -o eth134 -j mangle --mangle-ip-s 10.0.0.1
+$XT_MULTI arptables -A OUTPUT -o eth432 -j CLASSIFY --set-class feed:babe
+$XT_MULTI arptables -A OUTPUT -o eth432 --opcode Request -j CLASSIFY --set-class feed:babe
+$XT_MULTI arptables -P OUTPUT DROP
+
+# compare against stored arptables dump
+
+DUMP='*filter
+:INPUT ACCEPT
+:OUTPUT DROP
+:foo -
+-A INPUT -j ACCEPT -s 10.0.0.0/8
+-A INPUT -j ACCEPT -d 192.168.123.1
+-A INPUT -j ACCEPT --src-mac fe:ed:ba:be:00:01
+-A INPUT -j ACCEPT --dst-mac fe:ed:ba:be:00:01
+-A INPUT -j foo
+-A INPUT 
+-A OUTPUT -j ACCEPT -o lo
+-A OUTPUT -j mangle -o eth134 --mangle-ip-s 10.0.0.1
+-A OUTPUT -j CLASSIFY -o eth432 --set-class feed:babe
+-A OUTPUT -j CLASSIFY -o eth432 --opcode 1 --set-class feed:babe
+-A foo -j ACCEPT -i lo
+-A foo -j ACCEPT
+-A foo -j MARK --set-mark 12345
+-A foo -j ACCEPT --opcode 1
+-A foo -j ACCEPT --proto-type 0x800
+-A foo -j ACCEPT -i lo --opcode 1 --proto-type 0x800'
+
+diff -u <(echo -e "$DUMP") <($XT_MULTI arptables-save | grep -v "^#")
+
+# make sure dump can be restored and check it didn't change
+
+$XT_MULTI arptables -F
+$XT_MULTI arptables-restore <<<$DUMP
+diff -u <(echo -e "$DUMP") <($XT_MULTI arptables-save | grep -v "^#")
diff --git a/iptables/tests/shell/testcases/arptables/0002-arptables-restore-defaults_0 b/iptables/tests/shell/testcases/arptables/0002-arptables-restore-defaults_0
new file mode 100755
index 0000000..afd0fcb
--- /dev/null
+++ b/iptables/tests/shell/testcases/arptables/0002-arptables-restore-defaults_0
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+set -e
+
+# there is no legacy backend to test
+[[ $XT_MULTI == *xtables-nft-multi ]] || { echo "skip $XT_MULTI"; exit 0; }
+
+# arptables-restore reuses preloaded targets and matches, make sure defaults
+# apply to consecutive rules using the same target/match as a previous one
+
+DUMP='*filter
+:OUTPUT ACCEPT
+-A OUTPUT -j mangle --mangle-ip-s 10.0.0.1
+-A OUTPUT -j mangle --mangle-ip-d 10.0.0.2'
+
+# note how mangle-ip-s is unset in second rule
+
+EXPECT='*filter
+:INPUT ACCEPT
+:OUTPUT ACCEPT
+-A OUTPUT -j mangle --mangle-ip-s 10.0.0.1
+-A OUTPUT -j mangle --mangle-ip-d 10.0.0.2'
+
+$XT_MULTI arptables -F
+$XT_MULTI arptables-restore <<<$DUMP
+diff -u <(echo -e "$EXPECT") <($XT_MULTI arptables-save | grep -v '^#')
diff --git a/iptables/tests/shell/testcases/arptables/0003-arptables-verbose-output_0 b/iptables/tests/shell/testcases/arptables/0003-arptables-verbose-output_0
new file mode 100755
index 0000000..952cfa7
--- /dev/null
+++ b/iptables/tests/shell/testcases/arptables/0003-arptables-verbose-output_0
@@ -0,0 +1,63 @@
+#!/bin/bash
+
+set -e
+set -x
+
+# there is no legacy backend to test
+[[ $XT_MULTI == *xtables-nft-multi ]] || { echo "skip $XT_MULTI"; exit 0; }
+
+$XT_MULTI arptables -N foo
+
+# check verbose output matches expectations
+
+RULE1='-i eth23 -j ACCEPT'
+VOUT1='-j ACCEPT -i eth23 -o *'
+
+RULE2='-i eth23'
+VOUT2='-i eth23 -o *'
+
+RULE3='-i eth23 -j MARK --set-mark 42'
+VOUT3='-j MARK -i eth23 -o * --set-mark 42'
+
+RULE4='-o eth23 -j CLASSIFY --set-class 23:42'
+VOUT4='-j CLASSIFY -i * -o eth23 --set-class 23:42'
+
+RULE5='-o eth23 -j foo'
+VOUT5='-j foo -i * -o eth23'
+
+RULE6='-o eth23 -j mangle --mangle-ip-s 10.0.0.1'
+VOUT6='-j mangle -i * -o eth23 --mangle-ip-s 10.0.0.1'
+
+diff -u -Z <(echo -e "$VOUT1") <($XT_MULTI arptables -v -A INPUT $RULE1)
+diff -u -Z <(echo -e "$VOUT2") <($XT_MULTI arptables -v -A INPUT $RULE2)
+diff -u -Z <(echo -e "$VOUT3") <($XT_MULTI arptables -v -A INPUT $RULE3)
+diff -u -Z <(echo -e "$VOUT4") <($XT_MULTI arptables -v -A OUTPUT $RULE4)
+diff -u -Z <(echo -e "$VOUT5") <($XT_MULTI arptables -v -A OUTPUT $RULE5)
+diff -u -Z <(echo -e "$VOUT6") <($XT_MULTI arptables -v -A foo $RULE6)
+
+EXPECT='Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
+-j ACCEPT -i eth23 -o * , pcnt=0 -- bcnt=0
+-i eth23 -o * , pcnt=0 -- bcnt=0
+-j MARK -i eth23 -o * --set-mark 42 , pcnt=0 -- bcnt=0
+
+Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
+-j CLASSIFY -i * -o eth23 --set-class 23:42 , pcnt=0 -- bcnt=0
+-j foo -i * -o eth23 , pcnt=0 -- bcnt=0
+
+Chain foo (1 references)
+-j mangle -i * -o eth23 --mangle-ip-s 10.0.0.1 , pcnt=0 -- bcnt=0'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI arptables -v -n -L)
+
+EXPECT='*filter
+:INPUT ACCEPT
+:OUTPUT ACCEPT
+:foo -
+-A INPUT -j ACCEPT -i eth23
+-A INPUT -i eth23
+-A INPUT -j MARK -i eth23 --set-mark 42
+-A OUTPUT -j CLASSIFY -o eth23 --set-class 23:42
+-A OUTPUT -j foo -o eth23
+-A foo -j mangle -o eth23 --mangle-ip-s 10.0.0.1'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI arptables-save | grep -v '^#')
diff --git a/iptables/tests/shell/testcases/chain/0001duplicate_1 b/iptables/tests/shell/testcases/chain/0001duplicate_1
new file mode 100755
index 0000000..80ebb11
--- /dev/null
+++ b/iptables/tests/shell/testcases/chain/0001duplicate_1
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+set -x
+
+$XT_MULTI iptables -t filter -N c1 || exit 0
+$XT_MULTI iptables -t filter -N c1 || exit 1
+
+$XT_MULTI ip6tables -t filter -N c1 || exit 0
+$XT_MULTI ip6tables -t filter -N c1 || exit 1
+
+echo "E: Duplicate chains" >&2
+exit 0
diff --git a/iptables/tests/shell/testcases/chain/0002newchain_0 b/iptables/tests/shell/testcases/chain/0002newchain_0
new file mode 100755
index 0000000..53f8a3a
--- /dev/null
+++ b/iptables/tests/shell/testcases/chain/0002newchain_0
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+set -e
+
+$XT_MULTI iptables -N c1
+$XT_MULTI ip6tables -N c1
+
+$XT_MULTI iptables -N c2
+$XT_MULTI ip6tables -N c2
diff --git a/iptables/tests/shell/testcases/chain/0003rename_1 b/iptables/tests/shell/testcases/chain/0003rename_1
new file mode 100755
index 0000000..975c8e1
--- /dev/null
+++ b/iptables/tests/shell/testcases/chain/0003rename_1
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+$XT_MULTI iptables -N c1 || exit 0
+$XT_MULTI iptables -N c2 || exit 0
+$XT_MULTI iptables -E c1 c2 || exit 1
+
+$XT_MULTI ip6tables -N c1 || exit 0
+$XT_MULTI ip6tables -N c2 || exit 0
+$XT_MULTI ip6tables -E c1 c2 || exit 1
+
+echo "E: Renamed with existing chain" >&2
+exit 0
diff --git a/iptables/tests/shell/testcases/ebtables/0001-ebtables-basic_0 b/iptables/tests/shell/testcases/ebtables/0001-ebtables-basic_0
new file mode 100755
index 0000000..6f11bd1
--- /dev/null
+++ b/iptables/tests/shell/testcases/ebtables/0001-ebtables-basic_0
@@ -0,0 +1,93 @@
+#!/bin/sh
+
+case "$XT_MULTI" in
+*xtables-nft-multi)
+	;;
+*)
+	echo "skip $XT_MULTI"
+	exit 0
+	;;
+esac
+
+get_entries_count() { # (chain)
+	$XT_MULTI ebtables -L $1 | sed -n 's/.*entries: \([0-9]*\).*/\1/p'
+}
+
+set -x
+
+for t in filter nat;do
+	$XT_MULTI ebtables -t $t -L || exit 1
+	$XT_MULTI ebtables -t $t -X || exit 1
+	$XT_MULTI ebtables -t $t -F || exit 1
+done
+
+for t in broute foobar ;do
+	$XT_MULTI ebtables -t $t -L &&
+	$XT_MULTI ebtables -t $t -X &&
+	$XT_MULTI ebtables -t $t -F
+	if [ $? -eq 0 ]; then
+		echo "Expect nonzero return for unsupported table"
+		exit 1
+	fi
+done
+
+
+$XT_MULTI ebtables -t filter -N FOO || exit 1
+$XT_MULTI ebtables -t filter -N FOO
+if [ $? -eq 0 ]; then
+	echo "Duplicate chain FOO"
+	$XT_MULTI ebtables -t filter -L
+	exit 1
+fi
+
+entries=$(get_entries_count FOO)
+if [ $entries -ne 0 ]; then
+	echo "Unexpected entries count in empty unreferenced chain (expected 0, have $entries)"
+	$XT_MULTI ebtables -L
+	exit 1
+fi
+
+$XT_MULTI ebtables -A FORWARD -j FOO
+entries=$(get_entries_count FORWARD)
+if [ $entries -ne 1 ]; then
+	echo "Unexpected entries count in FORWARD chain (expected 1, have $entries)"
+	$XT_MULTI ebtables -L
+	exit 1
+fi
+
+entries=$(get_entries_count FOO)
+if [ $entries -ne 0 ]; then
+	echo "Unexpected entries count in empty referenced chain (expected 0, have $entries)"
+	$XT_MULTI ebtables -L
+	exit 1
+fi
+
+$XT_MULTI ebtables -A FOO -j ACCEPT
+entries=$(get_entries_count FOO)
+if [ $entries -ne 1 ]; then
+	echo "Unexpected entries count in non-empty referenced chain (expected 1, have $entries)"
+	$XT_MULTI ebtables -L
+	exit 1
+fi
+
+$XT_MULTI ebtables -t filter -N BAR || exit 1
+$XT_MULTI ebtables -t filter -N BAZ || exit 1
+
+$XT_MULTI ebtables -t filter -L | grep -q FOO || exit 1
+$XT_MULTI ebtables -t filter -L | grep -q BAR || exit 1
+$XT_MULTI ebtables -t filter -L | grep -q BAZ || exit 1
+
+$XT_MULTI ebtables -t filter -L BAZ || exit 1
+$XT_MULTI ebtables -t filter -X BAZ || exit 1
+$XT_MULTI ebtables -t filter -L BAZ | grep -q BAZ
+if [ $? -eq 0 ]; then
+	echo "Deleted chain -L BAZ ok, expected failure"
+	$XT_MULTI ebtables -t filter -L
+	exit 1
+fi
+
+$XT_MULTI ebtables -t filter -E FOO BAZ || exit 1
+$XT_MULTI ebtables -t filter -L | grep -q FOO && exit 1
+$XT_MULTI ebtables -t filter -L | grep -q BAZ || exit 1
+
+$XT_MULTI ebtables -t $t -F || exit 0
diff --git a/iptables/tests/shell/testcases/ebtables/0002-ebtables-save-restore_0 b/iptables/tests/shell/testcases/ebtables/0002-ebtables-save-restore_0
new file mode 100755
index 0000000..ccdef19
--- /dev/null
+++ b/iptables/tests/shell/testcases/ebtables/0002-ebtables-save-restore_0
@@ -0,0 +1,117 @@
+#!/bin/bash
+
+set -e
+#set -x
+
+# there is no legacy backend to test
+[[ $XT_MULTI == *xtables-nft-multi ]] || { echo "skip $XT_MULTI"; exit 0; }
+
+# fill ebtables manually
+
+$XT_MULTI ebtables --init-table
+$XT_MULTI ebtables -A INPUT -p IPv4 -i lo -j ACCEPT
+$XT_MULTI ebtables -P FORWARD DROP
+$XT_MULTI ebtables -A OUTPUT -s ff:ff:ff:ff:ff:ff/ff:ff:ff:ff:ff:ff -j DROP
+$XT_MULTI ebtables -N foo
+$XT_MULTI ebtables -A foo --802_3-sap 0x23 -j ACCEPT
+$XT_MULTI ebtables -A foo --802_3-sap 0xaa --802_3-type 0x1337 -j ACCEPT
+#$XT_MULTI ebtables -A foo --among-dst fe:ed:ba:be:00:01,fe:ed:ba:be:00:02,fe:ed:ba:be:00:03 -j ACCEPT
+$XT_MULTI ebtables -A foo -p ARP --arp-gratuitous -j ACCEPT
+$XT_MULTI ebtables -A foo -p ARP --arp-opcode Request -j ACCEPT
+$XT_MULTI ebtables -A foo -p ARP --arp-ip-src 10.0.0.1 -j ACCEPT
+$XT_MULTI ebtables -A foo -p ARP --arp-ip-dst 10.0.0.0/8 -j ACCEPT
+$XT_MULTI ebtables -A foo -p ARP --arp-mac-src fe:ed:ba:be:00:01 -j ACCEPT
+$XT_MULTI ebtables -A foo -p ARP --arp-mac-dst fe:ed:ba:be:00:01/ff:ff:ff:00:00:00 -j ACCEPT
+
+$XT_MULTI ebtables -A foo -p IPv4 --ip-src 10.0.0.1 -j ACCEPT
+$XT_MULTI ebtables -A foo -p IPv4 --ip-dst 10.0.0.0/8 -j ACCEPT
+$XT_MULTI ebtables -A foo -p IPv4 --ip-tos 0x10 -j ACCEPT
+$XT_MULTI ebtables -A foo -p IPv4 --ip-protocol tcp -j ACCEPT
+#$XT_MULTI ebtables -A foo -p IPv4 --ip-sport 23 -j ACCEPT
+#$XT_MULTI ebtables -A foo -p IPv4 --ip-dport 1024:4096 -j ACCEPT
+
+$XT_MULTI ebtables -A foo -p IPv6 --ip6-src feed:babe::1 -j ACCEPT
+$XT_MULTI ebtables -A foo -p IPv6 --ip6-dst feed:babe::/64 -j ACCEPT
+$XT_MULTI ebtables -A foo -p IPv6 --ip6-proto tcp -j ACCEPT
+#$XT_MULTI ebtables -A foo -p IPv6 --ip6-sport 23 -j ACCEPT
+#$XT_MULTI ebtables -A foo -p IPv6 --ip6-dport 1024:4096 -j ACCEPT
+
+$XT_MULTI ebtables -A foo --limit 100 --limit-burst 42 -j ACCEPT
+$XT_MULTI ebtables -A foo --log
+$XT_MULTI ebtables -A foo --mark-set 0x23 --mark-target ACCEPT
+$XT_MULTI ebtables -A foo --nflog
+$XT_MULTI ebtables -A foo --pkttype-type multicast -j ACCEPT
+$XT_MULTI ebtables -A foo --stp-type config -j ACCEPT
+#$XT_MULTI ebtables -A foo --vlan-id 42 -j ACCEPT
+
+$XT_MULTI ebtables -A foo --802_3-sap 0x23 --limit 100 -j ACCEPT
+$XT_MULTI ebtables -A foo --pkttype-type multicast --log
+$XT_MULTI ebtables -A foo --pkttype-type multicast --limit 100 -j ACCEPT
+
+$XT_MULTI ebtables -A FORWARD -j foo
+
+$XT_MULTI ebtables -N bar
+$XT_MULTI ebtables -P bar RETURN
+
+$XT_MULTI ebtables -t nat -A PREROUTING --redirect-target ACCEPT
+#$XT_MULTI ebtables -t nat -A PREROUTING --to-src fe:ed:ba:be:00:01
+
+$XT_MULTI ebtables -t nat -A OUTPUT -j ACCEPT
+$XT_MULTI ebtables -t nat -P OUTPUT DROP
+
+$XT_MULTI ebtables -t nat -A POSTROUTING -j ACCEPT
+#$XT_MULTI ebtables -t nat -A POSTROUTING --to-dst fe:ed:ba:be:00:01 --dnat-target ACCEPT
+
+$XT_MULTI ebtables -t nat -N nat_foo -P DROP
+
+# compare against stored ebtables dump
+
+DUMP='*filter
+:INPUT ACCEPT
+:FORWARD DROP
+:OUTPUT ACCEPT
+:bar RETURN
+:foo ACCEPT
+-A INPUT -p IPv4 -i lo -j ACCEPT
+-A FORWARD -j foo
+-A OUTPUT -s Broadcast -j DROP
+-A foo --802_3-sap 0x23 -j ACCEPT
+-A foo --802_3-sap 0xaa --802_3-type 0x1337 -j ACCEPT
+-A foo -p ARP --arp-gratuitous -j ACCEPT
+-A foo -p ARP --arp-op Request -j ACCEPT
+-A foo -p ARP --arp-ip-src 10.0.0.1 -j ACCEPT
+-A foo -p ARP --arp-ip-dst 10.0.0.0/8 -j ACCEPT
+-A foo -p ARP --arp-mac-src fe:ed:ba:be:00:01 -j ACCEPT
+-A foo -p ARP --arp-mac-dst fe:ed:ba:00:00:00/ff:ff:ff:00:00:00 -j ACCEPT
+-A foo -p IPv4 --ip-src 10.0.0.1 -j ACCEPT
+-A foo -p IPv4 --ip-dst 10.0.0.0/8 -j ACCEPT
+-A foo -p IPv4 --ip-tos 0x10 -j ACCEPT
+-A foo -p IPv4 --ip-proto tcp -j ACCEPT
+-A foo -p IPv6 --ip6-src feed:babe::1 -j ACCEPT
+-A foo -p IPv6 --ip6-dst feed:babe::/64 -j ACCEPT
+-A foo -p IPv6 --ip6-proto tcp -j ACCEPT
+-A foo --limit 100/sec --limit-burst 42 -j ACCEPT
+-A foo --log-level notice --log-prefix "" -j CONTINUE
+-A foo -j mark --mark-set 0x23 --mark-target ACCEPT
+-A foo --nflog-group 1 -j CONTINUE
+-A foo --pkttype-type multicast -j ACCEPT
+-A foo --stp-type config -j ACCEPT
+-A foo --802_3-sap 0x23 --limit 100/sec --limit-burst 5 -j ACCEPT
+-A foo --pkttype-type multicast --log-level notice --log-prefix "" -j CONTINUE
+-A foo --pkttype-type multicast --limit 100/sec --limit-burst 5 -j ACCEPT
+*nat
+:PREROUTING ACCEPT
+:OUTPUT DROP
+:POSTROUTING ACCEPT
+:nat_foo DROP
+-A PREROUTING -j redirect 
+-A OUTPUT -j ACCEPT
+-A POSTROUTING -j ACCEPT'
+
+diff -u <(echo -e "$DUMP") <($XT_MULTI ebtables-save | grep -v '^#')
+
+# make sure dump can be restored and check it didn't change
+
+$XT_MULTI ebtables --init-table
+$XT_MULTI ebtables-restore <<<$DUMP
+diff -u <(echo -e "$DUMP") <($XT_MULTI ebtables-save | grep -v '^#')
diff --git a/iptables/tests/shell/testcases/ebtables/0003-ebtables-restore-defaults_0 b/iptables/tests/shell/testcases/ebtables/0003-ebtables-restore-defaults_0
new file mode 100755
index 0000000..63891c1
--- /dev/null
+++ b/iptables/tests/shell/testcases/ebtables/0003-ebtables-restore-defaults_0
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+set -e
+
+# there is no legacy backend to test
+[[ $XT_MULTI == *xtables-nft-multi ]] || { echo "skip $XT_MULTI"; exit 0; }
+
+# ebtables-restore reuses preloaded targets and matches, make sure defaults
+# apply to consecutive rules using the same target/match as a previous one
+
+DUMP='*filter
+:FORWARD ACCEPT
+-A FORWARD --limit 100 --limit-burst 42 -j ACCEPT
+-A FORWARD --limit 1000 -j ACCEPT
+-A FORWARD --log --log-prefix "foobar"
+-A FORWARD --log'
+
+# note how limit-burst is 5 in second rule and log-prefix empty in fourth one
+
+EXPECT='*filter
+:INPUT ACCEPT
+:FORWARD ACCEPT
+:OUTPUT ACCEPT
+-A FORWARD --limit 100/sec --limit-burst 42 -j ACCEPT
+-A FORWARD --limit 1000/sec --limit-burst 5 -j ACCEPT
+-A FORWARD --log-level notice --log-prefix "foobar" -j CONTINUE
+-A FORWARD --log-level notice --log-prefix "" -j CONTINUE'
+
+$XT_MULTI ebtables --init-table
+$XT_MULTI ebtables-restore <<<$DUMP
+diff -u <(echo -e "$EXPECT") <($XT_MULTI ebtables-save | grep -v '^#')
diff --git a/iptables/tests/shell/testcases/ebtables/0004-save-counters_0 b/iptables/tests/shell/testcases/ebtables/0004-save-counters_0
new file mode 100755
index 0000000..d52db90
--- /dev/null
+++ b/iptables/tests/shell/testcases/ebtables/0004-save-counters_0
@@ -0,0 +1,64 @@
+#!/bin/bash
+
+set -e
+
+# there is no legacy backend to test
+[[ $XT_MULTI == *xtables-nft-multi ]] || { echo "skip $XT_MULTI"; exit 0; }
+
+$XT_MULTI ebtables --init-table
+$XT_MULTI ebtables -A FORWARD -i nodev123 -o nodev432 -j ACCEPT
+$XT_MULTI ebtables -A FORWARD -i nodev432 -o nodev123 -j ACCEPT
+
+EXPECT='Bridge table: filter
+
+Bridge chain: FORWARD, entries: 2, policy: ACCEPT
+-i nodev123 -o nodev432 -j ACCEPT
+-i nodev432 -o nodev123 -j ACCEPT'
+
+echo "ebtables -L FORWARD"
+diff -u <(echo -e "$EXPECT") <($XT_MULTI ebtables -L FORWARD)
+
+EXPECT='Bridge table: filter
+
+Bridge chain: FORWARD, entries: 2, policy: ACCEPT
+-i nodev123 -o nodev432 -j ACCEPT , pcnt = 0 -- bcnt = 0
+-i nodev432 -o nodev123 -j ACCEPT , pcnt = 0 -- bcnt = 0'
+
+echo "ebtables -L FORWARD --Lc"
+diff -u <(echo -e "$EXPECT") <($XT_MULTI ebtables -L FORWARD --Lc)
+
+EXPECT='*filter
+:INPUT ACCEPT
+:FORWARD ACCEPT
+:OUTPUT ACCEPT
+-A FORWARD -i nodev123 -o nodev432 -j ACCEPT
+-A FORWARD -i nodev432 -o nodev123 -j ACCEPT'
+
+echo "ebtables-save"
+diff -u <(echo -e "$EXPECT") <($XT_MULTI ebtables-save | grep -v '^#')
+
+EXPECT='*filter
+:INPUT ACCEPT
+:FORWARD ACCEPT
+:OUTPUT ACCEPT
+[0:0] -A FORWARD -i nodev123 -o nodev432 -j ACCEPT
+[0:0] -A FORWARD -i nodev432 -o nodev123 -j ACCEPT'
+
+echo "ebtables-save -c"
+diff -u <(echo -e "$EXPECT") <($XT_MULTI ebtables-save -c | grep -v '^#')
+
+export EBTABLES_SAVE_COUNTER=yes
+
+# -c flag overrides EBTABLES_SAVE_COUNTER variable
+echo "EBTABLES_SAVE_COUNTER=yes ebtables-save -c"
+diff -u <(echo -e "$EXPECT") <($XT_MULTI ebtables-save -c | grep -v '^#')
+
+EXPECT='*filter
+:INPUT ACCEPT
+:FORWARD ACCEPT
+:OUTPUT ACCEPT
+-A FORWARD -i nodev123 -o nodev432 -j ACCEPT -c 0 0
+-A FORWARD -i nodev432 -o nodev123 -j ACCEPT -c 0 0'
+
+echo "EBTABLES_SAVE_COUNTER=yes ebtables-save"
+diff -u <(echo -e "$EXPECT") <($XT_MULTI ebtables-save | grep -v '^#')
diff --git a/iptables/tests/shell/testcases/ebtables/0005-ifnamechecks_0 b/iptables/tests/shell/testcases/ebtables/0005-ifnamechecks_0
new file mode 100755
index 0000000..0b3acfd
--- /dev/null
+++ b/iptables/tests/shell/testcases/ebtables/0005-ifnamechecks_0
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+set -e
+
+# there is no legacy backend to test
+[[ $XT_MULTI == *xtables-nft-multi ]] || { echo "skip $XT_MULTI"; exit 0; }
+
+EXPECT='*filter
+:INPUT ACCEPT
+:FORWARD ACCEPT
+:OUTPUT ACCEPT
+:PVEFW-FORWARD ACCEPT
+:PVEFW-FWBR-OUT ACCEPT
+-A FORWARD -j PVEFW-FORWARD
+-A PVEFW-FORWARD -p IPv4 -j ACCEPT
+-A PVEFW-FORWARD -p IPv6 -j ACCEPT
+-A PVEFW-FORWARD -i fwln+ -j ACCEPT
+-A PVEFW-FORWARD -o fwln+ -j PVEFW-FWBR-OUT'
+
+$XT_MULTI ebtables-restore <<<$EXPECT
+exec diff -u <(echo -e "$EXPECT") <($XT_MULTI ebtables-save | grep -v '^#')
diff --git a/iptables/tests/shell/testcases/firewalld-restore/0001-firewalld_0 b/iptables/tests/shell/testcases/firewalld-restore/0001-firewalld_0
new file mode 100755
index 0000000..4900554
--- /dev/null
+++ b/iptables/tests/shell/testcases/firewalld-restore/0001-firewalld_0
@@ -0,0 +1,238 @@
+#!/bin/sh
+
+$XT_MULTI iptables -w -L -n > /dev/null || exit 1
+$XT_MULTI iptables -w2 -L -n > /dev/null || exit 1
+
+echo -n '#foo' | $XT_MULTI iptables-restore -w || exit 1
+
+# table probing
+for table in security raw mangle nat filter;do
+	$XT_MULTI iptables -w2 -t $table -L -n > /dev/null
+done
+
+$XT_MULTI iptables -w2 -p icmp --help | grep -q 'Valid ICMP Types' || exit 1
+
+cat <<EOF | $XT_MULTI iptables-restore -w -n
+*nat
+-F
+-X
+-Z
+-N PREROUTING_direct
+-I PREROUTING 1 -j PREROUTING_direct
+-N PREROUTING_ZONES_SOURCE
+-N PREROUTING_ZONES
+-I PREROUTING 2 -j PREROUTING_ZONES_SOURCE
+-I PREROUTING 3 -j PREROUTING_ZONES
+-N POSTROUTING_direct
+-I POSTROUTING 1 -j POSTROUTING_direct
+-N POSTROUTING_ZONES_SOURCE
+-N POSTROUTING_ZONES
+-I POSTROUTING 2 -j POSTROUTING_ZONES_SOURCE
+-I POSTROUTING 3 -j POSTROUTING_ZONES
+-N OUTPUT_direct
+-I OUTPUT 1 -j OUTPUT_direct
+COMMIT
+*mangle
+-F
+-X
+-Z
+-N PREROUTING_direct
+-I PREROUTING 1 -j PREROUTING_direct
+-N PREROUTING_ZONES_SOURCE
+-N PREROUTING_ZONES
+-I PREROUTING 2 -j PREROUTING_ZONES_SOURCE
+-I PREROUTING 3 -j PREROUTING_ZONES
+-N POSTROUTING_direct
+-I POSTROUTING 1 -j POSTROUTING_direct
+-N INPUT_direct
+-I INPUT 1 -j INPUT_direct
+-N OUTPUT_direct
+-I OUTPUT 1 -j OUTPUT_direct
+-N FORWARD_direct
+-I FORWARD 1 -j FORWARD_direct
+COMMIT
+*raw
+-F
+-X
+-Z
+-N PREROUTING_direct
+-I PREROUTING 1 -j PREROUTING_direct
+-N PREROUTING_ZONES_SOURCE
+-N PREROUTING_ZONES
+-I PREROUTING 2 -j PREROUTING_ZONES_SOURCE
+-I PREROUTING 3 -j PREROUTING_ZONES
+-N OUTPUT_direct
+-I OUTPUT 1 -j OUTPUT_direct
+COMMIT
+*filter
+-F
+-X
+-Z
+-N INPUT_direct
+-N INPUT_ZONES_SOURCE
+-N INPUT_ZONES
+-I INPUT 1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+-I INPUT 2 -i lo -j ACCEPT
+-I INPUT 3 -j INPUT_direct
+-I INPUT 4 -j INPUT_ZONES_SOURCE
+-I INPUT 5 -j INPUT_ZONES
+-I INPUT 6 -m conntrack --ctstate INVALID -j DROP
+-I INPUT 7 -j REJECT --reject-with icmp-host-prohibited
+-N FORWARD_direct
+-N FORWARD_IN_ZONES_SOURCE
+-N FORWARD_IN_ZONES
+-N FORWARD_OUT_ZONES_SOURCE
+-N FORWARD_OUT_ZONES
+-I FORWARD 1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+-I FORWARD 2 -i lo -j ACCEPT
+-I FORWARD 3 -j FORWARD_direct
+-I FORWARD 4 -j FORWARD_IN_ZONES_SOURCE
+-I FORWARD 5 -j FORWARD_IN_ZONES
+-I FORWARD 6 -j FORWARD_OUT_ZONES_SOURCE
+-I FORWARD 7 -j FORWARD_OUT_ZONES
+-I FORWARD 8 -m conntrack --ctstate INVALID -j DROP
+-I FORWARD 9 -j REJECT --reject-with icmp-host-prohibited
+-N OUTPUT_direct
+-I OUTPUT 1 -j OUTPUT_direct
+COMMIT
+EOF
+
+if [ $? -ne 0 ]; then
+	echo "Error during first iptables-restore"
+	exit 1
+fi
+
+cat <<EOF | $XT_MULTI iptables-restore -w -n
+*raw
+-N PRE_public
+-N PRE_public_log
+-N PRE_public_deny
+-N PRE_public_allow
+-I PRE_public 1 -j PRE_public_log
+-I PRE_public 2 -j PRE_public_deny
+-I PRE_public 3 -j PRE_public_allow
+-A PREROUTING_ZONES -i + -g PRE_public
+COMMIT
+*filter
+-N IN_public
+-N IN_public_log
+-N IN_public_deny
+-N IN_public_allow
+-I IN_public 1 -j IN_public_log
+-I IN_public 2 -j IN_public_deny
+-I IN_public 3 -j IN_public_allow
+-A IN_public_allow -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
+-A IN_public_allow -p udp --dport 5353 -d 224.0.0.251 -m conntrack --ctstate NEW -j ACCEPT
+-N FWDI_public
+-N FWDI_public_log
+-N FWDI_public_deny
+-N FWDI_public_allow
+-I FWDI_public 1 -j FWDI_public_log
+-I FWDI_public 2 -j FWDI_public_deny
+-I FWDI_public 3 -j FWDI_public_allow
+-I IN_public 4 -p icmp -j ACCEPT
+-I FWDI_public 4 -p icmp -j ACCEPT
+-A INPUT_ZONES -i + -g IN_public
+-A FORWARD_IN_ZONES -i + -g FWDI_public
+-N FWDO_public
+-N FWDO_public_log
+-N FWDO_public_deny
+-N FWDO_public_allow
+-I FWDO_public 1 -j FWDO_public_log
+-I FWDO_public 2 -j FWDO_public_deny
+-I FWDO_public 3 -j FWDO_public_allow
+-A FORWARD_OUT_ZONES -o + -g FWDO_public
+COMMIT
+*nat
+-N PRE_public
+-N PRE_public_log
+-N PRE_public_deny
+-N PRE_public_allow
+-I PRE_public 1 -j PRE_public_log
+-I PRE_public 2 -j PRE_public_deny
+-I PRE_public 3 -j PRE_public_allow
+-A PREROUTING_ZONES -i + -g PRE_public
+-N POST_public
+-N POST_public_log
+-N POST_public_deny
+-N POST_public_allow
+-I POST_public 1 -j POST_public_log
+-I POST_public 2 -j POST_public_deny
+-I POST_public 3 -j POST_public_allow
+-A POSTROUTING_ZONES -o + -g POST_public
+COMMIT
+*mangle
+-N PRE_public
+-N PRE_public_log
+-N PRE_public_deny
+-N PRE_public_allow
+-I PRE_public 1 -j PRE_public_log
+-I PRE_public 2 -j PRE_public_deny
+-I PRE_public 3 -j PRE_public_allow
+-A PREROUTING_ZONES -i + -g PRE_public
+COMMIT
+EOF
+
+if [ $? -ne 0 ]; then
+	echo "Error during 2nd iptables-restore"
+	exit 1
+fi
+
+cat <<EOF | $XT_MULTI iptables-restore -w -n
+*mangle
+-P PREROUTING ACCEPT
+-P POSTROUTING ACCEPT
+-P INPUT ACCEPT
+-P OUTPUT ACCEPT
+-P FORWARD ACCEPT
+COMMIT
+*raw
+-P PREROUTING ACCEPT
+-P OUTPUT ACCEPT
+COMMIT
+*filter
+-P INPUT ACCEPT
+-P OUTPUT ACCEPT
+-P FORWARD ACCEPT
+COMMIT
+EOF
+
+if [ $? -ne 0 ]; then
+	echo "Error during 3rd iptables-restore"
+	exit 1
+fi
+
+cat <<EOF | $XT_MULTI iptables-restore -w -n
+*filter
+-I INPUT_ZONES 1 -i enp3s0 -g IN_public
+-I FORWARD_IN_ZONES 1 -i enp3s0 -g FWDI_public
+-I FORWARD_OUT_ZONES 1 -o enp3s0 -g FWDO_public
+COMMIT
+*nat
+-I PREROUTING_ZONES 1 -i enp3s0 -g PRE_public
+-I POSTROUTING_ZONES 1 -o enp3s0 -g POST_public
+COMMIT
+*mangle
+-I PREROUTING_ZONES 1 -i enp3s0 -g PRE_public
+COMMIT
+*raw
+-I PREROUTING_ZONES 1 -i enp3s0 -g PRE_public
+COMMIT
+EOF
+
+if [ $? -ne 0 ]; then
+	echo "Error during 4th iptables-restore"
+	exit 1
+fi
+
+tmpfile=$(mktemp) || exit 1
+for table in nat mangle raw filter;do
+	$XT_MULTI iptables-save -t $table | grep -v '^#' >> "$tmpfile"
+done
+
+diff -u $tmpfile  $(dirname "$0")/dumps/ipt-save-completed.txt
+RET=$?
+
+rm -f "$tmpfile"
+
+exit $RET
diff --git a/iptables/tests/shell/testcases/firewalld-restore/0002-firewalld-restart_0 b/iptables/tests/shell/testcases/firewalld-restore/0002-firewalld-restart_0
new file mode 100755
index 0000000..50e9492
--- /dev/null
+++ b/iptables/tests/shell/testcases/firewalld-restore/0002-firewalld-restart_0
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+# simulate restart after it went down, so first restore
+# the complete ruleset
+
+$XT_MULTI iptables-restore < $(dirname "$0")/dumps/ipt-save-completed.txt
+
+# add dummy rules to see if they get cleared or not.
+for table in raw mangle nat filter;do
+	$XT_MULTI iptables -t $table -N FOO$table || exit 1
+	$XT_MULTI iptables -t $table -A OUTPUT -m comment --comment '"dummy rule in table $table OUTPUT"' || exit 1
+	$XT_MULTI iptables -t $table -A FOO$table -m comment --comment '"dummy rule in table $table FOO$table"' || exit 1
+done
+
+# then run the other test script so it finds already-existing ruleset.
+
+exec $(dirname "$0")/0001-firewalld_0
diff --git a/iptables/tests/shell/testcases/firewalld-restore/dumps/ipt-save-completed.txt b/iptables/tests/shell/testcases/firewalld-restore/dumps/ipt-save-completed.txt
new file mode 100644
index 0000000..03704ec
--- /dev/null
+++ b/iptables/tests/shell/testcases/firewalld-restore/dumps/ipt-save-completed.txt
@@ -0,0 +1,151 @@
+*nat
+:PREROUTING ACCEPT [0:0]
+:INPUT ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+:POSTROUTING ACCEPT [0:0]
+:OUTPUT_direct - [0:0]
+:POSTROUTING_ZONES - [0:0]
+:POSTROUTING_ZONES_SOURCE - [0:0]
+:POSTROUTING_direct - [0:0]
+:POST_public - [0:0]
+:POST_public_allow - [0:0]
+:POST_public_deny - [0:0]
+:POST_public_log - [0:0]
+:PREROUTING_ZONES - [0:0]
+:PREROUTING_ZONES_SOURCE - [0:0]
+:PREROUTING_direct - [0:0]
+:PRE_public - [0:0]
+:PRE_public_allow - [0:0]
+:PRE_public_deny - [0:0]
+:PRE_public_log - [0:0]
+-A PREROUTING -j PREROUTING_direct
+-A PREROUTING -j PREROUTING_ZONES_SOURCE
+-A PREROUTING -j PREROUTING_ZONES
+-A OUTPUT -j OUTPUT_direct
+-A POSTROUTING -j POSTROUTING_direct
+-A POSTROUTING -j POSTROUTING_ZONES_SOURCE
+-A POSTROUTING -j POSTROUTING_ZONES
+-A POSTROUTING_ZONES -o enp3s0 -g POST_public
+-A POSTROUTING_ZONES -g POST_public
+-A POST_public -j POST_public_log
+-A POST_public -j POST_public_deny
+-A POST_public -j POST_public_allow
+-A PREROUTING_ZONES -i enp3s0 -g PRE_public
+-A PREROUTING_ZONES -g PRE_public
+-A PRE_public -j PRE_public_log
+-A PRE_public -j PRE_public_deny
+-A PRE_public -j PRE_public_allow
+COMMIT
+*mangle
+:PREROUTING ACCEPT [0:0]
+:INPUT ACCEPT [0:0]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+:POSTROUTING ACCEPT [0:0]
+:FORWARD_direct - [0:0]
+:INPUT_direct - [0:0]
+:OUTPUT_direct - [0:0]
+:POSTROUTING_direct - [0:0]
+:PREROUTING_ZONES - [0:0]
+:PREROUTING_ZONES_SOURCE - [0:0]
+:PREROUTING_direct - [0:0]
+:PRE_public - [0:0]
+:PRE_public_allow - [0:0]
+:PRE_public_deny - [0:0]
+:PRE_public_log - [0:0]
+-A PREROUTING -j PREROUTING_direct
+-A PREROUTING -j PREROUTING_ZONES_SOURCE
+-A PREROUTING -j PREROUTING_ZONES
+-A INPUT -j INPUT_direct
+-A FORWARD -j FORWARD_direct
+-A OUTPUT -j OUTPUT_direct
+-A POSTROUTING -j POSTROUTING_direct
+-A PREROUTING_ZONES -i enp3s0 -g PRE_public
+-A PREROUTING_ZONES -g PRE_public
+-A PRE_public -j PRE_public_log
+-A PRE_public -j PRE_public_deny
+-A PRE_public -j PRE_public_allow
+COMMIT
+*raw
+:PREROUTING ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+:OUTPUT_direct - [0:0]
+:PREROUTING_ZONES - [0:0]
+:PREROUTING_ZONES_SOURCE - [0:0]
+:PREROUTING_direct - [0:0]
+:PRE_public - [0:0]
+:PRE_public_allow - [0:0]
+:PRE_public_deny - [0:0]
+:PRE_public_log - [0:0]
+-A PREROUTING -j PREROUTING_direct
+-A PREROUTING -j PREROUTING_ZONES_SOURCE
+-A PREROUTING -j PREROUTING_ZONES
+-A OUTPUT -j OUTPUT_direct
+-A PREROUTING_ZONES -i enp3s0 -g PRE_public
+-A PREROUTING_ZONES -g PRE_public
+-A PRE_public -j PRE_public_log
+-A PRE_public -j PRE_public_deny
+-A PRE_public -j PRE_public_allow
+COMMIT
+*filter
+:INPUT ACCEPT [0:0]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+:FORWARD_IN_ZONES - [0:0]
+:FORWARD_IN_ZONES_SOURCE - [0:0]
+:FORWARD_OUT_ZONES - [0:0]
+:FORWARD_OUT_ZONES_SOURCE - [0:0]
+:FORWARD_direct - [0:0]
+:FWDI_public - [0:0]
+:FWDI_public_allow - [0:0]
+:FWDI_public_deny - [0:0]
+:FWDI_public_log - [0:0]
+:FWDO_public - [0:0]
+:FWDO_public_allow - [0:0]
+:FWDO_public_deny - [0:0]
+:FWDO_public_log - [0:0]
+:INPUT_ZONES - [0:0]
+:INPUT_ZONES_SOURCE - [0:0]
+:INPUT_direct - [0:0]
+:IN_public - [0:0]
+:IN_public_allow - [0:0]
+:IN_public_deny - [0:0]
+:IN_public_log - [0:0]
+:OUTPUT_direct - [0:0]
+-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+-A INPUT -i lo -j ACCEPT
+-A INPUT -j INPUT_direct
+-A INPUT -j INPUT_ZONES_SOURCE
+-A INPUT -j INPUT_ZONES
+-A INPUT -m conntrack --ctstate INVALID -j DROP
+-A INPUT -j REJECT --reject-with icmp-host-prohibited
+-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+-A FORWARD -i lo -j ACCEPT
+-A FORWARD -j FORWARD_direct
+-A FORWARD -j FORWARD_IN_ZONES_SOURCE
+-A FORWARD -j FORWARD_IN_ZONES
+-A FORWARD -j FORWARD_OUT_ZONES_SOURCE
+-A FORWARD -j FORWARD_OUT_ZONES
+-A FORWARD -m conntrack --ctstate INVALID -j DROP
+-A FORWARD -j REJECT --reject-with icmp-host-prohibited
+-A OUTPUT -j OUTPUT_direct
+-A FORWARD_IN_ZONES -i enp3s0 -g FWDI_public
+-A FORWARD_IN_ZONES -g FWDI_public
+-A FORWARD_OUT_ZONES -o enp3s0 -g FWDO_public
+-A FORWARD_OUT_ZONES -g FWDO_public
+-A FWDI_public -j FWDI_public_log
+-A FWDI_public -j FWDI_public_deny
+-A FWDI_public -j FWDI_public_allow
+-A FWDI_public -p icmp -j ACCEPT
+-A FWDO_public -j FWDO_public_log
+-A FWDO_public -j FWDO_public_deny
+-A FWDO_public -j FWDO_public_allow
+-A INPUT_ZONES -i enp3s0 -g IN_public
+-A INPUT_ZONES -g IN_public
+-A IN_public -j IN_public_log
+-A IN_public -j IN_public_deny
+-A IN_public -j IN_public_allow
+-A IN_public -p icmp -j ACCEPT
+-A IN_public_allow -p tcp -m tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
+-A IN_public_allow -d 224.0.0.251/32 -p udp -m udp --dport 5353 -m conntrack --ctstate NEW -j ACCEPT
+COMMIT
diff --git a/iptables/tests/shell/testcases/ip6tables/0002-verbose-output_0 b/iptables/tests/shell/testcases/ip6tables/0002-verbose-output_0
new file mode 100755
index 0000000..7b0e646
--- /dev/null
+++ b/iptables/tests/shell/testcases/ip6tables/0002-verbose-output_0
@@ -0,0 +1,51 @@
+#!/bin/bash
+
+set -e
+#set -x
+
+# ensure verbose output is identical between legacy and nft tools
+
+RULE1='-i eth2 -o eth3 -s feed:babe::1 -d feed:babe::2 -j ACCEPT'
+VOUT1='ACCEPT  all opt    in eth2 out eth3  feed:babe::1  -> feed:babe::2'
+RULE2='-i eth2 -o eth3 -s feed:babe::4 -d feed:babe::5 -j ACCEPT'
+VOUT2='ACCEPT  all opt    in eth2 out eth3  feed:babe::4  -> feed:babe::5'
+
+diff -u -Z <(echo -e "$VOUT1") <($XT_MULTI ip6tables -v -A FORWARD $RULE1)
+diff -u -Z <(echo -e "$VOUT2") <($XT_MULTI ip6tables -v -I FORWARD 2 $RULE2)
+
+diff -u -Z <(echo -e "$VOUT1") <($XT_MULTI ip6tables -v -C FORWARD $RULE1)
+diff -u -Z <(echo -e "$VOUT2") <($XT_MULTI ip6tables -v -C FORWARD $RULE2)
+
+EXPECT='Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
+ pkts bytes target     prot opt in     out     source               destination
+
+Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
+ pkts bytes target     prot opt in     out     source               destination
+    0     0 ACCEPT     all      eth2   eth3    feed:babe::1         feed:babe::2
+    0     0 ACCEPT     all      eth2   eth3    feed:babe::4         feed:babe::5
+
+Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
+ pkts bytes target     prot opt in     out     source               destination'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI ip6tables -v -n -L)
+
+diff -u -Z <(echo -e "$VOUT1") <($XT_MULTI ip6tables -v -D FORWARD $RULE1)
+diff -u -Z <(echo -e "$VOUT2") <($XT_MULTI ip6tables -v -D FORWARD $RULE2)
+
+EXPECT="Flushing chain \`INPUT'
+Flushing chain \`FORWARD'
+Flushing chain \`OUTPUT'"
+
+diff -u <(echo -e "$EXPECT") <($XT_MULTI ip6tables -v -F)
+
+EXPECT="Zeroing chain \`INPUT'
+Zeroing chain \`FORWARD'
+Zeroing chain \`OUTPUT'"
+
+diff -u <(echo -e "$EXPECT") <($XT_MULTI ip6tables -v -Z)
+
+diff -u <(echo "Flushing chain \`OUTPUT'") <($XT_MULTI ip6tables -v -F OUTPUT)
+diff -u <(echo "Zeroing chain \`OUTPUT'") <($XT_MULTI ip6tables -v -Z OUTPUT)
+
+$XT_MULTI ip6tables -N foo
+diff -u <(echo "Deleting chain \`foo'") <($XT_MULTI ip6tables -v -X foo)
diff --git a/iptables/tests/shell/testcases/ip6tables/0003-list-rules_0 b/iptables/tests/shell/testcases/ip6tables/0003-list-rules_0
new file mode 100755
index 0000000..c98bdd6
--- /dev/null
+++ b/iptables/tests/shell/testcases/ip6tables/0003-list-rules_0
@@ -0,0 +1,64 @@
+#!/bin/bash
+
+set -e
+
+$XT_MULTI ip6tables -N foo
+$XT_MULTI ip6tables -A FORWARD -i eth23 -o eth42 -j ACCEPT
+$XT_MULTI ip6tables -A FORWARD -i eth42 -o eth23 -g foo
+$XT_MULTI ip6tables -t nat -A OUTPUT -o eth123 -m mark --mark 0x42 -j ACCEPT
+
+EXPECT='-P INPUT ACCEPT
+-P FORWARD ACCEPT
+-P OUTPUT ACCEPT
+-N foo
+-A FORWARD -i eth23 -o eth42 -j ACCEPT
+-A FORWARD -i eth42 -o eth23 -g foo'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI ip6tables -S)
+
+EXPECT='-P INPUT ACCEPT -c 0 0
+-P FORWARD ACCEPT -c 0 0
+-P OUTPUT ACCEPT -c 0 0
+-N foo
+-A FORWARD -i eth23 -o eth42 -c 0 0 -j ACCEPT
+-A FORWARD -i eth42 -o eth23 -c 0 0 -g foo'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI ip6tables -v -S)
+
+EXPECT='-P FORWARD ACCEPT
+-A FORWARD -i eth23 -o eth42 -j ACCEPT
+-A FORWARD -i eth42 -o eth23 -g foo'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI ip6tables -S FORWARD)
+
+EXPECT='-P FORWARD ACCEPT -c 0 0
+-A FORWARD -i eth23 -o eth42 -c 0 0 -j ACCEPT
+-A FORWARD -i eth42 -o eth23 -c 0 0 -g foo'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI ip6tables -v -S FORWARD)
+
+EXPECT='-P OUTPUT ACCEPT
+-A OUTPUT -o eth123 -m mark --mark 0x42 -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI ip6tables -t nat -S OUTPUT)
+
+EXPECT='-P OUTPUT ACCEPT -c 0 0
+-A OUTPUT -o eth123 -m mark --mark 0x42 -c 0 0 -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI ip6tables -v -t nat -S OUTPUT)
+
+# some of the following commands are supposed to fail
+set +e
+
+$XT_MULTI ip6tables -S nonexistent && {
+	echo "list-rules in non-existent chain should fail"
+	exit 1
+}
+$XT_MULTI ip6tables -S nonexistent 23 && {
+	echo "list-rules in non-existent chain with given rule number should fail"
+	exit 1
+}
+$XT_MULTI ip6tables -S FORWARD 234 || {
+	echo "list-rules in existent chain with invalid rule number should succeed"
+	exit 1
+}
diff --git a/iptables/tests/shell/testcases/ipt-restore/0001load-specific-table_0 b/iptables/tests/shell/testcases/ipt-restore/0001load-specific-table_0
new file mode 100755
index 0000000..3f443a9
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0001load-specific-table_0
@@ -0,0 +1,41 @@
+#!/bin/bash
+
+RET=0
+tmpfile=""
+
+set -x
+
+clean_tempfile()
+{
+	if [ -n "${tmpfile}" ]; then
+		rm -f "${tmpfile}"
+	fi
+}
+
+trap clean_tempfile EXIT
+
+tmpfile=$(mktemp) || exit 1
+
+do_simple()
+{
+	iptables="${1}"
+	table="${2}"
+	dumpfile="$(dirname "${0}")/dumps/${iptables}.dump"
+
+	"$XT_MULTI" "${iptables}-restore" --table="${table}" "${dumpfile}"; rv=$?
+
+	if [ "${rv}" -ne 0 ]; then
+		RET=1
+	fi
+}
+
+do_simple "iptables" "filter"
+do_simple "iptables" "mangle"
+do_simple "iptables" "raw"
+do_simple "iptables" "nat"
+do_simple "ip6tables" "filter"
+do_simple "ip6tables" "mangle"
+do_simple "ip6tables" "raw"
+do_simple "ip6tables" "nat"
+
+exit "${RET}"
diff --git a/iptables/tests/shell/testcases/ipt-restore/0002-parameters_0 b/iptables/tests/shell/testcases/ipt-restore/0002-parameters_0
new file mode 100755
index 0000000..5c8748e
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0002-parameters_0
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+set -e
+
+# make sure wait and wait-interval options are accepted
+
+clean_tempfile()
+{
+	if [ -n "${tmpfile}" ]; then
+		rm -f "${tmpfile}"
+	fi
+}
+
+trap clean_tempfile EXIT
+
+tmpfile=$(mktemp) || exit 1
+
+$XT_MULTI iptables-save -f $tmpfile
+$XT_MULTI iptables-restore $tmpfile
+$XT_MULTI iptables-restore -w 5 $tmpfile
+$XT_MULTI iptables-restore -w 5 -W 1 $tmpfile
diff --git a/iptables/tests/shell/testcases/ipt-restore/0003-restore-ordering_0 b/iptables/tests/shell/testcases/ipt-restore/0003-restore-ordering_0
new file mode 100755
index 0000000..3f1d229
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0003-restore-ordering_0
@@ -0,0 +1,125 @@
+#!/bin/bash
+
+# Make sure iptables-restore does the right thing
+# when encountering INSERT rules with index.
+
+set -e
+
+# show rules, drop uninteresting policy settings
+ipt_show() {
+	$XT_MULTI iptables -S | grep -v '^-P'
+}
+
+# basic issue reproducer
+
+$XT_MULTI iptables-restore <<EOF
+*filter
+-A FORWARD -m comment --comment "rule 4" -j ACCEPT
+-I FORWARD 1 -m comment --comment "rule 1" -j ACCEPT
+-I FORWARD 2 -m comment --comment "rule 2" -j ACCEPT
+-I FORWARD 3 -m comment --comment "rule 3" -j ACCEPT
+COMMIT
+EOF
+
+EXPECT='-A FORWARD -m comment --comment "rule 1" -j ACCEPT
+-A FORWARD -m comment --comment "rule 2" -j ACCEPT
+-A FORWARD -m comment --comment "rule 3" -j ACCEPT
+-A FORWARD -m comment --comment "rule 4" -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <(ipt_show)
+
+# insert rules into existing ruleset
+
+$XT_MULTI iptables-restore --noflush <<EOF
+*filter
+-A FORWARD -m comment --comment "rule 5" -j ACCEPT
+-I FORWARD 1 -m comment --comment "rule 0.5" -j ACCEPT
+-I FORWARD 3 -m comment --comment "rule 1.5" -j ACCEPT
+-I FORWARD 5 -m comment --comment "rule 2.5" -j ACCEPT
+-I FORWARD 7 -m comment --comment "rule 3.5" -j ACCEPT
+-I FORWARD 9 -m comment --comment "rule 4.5" -j ACCEPT
+-I FORWARD 11 -m comment --comment "rule 5.5" -j ACCEPT
+-A FORWARD -m comment --comment "rule 6" -j ACCEPT
+COMMIT
+EOF
+
+EXPECT='-A FORWARD -m comment --comment "rule 0.5" -j ACCEPT
+-A FORWARD -m comment --comment "rule 1" -j ACCEPT
+-A FORWARD -m comment --comment "rule 1.5" -j ACCEPT
+-A FORWARD -m comment --comment "rule 2" -j ACCEPT
+-A FORWARD -m comment --comment "rule 2.5" -j ACCEPT
+-A FORWARD -m comment --comment "rule 3" -j ACCEPT
+-A FORWARD -m comment --comment "rule 3.5" -j ACCEPT
+-A FORWARD -m comment --comment "rule 4" -j ACCEPT
+-A FORWARD -m comment --comment "rule 4.5" -j ACCEPT
+-A FORWARD -m comment --comment "rule 5" -j ACCEPT
+-A FORWARD -m comment --comment "rule 5.5" -j ACCEPT
+-A FORWARD -m comment --comment "rule 6" -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <(ipt_show)
+
+# insert rules in between added ones
+
+$XT_MULTI iptables-restore <<EOF
+*filter
+-A FORWARD -m comment --comment "appended rule 1" -j ACCEPT
+-A FORWARD -m comment --comment "appended rule 2" -j ACCEPT
+-A FORWARD -m comment --comment "appended rule 3" -j ACCEPT
+-I FORWARD 1 -m comment --comment "rule 1" -j ACCEPT
+-I FORWARD 3 -m comment --comment "rule 2" -j ACCEPT
+-I FORWARD 5 -m comment --comment "rule 3" -j ACCEPT
+COMMIT
+EOF
+
+EXPECT='-A FORWARD -m comment --comment "rule 1" -j ACCEPT
+-A FORWARD -m comment --comment "appended rule 1" -j ACCEPT
+-A FORWARD -m comment --comment "rule 2" -j ACCEPT
+-A FORWARD -m comment --comment "appended rule 2" -j ACCEPT
+-A FORWARD -m comment --comment "rule 3" -j ACCEPT
+-A FORWARD -m comment --comment "appended rule 3" -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <(ipt_show)
+
+# test rule deletion in dump files
+
+$XT_MULTI iptables-restore --noflush <<EOF
+*filter
+-A FORWARD -m comment --comment "appended rule 4" -j ACCEPT
+-D FORWARD 7
+-D FORWARD -m comment --comment "appended rule 1" -j ACCEPT
+-D FORWARD 3
+-I FORWARD 3 -m comment --comment "manually replaced rule 2" -j ACCEPT
+COMMIT
+EOF
+
+EXPECT='-A FORWARD -m comment --comment "rule 1" -j ACCEPT
+-A FORWARD -m comment --comment "rule 2" -j ACCEPT
+-A FORWARD -m comment --comment "manually replaced rule 2" -j ACCEPT
+-A FORWARD -m comment --comment "rule 3" -j ACCEPT
+-A FORWARD -m comment --comment "appended rule 3" -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <(ipt_show)
+
+# test rule replacement in dump files
+
+$XT_MULTI iptables-restore <<EOF
+*filter
+-A FORWARD -m comment --comment "rule 1" -j ACCEPT
+-A FORWARD -m comment --comment "rule to be replaced" -j ACCEPT
+-A FORWARD -m comment --comment "rule 3" -j ACCEPT
+COMMIT
+EOF
+
+$XT_MULTI iptables-restore --noflush <<EOF
+*filter
+-R FORWARD 2 -m comment --comment "replacement" -j ACCEPT
+-I FORWARD 2 -m comment --comment "insert referencing replaced rule" -j ACCEPT
+COMMIT
+EOF
+
+EXPECT='-A FORWARD -m comment --comment "rule 1" -j ACCEPT
+-A FORWARD -m comment --comment "insert referencing replaced rule" -j ACCEPT
+-A FORWARD -m comment --comment replacement -j ACCEPT
+-A FORWARD -m comment --comment "rule 3" -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <(ipt_show)
diff --git a/iptables/tests/shell/testcases/ipt-restore/0004-restore-race_0 b/iptables/tests/shell/testcases/ipt-restore/0004-restore-race_0
new file mode 100755
index 0000000..a7fae41
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0004-restore-race_0
@@ -0,0 +1,117 @@
+#!/bin/bash
+
+have_nft=false
+nft -v > /dev/null && have_nft=true
+
+dumpfile=""
+tmpfile=""
+
+set -e
+
+clean()
+{
+	$XT_MULTI iptables -t filter -F
+	$XT_MULTI iptables -t filter -X
+	$have_nft && nft flush ruleset
+}
+
+clean_tempfile()
+{
+	[ -n "${tmpfile}" ] && rm -f "${tmpfile}"
+	[ -n "${dumpfile}" ] && rm -f "${dumpfile}"
+	clean
+}
+
+trap clean_tempfile EXIT
+
+ENTRY_NUM=$((RANDOM%10))
+UCHAIN_NUM=$((RANDOM%10))
+
+get_target()
+{
+	if [ $UCHAIN_NUM -eq 0 ]; then
+		echo -n "ACCEPT"
+		return
+	fi
+
+
+	x=$((RANDOM%2))
+	if [ $x -eq 0 ];then
+		echo -n "ACCEPT"
+	else
+		printf -- "UC-%x" $((RANDOM%UCHAIN_NUM))
+	fi
+}
+
+make_dummy_rules()
+{
+	echo "*${1:-filter}"
+	echo ":INPUT ACCEPT [0:0]"
+	echo ":FORWARD ACCEPT [0:0]"
+	echo ":OUTPUT ACCEPT [0:0]"
+
+	if [ $UCHAIN_NUM -gt 0 ]; then
+		for i in $(seq 0 $UCHAIN_NUM); do
+			printf -- ":UC-%x - [0:0]\n" $i
+		done
+	fi
+
+	for proto in tcp udp sctp; do
+		for i in $(seq 0 $ENTRY_NUM); do
+			t=$(get_target)
+			printf -- "-A INPUT -i lo -p $proto --dport %d -j %s\n" $((61000-i)) $t
+			t=$(get_target)
+			printf -- "-A FORWARD -i lo -o lo -p $proto --dport %d -j %s\n" $((61000-i)) $t
+			t=$(get_target)
+			printf -- "-A OUTPUT -o lo -p $proto --dport %d -j %s\n" $((61000-i)) $t
+			[ $UCHAIN_NUM -gt 0 ] && printf -- "-A UC-%x -j ACCEPT\n" $((RANDOM%UCHAIN_NUM))
+		done
+	done
+	echo COMMIT
+}
+
+tmpfile=$(mktemp) || exit 1
+dumpfile=$(mktemp) || exit 1
+
+(make_dummy_rules; make_dummy_rules security) > $dumpfile
+$XT_MULTI iptables-restore -w < $dumpfile
+LINES1=$(wc -l < $dumpfile)
+$XT_MULTI iptables-save | grep -v '^#' > $dumpfile
+LINES2=$(wc -l < $dumpfile)
+
+if [ $LINES1 -ne $LINES2 ]; then
+	echo "Original dump has $LINES1, not $LINES2" 1>&2
+	exit 111
+fi
+
+case "$XT_MULTI" in
+*xtables-nft-multi)
+	attempts=$((RANDOM%10))
+	attempts=$((attempts+1))
+	;;
+*)
+	attempts=1
+	;;
+esac
+
+while [ $attempts -gt 0 ]; do
+	attempts=$((attempts-1))
+
+	clean
+
+	for i in $(seq 1 10); do
+		$XT_MULTI iptables-restore -w 15 < $dumpfile &
+	done
+
+	for i in $(seq 1 10); do
+		# causes exit in case ipt-restore failed (runs with set -e)
+		wait %$i
+	done
+
+	$XT_MULTI iptables-save | grep -v '^#' > $tmpfile
+
+	clean
+	cmp $tmpfile $dumpfile
+done
+
+exit 0
diff --git a/iptables/tests/shell/testcases/ipt-restore/0005-ipt-6_0 b/iptables/tests/shell/testcases/ipt-restore/0005-ipt-6_0
new file mode 100755
index 0000000..dd06977
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0005-ipt-6_0
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+# Make sure iptables-restore simply ignores
+# rules starting with -6
+
+set -e
+
+# show rules, drop uninteresting policy settings
+ipt_show() {
+	$XT_MULTI iptables -S | grep -v '^-P'
+}
+
+# issue reproducer for iptables-restore
+
+$XT_MULTI iptables-restore <<EOF
+*filter
+-A FORWARD -m comment --comment any -j ACCEPT
+-4 -A FORWARD -m comment --comment ipv4 -j ACCEPT
+-6 -A FORWARD -m comment --comment ipv6 -j ACCEPT
+COMMIT
+EOF
+
+EXPECT='-A FORWARD -m comment --comment any -j ACCEPT
+-A FORWARD -m comment --comment ipv4 -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <(ipt_show)
diff --git a/iptables/tests/shell/testcases/ipt-restore/0006-ip6t-4_0 b/iptables/tests/shell/testcases/ipt-restore/0006-ip6t-4_0
new file mode 100755
index 0000000..a37253a
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0006-ip6t-4_0
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+# Make sure ip6tables-restore simply ignores
+# rules starting with -4
+
+set -e
+
+# show rules, drop uninteresting policy settings
+ipt_show() {
+	$XT_MULTI ip6tables -S | grep -v '^-P'
+}
+
+# issue reproducer for ip6tables-restore
+
+$XT_MULTI ip6tables-restore <<EOF
+*filter
+-A FORWARD -m comment --comment any -j ACCEPT
+-4 -A FORWARD -m comment --comment ipv4 -j ACCEPT
+-6 -A FORWARD -m comment --comment ipv6 -j ACCEPT
+COMMIT
+EOF
+
+EXPECT='-A FORWARD -m comment --comment any -j ACCEPT
+-A FORWARD -m comment --comment ipv6 -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <(ipt_show)
diff --git a/iptables/tests/shell/testcases/ipt-restore/0007-flush-noflush_0 b/iptables/tests/shell/testcases/ipt-restore/0007-flush-noflush_0
new file mode 100755
index 0000000..e705b28
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0007-flush-noflush_0
@@ -0,0 +1,42 @@
+#!/bin/bash
+
+# Make sure iptables-restore without --noflush does not flush tables other than
+# those contained in the dump it's reading from
+
+set -e
+
+$XT_MULTI iptables-restore <<EOF
+*nat
+-A POSTROUTING -j ACCEPT
+COMMIT
+EOF
+
+EXPECT="*nat
+:PREROUTING ACCEPT [0:0]
+:INPUT ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+:POSTROUTING ACCEPT [0:0]
+-A POSTROUTING -j ACCEPT
+COMMIT"
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI iptables-save | grep -v '^#')
+
+$XT_MULTI iptables-restore <<EOF
+*filter
+-A FORWARD -j ACCEPT
+COMMIT
+EOF
+
+EXPECT="*filter
+:INPUT ACCEPT [0:0]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+-A FORWARD -j ACCEPT
+COMMIT
+*nat
+:PREROUTING ACCEPT [0:0]
+:INPUT ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+:POSTROUTING ACCEPT [0:0]
+-A POSTROUTING -j ACCEPT
+COMMIT"
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI iptables-save | grep -v '^#')
diff --git a/iptables/tests/shell/testcases/ipt-restore/0008-restore-counters_0 b/iptables/tests/shell/testcases/ipt-restore/0008-restore-counters_0
new file mode 100755
index 0000000..5ac7068
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0008-restore-counters_0
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+set -e
+
+DUMP="*filter
+:foo - [23:42]
+[13:37] -A foo -j ACCEPT
+COMMIT
+"
+
+EXPECT=":foo - [0:0]
+[0:0] -A foo -j ACCEPT"
+
+$XT_MULTI iptables-restore <<< "$DUMP"
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI iptables-save --counters | grep foo)
+
+# iptables-*-restore ignores custom chain counters :(
+EXPECT=":foo - [0:0]
+[13:37] -A foo -j ACCEPT"
+
+$XT_MULTI iptables-restore --counters <<< "$DUMP"
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI iptables-save --counters | grep foo)
diff --git a/iptables/tests/shell/testcases/ipt-restore/0009-table-name-comment_0 b/iptables/tests/shell/testcases/ipt-restore/0009-table-name-comment_0
new file mode 100755
index 0000000..e961407
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0009-table-name-comment_0
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+# when restoring a ruleset, *tables-restore prefixes each rule with
+# '-t <tablename>' so standard rule parsing routines may be used. This means
+# that it has to detect and reject rules which already contain a table option.
+
+families="ip ip6"
+[[ $(basename $XT_MULTI) == xtables-nft-multi ]] && families+=" eb"
+
+for fam in $families; do
+	$XT_MULTI ${fam}tables-restore <<EOF
+*filter
+-t nat -A FORWARD -j ACCEPT
+COMMIT
+EOF
+	[[ $? != 0 ]] || {
+		echo "${fam}tables-restore did not fail when it should have"
+		exit 1
+	}
+
+	$XT_MULTI ${fam}tables-restore <<EOF
+*filter
+-A FORWARD -j ACCEPT
+COMMIT
+EOF
+	[[ $? == 0 ]] || {
+		echo "${fam}tables-restore failed when it should not have"
+		exit 1
+	}
+done
diff --git a/iptables/tests/shell/testcases/ipt-restore/0010-noflush-new-chain_0 b/iptables/tests/shell/testcases/ipt-restore/0010-noflush-new-chain_0
new file mode 100755
index 0000000..2817376
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0010-noflush-new-chain_0
@@ -0,0 +1,11 @@
+#!/bin/sh -e
+
+# assert input feed from buffer doesn't trip over
+# added nul-chars from parsing chain line.
+
+$XT_MULTI iptables-restore --noflush <<EOF
+*filter
+:foobar - [0:0]
+-A foobar -j ACCEPT
+COMMIT
+EOF
diff --git a/iptables/tests/shell/testcases/ipt-restore/0011-noflush-empty-line_0 b/iptables/tests/shell/testcases/ipt-restore/0011-noflush-empty-line_0
new file mode 100755
index 0000000..bea1a69
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0011-noflush-empty-line_0
@@ -0,0 +1,16 @@
+#!/bin/bash -e
+
+# make sure empty lines won't break --noflush
+
+cat <<EOF | $XT_MULTI iptables-restore --noflush
+# just a comment followed by innocent empty line
+
+*filter
+-A FORWARD -j ACCEPT
+COMMIT
+EOF
+
+EXPECT='Chain FORWARD (policy ACCEPT)
+target     prot opt source               destination         
+ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0           '
+diff -u <(echo "$EXPECT") <($XT_MULTI iptables -n -L FORWARD)
diff --git a/iptables/tests/shell/testcases/ipt-restore/0012-dash-F_0 b/iptables/tests/shell/testcases/ipt-restore/0012-dash-F_0
new file mode 100755
index 0000000..fd82afa
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0012-dash-F_0
@@ -0,0 +1,12 @@
+#!/bin/bash -e
+
+# make sure -F lines don't cause segfaults
+
+RULESET='*nat
+-F PREROUTING
+-A PREROUTING -j ACCEPT
+-F PREROUTING
+COMMIT'
+
+echo -e "$RULESET" | $XT_MULTI iptables-restore
+echo -e "$RULESET" | $XT_MULTI iptables-restore -n
diff --git a/iptables/tests/shell/testcases/ipt-restore/0013-test-mode_0 b/iptables/tests/shell/testcases/ipt-restore/0013-test-mode_0
new file mode 100755
index 0000000..65c3b9a
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0013-test-mode_0
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+set -e
+
+# segfault with --test reported in nfbz#1391
+
+printf '%s\nCOMMIT\n' '*nat' '*raw' '*filter' | $XT_MULTI iptables-restore --test
diff --git a/iptables/tests/shell/testcases/ipt-restore/0014-verbose-restore_0 b/iptables/tests/shell/testcases/ipt-restore/0014-verbose-restore_0
new file mode 100755
index 0000000..fc8559c
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0014-verbose-restore_0
@@ -0,0 +1,76 @@
+#!/bin/bash
+
+set -e
+
+DUMP="*filter
+:foo - [0:0]
+:bar - [0:0]
+-A foo -j ACCEPT
+COMMIT
+*nat
+:natfoo - [0:0]
+:natbar - [0:0]
+-A natfoo -j ACCEPT
+COMMIT
+*raw
+:rawfoo - [0:0]
+COMMIT
+*mangle
+:manglefoo - [0:0]
+COMMIT
+*security
+:secfoo - [0:0]
+COMMIT
+"
+
+$XT_MULTI iptables-restore <<< "$DUMP"
+$XT_MULTI ip6tables-restore <<< "$DUMP"
+
+EXPECT="Flushing chain \`INPUT'
+Flushing chain \`FORWARD'
+Flushing chain \`OUTPUT'
+Flushing chain \`bar'
+Flushing chain \`foo'
+Deleting chain \`bar'
+Deleting chain \`foo'
+Flushing chain \`PREROUTING'
+Flushing chain \`INPUT'
+Flushing chain \`OUTPUT'
+Flushing chain \`POSTROUTING'
+Flushing chain \`natbar'
+Flushing chain \`natfoo'
+Deleting chain \`natbar'
+Deleting chain \`natfoo'
+Flushing chain \`PREROUTING'
+Flushing chain \`OUTPUT'
+Flushing chain \`rawfoo'
+Deleting chain \`rawfoo'
+Flushing chain \`PREROUTING'
+Flushing chain \`INPUT'
+Flushing chain \`FORWARD'
+Flushing chain \`OUTPUT'
+Flushing chain \`POSTROUTING'
+Flushing chain \`manglefoo'
+Deleting chain \`manglefoo'
+Flushing chain \`INPUT'
+Flushing chain \`FORWARD'
+Flushing chain \`OUTPUT'
+Flushing chain \`secfoo'
+Deleting chain \`secfoo'"
+
+for ipt in iptables-restore ip6tables-restore; do
+	diff -u -Z <(echo "$EXPECT") <($XT_MULTI $ipt -v <<< "$DUMP")
+done
+
+DUMP="*filter
+:baz - [0:0]
+-F foo
+-X bar
+-A foo -j ACCEPT
+COMMIT
+"
+
+EXPECT=""
+for ipt in iptables-restore ip6tables-restore; do
+	diff -u -Z <(echo -ne "$EXPECT") <($XT_MULTI $ipt -v --noflush <<< "$DUMP")
+done
diff --git a/iptables/tests/shell/testcases/ipt-restore/0016-concurrent-restores_0 b/iptables/tests/shell/testcases/ipt-restore/0016-concurrent-restores_0
new file mode 100755
index 0000000..aa746ab
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0016-concurrent-restores_0
@@ -0,0 +1,67 @@
+#!/bin/bash
+
+# test for iptables-restore --noflush skipping an explicitly requested chain
+# flush because the chain did not exist when cache was fetched. In order to
+# expect for that chain to appear when refreshing the transaction (due to a
+# concurrent ruleset change), the chain flush job has to be present in batch
+# job list (although disabled at first).
+# The input line requesting chain flush is ':FOO - [0:0]'. RS1 and RS2 contents
+# are crafted to cause EBUSY when deleting the BAR* chains if FOO is not
+# flushed in the same transaction.
+
+set -e
+
+RS="*filter
+:INPUT ACCEPT [12024:3123388]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [12840:2144421]
+:FOO - [0:0]
+:BAR0 - [0:0]
+:BAR1 - [0:0]
+:BAR2 - [0:0]
+:BAR3 - [0:0]
+:BAR4 - [0:0]
+:BAR5 - [0:0]
+:BAR6 - [0:0]
+:BAR7 - [0:0]
+:BAR8 - [0:0]
+:BAR9 - [0:0]
+"
+
+RS1="$RS
+-X BAR3
+-X BAR6
+-X BAR9
+-A FOO -s 9.9.0.1/32 -j BAR1
+-A FOO -s 9.9.0.2/32 -j BAR2
+-A FOO -s 9.9.0.4/32 -j BAR4
+-A FOO -s 9.9.0.5/32 -j BAR5
+-A FOO -s 9.9.0.7/32 -j BAR7
+-A FOO -s 9.9.0.8/32 -j BAR8
+COMMIT
+"
+
+RS2="$RS
+-X BAR2
+-X BAR5
+-X BAR7
+-A FOO -s 9.9.0.1/32 -j BAR1
+-A FOO -s 9.9.0.3/32 -j BAR3
+-A FOO -s 9.9.0.4/32 -j BAR4
+-A FOO -s 9.9.0.6/32 -j BAR6
+-A FOO -s 9.9.0.8/32 -j BAR8
+-A FOO -s 9.9.0.9/32 -j BAR9
+COMMIT
+"
+
+NORS="*filter
+COMMIT
+"
+
+for n in $(seq 1 10); do
+	$XT_MULTI iptables-restore <<< "$NORS"
+	$XT_MULTI iptables-restore --noflush -w <<< "$RS1" &
+	$XT_MULTI iptables-restore --noflush -w <<< "$RS2" &
+	wait -n
+	wait -n
+done
diff --git a/iptables/tests/shell/testcases/ipt-restore/0017-pointless-compat-checks_0 b/iptables/tests/shell/testcases/ipt-restore/0017-pointless-compat-checks_0
new file mode 100755
index 0000000..cf73de3
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/0017-pointless-compat-checks_0
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+# A bug in extension registration would leave unsupported older extension
+# revisions in pending list and get compatibility checked again for each rule
+# using them. With SELinux enabled, the resulting socket() call per rule leads
+# to significant slowdown (~50% performance in worst cases).
+
+set -e
+
+strace --version >/dev/null || { echo "skip for missing strace"; exit 0; }
+
+RULESET="$(
+	echo "*filter"
+	for ((i = 0; i < 100; i++)); do
+		echo "-A FORWARD -m conntrack --ctstate NEW"
+	done
+	echo "COMMIT"
+)"
+
+cmd="$XT_MULTI iptables-restore"
+socketcount=$(strace -esocket $cmd <<< "$RULESET" 2>&1 | wc -l)
+
+# unpatched iptables-restore would open 111 sockets,
+# patched only 12 but keep a certain margin for future changes
+[[ $socketcount -lt 20 ]]
diff --git a/iptables/tests/shell/testcases/ipt-restore/dumps/ip6tables.dump b/iptables/tests/shell/testcases/ipt-restore/dumps/ip6tables.dump
new file mode 100644
index 0000000..4ac4f88
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/dumps/ip6tables.dump
@@ -0,0 +1,30 @@
+*nat
+:PREROUTING ACCEPT [0:0]
+:INPUT ACCEPT [0:0]
+:OUTPUT ACCEPT [8:656]
+:POSTROUTING ACCEPT [8:656]
+COMMIT
+
+*mangle
+:PREROUTING ACCEPT [794:190738]
+:INPUT ACCEPT [794:190738]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [991:170303]
+:POSTROUTING ACCEPT [991:170303]
+COMMIT
+
+*raw
+:PREROUTING ACCEPT [794:190738]
+:OUTPUT ACCEPT [991:170303]
+COMMIT
+
+*filter
+:INPUT DROP [0:0]
+:FORWARD DROP [0:0]
+:OUTPUT ACCEPT [991:170303]
+-A INPUT -i lo -j ACCEPT
+-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
+-A INPUT -p ipv6-icmp -j ACCEPT
+-A OUTPUT -p tcp -m tcp --dport 137 -j REJECT --reject-with icmp6-port-unreachable
+-A OUTPUT -p udp -m udp --dport 137 -j REJECT --reject-with icmp6-port-unreachable
+COMMIT
diff --git a/iptables/tests/shell/testcases/ipt-restore/dumps/iptables.dump b/iptables/tests/shell/testcases/ipt-restore/dumps/iptables.dump
new file mode 100644
index 0000000..6e4e42d
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-restore/dumps/iptables.dump
@@ -0,0 +1,30 @@
+*nat
+:PREROUTING ACCEPT [1:89]
+:INPUT ACCEPT [0:0]
+:OUTPUT ACCEPT [351:24945]
+:POSTROUTING ACCEPT [351:24945]
+COMMIT
+
+*mangle
+:PREROUTING ACCEPT [3270:1513114]
+:INPUT ACCEPT [3270:1513114]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [3528:1087907]
+:POSTROUTING ACCEPT [3546:1090751]
+COMMIT
+
+*raw
+:PREROUTING ACCEPT [3270:1513114]
+:OUTPUT ACCEPT [3528:1087907]
+COMMIT
+
+*filter
+:INPUT DROP [37:4057]
+:FORWARD DROP [0:0]
+:OUTPUT ACCEPT [3528:1087907]
+-A INPUT -i lo -j ACCEPT
+-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
+-A INPUT -p icmp -j ACCEPT
+-A OUTPUT -p tcp -m tcp --dport 137 -j REJECT --reject-with icmp-port-unreachable
+-A OUTPUT -p udp -m udp --dport 137 -j REJECT --reject-with icmp-port-unreachable
+COMMIT
diff --git a/iptables/tests/shell/testcases/ipt-save/0001load-dumps_0 b/iptables/tests/shell/testcases/ipt-save/0001load-dumps_0
new file mode 100755
index 0000000..4e0be51
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-save/0001load-dumps_0
@@ -0,0 +1,53 @@
+#!/bin/bash
+
+RET=0
+tmpfile=""
+set -x
+
+
+clean_tmpfile()
+{
+        if [ ! -z "$tmpfile" ];then
+                rm -f "$tmpfile"
+        fi
+}
+
+trap clean_tmpfile EXIT
+
+do_diff()
+{
+	A="$1"
+	B="$2"
+
+	AT=$(mktemp)
+	grep -v "^#" "$A" > "$AT"
+
+	diff -u "$AT" "$B"
+
+	x=$?
+	rm -f "$AT"
+	echo "Return $x for $XT_MULTI $A"
+
+	return $x
+}
+
+tmpfile=$(mktemp) || exit 1
+do_simple()
+{
+	iptables="$1"
+	dumpfile="$2"
+
+	$XT_MULTI ${iptables}-restore < "$dumpfile"
+	$XT_MULTI ${iptables}-save | grep -v "^#" > "$tmpfile"
+	do_diff $dumpfile "$tmpfile"
+	if [ $? -ne 0 ]; then
+		# cp "$tmpfile" "$dumpfile.got"
+		RET=1
+	fi
+}
+
+do_simple "iptables" $(dirname "$0")/dumps/ipt-save-filter.txt
+do_simple "iptables" $(dirname "$0")/dumps/policy-drop.txt
+do_simple "iptables" $(dirname "$0")/dumps/wireless.txt
+
+exit $RET
diff --git a/iptables/tests/shell/testcases/ipt-save/0002load-fedora27-firewalld_0 b/iptables/tests/shell/testcases/ipt-save/0002load-fedora27-firewalld_0
new file mode 100755
index 0000000..2ab08b7
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-save/0002load-fedora27-firewalld_0
@@ -0,0 +1,59 @@
+#!/bin/bash
+
+RET=0
+tmpfile=""
+
+clean_tmpfile()
+{
+        if [ ! -z "$tmpfile" ];then
+                rm -f "$tmpfile"
+        fi
+}
+
+trap clean_tmpfile EXIT
+
+do_diff()
+{
+	A="$1"
+	B="$2"
+
+	AT=$(mktemp)
+	grep -v "^#" "$A" > "$AT"
+
+	diff -u "$AT" "$B"
+	x=$?
+	rm -f "$AT"
+
+	return $x
+}
+
+tmpfile=$(mktemp) || exit 1
+do_simple()
+{
+	iptables="$1"
+	dumpfile="$2"
+	opt="$3"
+
+	$XT_MULTI ${iptables}-restore $opt < "$dumpfile"
+	if [ $? -ne 0 ]; then
+		echo "$XT_MULTI ${iptables}-restore $opt $dumpfile failed" 1>&2
+		exit 1
+	fi
+
+	:> "$tmpfile"
+
+	for table in mangle raw filter; do
+		$XT_MULTI ${iptables}-save -t $table $opt | grep -v "^#" >> "$tmpfile"
+	done
+
+	do_diff $dumpfile "$tmpfile"
+
+	if [ $? -ne 0 ]; then
+		RET=1
+	fi
+}
+# fedora27-iptables dump contains chain counters to test counter restore/save
+do_simple "iptables" $(dirname "$0")/dumps/fedora27-iptables "-c"
+do_simple "ip6tables" $(dirname "$0")/dumps/fedora27-ip6tables
+
+exit $RET
diff --git a/iptables/tests/shell/testcases/ipt-save/0003save-restore_0 b/iptables/tests/shell/testcases/ipt-save/0003save-restore_0
new file mode 100644
index 0000000..6b41ede
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-save/0003save-restore_0
@@ -0,0 +1,47 @@
+#!/bin/bash
+
+tmpfile=""
+tmpfile1=""
+set -x
+
+clean_tmpfile()
+{
+	if [ ! -z "$tmpfile" ];then
+		rm -f "$tmpfile"
+	fi
+	if [ ! -z "$tmpfile1" ];then
+                rm -f "$tmpfile1"
+	fi
+}
+
+trap clean_tmpfile EXIT
+
+tmpfile=$(mktemp) || exit 1
+tmpfile1=$(mktemp) || exit 1
+
+do_diff()
+{
+	diff -u "$1" "$2"
+	if [ $? -ne 0 ]; then
+		echo "iptables configuration is not restored" 1>&2
+		exit 1
+	else
+		exit 0
+	fi
+}
+
+$XT_MULTI iptables -N FOO || exit 1
+$XT_MULTI iptables -I INPUT || exit 1
+$XT_MULTI iptables -I FOO || exit 1
+$XT_MULTI iptables -I FOO || exit 1
+
+$XT_MULTI iptables-save | grep -v "^#" > "$tmpfile" || exit 1
+$XT_MULTI iptables-restore < "$tmpfile" || exit 1
+
+$XT_MULTI iptables -N BAR || exit 1
+$XT_MULTI iptables -A BAR || exit 1
+
+$XT_MULTI iptables-restore  < "$tmpfile" || exit 1
+$XT_MULTI iptables-save | grep -v "^#" > "$tmpfile1" || exit 1
+
+do_diff $tmpfile1 "$tmpfile"
diff --git a/iptables/tests/shell/testcases/ipt-save/0005iptables_0 b/iptables/tests/shell/testcases/ipt-save/0005iptables_0
new file mode 100755
index 0000000..d5eb76a
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-save/0005iptables_0
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+set -e
+
+tmpfile1=$(mktemp)
+tmpfile2=$(mktemp)
+
+clean_tmpfile()
+{
+	rm -f "$tmpfile1" "$tmpfile2"
+}
+
+trap clean_tmpfile EXIT
+
+
+cat > $tmpfile1<<EOF
+-P INPUT ACCEPT
+-P FORWARD ACCEPT
+-P OUTPUT ACCEPT
+-N FOO
+-A FOO -j DROP
+EOF
+
+$XT_MULTI iptables -N FOO
+$XT_MULTI iptables -A FOO -j DROP
+$XT_MULTI iptables -S > $tmpfile2
+
+diff -u $tmpfile1 $tmpfile2
+
+rm -f $tmpfile1 $tmpfile2
diff --git a/iptables/tests/shell/testcases/ipt-save/0006iptables-xml_0 b/iptables/tests/shell/testcases/ipt-save/0006iptables-xml_0
new file mode 100755
index 0000000..50c0cae
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-save/0006iptables-xml_0
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+case "$(basename $XT_MULTI)" in
+	xtables-legacy-multi)
+		;;
+	*)
+		echo "skip $XT_MULTI"
+		exit 0
+		;;
+esac
+
+dump=$(dirname $0)/dumps/fedora27-iptables
+diff -u -Z <(cat ${dump}.xml) <($XT_MULTI iptables-xml <$dump)
diff --git a/iptables/tests/shell/testcases/ipt-save/dumps/fedora27-ip6tables b/iptables/tests/shell/testcases/ipt-save/dumps/fedora27-ip6tables
new file mode 100644
index 0000000..6c426a7
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-save/dumps/fedora27-ip6tables
@@ -0,0 +1,125 @@
+# Generated by ip6tables-save v1.6.1 on Sat Feb 17 10:51:39 2018
+*mangle
+:PREROUTING ACCEPT [0:0]
+:INPUT ACCEPT [0:0]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+:POSTROUTING ACCEPT [0:0]
+:FORWARD_direct - [0:0]
+:INPUT_direct - [0:0]
+:OUTPUT_direct - [0:0]
+:POSTROUTING_direct - [0:0]
+:PREROUTING_ZONES - [0:0]
+:PREROUTING_ZONES_SOURCE - [0:0]
+:PREROUTING_direct - [0:0]
+:PRE_FedoraWorkstation - [0:0]
+:PRE_FedoraWorkstation_allow - [0:0]
+:PRE_FedoraWorkstation_deny - [0:0]
+:PRE_FedoraWorkstation_log - [0:0]
+-A PREROUTING -j PREROUTING_direct
+-A PREROUTING -j PREROUTING_ZONES_SOURCE
+-A PREROUTING -j PREROUTING_ZONES
+-A INPUT -j INPUT_direct
+-A FORWARD -j FORWARD_direct
+-A OUTPUT -j OUTPUT_direct
+-A POSTROUTING -j POSTROUTING_direct
+-A PREROUTING_ZONES -i wlp58s0 -g PRE_FedoraWorkstation
+-A PREROUTING_ZONES -g PRE_FedoraWorkstation
+-A PRE_FedoraWorkstation -j PRE_FedoraWorkstation_log
+-A PRE_FedoraWorkstation -j PRE_FedoraWorkstation_deny
+-A PRE_FedoraWorkstation -j PRE_FedoraWorkstation_allow
+COMMIT
+# Completed on Sat Feb 17 10:51:39 2018
+# Generated by ip6tables-save v1.6.1 on Sat Feb 17 10:51:39 2018
+*raw
+:PREROUTING ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+:OUTPUT_direct - [0:0]
+:PREROUTING_ZONES - [0:0]
+:PREROUTING_ZONES_SOURCE - [0:0]
+:PREROUTING_direct - [0:0]
+:PRE_FedoraWorkstation - [0:0]
+:PRE_FedoraWorkstation_allow - [0:0]
+:PRE_FedoraWorkstation_deny - [0:0]
+:PRE_FedoraWorkstation_log - [0:0]
+-A PREROUTING -p ipv6-icmp -m icmp6 --icmpv6-type 134 -j ACCEPT
+-A PREROUTING -j PREROUTING_direct
+-A PREROUTING -j PREROUTING_ZONES_SOURCE
+-A PREROUTING -j PREROUTING_ZONES
+-A OUTPUT -j OUTPUT_direct
+-A PREROUTING_ZONES -i wlp58s0 -g PRE_FedoraWorkstation
+-A PREROUTING_ZONES -g PRE_FedoraWorkstation
+-A PRE_FedoraWorkstation -j PRE_FedoraWorkstation_log
+-A PRE_FedoraWorkstation -j PRE_FedoraWorkstation_deny
+-A PRE_FedoraWorkstation -j PRE_FedoraWorkstation_allow
+COMMIT
+# Completed on Sat Feb 17 10:51:39 2018
+# Generated by ip6tables-save v1.6.1 on Sat Feb 17 10:51:39 2018
+*filter
+:INPUT ACCEPT [0:0]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+:FORWARD_IN_ZONES - [0:0]
+:FORWARD_IN_ZONES_SOURCE - [0:0]
+:FORWARD_OUT_ZONES - [0:0]
+:FORWARD_OUT_ZONES_SOURCE - [0:0]
+:FORWARD_direct - [0:0]
+:FWDI_FedoraWorkstation - [0:0]
+:FWDI_FedoraWorkstation_allow - [0:0]
+:FWDI_FedoraWorkstation_deny - [0:0]
+:FWDI_FedoraWorkstation_log - [0:0]
+:FWDO_FedoraWorkstation - [0:0]
+:FWDO_FedoraWorkstation_allow - [0:0]
+:FWDO_FedoraWorkstation_deny - [0:0]
+:FWDO_FedoraWorkstation_log - [0:0]
+:INPUT_ZONES - [0:0]
+:INPUT_ZONES_SOURCE - [0:0]
+:INPUT_direct - [0:0]
+:IN_FedoraWorkstation - [0:0]
+:IN_FedoraWorkstation_allow - [0:0]
+:IN_FedoraWorkstation_deny - [0:0]
+:IN_FedoraWorkstation_log - [0:0]
+:OUTPUT_direct - [0:0]
+-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+-A INPUT -i lo -j ACCEPT
+-A INPUT -j INPUT_direct
+-A INPUT -j INPUT_ZONES_SOURCE
+-A INPUT -j INPUT_ZONES
+-A INPUT -m conntrack --ctstate INVALID -j DROP
+-A INPUT -j REJECT --reject-with icmp6-adm-prohibited
+-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+-A FORWARD -i lo -j ACCEPT
+-A FORWARD -j FORWARD_direct
+-A FORWARD -j FORWARD_IN_ZONES_SOURCE
+-A FORWARD -j FORWARD_IN_ZONES
+-A FORWARD -j FORWARD_OUT_ZONES_SOURCE
+-A FORWARD -j FORWARD_OUT_ZONES
+-A FORWARD -m conntrack --ctstate INVALID -j DROP
+-A FORWARD -j REJECT --reject-with icmp6-adm-prohibited
+-A OUTPUT -j OUTPUT_direct
+-A FORWARD_IN_ZONES -i wlp58s0 -g FWDI_FedoraWorkstation
+-A FORWARD_IN_ZONES -g FWDI_FedoraWorkstation
+-A FORWARD_OUT_ZONES -o wlp58s0 -g FWDO_FedoraWorkstation
+-A FORWARD_OUT_ZONES -g FWDO_FedoraWorkstation
+-A FWDI_FedoraWorkstation -j FWDI_FedoraWorkstation_log
+-A FWDI_FedoraWorkstation -j FWDI_FedoraWorkstation_deny
+-A FWDI_FedoraWorkstation -j FWDI_FedoraWorkstation_allow
+-A FWDI_FedoraWorkstation -p ipv6-icmp -j ACCEPT
+-A FWDO_FedoraWorkstation -j FWDO_FedoraWorkstation_log
+-A FWDO_FedoraWorkstation -j FWDO_FedoraWorkstation_deny
+-A FWDO_FedoraWorkstation -j FWDO_FedoraWorkstation_allow
+-A INPUT_ZONES -i wlp58s0 -g IN_FedoraWorkstation
+-A INPUT_ZONES -g IN_FedoraWorkstation
+-A IN_FedoraWorkstation -j IN_FedoraWorkstation_log
+-A IN_FedoraWorkstation -j IN_FedoraWorkstation_deny
+-A IN_FedoraWorkstation -j IN_FedoraWorkstation_allow
+-A IN_FedoraWorkstation -p ipv6-icmp -j ACCEPT
+-A IN_FedoraWorkstation_allow -p udp -m udp --dport 137 -m conntrack --ctstate NEW -j ACCEPT
+-A IN_FedoraWorkstation_allow -p udp -m udp --dport 138 -m conntrack --ctstate NEW -j ACCEPT
+-A IN_FedoraWorkstation_allow -p tcp -m tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
+-A IN_FedoraWorkstation_allow -d ff02::fb/128 -p udp -m udp --dport 5353 -m conntrack --ctstate NEW -j ACCEPT
+-A IN_FedoraWorkstation_allow -d fe80::/64 -p udp -m udp --dport 546 -m conntrack --ctstate NEW -j ACCEPT
+-A IN_FedoraWorkstation_allow -p udp -m udp --dport 1025:65535 -m conntrack --ctstate NEW -j ACCEPT
+-A IN_FedoraWorkstation_allow -p tcp -m tcp --dport 1025:65535 -m conntrack --ctstate NEW -j ACCEPT
+COMMIT
+# Completed on Sat Feb 17 10:51:39 2018
diff --git a/iptables/tests/shell/testcases/ipt-save/dumps/fedora27-iptables b/iptables/tests/shell/testcases/ipt-save/dumps/fedora27-iptables
new file mode 100644
index 0000000..89a05fc
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-save/dumps/fedora27-iptables
@@ -0,0 +1,136 @@
+# Completed on Sat Feb 17 10:50:33 2018
+# Generated by iptables-save v1.6.1 on Sat Feb 17 10:50:33 2018
+*mangle
+:PREROUTING ACCEPT [0:0]
+:INPUT ACCEPT [0:0]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+:POSTROUTING ACCEPT [0:0]
+:FORWARD_direct - [0:0]
+:INPUT_direct - [0:0]
+:OUTPUT_direct - [0:0]
+:POSTROUTING_direct - [0:0]
+:PREROUTING_ZONES - [0:0]
+:PREROUTING_ZONES_SOURCE - [0:0]
+:PREROUTING_direct - [0:0]
+:PRE_FedoraWorkstation - [0:0]
+:PRE_FedoraWorkstation_allow - [0:0]
+:PRE_FedoraWorkstation_deny - [0:0]
+:PRE_FedoraWorkstation_log - [0:0]
+[1:2] -A PREROUTING -j PREROUTING_direct
+[3:4] -A PREROUTING -j PREROUTING_ZONES_SOURCE
+[0:0] -A PREROUTING -j PREROUTING_ZONES
+[0:0] -A INPUT -j INPUT_direct
+[0:0] -A FORWARD -j FORWARD_direct
+[0:0] -A OUTPUT -j OUTPUT_direct
+[0:0] -A POSTROUTING -o virbr0 -p udp -m udp --dport 68 -j CHECKSUM --checksum-fill
+[0:0] -A POSTROUTING -j POSTROUTING_direct
+[0:0] -A PREROUTING_ZONES -i wlp58s0 -g PRE_FedoraWorkstation
+[0:0] -A PREROUTING_ZONES -g PRE_FedoraWorkstation
+[0:0] -A PRE_FedoraWorkstation -j PRE_FedoraWorkstation_log
+[0:0] -A PRE_FedoraWorkstation -j PRE_FedoraWorkstation_deny
+[0:0] -A PRE_FedoraWorkstation -j PRE_FedoraWorkstation_allow
+COMMIT
+# Completed on Sat Feb 17 10:50:33 2018
+# Generated by iptables-save v1.6.1 on Sat Feb 17 10:50:33 2018
+*raw
+:PREROUTING ACCEPT [1681:2620433]
+:OUTPUT ACCEPT [1619:171281]
+:OUTPUT_direct - [0:0]
+:PREROUTING_ZONES - [0:0]
+:PREROUTING_ZONES_SOURCE - [0:0]
+:PREROUTING_direct - [0:0]
+:PRE_FedoraWorkstation - [0:0]
+:PRE_FedoraWorkstation_allow - [0:0]
+:PRE_FedoraWorkstation_deny - [0:0]
+:PRE_FedoraWorkstation_log - [0:0]
+[0:0] -A PREROUTING -j PREROUTING_direct
+[0:0] -A PREROUTING -j PREROUTING_ZONES_SOURCE
+[0:0] -A PREROUTING -j PREROUTING_ZONES
+[0:0] -A OUTPUT -j OUTPUT_direct
+[0:0] -A PREROUTING_ZONES -i wlp58s0 -g PRE_FedoraWorkstation
+[0:0] -A PREROUTING_ZONES -g PRE_FedoraWorkstation
+[0:0] -A PRE_FedoraWorkstation -j PRE_FedoraWorkstation_log
+[0:0] -A PRE_FedoraWorkstation -j PRE_FedoraWorkstation_deny
+[0:0] -A PRE_FedoraWorkstation -j PRE_FedoraWorkstation_allow
+[0:0] -A PRE_FedoraWorkstation_allow -p udp -m udp --dport 137 -j CT --helper netbios-ns
+COMMIT
+# Completed on Sat Feb 17 10:50:33 2018
+# Generated by iptables-save v1.6.1 on Sat Feb 17 10:50:33 2018
+*filter
+:INPUT ACCEPT [0:0]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [1619:171281]
+:FORWARD_IN_ZONES - [0:0]
+:FORWARD_IN_ZONES_SOURCE - [0:0]
+:FORWARD_OUT_ZONES - [0:0]
+:FORWARD_OUT_ZONES_SOURCE - [0:0]
+:FORWARD_direct - [0:0]
+:FWDI_FedoraWorkstation - [0:0]
+:FWDI_FedoraWorkstation_allow - [0:0]
+:FWDI_FedoraWorkstation_deny - [0:0]
+:FWDI_FedoraWorkstation_log - [0:0]
+:FWDO_FedoraWorkstation - [0:0]
+:FWDO_FedoraWorkstation_allow - [0:0]
+:FWDO_FedoraWorkstation_deny - [0:0]
+:FWDO_FedoraWorkstation_log - [0:0]
+:INPUT_ZONES - [0:0]
+:INPUT_ZONES_SOURCE - [0:0]
+:INPUT_direct - [0:0]
+:IN_FedoraWorkstation - [0:0]
+:IN_FedoraWorkstation_allow - [0:0]
+:IN_FedoraWorkstation_deny - [0:0]
+:IN_FedoraWorkstation_log - [0:0]
+:OUTPUT_direct - [0:0]
+[5:6] -A INPUT -i virbr0 -p udp -m udp --dport 53 -j ACCEPT
+[0:123456789] -A INPUT -i virbr0 -p tcp -m tcp --dport 53 -j ACCEPT
+[0:0] -A INPUT -i virbr0 -p udp -m udp --dport 67 -j ACCEPT
+[0:0] -A INPUT -i virbr0 -p tcp -m tcp --dport 67 -j ACCEPT
+[0:0] -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+[0:0] -A INPUT -i lo -j ACCEPT
+[0:0] -A INPUT -j INPUT_direct
+[0:0] -A INPUT -j INPUT_ZONES_SOURCE
+[0:0] -A INPUT -j INPUT_ZONES
+[0:0] -A INPUT -m conntrack --ctstate INVALID -j DROP
+[0:0] -A INPUT -j REJECT --reject-with icmp-host-prohibited
+[0:0] -A FORWARD -d 192.168.122.0/24 -o virbr0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+[0:0] -A FORWARD -s 192.168.122.0/24 -i virbr0 -j ACCEPT
+[0:0] -A FORWARD -i virbr0 -o virbr0 -j ACCEPT
+[0:0] -A FORWARD -o virbr0 -j REJECT --reject-with icmp-port-unreachable
+[0:0] -A FORWARD -i virbr0 -j REJECT --reject-with icmp-port-unreachable
+[0:0] -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+[0:0] -A FORWARD -i lo -j ACCEPT
+[0:0] -A FORWARD -j FORWARD_direct
+[0:0] -A FORWARD -j FORWARD_IN_ZONES_SOURCE
+[0:0] -A FORWARD -j FORWARD_IN_ZONES
+[0:0] -A FORWARD -j FORWARD_OUT_ZONES_SOURCE
+[0:0] -A FORWARD -j FORWARD_OUT_ZONES
+[0:0] -A FORWARD -m conntrack --ctstate INVALID -j DROP
+[0:0] -A FORWARD -j REJECT --reject-with icmp-host-prohibited
+[0:0] -A OUTPUT -o virbr0 -p udp -m udp --dport 68 -j ACCEPT
+[0:0] -A OUTPUT -j OUTPUT_direct
+[0:0] -A FORWARD_IN_ZONES -i wlp58s0 -g FWDI_FedoraWorkstation
+[0:0] -A FORWARD_IN_ZONES -g FWDI_FedoraWorkstation
+[0:0] -A FORWARD_OUT_ZONES -o wlp58s0 -g FWDO_FedoraWorkstation
+[0:0] -A FORWARD_OUT_ZONES -g FWDO_FedoraWorkstation
+[0:0] -A FWDI_FedoraWorkstation -j FWDI_FedoraWorkstation_log
+[0:0] -A FWDI_FedoraWorkstation -j FWDI_FedoraWorkstation_deny
+[0:0] -A FWDI_FedoraWorkstation -j FWDI_FedoraWorkstation_allow
+[0:0] -A FWDI_FedoraWorkstation -p icmp -j ACCEPT
+[0:0] -A FWDO_FedoraWorkstation -j FWDO_FedoraWorkstation_log
+[0:0] -A FWDO_FedoraWorkstation -j FWDO_FedoraWorkstation_deny
+[0:0] -A FWDO_FedoraWorkstation -j FWDO_FedoraWorkstation_allow
+[0:0] -A INPUT_ZONES -i wlp58s0 -g IN_FedoraWorkstation
+[0:0] -A INPUT_ZONES -g IN_FedoraWorkstation
+[0:0] -A IN_FedoraWorkstation -j IN_FedoraWorkstation_log
+[0:0] -A IN_FedoraWorkstation -j IN_FedoraWorkstation_deny
+[0:0] -A IN_FedoraWorkstation -j IN_FedoraWorkstation_allow
+[0:0] -A IN_FedoraWorkstation -p icmp -j ACCEPT
+[0:0] -A IN_FedoraWorkstation_allow -p udp -m udp --dport 137 -m conntrack --ctstate NEW -j ACCEPT
+[0:0] -A IN_FedoraWorkstation_allow -p udp -m udp --dport 138 -m conntrack --ctstate NEW -j ACCEPT
+[0:0] -A IN_FedoraWorkstation_allow -p tcp -m tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
+[0:0] -A IN_FedoraWorkstation_allow -d 224.0.0.251/32 -p udp -m udp --dport 5353 -m conntrack --ctstate NEW -j ACCEPT
+[0:0] -A IN_FedoraWorkstation_allow -p udp -m udp --dport 1025:65535 -m conntrack --ctstate NEW -j ACCEPT
+[7:8] -A IN_FedoraWorkstation_allow -p tcp -m tcp --dport 1025:65535 -m conntrack --ctstate NEW -j ACCEPT
+COMMIT
+# Completed on Sat Feb 17 10:50:33 2018
diff --git a/iptables/tests/shell/testcases/ipt-save/dumps/fedora27-iptables.xml b/iptables/tests/shell/testcases/ipt-save/dumps/fedora27-iptables.xml
new file mode 100644
index 0000000..400be03
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-save/dumps/fedora27-iptables.xml
@@ -0,0 +1,925 @@
+<iptables-rules version="1.0">
+<!-- # Completed on Sat Feb 17 10:50:33 2018 -->
+<!-- # Generated by iptables*-save v1.6.1 on Sat Feb 17 10:50:33 2018 -->
+  <table name="mangle" >
+    <chain name="PREROUTING" policy="ACCEPT" packet-count="0" byte-count="0" >
+      <rule packet-count="1" byte-count="2" >
+       <actions>
+        <call >
+          <PREROUTING_direct />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="3" byte-count="4" >
+       <actions>
+        <call >
+          <PREROUTING_ZONES_SOURCE />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <PREROUTING_ZONES />
+        </call>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="INPUT" policy="ACCEPT" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <INPUT_direct />
+        </call>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="FORWARD" policy="ACCEPT" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <FORWARD_direct />
+        </call>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="OUTPUT" policy="ACCEPT" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <OUTPUT_direct />
+        </call>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="POSTROUTING" policy="ACCEPT" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <o >virbr0</o>
+          <p >udp</p>
+        </match>
+        <udp >
+          <dport >68</dport>
+        </udp>
+       </conditions>
+       <actions>
+        <CHECKSUM >
+          <checksum-fill  />
+        </CHECKSUM>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <POSTROUTING_direct />
+        </call>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="PREROUTING_ZONES" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <i >wlp58s0</i>
+        </match>
+       </conditions>
+       <actions>
+        <goto >
+          <PRE_FedoraWorkstation />
+        </goto>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <goto >
+          <PRE_FedoraWorkstation />
+        </goto>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="PRE_FedoraWorkstation" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <PRE_FedoraWorkstation_log />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <PRE_FedoraWorkstation_deny />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <PRE_FedoraWorkstation_allow />
+        </call>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="FORWARD_direct" packet-count="0" byte-count="0" />
+    <chain name="INPUT_direct" packet-count="0" byte-count="0" />
+    <chain name="OUTPUT_direct" packet-count="0" byte-count="0" />
+    <chain name="POSTROUTING_direct" packet-count="0" byte-count="0" />
+    <chain name="PREROUTING_ZONES_SOURCE" packet-count="0" byte-count="0" />
+    <chain name="PREROUTING_direct" packet-count="0" byte-count="0" />
+    <chain name="PRE_FedoraWorkstation_allow" packet-count="0" byte-count="0" />
+    <chain name="PRE_FedoraWorkstation_deny" packet-count="0" byte-count="0" />
+    <chain name="PRE_FedoraWorkstation_log" packet-count="0" byte-count="0" />
+  </table>
+<!-- # Completed on Sat Feb 17 10:50:33 2018 -->
+<!-- # Generated by iptables*-save v1.6.1 on Sat Feb 17 10:50:33 2018 -->
+  <table name="raw" >
+    <chain name="PREROUTING" policy="ACCEPT" packet-count="1681" byte-count="2620433" >
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <PREROUTING_direct />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <PREROUTING_ZONES_SOURCE />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <PREROUTING_ZONES />
+        </call>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="OUTPUT" policy="ACCEPT" packet-count="1619" byte-count="171281" >
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <OUTPUT_direct />
+        </call>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="PREROUTING_ZONES" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <i >wlp58s0</i>
+        </match>
+       </conditions>
+       <actions>
+        <goto >
+          <PRE_FedoraWorkstation />
+        </goto>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <goto >
+          <PRE_FedoraWorkstation />
+        </goto>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="PRE_FedoraWorkstation" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <PRE_FedoraWorkstation_log />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <PRE_FedoraWorkstation_deny />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <PRE_FedoraWorkstation_allow />
+        </call>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="PRE_FedoraWorkstation_allow" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <p >udp</p>
+        </match>
+        <udp >
+          <dport >137</dport>
+        </udp>
+       </conditions>
+       <actions>
+        <CT >
+          <helper >netbios-ns</helper>
+        </CT>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="OUTPUT_direct" packet-count="0" byte-count="0" />
+    <chain name="PREROUTING_ZONES_SOURCE" packet-count="0" byte-count="0" />
+    <chain name="PREROUTING_direct" packet-count="0" byte-count="0" />
+    <chain name="PRE_FedoraWorkstation_deny" packet-count="0" byte-count="0" />
+    <chain name="PRE_FedoraWorkstation_log" packet-count="0" byte-count="0" />
+  </table>
+<!-- # Completed on Sat Feb 17 10:50:33 2018 -->
+<!-- # Generated by iptables*-save v1.6.1 on Sat Feb 17 10:50:33 2018 -->
+  <table name="filter" >
+    <chain name="INPUT" policy="ACCEPT" packet-count="0" byte-count="0" >
+      <rule packet-count="5" byte-count="6" >
+       <conditions>
+        <match >
+          <i >virbr0</i>
+          <p >udp</p>
+        </match>
+        <udp >
+          <dport >53</dport>
+        </udp>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="123456789" >
+       <conditions>
+        <match >
+          <i >virbr0</i>
+          <p >tcp</p>
+        </match>
+        <tcp >
+          <dport >53</dport>
+        </tcp>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <i >virbr0</i>
+          <p >udp</p>
+        </match>
+        <udp >
+          <dport >67</dport>
+        </udp>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <i >virbr0</i>
+          <p >tcp</p>
+        </match>
+        <tcp >
+          <dport >67</dport>
+        </tcp>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <conntrack >
+          <ctstate >RELATED,ESTABLISHED</ctstate>
+        </conntrack>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <i >lo</i>
+        </match>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <INPUT_direct />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <INPUT_ZONES_SOURCE />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <INPUT_ZONES />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <conntrack >
+          <ctstate >INVALID</ctstate>
+        </conntrack>
+       </conditions>
+       <actions>
+        <DROP  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <REJECT >
+          <reject-with >icmp-host-prohibited</reject-with>
+        </REJECT>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="FORWARD" policy="ACCEPT" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <d >192.168.122.0/24</d>
+          <o >virbr0</o>
+        </match>
+        <conntrack >
+          <ctstate >RELATED,ESTABLISHED</ctstate>
+        </conntrack>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <s >192.168.122.0/24</s>
+          <i >virbr0</i>
+        </match>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <i >virbr0</i>
+          <o >virbr0</o>
+        </match>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <o >virbr0</o>
+        </match>
+       </conditions>
+       <actions>
+        <REJECT >
+          <reject-with >icmp-port-unreachable</reject-with>
+        </REJECT>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <i >virbr0</i>
+        </match>
+       </conditions>
+       <actions>
+        <REJECT >
+          <reject-with >icmp-port-unreachable</reject-with>
+        </REJECT>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <conntrack >
+          <ctstate >RELATED,ESTABLISHED</ctstate>
+        </conntrack>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <i >lo</i>
+        </match>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <FORWARD_direct />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <FORWARD_IN_ZONES_SOURCE />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <FORWARD_IN_ZONES />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <FORWARD_OUT_ZONES_SOURCE />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <FORWARD_OUT_ZONES />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <conntrack >
+          <ctstate >INVALID</ctstate>
+        </conntrack>
+       </conditions>
+       <actions>
+        <DROP  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <REJECT >
+          <reject-with >icmp-host-prohibited</reject-with>
+        </REJECT>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="OUTPUT" policy="ACCEPT" packet-count="1619" byte-count="171281" >
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <o >virbr0</o>
+          <p >udp</p>
+        </match>
+        <udp >
+          <dport >68</dport>
+        </udp>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <OUTPUT_direct />
+        </call>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="FORWARD_IN_ZONES" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <i >wlp58s0</i>
+        </match>
+       </conditions>
+       <actions>
+        <goto >
+          <FWDI_FedoraWorkstation />
+        </goto>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <goto >
+          <FWDI_FedoraWorkstation />
+        </goto>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="FORWARD_OUT_ZONES" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <o >wlp58s0</o>
+        </match>
+       </conditions>
+       <actions>
+        <goto >
+          <FWDO_FedoraWorkstation />
+        </goto>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <goto >
+          <FWDO_FedoraWorkstation />
+        </goto>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="FWDI_FedoraWorkstation" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <FWDI_FedoraWorkstation_log />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <FWDI_FedoraWorkstation_deny />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <FWDI_FedoraWorkstation_allow />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <p >icmp</p>
+        </match>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="FWDO_FedoraWorkstation" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <FWDO_FedoraWorkstation_log />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <FWDO_FedoraWorkstation_deny />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <FWDO_FedoraWorkstation_allow />
+        </call>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="INPUT_ZONES" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <i >wlp58s0</i>
+        </match>
+       </conditions>
+       <actions>
+        <goto >
+          <IN_FedoraWorkstation />
+        </goto>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <goto >
+          <IN_FedoraWorkstation />
+        </goto>
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="IN_FedoraWorkstation" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <IN_FedoraWorkstation_log />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <IN_FedoraWorkstation_deny />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <actions>
+        <call >
+          <IN_FedoraWorkstation_allow />
+        </call>
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <p >icmp</p>
+        </match>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="IN_FedoraWorkstation_allow" packet-count="0" byte-count="0" >
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <p >udp</p>
+        </match>
+        <udp >
+          <dport >137</dport>
+        </udp>
+        <conntrack >
+          <ctstate >NEW</ctstate>
+        </conntrack>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <p >udp</p>
+        </match>
+        <udp >
+          <dport >138</dport>
+        </udp>
+        <conntrack >
+          <ctstate >NEW</ctstate>
+        </conntrack>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <p >tcp</p>
+        </match>
+        <tcp >
+          <dport >22</dport>
+        </tcp>
+        <conntrack >
+          <ctstate >NEW</ctstate>
+        </conntrack>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <d >224.0.0.251/32</d>
+          <p >udp</p>
+        </match>
+        <udp >
+          <dport >5353</dport>
+        </udp>
+        <conntrack >
+          <ctstate >NEW</ctstate>
+        </conntrack>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="0" byte-count="0" >
+       <conditions>
+        <match >
+          <p >udp</p>
+        </match>
+        <udp >
+          <dport >1025:65535</dport>
+        </udp>
+        <conntrack >
+          <ctstate >NEW</ctstate>
+        </conntrack>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+      <rule packet-count="7" byte-count="8" >
+       <conditions>
+        <match >
+          <p >tcp</p>
+        </match>
+        <tcp >
+          <dport >1025:65535</dport>
+        </tcp>
+        <conntrack >
+          <ctstate >NEW</ctstate>
+        </conntrack>
+       </conditions>
+       <actions>
+        <ACCEPT  />
+       </actions>
+
+      </rule>
+
+    </chain>
+    <chain name="FORWARD_IN_ZONES_SOURCE" packet-count="0" byte-count="0" />
+    <chain name="FORWARD_OUT_ZONES_SOURCE" packet-count="0" byte-count="0" />
+    <chain name="FORWARD_direct" packet-count="0" byte-count="0" />
+    <chain name="FWDI_FedoraWorkstation_allow" packet-count="0" byte-count="0" />
+    <chain name="FWDI_FedoraWorkstation_deny" packet-count="0" byte-count="0" />
+    <chain name="FWDI_FedoraWorkstation_log" packet-count="0" byte-count="0" />
+    <chain name="FWDO_FedoraWorkstation_allow" packet-count="0" byte-count="0" />
+    <chain name="FWDO_FedoraWorkstation_deny" packet-count="0" byte-count="0" />
+    <chain name="FWDO_FedoraWorkstation_log" packet-count="0" byte-count="0" />
+    <chain name="INPUT_ZONES_SOURCE" packet-count="0" byte-count="0" />
+    <chain name="INPUT_direct" packet-count="0" byte-count="0" />
+    <chain name="IN_FedoraWorkstation_deny" packet-count="0" byte-count="0" />
+    <chain name="IN_FedoraWorkstation_log" packet-count="0" byte-count="0" />
+    <chain name="OUTPUT_direct" packet-count="0" byte-count="0" />
+  </table>
+<!-- # Completed on Sat Feb 17 10:50:33 2018 -->
+</iptables-rules>
diff --git a/iptables/tests/shell/testcases/ipt-save/dumps/ipt-save-filter.txt b/iptables/tests/shell/testcases/ipt-save/dumps/ipt-save-filter.txt
new file mode 100644
index 0000000..6e42de7
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-save/dumps/ipt-save-filter.txt
@@ -0,0 +1,69 @@
+# Generated by iptables-save v1.2.4 on Mon Mar 17 19:59:10 2003
+*filter
+:INPUT DROP [0:0]
+:FORWARD DROP [0:0]
+:OUTPUT DROP [0:0]
+:WLAN - [0:0]
+:accept_log - [0:0]
+:block - [0:0]
+:in_icmp - [0:0]
+:in_trusted - [0:0]
+:reject_log - [0:0]
+:wlanout - [0:0]
+-A INPUT -i wlan0 -j WLAN
+-A INPUT -s 127.0.0.1/32 -d 127.0.0.1/32 -i lo -j ACCEPT
+-A INPUT -i ppp0 -p icmp -m limit --limit 1/sec -j in_icmp
+-A INPUT -i ppp0 -p tcp -m tcp --dport 22 -j in_trusted
+-A INPUT -j block
+-A FORWARD -d 192.168.100.77/32 -i ppp0 -p udp -m udp --dport 4166 -j ACCEPT
+-A FORWARD -d 192.168.100.77/32 -i ppp0 -p tcp -m tcp --dport 4180 -j ACCEPT
+-A FORWARD -d 192.168.100.77/32 -i ppp0 -p tcp -m tcp --dport 4162 -j ACCEPT
+-A FORWARD -d 192.168.100.77/32 -i ppp0 -p tcp -m tcp --dport 20376 -j ACCEPT
+-A FORWARD -d 192.168.100.2/32 -i ppp0 -p tcp -m tcp --dport 10209 -j ACCEPT
+-A FORWARD -d 192.168.100.2/32 -i ppp0 -p tcp -m tcp --dport 881 -j ACCEPT
+-A FORWARD ! -s 192.168.0.0/24 -i eth2 -p icmp -j DROP
+-A FORWARD ! -s 192.168.0.0/24 -i eth2 -p udp -j DROP
+-A FORWARD ! -s 192.168.0.0/24 -i eth2 -p tcp -j DROP
+-A FORWARD ! -s 192.168.100.0/24 -i eth1 -p icmp -j DROP
+-A FORWARD ! -s 192.168.100.0/24 -i eth1 -p udp -j DROP
+-A FORWARD ! -s 192.168.100.0/24 -i eth1 -p tcp -j DROP
+-A FORWARD -o ppp0 -p udp -m udp --sport 137:139 -j DROP
+-A FORWARD -o ppp0 -p udp -m udp --sport 445 -j DROP
+-A FORWARD -o ppp0 -p tcp -m tcp --sport 137:139 -j DROP
+-A FORWARD -o ppp0 -p tcp -m tcp --sport 445 -j DROP
+-A FORWARD -i ppp0 -p udp -m udp --dport 137:139 -j DROP
+-A FORWARD -i ppp0 -p udp -m udp --dport 445 -j DROP
+-A FORWARD -i ppp0 -p tcp -m tcp --dport 137:139 -j DROP
+-A FORWARD -i ppp0 -p tcp -m tcp --dport 445 -j DROP
+-A FORWARD -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
+-A FORWARD -j block
+-A OUTPUT -s 127.0.0.1/32 -d 127.0.0.1/32 -o lo -j ACCEPT
+-A OUTPUT -o wlan0 -j wlanout
+-A OUTPUT -j block
+-A WLAN -s 192.168.200.4/32 -m mac --mac-source 00:00:f1:05:a0:e0 -j RETURN
+-A WLAN -s 192.168.200.9/32 -m mac --mac-source 00:00:f1:05:99:85 -j RETURN
+-A WLAN -m limit --limit 12/min -j LOG --log-prefix "UNKNOWN WLAN dropped:"
+-A WLAN -j DROP
+-A accept_log -i ppp0 -p tcp -m tcp --tcp-flags SYN,RST,ACK SYN -m limit --limit 1/sec -j LOG --log-prefix "TCPConnect on ppp0:"
+-A accept_log -i ppp0 ! -p tcp -m limit --limit 1/sec -j LOG --log-prefix "Accepted Datagram on ppp0:"
+-A accept_log -j ACCEPT
+-A block -m state --state RELATED,ESTABLISHED -j ACCEPT
+-A block ! -i ppp0 -m state --state NEW -j ACCEPT
+-A block -p tcp -j reject_log
+-A block -p udp -j reject_log
+-A in_icmp -p icmp -m icmp --icmp-type 8 -j ACCEPT
+-A in_icmp -p icmp -m icmp --icmp-type 4 -j ACCEPT
+-A in_icmp -p icmp -m icmp --icmp-type 1 -j ACCEPT
+-A in_icmp -p icmp -m icmp --icmp-type 3 -j ACCEPT
+-A in_icmp -p icmp -m icmp --icmp-type 11 -j ACCEPT
+-A in_icmp -p icmp -m icmp --icmp-type 12 -j ACCEPT
+-A in_trusted -s 10.230.173.148/32 -j ACCEPT
+-A in_trusted -s 10.230.173.151/32 -j ACCEPT
+-A reject_log -i ppp0 -p tcp -m tcp --dport 22:80 --tcp-flags SYN,RST,ACK SYN -m limit --limit 1/sec -j LOG --log-prefix "RejectTCPConnectReq on ppp0:"
+-A reject_log -p tcp -j REJECT --reject-with tcp-reset
+-A reject_log -p udp -j REJECT --reject-with icmp-port-unreachable
+-A wlanout -d 192.168.200.4/32 -j RETURN
+-A wlanout -d 192.168.200.9/32 -j RETURN
+-A wlanout -j DROP
+COMMIT
+# Completed on Mon Mar 17 19:59:10 2003
diff --git a/iptables/tests/shell/testcases/ipt-save/dumps/policy-drop.txt b/iptables/tests/shell/testcases/ipt-save/dumps/policy-drop.txt
new file mode 100644
index 0000000..7522231
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-save/dumps/policy-drop.txt
@@ -0,0 +1,8 @@
+# Generated by xtables-save v1.6.2 on Tue Jun 26 22:28:41 2018
+*filter
+:INPUT DROP [0:0]
+:FORWARD DROP [0:0]
+:OUTPUT DROP [0:0]
+-A OUTPUT -j ACCEPT
+COMMIT
+# Completed on Tue Jun 26 22:28:41 2018
diff --git a/iptables/tests/shell/testcases/ipt-save/dumps/wireless.txt b/iptables/tests/shell/testcases/ipt-save/dumps/wireless.txt
new file mode 100644
index 0000000..2bd3832
--- /dev/null
+++ b/iptables/tests/shell/testcases/ipt-save/dumps/wireless.txt
@@ -0,0 +1,81 @@
+# Generated by iptables-save v1.4.21 on Thu Jun 29 18:03:06 2017
+*raw
+:PREROUTING ACCEPT [0:0]
+:OUTPUT ACCEPT [0:0]
+:port_assignment - [0:0]
+-A PREROUTING -j port_assignment
+-A OUTPUT -j port_assignment
+-A port_assignment -p tcp -m tcp --dport 1723 -j CT --helper pptp
+COMMIT
+# Completed on Thu Jun 29 18:03:06 2017
+# Generated by iptables-save v1.4.21 on Thu Jun 29 18:03:06 2017
+*filter
+:INPUT DROP [0:0]
+:FORWARD DROP [0:0]
+:OUTPUT ACCEPT [0:0]
+:CUST_I15_IN - [0:0]
+:CUST_I15_OUT - [0:0]
+:CUST_I16_IN - [0:0]
+:CUST_I16_OUT - [0:0]
+:L_ACCEPT - [0:0]
+:L_DROP - [0:0]
+:L_REJECT - [0:0]
+:VPN_USERS_IN - [0:0]
+:VPN_USERS_OUT - [0:0]
+-A INPUT -m conntrack --ctstate INVALID -j L_DROP
+-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j L_ACCEPT
+-A INPUT -i lo -j L_ACCEPT
+-A INPUT -s 10.78.129.130/32 -p tcp -m tcp --dport 5666 -j L_ACCEPT
+-A INPUT -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -m multiport --dports 22,80,443,873,1723 -j L_ACCEPT
+-A INPUT -p udp -m udp -m multiport --dports 500,1701,4500 -j L_ACCEPT
+-A INPUT -p icmp -m icmp --icmp-type 8 -j L_ACCEPT
+-A INPUT -s 10.31.70.8/29 -i bond0.208 -p tcp -m tcp --dport 179 --tcp-flags FIN,SYN,RST,ACK SYN -j L_ACCEPT
+-A INPUT -s 10.44.224.8/29 -i bond0.686 -p tcp -m tcp --dport 179 --tcp-flags FIN,SYN,RST,ACK SYN -j L_ACCEPT
+-A INPUT -p esp -j L_ACCEPT
+-A INPUT -s 168.209.255.75/32 -p gre -j L_ACCEPT
+-A INPUT -s 168.209.255.106/32 -p gre -j L_ACCEPT
+-A INPUT -s 10.35.167.46/32 -p gre -j L_ACCEPT
+-A INPUT -s 10.35.167.45/32 -p gre -j L_ACCEPT
+-A INPUT -i gre-wbcore -j L_ACCEPT
+-A INPUT -i gre-davo-+ -j L_ACCEPT
+-A INPUT -i bond0.208 -j L_DROP
+-A INPUT -i bond0.686 -j L_DROP
+-A INPUT -j L_ACCEPT
+-A FORWARD -i bond0.10 -j ACCEPT
+-A FORWARD -m conntrack --ctstate INVALID -j L_DROP
+-A FORWARD -p tcp -m tcp --tcp-flags FIN,SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
+-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j L_ACCEPT
+-A FORWARD -d 10.31.63.80/30 -o bond0.10 -j L_ACCEPT
+-A FORWARD -o bond0.11 -j CUST_I16_IN
+-A FORWARD -i bond0.11 -j CUST_I16_OUT
+-A FORWARD -o bond0.12 -j CUST_I15_IN
+-A FORWARD -i bond0.12 -j CUST_I15_OUT
+-A FORWARD -s 192.168.255.0/24 -i ppp+ -o bond0.208 -j L_DROP
+-A FORWARD -s 192.168.255.0/24 -i ppp+ -o bond0.686 -j L_DROP
+-A FORWARD -j L_ACCEPT
+-A CUST_I15_IN -p tcp -m tcp --dport 22 --tcp-flags FIN,SYN,RST,ACK SYN -j L_ACCEPT
+-A CUST_I15_IN -p tcp -m tcp --dport 80 --tcp-flags FIN,SYN,RST,ACK SYN -j L_ACCEPT
+-A CUST_I15_IN -p tcp -m tcp --dport 433 --tcp-flags FIN,SYN,RST,ACK SYN -j L_ACCEPT
+-A CUST_I15_IN -p tcp -m tcp --dport 3306 --tcp-flags FIN,SYN,RST,ACK SYN -j L_ACCEPT
+-A CUST_I15_IN -p tcp -m tcp --dport 3390 --tcp-flags FIN,SYN,RST,ACK SYN -j L_ACCEPT
+-A CUST_I15_IN -j L_DROP
+-A CUST_I15_OUT -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -m multiport --dports 80,443 -j L_ACCEPT
+-A CUST_I15_OUT -j L_DROP
+-A CUST_I16_IN -p tcp -m tcp --dport 3390 --tcp-flags FIN,SYN,RST,ACK SYN -j L_ACCEPT
+-A CUST_I16_IN -p tcp -m tcp --dport 21 --tcp-flags FIN,SYN,RST,ACK SYN -j L_ACCEPT
+-A CUST_I16_IN -p icmp -m icmp --icmp-type 8 -j L_ACCEPT
+-A CUST_I16_IN -j L_DROP
+-A CUST_I16_OUT -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -m multiport --dports 80,443 -j L_ACCEPT
+-A CUST_I16_OUT -d 154.73.34.12/32 -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -m multiport --dports 25 -j L_ACCEPT
+-A CUST_I16_OUT -j L_DROP
+-A L_ACCEPT -j NFLOG --nflog-group 1 --nflog-threshold 5
+-A L_ACCEPT -j ACCEPT
+-A L_DROP -j LOG --log-prefix "L_DROP: "
+-A L_DROP -j NFLOG --nflog-group 2 --nflog-threshold 5
+-A L_DROP -j DROP
+-A L_REJECT -j NFLOG --nflog-group 3 --nflog-threshold 5
+-A L_REJECT -j REJECT --reject-with icmp-port-unreachable
+-A VPN_USERS_IN -i ppp0 -m comment --comment "User: " -j ACCEPT
+-A VPN_USERS_OUT -o ppp0 -m comment --comment "User: " -j ACCEPT
+COMMIT
+# Completed on Thu Jun 29 18:03:06 2017
diff --git a/iptables/tests/shell/testcases/iptables/0001-chain-refs_0 b/iptables/tests/shell/testcases/iptables/0001-chain-refs_0
new file mode 100755
index 0000000..e55506e
--- /dev/null
+++ b/iptables/tests/shell/testcases/iptables/0001-chain-refs_0
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+# make sure rules are not counted in references of iptables output
+
+set -e
+
+$XT_MULTI iptables -N foo
+$XT_MULTI iptables -L | grep 'Chain foo (0 references)'
+
+$XT_MULTI iptables -A foo -j ACCEPT
+$XT_MULTI iptables -L | grep 'Chain foo (0 references)'
+
+$XT_MULTI iptables -A FORWARD -j foo
+$XT_MULTI iptables -L | grep 'Chain foo (1 references)'
diff --git a/iptables/tests/shell/testcases/iptables/0002-verbose-output_0 b/iptables/tests/shell/testcases/iptables/0002-verbose-output_0
new file mode 100755
index 0000000..b1ef91f
--- /dev/null
+++ b/iptables/tests/shell/testcases/iptables/0002-verbose-output_0
@@ -0,0 +1,56 @@
+#!/bin/bash
+
+set -e
+#set -x
+
+# ensure verbose output is identical between legacy and nft tools
+
+RULE1='-i eth2 -o eth3 -s 10.0.0.1 -d 10.0.0.2 -j ACCEPT'
+VOUT1='ACCEPT  all opt -- in eth2 out eth3  10.0.0.1  -> 10.0.0.2'
+RULE2='-i eth2 -o eth3 -s 10.0.0.4 -d 10.0.0.5 -j ACCEPT'
+VOUT2='ACCEPT  all opt -- in eth2 out eth3  10.0.0.4  -> 10.0.0.5'
+
+diff -u -Z <(echo -e "$VOUT1") <($XT_MULTI iptables -v -A FORWARD $RULE1)
+diff -u -Z <(echo -e "$VOUT2") <($XT_MULTI iptables -v -I FORWARD 2 $RULE2)
+
+diff -u -Z <(echo -e "$VOUT1") <($XT_MULTI iptables -v -C FORWARD $RULE1)
+diff -u -Z <(echo -e "$VOUT2") <($XT_MULTI iptables -v -C FORWARD $RULE2)
+
+EXPECT='Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
+ pkts bytes target     prot opt in     out     source               destination
+
+Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
+ pkts bytes target     prot opt in     out     source               destination
+    0     0 ACCEPT     all  --  eth2   eth3    10.0.0.1             10.0.0.2
+    0     0 ACCEPT     all  --  eth2   eth3    10.0.0.4             10.0.0.5
+
+Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
+ pkts bytes target     prot opt in     out     source               destination'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI iptables -v -n -L)
+
+[[ -z $($XT_MULTI iptables -v -N foobar) ]] || exit 1
+
+diff -u -Z <(echo -e "$VOUT1") <($XT_MULTI iptables -v -D FORWARD $RULE1)
+diff -u -Z <(echo -e "$VOUT2") <($XT_MULTI iptables -v -D FORWARD $RULE2)
+
+EXPECT="Flushing chain \`INPUT'
+Flushing chain \`FORWARD'
+Flushing chain \`OUTPUT'
+Flushing chain \`foobar'"
+
+diff -u <(echo -e "$EXPECT") <($XT_MULTI iptables -v -F)
+
+EXPECT="Zeroing chain \`INPUT'
+Zeroing chain \`FORWARD'
+Zeroing chain \`OUTPUT'
+Zeroing chain \`foobar'"
+
+diff -u <(echo -e "$EXPECT") <($XT_MULTI iptables -v -Z)
+
+diff -u <(echo "Flushing chain \`OUTPUT'") <($XT_MULTI iptables -v -F OUTPUT)
+diff -u <(echo "Zeroing chain \`OUTPUT'") <($XT_MULTI iptables -v -Z OUTPUT)
+diff -u <(echo "Flushing chain \`foobar'") <($XT_MULTI iptables -v -F foobar)
+diff -u <(echo "Zeroing chain \`foobar'") <($XT_MULTI iptables -v -Z foobar)
+
+diff -u <(echo "Deleting chain \`foobar'") <($XT_MULTI iptables -v -X foobar)
diff --git a/iptables/tests/shell/testcases/iptables/0003-list-rules_0 b/iptables/tests/shell/testcases/iptables/0003-list-rules_0
new file mode 100755
index 0000000..d335d44
--- /dev/null
+++ b/iptables/tests/shell/testcases/iptables/0003-list-rules_0
@@ -0,0 +1,64 @@
+#!/bin/bash
+
+set -e
+
+$XT_MULTI iptables -N foo
+$XT_MULTI iptables -A FORWARD -i eth23 -o eth42 -j ACCEPT
+$XT_MULTI iptables -A FORWARD -i eth42 -o eth23 -g foo
+$XT_MULTI iptables -t nat -A OUTPUT -o eth123 -m mark --mark 0x42 -j ACCEPT
+
+EXPECT='-P INPUT ACCEPT
+-P FORWARD ACCEPT
+-P OUTPUT ACCEPT
+-N foo
+-A FORWARD -i eth23 -o eth42 -j ACCEPT
+-A FORWARD -i eth42 -o eth23 -g foo'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI iptables -S)
+
+EXPECT='-P INPUT ACCEPT -c 0 0
+-P FORWARD ACCEPT -c 0 0
+-P OUTPUT ACCEPT -c 0 0
+-N foo
+-A FORWARD -i eth23 -o eth42 -c 0 0 -j ACCEPT
+-A FORWARD -i eth42 -o eth23 -c 0 0 -g foo'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI iptables -v -S)
+
+EXPECT='-P FORWARD ACCEPT
+-A FORWARD -i eth23 -o eth42 -j ACCEPT
+-A FORWARD -i eth42 -o eth23 -g foo'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI iptables -S FORWARD)
+
+EXPECT='-P FORWARD ACCEPT -c 0 0
+-A FORWARD -i eth23 -o eth42 -c 0 0 -j ACCEPT
+-A FORWARD -i eth42 -o eth23 -c 0 0 -g foo'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI iptables -v -S FORWARD)
+
+EXPECT='-P OUTPUT ACCEPT
+-A OUTPUT -o eth123 -m mark --mark 0x42 -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI iptables -t nat -S OUTPUT)
+
+EXPECT='-P OUTPUT ACCEPT -c 0 0
+-A OUTPUT -o eth123 -m mark --mark 0x42 -c 0 0 -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <($XT_MULTI iptables -v -t nat -S OUTPUT)
+
+# some of the following commands are supposed to fail
+set +e
+
+$XT_MULTI iptables -S nonexistent && {
+	echo "list-rules in non-existent chain should fail"
+	exit 1
+}
+$XT_MULTI iptables -S nonexistent 23 && {
+	echo "list-rules in non-existent chain with given rule number should fail"
+	exit 1
+}
+$XT_MULTI iptables -S FORWARD 234 || {
+	echo "list-rules in existent chain with invalid rule number should succeed"
+	exit 1
+}
diff --git a/iptables/tests/shell/testcases/iptables/0004-return-codes_0 b/iptables/tests/shell/testcases/iptables/0004-return-codes_0
new file mode 100755
index 0000000..dcd9dfd
--- /dev/null
+++ b/iptables/tests/shell/testcases/iptables/0004-return-codes_0
@@ -0,0 +1,96 @@
+#!/bin/bash
+
+# make sure error return codes are as expected useful cases
+# (e.g. commands to check ruleset state)
+
+global_rc=0
+
+cmd() { # (rc, msg, cmd, [args ...])
+	rc_exp=$1; shift
+
+	msg_exp=""
+	[ $rc_exp != 0 ] && {
+		msg_exp="$1"; shift
+	}
+
+	for ipt in iptables ip6tables; do
+		msg="$($XT_MULTI $ipt "$@" 2>&1 >/dev/null)"
+		rc=$?
+
+		[ $rc -eq $rc_exp ] || {
+			echo "---> expected return code $rc_exp, got $rc for command '$ipt $@'"
+			global_rc=1
+		}
+
+		[ -n "$msg_exp" ] || continue
+		msg_exp_full="${ipt}$msg_exp"
+		grep -q "$msg_exp_full" <<< $msg || {
+			echo "---> expected error message '$msg_exp_full', got '$msg' for command '$ipt $@'"
+			global_rc=1
+		}
+	done
+}
+
+EEXIST_F=": File exists."
+EEXIST=": Chain already exists."
+ENOENT=": No chain/target/match by that name."
+E2BIG_I=": Index of insertion too big."
+E2BIG_D=": Index of deletion too big."
+E2BIG_R=": Index of replacement too big."
+EBADRULE=": Bad rule (does a matching rule exist in that chain?)."
+#ENOTGT=" v[0-9\.]* [^ ]*: Couldn't load target \`foobar':No such file or directory"
+ENOMTH=" v[0-9\.]* [^ ]*: Couldn't load match \`foobar':No such file or directory"
+ENOTBL=": can't initialize iptables table \`foobar': Table does not exist"
+
+# test chain creation
+cmd 0 -N foo
+cmd 1 "$EEXIST" -N foo
+# iptables-nft allows this - bug or feature?
+#cmd 2 -N "invalid name"
+
+# test chain flushing/zeroing
+cmd 0 -F foo
+cmd 0 -Z foo
+cmd 1 "$ENOENT" -F bar
+cmd 1 "$ENOENT" -Z bar
+
+# test chain rename
+cmd 0 -E foo bar
+cmd 1 "$EEXIST_F" -E foo bar
+cmd 1 "$ENOENT" -E foo bar2
+cmd 0 -N foo2
+cmd 1 "$EEXIST_F" -E foo2 bar
+
+# test rule adding
+cmd 0 -A INPUT -j ACCEPT
+cmd 1 "$ENOENT" -A noexist -j ACCEPT
+# next three differ:
+# legacy: Couldn't load target `foobar':No such file or directory
+# nft:    Chain 'foobar' does not exist
+cmd 2 "" -I INPUT -j foobar
+cmd 2 "" -R INPUT 1 -j foobar
+cmd 2 "" -D INPUT -j foobar
+cmd 1 "$EBADRULE" -D INPUT -p tcp --dport 22 -j ACCEPT
+
+# test rulenum commands
+cmd 1 "$E2BIG_I" -I INPUT 23 -j ACCEPT
+cmd 1 "$E2BIG_D" -D INPUT 23
+cmd 1 "$E2BIG_R" -R INPUT 23 -j ACCEPT
+cmd 1 "$ENOENT" -I nonexist 23 -j ACCEPT
+cmd 1 "$ENOENT" -D nonexist 23
+cmd 1 "$ENOENT" -R nonexist 23 -j ACCEPT
+
+# test rule checking
+cmd 0 -C INPUT -j ACCEPT
+cmd 1 "$EBADRULE" -C FORWARD -j ACCEPT
+cmd 1 "$BADRULE" -C nonexist -j ACCEPT
+cmd 2 "$ENOMTH" -C INPUT -m foobar -j ACCEPT
+# messages of those don't match, but iptables-nft ones are actually nicer.
+# legacy: Couldn't load target `foobar':No such file or directory
+# nft:    Chain 'foobar' does not exist
+cmd 2 "" -C INPUT -j foobar
+# legacy: can't initialize ip6tables table `foobar': Table does not exist (do you need to insmod?)
+# nft:    table 'foobar' does not exist
+cmd 3 "" -t foobar -C INPUT -j ACCEPT
+
+exit $global_rc
diff --git a/iptables/tests/shell/testcases/iptables/0005-delete-rules_0 b/iptables/tests/shell/testcases/iptables/0005-delete-rules_0
new file mode 100755
index 0000000..5038cbc
--- /dev/null
+++ b/iptables/tests/shell/testcases/iptables/0005-delete-rules_0
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+# test for crash when comparing rules with standard target
+
+$XT_MULTI iptables -A FORWARD -i eth23 -o eth42 -j DROP
+$XT_MULTI iptables -D FORWARD -i eth23 -o eth42 -j REJECT
+[[ $? -eq 1 ]] || exit 1
+
+# test incorrect deletion of rules with deviating payload
+# in non-standard target
+
+$XT_MULTI iptables -A FORWARD -i eth23 -o eth42 -j MARK --set-mark 23
+$XT_MULTI iptables -D FORWARD -i eth23 -o eth42 -j MARK --set-mark 42
+[[ $? -eq 1 ]] || exit 1
diff --git a/iptables/tests/shell/testcases/iptables/0005-rule-replace_0 b/iptables/tests/shell/testcases/iptables/0005-rule-replace_0
new file mode 100755
index 0000000..5a3e922
--- /dev/null
+++ b/iptables/tests/shell/testcases/iptables/0005-rule-replace_0
@@ -0,0 +1,38 @@
+#!/bin/bash
+
+# test rule replacement
+
+set -e
+
+# show rules, drop uninteresting policy settings
+ipt_show() {
+	$XT_MULTI iptables -S | grep -v '^-P'
+}
+
+$XT_MULTI iptables -A FORWARD -m comment --comment "rule 1" -j ACCEPT
+$XT_MULTI iptables -A FORWARD -m comment --comment "rule 2" -j ACCEPT
+$XT_MULTI iptables -A FORWARD -m comment --comment "rule 3" -j ACCEPT
+
+$XT_MULTI iptables -R FORWARD 2 -m comment --comment "replaced 2" -j ACCEPT
+
+EXPECT='-A FORWARD -m comment --comment "rule 1" -j ACCEPT
+-A FORWARD -m comment --comment "replaced 2" -j ACCEPT
+-A FORWARD -m comment --comment "rule 3" -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <(ipt_show)
+
+$XT_MULTI iptables -R FORWARD 1 -m comment --comment "replaced 1" -j ACCEPT
+
+EXPECT='-A FORWARD -m comment --comment "replaced 1" -j ACCEPT
+-A FORWARD -m comment --comment "replaced 2" -j ACCEPT
+-A FORWARD -m comment --comment "rule 3" -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <(ipt_show)
+
+$XT_MULTI iptables -R FORWARD 3 -m comment --comment "replaced 3" -j ACCEPT
+
+EXPECT='-A FORWARD -m comment --comment "replaced 1" -j ACCEPT
+-A FORWARD -m comment --comment "replaced 2" -j ACCEPT
+-A FORWARD -m comment --comment "replaced 3" -j ACCEPT'
+
+diff -u -Z <(echo -e "$EXPECT") <(ipt_show)
diff --git a/iptables/tests/shell/testcases/iptables/0006-46-args_0 b/iptables/tests/shell/testcases/iptables/0006-46-args_0
new file mode 100755
index 0000000..17a0a01
--- /dev/null
+++ b/iptables/tests/shell/testcases/iptables/0006-46-args_0
@@ -0,0 +1,88 @@
+#!/bin/bash
+
+RC=0
+
+$XT_MULTI iptables -6 -A FORWARD -j ACCEPT
+rc=$?
+if [[ $rc -ne 2 ]]; then
+	echo "'iptables -6' returned $rc instead of 2"
+	RC=1
+fi
+
+$XT_MULTI ip6tables -4 -A FORWARD -j ACCEPT
+rc=$?
+if [[ $rc -ne 2 ]]; then
+	echo "'ip6tables -4' returned $rc instead of 2"
+	RC=1
+fi
+
+RULESET='*filter
+-4 -A FORWARD -d 10.0.0.1 -j ACCEPT
+-6 -A FORWARD -d fec0:10::1 -j ACCEPT
+COMMIT
+'
+EXPECT4='-P FORWARD ACCEPT
+-A FORWARD -d 10.0.0.1/32 -j ACCEPT'
+EXPECT6='-P FORWARD ACCEPT
+-A FORWARD -d fec0:10::1/128 -j ACCEPT'
+EXPECT_EMPTY='-P FORWARD ACCEPT'
+
+echo "$RULESET" | $XT_MULTI iptables-restore || {
+	echo "iptables-restore failed!"
+	RC=1
+}
+diff -u -Z <(echo -e "$EXPECT4") <($XT_MULTI iptables -S FORWARD) || {
+	echo "unexpected iptables ruleset"
+	RC=1
+}
+diff -u -Z <(echo -e "$EXPECT_EMPTY") <($XT_MULTI ip6tables -S FORWARD) || {
+	echo "unexpected non-empty ip6tables ruleset"
+	RC=1
+}
+
+$XT_MULTI iptables -F FORWARD
+
+echo "$RULESET" | $XT_MULTI ip6tables-restore || {
+	echo "ip6tables-restore failed!"
+	RC=1
+}
+diff -u -Z <(echo -e "$EXPECT6") <($XT_MULTI ip6tables -S FORWARD) || {
+	echo "unexpected ip6tables ruleset"
+	RC=1
+}
+diff -u -Z <(echo -e "$EXPECT_EMPTY") <($XT_MULTI iptables -S FORWARD) || {
+	echo "unexpected non-empty iptables ruleset"
+	RC=1
+}
+
+$XT_MULTI ip6tables -F FORWARD
+
+$XT_MULTI iptables -4 -A FORWARD -d 10.0.0.1 -j ACCEPT || {
+	echo "iptables failed!"
+	RC=1
+}
+diff -u -Z <(echo -e "$EXPECT4") <($XT_MULTI iptables -S FORWARD) || {
+	echo "unexpected iptables ruleset"
+	RC=1
+}
+diff -u -Z <(echo -e "$EXPECT_EMPTY") <($XT_MULTI ip6tables -S FORWARD) || {
+	echo "unexpected non-empty ip6tables ruleset"
+	RC=1
+}
+
+$XT_MULTI iptables -F FORWARD
+
+$XT_MULTI ip6tables -6 -A FORWARD -d fec0:10::1 -j ACCEPT || {
+	echo "ip6tables failed!"
+	RC=1
+}
+diff -u -Z <(echo -e "$EXPECT6") <($XT_MULTI ip6tables -S FORWARD) || {
+	echo "unexpected ip6tables ruleset"
+	RC=1
+}
+diff -u -Z <(echo -e "$EXPECT_EMPTY") <($XT_MULTI iptables -S FORWARD) || {
+	echo "unexpected non-empty iptables ruleset"
+	RC=1
+}
+
+exit $RC
diff --git a/iptables/tests/shell/testcases/nft-only/0001compat_0 b/iptables/tests/shell/testcases/nft-only/0001compat_0
new file mode 100755
index 0000000..a617c52
--- /dev/null
+++ b/iptables/tests/shell/testcases/nft-only/0001compat_0
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+# test case for bug fixed in
+# commit 873c5d5d293991ee3c06aed2b1dfc5764872582f (HEAD -> master)
+# xtables: avoid bogus 'is incompatible' warning
+
+case "$XT_MULTI" in
+*xtables-nft-multi)
+	;;
+*)
+	echo skip $XT_MULTI
+	exit 0
+	;;
+esac
+
+nft -v >/dev/null || exit 0
+nft 'add table ip nft-test; add chain ip nft-test foobar { type filter hook forward priority 42;  }' || exit 1
+nft 'add table ip6 nft-test; add chain ip6 nft-test foobar { type filter hook forward priority 42;  }' || exit 1
+
+$XT_MULTI iptables -L -t filter || exit 1
+$XT_MULTI ip6tables -L -t filter || exit 1
+exit 0
diff --git a/iptables/tests/shell/testcases/nft-only/0002invflags_0 b/iptables/tests/shell/testcases/nft-only/0002invflags_0
new file mode 100755
index 0000000..fe33874
--- /dev/null
+++ b/iptables/tests/shell/testcases/nft-only/0002invflags_0
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+set -e
+
+[[ $XT_MULTI == *xtables-nft-multi ]] || { echo "skip $XT_MULTI"; exit 0; }
+
+$XT_MULTI iptables -A INPUT -p tcp --dport 53 ! -s 192.168.0.1 -j ACCEPT
+$XT_MULTI ip6tables -A INPUT -p tcp --dport 53 ! -s feed:babe::1 -j ACCEPT
+$XT_MULTI ebtables -A INPUT -p IPv4 --ip-src 10.0.0.1 ! -i lo -j ACCEPT
+
diff --git a/iptables/tests/shell/testcases/nft-only/0003delete-with-comment_0 b/iptables/tests/shell/testcases/nft-only/0003delete-with-comment_0
new file mode 100755
index 0000000..ccb009e
--- /dev/null
+++ b/iptables/tests/shell/testcases/nft-only/0003delete-with-comment_0
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+set -e
+
+[[ $XT_MULTI == *xtables-nft-multi ]] || { echo "skip $XT_MULTI"; exit 0; }
+
+comment1="foo bar"
+comment2="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+
+for ipt in iptables ip6tables; do
+	for comment in "$comment1" "$comment2"; do
+		$XT_MULTI $ipt -A INPUT -m comment --comment "$comment" -j ACCEPT
+		$XT_MULTI $ipt -D INPUT -m comment --comment "$comment" -j ACCEPT
+	done
+done
diff --git a/iptables/tests/shell/testcases/nft-only/0006-policy-override_0 b/iptables/tests/shell/testcases/nft-only/0006-policy-override_0
new file mode 100755
index 0000000..68e2019
--- /dev/null
+++ b/iptables/tests/shell/testcases/nft-only/0006-policy-override_0
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+[[ $XT_MULTI == *xtables-nft-multi ]] || { echo "skip $XT_MULTI"; exit 0; }
+
+# make sure none of the commands invoking nft_xt_builtin_init() override
+# non-default chain policies via needless chain add.
+
+RC=0
+
+do_test() {
+	$XT_MULTI $@
+	$XT_MULTI iptables -S | grep -q -- '-P FORWARD DROP' && return
+
+	echo "command '$@' kills chain policies"
+	$XT_MULTI iptables -P FORWARD DROP
+	RC=1
+}
+
+$XT_MULTI iptables -P FORWARD DROP
+
+do_test iptables -A OUTPUT -j ACCEPT
+do_test iptables -F
+do_test iptables -N foo
+do_test iptables -E foo foo2
+do_test iptables -I OUTPUT -j ACCEPT
+do_test iptables -nL
+do_test iptables -S
+
+exit $RC
diff --git a/iptables/tests/shell/testcases/nft-only/0007-mid-restore-flush_0 b/iptables/tests/shell/testcases/nft-only/0007-mid-restore-flush_0
new file mode 100755
index 0000000..43880ff
--- /dev/null
+++ b/iptables/tests/shell/testcases/nft-only/0007-mid-restore-flush_0
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+[[ $XT_MULTI == *xtables-nft-multi ]] || { echo "skip $XT_MULTI"; exit 0; }
+nft -v >/dev/null || { echo "skip $XT_MULTI (no nft)"; exit 0; }
+
+coproc $XT_MULTI iptables-restore --noflush
+
+cat >&"${COPROC[1]}" <<EOF
+*filter
+:foo [0:0]
+COMMIT
+*filter
+:foo [0:0]
+EOF
+
+$XT_MULTI iptables-save | grep -q ':foo'
+nft flush ruleset
+
+echo "COMMIT" >&"${COPROC[1]}"
+sleep 1
+
+[[ -n $COPROC_PID ]] && kill $COPROC_PID
+wait
diff --git a/iptables/tests/shell/testcases/nft-only/0008-basechain-policy_0 b/iptables/tests/shell/testcases/nft-only/0008-basechain-policy_0
new file mode 100755
index 0000000..a81e9ba
--- /dev/null
+++ b/iptables/tests/shell/testcases/nft-only/0008-basechain-policy_0
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+[[ $XT_MULTI == *xtables-nft-multi ]] || { echo "skip $XT_MULTI"; exit 0; }
+set -e
+
+$XT_MULTI iptables -t raw -P OUTPUT DROP
+
+# make sure iptables-nft-restore can correctly handle basechain policies when
+# they aren't set with --noflush
+#
+$XT_MULTI iptables-restore --noflush <<EOF
+*raw
+:OUTPUT - [0:0]
+:PREROUTING - [0:0]
+:neutron-linuxbri-OUTPUT - [0:0]
+:neutron-linuxbri-PREROUTING - [0:0]
+-I OUTPUT 1 -j neutron-linuxbri-OUTPUT
+-I PREROUTING 1 -j neutron-linuxbri-PREROUTING
+-I neutron-linuxbri-PREROUTING 1 -m physdev --physdev-in brq7425e328-56 -j CT --zone 4097
+-I neutron-linuxbri-PREROUTING 2 -i brq7425e328-56 -j CT --zone 4097
+-I neutron-linuxbri-PREROUTING 3 -m physdev --physdev-in tap7f101a28-1d -j CT --zone 4097
+
+COMMIT
+EOF
+
+$XT_MULTI iptables-save | grep -C2 raw | grep OUTPUT | grep DROP
+if [ $? -ne 0 ]; then
+	exit 1
+fi
diff --git a/iptables/tests/shell/testcases/nft-only/0009-needless-bitwise_0 b/iptables/tests/shell/testcases/nft-only/0009-needless-bitwise_0
new file mode 100755
index 0000000..41588a1
--- /dev/null
+++ b/iptables/tests/shell/testcases/nft-only/0009-needless-bitwise_0
@@ -0,0 +1,346 @@
+#!/bin/bash -x
+
+[[ $XT_MULTI == *xtables-nft-multi ]] || { echo "skip $XT_MULTI"; exit 0; }
+set -e
+
+nft flush ruleset
+
+(
+	echo "*filter"
+	for plen in "" 32 30 24 16 8 0; do
+		addr="10.1.2.3${plen:+/}$plen"
+		echo "-A OUTPUT -d $addr"
+	done
+	echo "COMMIT"
+) | $XT_MULTI iptables-restore
+
+(
+	echo "*filter"
+	for plen in "" 128 124 120 112 88 80 64 48 16 8 0; do
+		addr="feed:c0ff:ee00:0102:0304:0506:0708:090A${plen:+/}$plen"
+		echo "-A OUTPUT -d $addr"
+	done
+	echo "COMMIT"
+) | $XT_MULTI ip6tables-restore
+
+masks="
+ff:ff:ff:ff:ff:ff
+ff:ff:ff:ff:ff:f0
+ff:ff:ff:ff:ff:00
+ff:ff:ff:ff:00:00
+ff:ff:ff:00:00:00
+ff:ff:00:00:00:00
+ff:00:00:00:00:00
+"
+(
+	echo "*filter"
+	for plen in "" 32 30 24 16 8 0; do
+		addr="10.1.2.3${plen:+/}$plen"
+		echo "-A OUTPUT -d $addr"
+	done
+	for mask in $masks; do
+		echo "-A OUTPUT --destination-mac fe:ed:00:c0:ff:ee/$mask"
+	done
+	echo "COMMIT"
+) | $XT_MULTI arptables-restore
+
+(
+	echo "*filter"
+	for mask in $masks; do
+		echo "-A OUTPUT -d fe:ed:00:c0:ff:ee/$mask"
+	done
+	echo "COMMIT"
+) | $XT_MULTI ebtables-restore
+
+EXPECT="ip filter OUTPUT 4
+  [ payload load 4b @ network header + 16 => reg 1 ]
+  [ cmp eq reg 1 0x0302010a ]
+  [ counter pkts 0 bytes 0 ]
+
+ip filter OUTPUT 5 4
+  [ payload load 4b @ network header + 16 => reg 1 ]
+  [ cmp eq reg 1 0x0302010a ]
+  [ counter pkts 0 bytes 0 ]
+
+ip filter OUTPUT 6 5
+  [ payload load 4b @ network header + 16 => reg 1 ]
+  [ bitwise reg 1 = ( reg 1 & 0xfcffffff ) ^ 0x00000000 ]
+  [ cmp eq reg 1 0x0002010a ]
+  [ counter pkts 0 bytes 0 ]
+
+ip filter OUTPUT 7 6
+  [ payload load 3b @ network header + 16 => reg 1 ]
+  [ cmp eq reg 1 0x0002010a ]
+  [ counter pkts 0 bytes 0 ]
+
+ip filter OUTPUT 8 7
+  [ payload load 2b @ network header + 16 => reg 1 ]
+  [ cmp eq reg 1 0x0000010a ]
+  [ counter pkts 0 bytes 0 ]
+
+ip filter OUTPUT 9 8
+  [ payload load 1b @ network header + 16 => reg 1 ]
+  [ cmp eq reg 1 0x0000000a ]
+  [ counter pkts 0 bytes 0 ]
+
+ip filter OUTPUT 10 9
+  [ counter pkts 0 bytes 0 ]
+
+ip6 filter OUTPUT 4
+  [ payload load 16b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0xffc0edfe 0x020100ee 0x06050403 0x0a090807 ]
+  [ counter pkts 0 bytes 0 ]
+
+ip6 filter OUTPUT 5 4
+  [ payload load 16b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0xffc0edfe 0x020100ee 0x06050403 0x0a090807 ]
+  [ counter pkts 0 bytes 0 ]
+
+ip6 filter OUTPUT 6 5
+  [ payload load 16b @ network header + 24 => reg 1 ]
+  [ bitwise reg 1 = ( reg 1 & 0xffffffff 0xffffffff 0xffffffff 0xf0ffffff ) ^ 0x00000000 0x00000000 0x00000000 0x00000000 ]
+  [ cmp eq reg 1 0xffc0edfe 0x020100ee 0x06050403 0x00090807 ]
+  [ counter pkts 0 bytes 0 ]
+
+ip6 filter OUTPUT 7 6
+  [ payload load 15b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0xffc0edfe 0x020100ee 0x06050403 0x00090807 ]
+  [ counter pkts 0 bytes 0 ]
+
+ip6 filter OUTPUT 8 7
+  [ payload load 14b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0xffc0edfe 0x020100ee 0x06050403 0x00000807 ]
+  [ counter pkts 0 bytes 0 ]
+
+ip6 filter OUTPUT 9 8
+  [ payload load 11b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0xffc0edfe 0x020100ee 0x00050403 ]
+  [ counter pkts 0 bytes 0 ]
+
+ip6 filter OUTPUT 10 9
+  [ payload load 10b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0xffc0edfe 0x020100ee 0x00000403 ]
+  [ counter pkts 0 bytes 0 ]
+
+ip6 filter OUTPUT 11 10
+  [ payload load 8b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0xffc0edfe 0x020100ee ]
+  [ counter pkts 0 bytes 0 ]
+
+ip6 filter OUTPUT 12 11
+  [ payload load 6b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0xffc0edfe 0x000000ee ]
+  [ counter pkts 0 bytes 0 ]
+
+ip6 filter OUTPUT 13 12
+  [ payload load 2b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0x0000edfe ]
+  [ counter pkts 0 bytes 0 ]
+
+ip6 filter OUTPUT 14 13
+  [ payload load 1b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0x000000fe ]
+  [ counter pkts 0 bytes 0 ]
+
+ip6 filter OUTPUT 15 14
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 3
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ payload load 4b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0x0302010a ]
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 4 3
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ payload load 4b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0x0302010a ]
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 5 4
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ payload load 4b @ network header + 24 => reg 1 ]
+  [ bitwise reg 1 = ( reg 1 & 0xfcffffff ) ^ 0x00000000 ]
+  [ cmp eq reg 1 0x0002010a ]
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 6 5
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ payload load 3b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0x0002010a ]
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 7 6
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ payload load 2b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0x0000010a ]
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 8 7
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ payload load 1b @ network header + 24 => reg 1 ]
+  [ cmp eq reg 1 0x0000000a ]
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 9 8
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 10 9
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ payload load 6b @ network header + 18 => reg 1 ]
+  [ cmp eq reg 1 0xc000edfe 0x0000eeff ]
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 11 10
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ payload load 6b @ network header + 18 => reg 1 ]
+  [ bitwise reg 1 = ( reg 1 & 0xffffffff 0x0000f0ff ) ^ 0x00000000 0x00000000 ]
+  [ cmp eq reg 1 0xc000edfe 0x0000e0ff ]
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 12 11
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ payload load 5b @ network header + 18 => reg 1 ]
+  [ cmp eq reg 1 0xc000edfe 0x000000ff ]
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 13 12
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ payload load 4b @ network header + 18 => reg 1 ]
+  [ cmp eq reg 1 0xc000edfe ]
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 14 13
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ payload load 3b @ network header + 18 => reg 1 ]
+  [ cmp eq reg 1 0x0000edfe ]
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 15 14
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ payload load 2b @ network header + 18 => reg 1 ]
+  [ cmp eq reg 1 0x0000edfe ]
+  [ counter pkts 0 bytes 0 ]
+
+arp filter OUTPUT 16 15
+  [ payload load 2b @ network header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x00000100 ]
+  [ payload load 1b @ network header + 4 => reg 1 ]
+  [ cmp eq reg 1 0x00000006 ]
+  [ payload load 1b @ network header + 5 => reg 1 ]
+  [ cmp eq reg 1 0x00000004 ]
+  [ payload load 1b @ network header + 18 => reg 1 ]
+  [ cmp eq reg 1 0x000000fe ]
+  [ counter pkts 0 bytes 0 ]
+
+bridge filter OUTPUT 4
+  [ payload load 6b @ link header + 0 => reg 1 ]
+  [ cmp eq reg 1 0xc000edfe 0x0000eeff ]
+  [ counter pkts 0 bytes 0 ]
+
+bridge filter OUTPUT 5 4
+  [ payload load 6b @ link header + 0 => reg 1 ]
+  [ bitwise reg 1 = ( reg 1 & 0xffffffff 0x0000f0ff ) ^ 0x00000000 0x00000000 ]
+  [ cmp eq reg 1 0xc000edfe 0x0000e0ff ]
+  [ counter pkts 0 bytes 0 ]
+
+bridge filter OUTPUT 6 5
+  [ payload load 5b @ link header + 0 => reg 1 ]
+  [ cmp eq reg 1 0xc000edfe 0x000000ff ]
+  [ counter pkts 0 bytes 0 ]
+
+bridge filter OUTPUT 7 6
+  [ payload load 4b @ link header + 0 => reg 1 ]
+  [ cmp eq reg 1 0xc000edfe ]
+  [ counter pkts 0 bytes 0 ]
+
+bridge filter OUTPUT 8 7
+  [ payload load 3b @ link header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x0000edfe ]
+  [ counter pkts 0 bytes 0 ]
+
+bridge filter OUTPUT 9 8
+  [ payload load 2b @ link header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x0000edfe ]
+  [ counter pkts 0 bytes 0 ]
+
+bridge filter OUTPUT 10 9
+  [ payload load 1b @ link header + 0 => reg 1 ]
+  [ cmp eq reg 1 0x000000fe ]
+  [ counter pkts 0 bytes 0 ]
+"
+
+# print nothing but:
+# - lines with bytecode (starting with '  [')
+# - empty lines (so printed diff is not a complete mess)
+filter() {
+	awk '/^(  \[|$)/{print}'
+}
+
+diff -u -Z <(filter <<< "$EXPECT") <(nft --debug=netlink list ruleset | filter)
diff --git a/iptables/xshared.c b/iptables/xshared.c
index 0e3857b..71f6899 100644
--- a/iptables/xshared.c
+++ b/iptables/xshared.c
@@ -1,4 +1,7 @@
+#include <config.h>
+#include <ctype.h>
 #include <getopt.h>
+#include <errno.h>
 #include <libgen.h>
 #include <netdb.h>
 #include <stdbool.h>
@@ -6,7 +9,14 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/file.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <fcntl.h>
 #include <xtables.h>
+#include <math.h>
 #include "xshared.h"
 
 /*
@@ -139,14 +149,13 @@
 
 		cs->proto_used = 1;
 
-		size = XT_ALIGN(sizeof(struct ip6t_entry_match)) + m->size;
+		size = XT_ALIGN(sizeof(struct xt_entry_match)) + m->size;
 
 		m->m = xtables_calloc(1, size);
 		m->m->u.match_size = size;
 		strcpy(m->m->u.user.name, m->name);
 		m->m->u.user.revision = m->revision;
-		if (m->init != NULL)
-			m->init(m->m);
+		xs_init_match(m);
 
 		if (m->x6_options != NULL)
 			gl->opts = xtables_options_xfrm(gl->orig_opts,
@@ -172,7 +181,6 @@
 		xtables_error(PARAMETER_PROBLEM, "unknown option "
 			      "\"%s\"", cs->argv[optind-1]);
 	xtables_error(PARAMETER_PROBLEM, "Unknown arg \"%s\"", optarg);
-	return 0;
 }
 
 static mainfunc_t subcmd_get(const char *cmd, const struct subcommand *cb)
@@ -207,3 +215,641 @@
 		fprintf(stderr, " * %s\n", cb->name);
 	exit(EXIT_FAILURE);
 }
+
+void xs_init_target(struct xtables_target *target)
+{
+	if (target->udata_size != 0) {
+		free(target->udata);
+		target->udata = calloc(1, target->udata_size);
+		if (target->udata == NULL)
+			xtables_error(RESOURCE_PROBLEM, "malloc");
+	}
+	if (target->init != NULL)
+		target->init(target->t);
+}
+
+void xs_init_match(struct xtables_match *match)
+{
+	if (match->udata_size != 0) {
+		/*
+		 * As soon as a subsequent instance of the same match
+		 * is used, e.g. "-m time -m time", the first instance
+		 * is no longer reachable anyway, so we can free udata.
+		 * Same goes for target.
+		 */
+		free(match->udata);
+		match->udata = calloc(1, match->udata_size);
+		if (match->udata == NULL)
+			xtables_error(RESOURCE_PROBLEM, "malloc");
+	}
+	if (match->init != NULL)
+		match->init(match->m);
+}
+
+static int xtables_lock(int wait, struct timeval *wait_interval)
+{
+	struct timeval time_left, wait_time;
+	const char *lock_file;
+	int fd, i = 0;
+
+	time_left.tv_sec = wait;
+	time_left.tv_usec = 0;
+
+	lock_file = getenv("XTABLES_LOCKFILE");
+	if (lock_file == NULL || lock_file[0] == '\0')
+		lock_file = XT_LOCK_NAME;
+
+	fd = open(lock_file, O_CREAT, 0600);
+	if (fd < 0) {
+		fprintf(stderr, "Fatal: can't open lock file %s: %s\n",
+			lock_file, strerror(errno));
+		return XT_LOCK_FAILED;
+	}
+
+	if (wait == -1) {
+		if (flock(fd, LOCK_EX) == 0)
+			return fd;
+
+		fprintf(stderr, "Can't lock %s: %s\n", lock_file,
+			strerror(errno));
+		return XT_LOCK_BUSY;
+	}
+
+	while (1) {
+		if (flock(fd, LOCK_EX | LOCK_NB) == 0)
+			return fd;
+		else if (timercmp(&time_left, wait_interval, <))
+			return XT_LOCK_BUSY;
+
+		if (++i % 10 == 0) {
+			fprintf(stderr, "Another app is currently holding the xtables lock; "
+				"still %lds %ldus time ahead to have a chance to grab the lock...\n",
+				time_left.tv_sec, time_left.tv_usec);
+		}
+
+		wait_time = *wait_interval;
+		select(0, NULL, NULL, NULL, &wait_time);
+		timersub(&time_left, wait_interval, &time_left);
+	}
+}
+
+void xtables_unlock(int lock)
+{
+	if (lock >= 0)
+		close(lock);
+}
+
+int xtables_lock_or_exit(int wait, struct timeval *wait_interval)
+{
+	int lock = xtables_lock(wait, wait_interval);
+
+	if (lock == XT_LOCK_FAILED) {
+		xtables_free_opts(1);
+		exit(RESOURCE_PROBLEM);
+	}
+
+	if (lock == XT_LOCK_BUSY) {
+		fprintf(stderr, "Another app is currently holding the xtables lock. ");
+		if (wait == 0)
+			fprintf(stderr, "Perhaps you want to use the -w option?\n");
+		else
+			fprintf(stderr, "Stopped waiting after %ds.\n", wait);
+		xtables_free_opts(1);
+		exit(RESOURCE_PROBLEM);
+	}
+
+	return lock;
+}
+
+int parse_wait_time(int argc, char *argv[])
+{
+	int wait = -1;
+
+	if (optarg) {
+		if (sscanf(optarg, "%i", &wait) != 1)
+			xtables_error(PARAMETER_PROBLEM,
+				"wait seconds not numeric");
+	} else if (xs_has_arg(argc, argv))
+		if (sscanf(argv[optind++], "%i", &wait) != 1)
+			xtables_error(PARAMETER_PROBLEM,
+				"wait seconds not numeric");
+
+	return wait;
+}
+
+void parse_wait_interval(int argc, char *argv[], struct timeval *wait_interval)
+{
+	const char *arg;
+	unsigned int usec;
+	int ret;
+
+	if (optarg)
+		arg = optarg;
+	else if (xs_has_arg(argc, argv))
+		arg = argv[optind++];
+	else
+		xtables_error(PARAMETER_PROBLEM, "wait interval value required");
+
+	ret = sscanf(arg, "%u", &usec);
+	if (ret == 1) {
+		if (usec > 999999)
+			xtables_error(PARAMETER_PROBLEM,
+				      "too long usec wait %u > 999999 usec",
+				      usec);
+
+		wait_interval->tv_sec = 0;
+		wait_interval->tv_usec = usec;
+		return;
+	}
+	xtables_error(PARAMETER_PROBLEM, "wait interval not numeric");
+}
+
+int parse_counters(const char *string, struct xt_counters *ctr)
+{
+	int ret;
+
+	if (!string)
+		return 0;
+
+	ret = sscanf(string, "[%llu:%llu]",
+		     (unsigned long long *)&ctr->pcnt,
+		     (unsigned long long *)&ctr->bcnt);
+
+	return ret == 2;
+}
+
+/* Tokenize counters argument of typical iptables-restore format rule.
+ *
+ * If *bufferp contains counters, update *pcntp and *bcntp to point at them,
+ * change bytes after counters in *bufferp to nul-bytes, update *bufferp to
+ * point to after the counters and return true.
+ * If *bufferp does not contain counters, return false.
+ * If syntax is wrong in *bufferp, call xtables_error() and hence exit().
+ * */
+bool tokenize_rule_counters(char **bufferp, char **pcntp, char **bcntp, int line)
+{
+	char *ptr, *buffer = *bufferp, *pcnt, *bcnt;
+
+	if (buffer[0] != '[')
+		return false;
+
+	/* 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);
+
+	*pcntp = pcnt;
+	*bcntp = bcnt;
+	/* start command parsing after counter */
+	*bufferp = ptr + 1;
+
+	return true;
+}
+
+inline bool xs_has_arg(int argc, char *argv[])
+{
+	return optind < argc &&
+	       argv[optind][0] != '-' &&
+	       argv[optind][0] != '!';
+}
+
+/* function adding one argument to store, updating argc
+ * returns if argument added, does not return otherwise */
+void add_argv(struct argv_store *store, const char *what, int quoted)
+{
+	DEBUGP("add_argv: %s\n", what);
+
+	if (store->argc + 1 >= MAX_ARGC)
+		xtables_error(PARAMETER_PROBLEM,
+			      "Parser cannot handle more arguments\n");
+	if (!what)
+		xtables_error(PARAMETER_PROBLEM,
+			      "Trying to store NULL argument\n");
+
+	store->argv[store->argc] = strdup(what);
+	store->argvattr[store->argc] = quoted;
+	store->argv[++store->argc] = NULL;
+}
+
+void free_argv(struct argv_store *store)
+{
+	while (store->argc) {
+		store->argc--;
+		free(store->argv[store->argc]);
+		store->argvattr[store->argc] = 0;
+	}
+}
+
+/* Save parsed rule for comparison with next rule to perform action aggregation
+ * on duplicate conditions.
+ */
+void save_argv(struct argv_store *dst, struct argv_store *src)
+{
+	int i;
+
+	free_argv(dst);
+	for (i = 0; i < src->argc; i++) {
+		dst->argvattr[i] = src->argvattr[i];
+		dst->argv[i] = src->argv[i];
+		src->argv[i] = NULL;
+	}
+	dst->argc = src->argc;
+	src->argc = 0;
+}
+
+struct xt_param_buf {
+	char	buffer[1024];
+	int 	len;
+};
+
+static void add_param(struct xt_param_buf *param, const char *curchar)
+{
+	param->buffer[param->len++] = *curchar;
+	if (param->len >= sizeof(param->buffer))
+		xtables_error(PARAMETER_PROBLEM,
+			      "Parameter too long!");
+}
+
+void add_param_to_argv(struct argv_store *store, char *parsestart, int line)
+{
+	int quote_open = 0, escaped = 0, quoted = 0;
+	struct xt_param_buf param = {};
+	char *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) {
+				add_param(&param, curchar);
+				escaped = 0;
+				continue;
+			} else if (*curchar == '\\') {
+				escaped = 1;
+				continue;
+			} else if (*curchar == '"') {
+				quote_open = 0;
+			} else {
+				add_param(&param, curchar);
+				continue;
+			}
+		} else {
+			if (*curchar == '"') {
+				quote_open = 1;
+				quoted = 1;
+				continue;
+			}
+		}
+
+		switch (*curchar) {
+		case '"':
+			break;
+		case ' ':
+		case '\t':
+		case '\n':
+			if (!param.len) {
+				/* two spaces? */
+				continue;
+			}
+			break;
+		default:
+			/* regular character, copy to buffer */
+			add_param(&param, curchar);
+			continue;
+		}
+
+		param.buffer[param.len] = '\0';
+		add_argv(store, param.buffer, quoted);
+		param.len = 0;
+		quoted = 0;
+	}
+	if (param.len) {
+		param.buffer[param.len] = '\0';
+		add_argv(store, param.buffer, 0);
+	}
+}
+
+#ifdef DEBUG
+void debug_print_argv(struct argv_store *store)
+{
+	int i;
+
+	for (i = 0; i < store->argc; i++)
+		fprintf(stderr, "argv[%d]: %s\n", i, store->argv[i]);
+}
+#endif
+
+static const char *ipv4_addr_to_string(const struct in_addr *addr,
+				       const struct in_addr *mask,
+				       unsigned int format)
+{
+	static char buf[BUFSIZ];
+
+	if (!mask->s_addr && !(format & FMT_NUMERIC))
+		return "anywhere";
+
+	if (format & FMT_NUMERIC)
+		strncpy(buf, xtables_ipaddr_to_numeric(addr), BUFSIZ - 1);
+	else
+		strncpy(buf, xtables_ipaddr_to_anyname(addr), BUFSIZ - 1);
+	buf[BUFSIZ - 1] = '\0';
+
+	strncat(buf, xtables_ipmask_to_numeric(mask),
+		BUFSIZ - strlen(buf) - 1);
+
+	return buf;
+}
+
+void print_ipv4_addresses(const struct ipt_entry *fw, unsigned int format)
+{
+	fputc(fw->ip.invflags & IPT_INV_SRCIP ? '!' : ' ', stdout);
+	printf(FMT("%-19s ", "%s "),
+	       ipv4_addr_to_string(&fw->ip.src, &fw->ip.smsk, format));
+
+	fputc(fw->ip.invflags & IPT_INV_DSTIP ? '!' : ' ', stdout);
+	printf(FMT("%-19s ", "-> %s"),
+	       ipv4_addr_to_string(&fw->ip.dst, &fw->ip.dmsk, format));
+}
+
+static const char *ipv6_addr_to_string(const struct in6_addr *addr,
+				       const struct in6_addr *mask,
+				       unsigned int format)
+{
+	static char buf[BUFSIZ];
+
+	if (IN6_IS_ADDR_UNSPECIFIED(addr) && !(format & FMT_NUMERIC))
+		return "anywhere";
+
+	if (format & FMT_NUMERIC)
+		strncpy(buf, xtables_ip6addr_to_numeric(addr), BUFSIZ - 1);
+	else
+		strncpy(buf, xtables_ip6addr_to_anyname(addr), BUFSIZ - 1);
+	buf[BUFSIZ - 1] = '\0';
+
+	strncat(buf, xtables_ip6mask_to_numeric(mask),
+		BUFSIZ - strlen(buf) - 1);
+
+	return buf;
+}
+
+void print_ipv6_addresses(const struct ip6t_entry *fw6, unsigned int format)
+{
+	fputc(fw6->ipv6.invflags & IP6T_INV_SRCIP ? '!' : ' ', stdout);
+	printf(FMT("%-19s ", "%s "),
+	       ipv6_addr_to_string(&fw6->ipv6.src,
+				   &fw6->ipv6.smsk, format));
+
+	fputc(fw6->ipv6.invflags & IP6T_INV_DSTIP ? '!' : ' ', stdout);
+	printf(FMT("%-19s ", "-> %s"),
+	       ipv6_addr_to_string(&fw6->ipv6.dst,
+				   &fw6->ipv6.dmsk, format));
+}
+
+/* Luckily, IPT_INV_VIA_IN and IPT_INV_VIA_OUT
+ * have the same values as IP6T_INV_VIA_IN and IP6T_INV_VIA_OUT
+ * so this function serves for both iptables and ip6tables */
+void print_ifaces(const char *iniface, const char *outiface, uint8_t invflags,
+		  unsigned int format)
+{
+	const char *anyname = format & FMT_NUMERIC ? "*" : "any";
+	char iface[IFNAMSIZ + 2];
+
+	if (!(format & FMT_VIA))
+		return;
+
+	snprintf(iface, IFNAMSIZ + 2, "%s%s",
+		 invflags & IPT_INV_VIA_IN ? "!" : "",
+		 iniface[0] != '\0' ? iniface : anyname);
+
+	printf(FMT(" %-6s ", "in %s "), iface);
+
+	snprintf(iface, IFNAMSIZ + 2, "%s%s",
+		 invflags & IPT_INV_VIA_OUT ? "!" : "",
+		 outiface[0] != '\0' ? outiface : anyname);
+
+	printf(FMT("%-6s ", "out %s "), iface);
+}
+
+void command_match(struct iptables_command_state *cs)
+{
+	struct option *opts = xt_params->opts;
+	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(xt_params->orig_opts, opts,
+					    m->x6_options, &m->option_offset);
+	else if (m->extra_opts != NULL)
+		opts = xtables_merge_options(xt_params->orig_opts, opts,
+					     m->extra_opts, &m->option_offset);
+	if (opts == NULL)
+		xtables_error(OTHER_PROBLEM, "can't alloc memory!");
+	xt_params->opts = opts;
+}
+
+const char *xt_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;
+}
+
+void command_jump(struct iptables_command_state *cs, const char *jumpto)
+{
+	struct option *opts = xt_params->opts;
+	size_t size;
+
+	cs->jumpto = xt_parse_target(jumpto);
+	/* 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(xt_params->orig_opts, opts,
+					    cs->target->x6_options,
+					    &cs->target->option_offset);
+	else
+		opts = xtables_merge_options(xt_params->orig_opts, opts,
+					     cs->target->extra_opts,
+					     &cs->target->option_offset);
+	if (opts == NULL)
+		xtables_error(OTHER_PROBLEM, "can't alloc memory!");
+	xt_params->opts = opts;
+}
+
+char cmd2char(int option)
+{
+	/* cmdflags index corresponds with position of bit in CMD_* values */
+	static const char cmdflags[] = { 'I', 'D', 'D', 'R', 'A', 'L', 'F', 'Z',
+					 'N', 'X', 'P', 'E', 'S', 'Z', 'C' };
+	int i;
+
+	for (i = 0; option > 1; option >>= 1, i++)
+		;
+	if (i >= ARRAY_SIZE(cmdflags))
+		xtables_error(OTHER_PROBLEM,
+			      "cmd2char(): Invalid command number %u.\n",
+			      1 << i);
+	return cmdflags[i];
+}
+
+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;
+}
+
+/* Can't be zero. */
+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;
+}
+
+/* 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 2 3 l 4 5 6 */
+/*INSERT*/    {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' ',' ',' ',' ',' ',' ',' ',' '},
+/*DELETE*/    {'x',' ',' ',' ',' ',' ','x',' ',' ','x','x',' ',' ',' ',' ',' ',' ',' '},
+/*DELETE_NUM*/{'x','x','x','x','x',' ','x','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','x','x','x','x','x','x'},
+/*FLUSH*/     {'x','x','x','x','x',' ','x','x','x','x','x','x','x','x','x','x','x','x'},
+/*ZERO*/      {'x','x','x','x','x',' ','x','x','x','x','x','x','x','x','x','x','x','x'},
+/*NEW_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x','x','x','x','x','x','x','x','x'},
+/*DEL_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x','x','x','x','x','x','x','x','x'},
+/*SET_POLICY*/{'x','x','x','x','x',' ','x','x','x','x',' ','x','x','x','x','x','x','x'},
+/*RENAME*/    {'x','x','x','x','x',' ','x','x','x','x','x','x','x','x','x','x','x','x'},
+/*LIST_RULES*/{'x','x','x','x','x',' ','x','x','x','x','x','x','x','x','x','x','x','x'},
+/*ZERO_NUM*/  {'x','x','x','x','x',' ','x','x','x','x','x','x','x','x','x','x','x','x'},
+/*CHECK*/     {'x',' ',' ',' ',' ',' ','x',' ',' ','x','x',' ',' ',' ',' ',' ',' ',' '},
+};
+
+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]);
+	}
+}
+
+char opt2char(int option)
+{
+	const char *ptr;
+
+	for (ptr = optflags; option > 1; option >>= 1, ptr++)
+		;
+
+	return *ptr;
+}
diff --git a/iptables/xshared.h b/iptables/xshared.h
index b44a3a3..9159b2b 100644
--- a/iptables/xshared.h
+++ b/iptables/xshared.h
@@ -2,12 +2,21 @@
 #define IPTABLES_XSHARED_H 1
 
 #include <limits.h>
+#include <stdbool.h>
 #include <stdint.h>
 #include <netinet/in.h>
 #include <net/if.h>
+#include <sys/time.h>
+#include <linux/netfilter_arp/arp_tables.h>
 #include <linux/netfilter_ipv4/ip_tables.h>
 #include <linux/netfilter_ipv6/ip6_tables.h>
 
+#ifdef DEBUG
+#define DEBUGP(x, args...) fprintf(stderr, x, ## args)
+#else
+#define DEBUGP(x, args...)
+#endif
+
 enum {
 	OPT_NONE        = 0,
 	OPT_NUMERIC     = 1 << 0,
@@ -21,8 +30,40 @@
 	OPT_VIANAMEOUT  = 1 << 8,
 	OPT_LINENUMBERS = 1 << 9,
 	OPT_COUNTERS    = 1 << 10,
+	OPT_FRAGMENT	= 1 << 11,
+	/* below are for arptables only */
+	OPT_S_MAC	= 1 << 12,
+	OPT_D_MAC	= 1 << 13,
+	OPT_H_LENGTH	= 1 << 14,
+	OPT_OPCODE	= 1 << 15,
+	OPT_H_TYPE	= 1 << 16,
+	OPT_P_TYPE	= 1 << 17,
 };
 
+#define NUMBER_OF_OPT	ARRAY_SIZE(optflags)
+static const char optflags[]
+= { 'n', 's', 'd', 'p', 'j', 'v', 'x', 'i', 'o', '0', 'c', 'f', 2, 3, 'l', 4, 5, 6 };
+
+enum {
+	CMD_NONE		= 0,
+	CMD_INSERT		= 1 << 0,
+	CMD_DELETE		= 1 << 1,
+	CMD_DELETE_NUM		= 1 << 2,
+	CMD_REPLACE		= 1 << 3,
+	CMD_APPEND		= 1 << 4,
+	CMD_LIST		= 1 << 5,
+	CMD_FLUSH		= 1 << 6,
+	CMD_ZERO		= 1 << 7,
+	CMD_NEW_CHAIN		= 1 << 8,
+	CMD_DELETE_CHAIN	= 1 << 9,
+	CMD_SET_POLICY		= 1 << 10,
+	CMD_RENAME_CHAIN	= 1 << 11,
+	CMD_LIST_RULES		= 1 << 12,
+	CMD_ZERO_NUM		= 1 << 13,
+	CMD_CHECK		= 1 << 14,
+};
+#define NUMBER_OF_CMD		16
+
 struct xtables_globals;
 struct xtables_rule_match;
 struct xtables_target;
@@ -47,20 +88,55 @@
 	int so_rev_target;
 };
 
+/* trick for ebtables-compat, since watchers are targets */
+struct ebt_match {
+	struct ebt_match			*next;
+	union {
+		struct xtables_match		*match;
+		struct xtables_target		*watcher;
+	} u;
+	bool					ismatch;
+};
+
+/* Fake ebt_entry */
+struct ebt_entry {
+	/* this needs to be the first field */
+	unsigned int bitmask;
+	unsigned int invflags;
+	uint16_t ethproto;
+	/* the physical in-dev */
+	char in[IFNAMSIZ];
+	/* the logical in-dev */
+	char logical_in[IFNAMSIZ];
+	/* the physical out-dev */
+	char out[IFNAMSIZ];
+	/* the logical out-dev */
+	char logical_out[IFNAMSIZ];
+	unsigned char sourcemac[6];
+	unsigned char sourcemsk[6];
+	unsigned char destmac[6];
+	unsigned char destmsk[6];
+};
+
 struct iptables_command_state {
 	union {
+		struct ebt_entry eb;
 		struct ipt_entry fw;
 		struct ip6t_entry fw6;
+		struct arpt_entry arp;
 	};
 	int invert;
 	int c;
 	unsigned int options;
 	struct xtables_rule_match *matches;
+	struct ebt_match *match_list;
 	struct xtables_target *target;
+	struct xt_counters counters;
 	char *protocol;
 	int proto_used;
 	const char *jumpto;
 	char **argv;
+	bool restore;
 };
 
 typedef int (*mainfunc_t)(int, char **);
@@ -81,7 +157,71 @@
 	struct xtables_globals *);
 extern struct xtables_match *load_proto(struct iptables_command_state *);
 extern int subcmd_main(int, char **, const struct subcommand *);
+extern void xs_init_target(struct xtables_target *);
+extern void xs_init_match(struct xtables_match *);
+
+/**
+ * Values for the iptables lock.
+ *
+ * A value >= 0 indicates the lock filedescriptor. Other values are:
+ *
+ * XT_LOCK_FAILED : The lock could not be acquired.
+ *
+ * XT_LOCK_BUSY : The lock was held by another process. xtables_lock only
+ * returns this value when |wait| == false. If |wait| == true, xtables_lock
+ * will not return unless the lock has been acquired.
+ *
+ * XT_LOCK_NOT_ACQUIRED : We have not yet attempted to acquire the lock.
+ */
+enum {
+	XT_LOCK_BUSY = -1,
+	XT_LOCK_FAILED = -2,
+	XT_LOCK_NOT_ACQUIRED  = -3,
+};
+extern void xtables_unlock(int lock);
+extern int xtables_lock_or_exit(int wait, struct timeval *tv);
+
+int parse_wait_time(int argc, char *argv[]);
+void parse_wait_interval(int argc, char *argv[], struct timeval *wait_interval);
+int parse_counters(const char *string, struct xt_counters *ctr);
+bool tokenize_rule_counters(char **bufferp, char **pcnt, char **bcnt, int line);
+bool xs_has_arg(int argc, char *argv[]);
 
 extern const struct xtables_afinfo *afinfo;
 
+#define MAX_ARGC	255
+struct argv_store {
+	int argc;
+	char *argv[MAX_ARGC];
+	int argvattr[MAX_ARGC];
+};
+
+void add_argv(struct argv_store *store, const char *what, int quoted);
+void free_argv(struct argv_store *store);
+void save_argv(struct argv_store *dst, struct argv_store *src);
+void add_param_to_argv(struct argv_store *store, char *parsestart, int line);
+#ifdef DEBUG
+void debug_print_argv(struct argv_store *store);
+#else
+#  define debug_print_argv(...) /* nothing */
+#endif
+
+void print_ipv4_addresses(const struct ipt_entry *fw, unsigned int format);
+void print_ipv6_addresses(const struct ip6t_entry *fw6, unsigned int format);
+
+void print_ifaces(const char *iniface, const char *outiface, uint8_t invflags,
+		  unsigned int format);
+
+void command_match(struct iptables_command_state *cs);
+const char *xt_parse_target(const char *targetname);
+void command_jump(struct iptables_command_state *cs, const char *jumpto);
+
+char cmd2char(int option);
+void add_command(unsigned int *cmd, const int newcmd,
+		 const int othercmds, int invert);
+int parse_rulenumber(const char *rule);
+
+void generic_opt_check(int command, int options);
+char opt2char(int option);
+
 #endif /* IPTABLES_XSHARED_H */
diff --git a/iptables/xtables-arp-standalone.c b/iptables/xtables-arp-standalone.c
new file mode 100644
index 0000000..04cf7dc
--- /dev/null
+++ b/iptables/xtables-arp-standalone.c
@@ -0,0 +1,65 @@
+/*
+ * Author: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au
+ *
+ * Based on the ipchains code by Paul Russell and Michael Neuling
+ *
+ * (C) 2000-2002 by the netfilter coreteam <coreteam@netfilter.org>:
+ * 		    Paul 'Rusty' Russell <rusty@rustcorp.com.au>
+ * 		    Marc Boucher <marc+nf@mbsi.ca>
+ * 		    James Morris <jmorris@intercode.com.au>
+ * 		    Harald Welte <laforge@gnumonks.org>
+ * 		    Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ *
+ *	arptables -- IP firewall administration for kernels with
+ *	firewall table (aimed for the 2.3 kernels)
+ *
+ *	See the accompanying manual page arptables(8) for information
+ *	about proper usage of this program.
+ *
+ *	This program is free software; you can redistribute it and/or modify
+ *	it under the terms of the GNU General Public License as published by
+ *	the Free Software Foundation; either version 2 of the License, or
+ *	(at your option) any later version.
+ *
+ *	This program is distributed in the hope that it will be useful,
+ *	but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *	GNU General Public License for more details.
+ *
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program; if not, write to the Free Software
+ *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <xtables.h>
+#include "nft.h"
+#include <linux/netfilter_arp/arp_tables.h>
+
+#include "xtables-multi.h"
+
+extern struct xtables_globals arptables_globals;
+
+int xtables_arp_main(int argc, char *argv[])
+{
+	int ret;
+	char *table = "filter";
+	struct nft_handle h;
+
+	nft_init_arp(&h, "arptables");
+
+	ret = do_commandarp(&h, argc, argv, &table, false);
+	if (ret)
+		ret = nft_commit(&h);
+
+	nft_fini(&h);
+	xtables_fini();
+
+	if (!ret)
+		fprintf(stderr, "arptables: %s\n", nft_strerror(errno));
+
+	exit(!ret);
+}
diff --git a/iptables/xtables-arp.c b/iptables/xtables-arp.c
new file mode 100644
index 0000000..4a89ae9
--- /dev/null
+++ b/iptables/xtables-arp.c
@@ -0,0 +1,971 @@
+/* Code to take an arptables-style command line and do it. */
+
+/*
+ * arptables:
+ * Author: Bart De Schuymer <bdschuym@pandora.be>, but
+ * almost all code is from the iptables userspace program, which has main
+ * authors: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au
+ *
+ *	This program is free software; you can redistribute it and/or modify
+ *	it under the terms of the GNU General Public License as published by
+ *	the Free Software Foundation; either version 2 of the License, or
+ *	(at your option) any later version.
+ *
+ *	This program is distributed in the hope that it will be useful,
+ *	but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *	GNU General Public License for more details.
+ *
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program; if not, write to the Free Software
+ *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+  Currently, only support for specifying hardware addresses for Ethernet
+  is available.
+  This tool is not luser-proof: you can specify an Ethernet source address
+  and set hardware length to something different than 6, f.e.
+*/
+#include "config.h"
+#include <getopt.h>
+#include <string.h>
+#include <netdb.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <dlfcn.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+#include <net/if.h>
+#include <netinet/ether.h>
+#include <iptables.h>
+#include <xtables.h>
+
+#include "xshared.h"
+
+#include "nft.h"
+#include "nft-arp.h"
+#include <linux/netfilter_arp/arp_tables.h>
+
+static struct option original_opts[] = {
+	{ "append", 1, 0, 'A' },
+	{ "delete", 1, 0,  'D' },
+	{ "insert", 1, 0,  'I' },
+	{ "replace", 1, 0,  'R' },
+	{ "list", 2, 0,  'L' },
+	{ "flush", 2, 0,  'F' },
+	{ "zero", 2, 0,  'Z' },
+	{ "new-chain", 1, 0,  'N' },
+	{ "delete-chain", 2, 0,  'X' },
+	{ "rename-chain", 1, 0,  'E' },
+	{ "policy", 1, 0,  'P' },
+	{ "source-ip", 1, 0, 's' },
+	{ "destination-ip", 1, 0,  'd' },
+	{ "src-ip", 1, 0,  's' },
+	{ "dst-ip", 1, 0,  'd' },
+	{ "source-mac", 1, 0, 2},
+	{ "destination-mac", 1, 0, 3},
+	{ "src-mac", 1, 0, 2},
+	{ "dst-mac", 1, 0, 3},
+	{ "h-length", 1, 0,  'l' },
+	{ "p-length", 1, 0,  8 },
+	{ "opcode", 1, 0,  4 },
+	{ "h-type", 1, 0,  5 },
+	{ "proto-type", 1, 0,  6 },
+	{ "in-interface", 1, 0, 'i' },
+	{ "jump", 1, 0, 'j' },
+	{ "table", 1, 0, 't' },
+	{ "match", 1, 0, 'm' },
+	{ "numeric", 0, 0, 'n' },
+	{ "out-interface", 1, 0, 'o' },
+	{ "verbose", 0, 0, 'v' },
+	{ "exact", 0, 0, 'x' },
+	{ "version", 0, 0, 'V' },
+	{ "help", 2, 0, 'h' },
+	{ "line-numbers", 0, 0, '0' },
+	{ "modprobe", 1, 0, 'M' },
+	{ "set-counters", 1, 0, 'c' },
+	{ 0 }
+};
+
+#define opts xt_params->opts
+
+extern void xtables_exit_error(enum xtables_exittype status, const char *msg, ...) __attribute__((noreturn, format(printf,2,3)));
+struct xtables_globals arptables_globals = {
+	.option_offset		= 0,
+	.program_version	= PACKAGE_VERSION,
+	.orig_opts		= original_opts,
+	.exit_err		= xtables_exit_error,
+	.compat_rev		= nft_compatible_revision,
+};
+
+/* index relates to bit of each OPT_* value */
+static int inverse_for_options[] =
+{
+/* -n */ 0,
+/* -s */ IPT_INV_SRCIP,
+/* -d */ IPT_INV_DSTIP,
+/* -p */ 0,
+/* -j */ 0,
+/* -v */ 0,
+/* -x */ 0,
+/* -i */ IPT_INV_VIA_IN,
+/* -o */ IPT_INV_VIA_OUT,
+/*--line*/ 0,
+/* -c */ 0,
+/* -f */ 0,
+/* 2 */ IPT_INV_SRCDEVADDR,
+/* 3 */ IPT_INV_TGTDEVADDR,
+/* -l */ IPT_INV_ARPHLN,
+/* 4 */ IPT_INV_ARPOP,
+/* 5 */ IPT_INV_ARPHRD,
+/* 6 */ IPT_INV_PROTO,
+};
+
+/***********************************************/
+/* ARPTABLES SPECIFIC NEW FUNCTIONS ADDED HERE */
+/***********************************************/
+
+static int getlength_and_mask(char *from, uint8_t *to, uint8_t *mask)
+{
+	char *p, *buffer;
+	int i;
+
+	if ( (p = strrchr(from, '/')) != NULL) {
+		*p = '\0';
+		i = strtol(p+1, &buffer, 10);
+		if (*buffer != '\0' || i < 0 || i > 255)
+			return -1;
+		*mask = (uint8_t)i;
+	} else
+		*mask = 255;
+	i = strtol(from, &buffer, 10);
+	if (*buffer != '\0' || i < 0 || i > 255)
+		return -1;
+	*to = (uint8_t)i;
+	return 0;
+}
+
+static int get16_and_mask(char *from, uint16_t *to, uint16_t *mask, int base)
+{
+	char *p, *buffer;
+	int i;
+
+	if ( (p = strrchr(from, '/')) != NULL) {
+		*p = '\0';
+		i = strtol(p+1, &buffer, base);
+		if (*buffer != '\0' || i < 0 || i > 65535)
+			return -1;
+		*mask = htons((uint16_t)i);
+	} else
+		*mask = 65535;
+	i = strtol(from, &buffer, base);
+	if (*buffer != '\0' || i < 0 || i > 65535)
+		return -1;
+	*to = htons((uint16_t)i);
+	return 0;
+}
+
+/*********************************************/
+/* ARPTABLES SPECIFIC NEW FUNCTIONS END HERE */
+/*********************************************/
+
+static void
+exit_tryhelp(int status)
+{
+	fprintf(stderr, "Try `%s -h' or '%s --help' for more information.\n",
+		arptables_globals.program_name,
+		arptables_globals.program_version);
+	exit(status);
+}
+
+static void
+printhelp(void)
+{
+	struct xtables_target *t = NULL;
+	int i;
+
+	printf("%s v%s\n\n"
+"Usage: %s -[AD] chain rule-specification [options]\n"
+"       %s -[RI] chain rulenum rule-specification [options]\n"
+"       %s -D chain rulenum [options]\n"
+"       %s -[LFZ] [chain] [options]\n"
+"       %s -[NX] chain\n"
+"       %s -E old-chain-name new-chain-name\n"
+"       %s -P chain target [options]\n"
+"       %s -h (print this help information)\n\n",
+	       arptables_globals.program_name,
+	       arptables_globals.program_version,
+	       arptables_globals.program_name,
+	       arptables_globals.program_name,
+	       arptables_globals.program_name,
+	       arptables_globals.program_name,
+	       arptables_globals.program_name,
+	       arptables_globals.program_name,
+	       arptables_globals.program_name,
+	       arptables_globals.program_name);
+	printf(
+"Commands:\n"
+"Either long or short options are allowed.\n"
+"  --append  -A chain		Append to chain\n"
+"  --delete  -D chain		Delete matching rule from chain\n"
+"  --delete  -D chain rulenum\n"
+"				Delete rule rulenum (1 = first) from chain\n"
+"  --insert  -I chain [rulenum]\n"
+"				Insert in chain as rulenum (default 1=first)\n"
+"  --replace -R chain rulenum\n"
+"				Replace rule rulenum (1 = first) in chain\n"
+"  --list    -L [chain]		List the rules in a chain or all chains\n"
+"  --flush   -F [chain]		Delete all rules in  chain or all chains\n"
+"  --zero    -Z [chain]		Zero counters in chain or all chains\n"
+"  --new     -N chain		Create a new user-defined chain\n"
+"  --delete-chain\n"
+"            -X [chain]		Delete a user-defined chain\n"
+"  --policy  -P chain target\n"
+"				Change policy on chain to target\n"
+"  --rename-chain\n"
+"            -E old-chain new-chain\n"
+"				Change chain name, (moving any references)\n"
+
+"Options:\n"
+"  --source-ip	-s [!] address[/mask]\n"
+"				source specification\n"
+"  --destination-ip -d [!] address[/mask]\n"
+"				destination specification\n"
+"  --source-mac [!] address[/mask]\n"
+"  --destination-mac [!] address[/mask]\n"
+"  --h-length   -l   length[/mask] hardware length (nr of bytes)\n"
+"  --opcode code[/mask] operation code (2 bytes)\n"
+"  --h-type   type[/mask]  hardware type (2 bytes, hexadecimal)\n"
+"  --proto-type   type[/mask]  protocol type (2 bytes)\n"
+"  --in-interface -i [!] input name[+]\n"
+"				network interface name ([+] for wildcard)\n"
+"  --out-interface -o [!] output name[+]\n"
+"				network interface name ([+] for wildcard)\n"
+"  --jump	-j target\n"
+"				target for rule (may load target extension)\n"
+"  --match	-m match\n"
+"				extended match (may load extension)\n"
+"  --numeric	-n		numeric output of addresses and ports\n"
+"  --table	-t table	table to manipulate (default: `filter')\n"
+"  --verbose	-v		verbose mode\n"
+"  --line-numbers		print line numbers when listing\n"
+"  --exact	-x		expand numbers (display exact values)\n"
+"  --modprobe=<command>		try to insert modules using this command\n"
+"  --set-counters -c PKTS BYTES	set the counter during insert/append\n"
+"[!] --version	-V		print package version.\n");
+	printf(" opcode strings: \n");
+        for (i = 0; i < NUMOPCODES; i++)
+                printf(" %d = %s\n", i + 1, arp_opcodes[i]);
+        printf(
+" hardware type string: 1 = Ethernet\n"
+" protocol type string: 0x800 = IPv4\n");
+
+	/* Print out any special helps. A user might like to be able
+		to add a --help to the commandline, and see expected
+		results. So we call help for all matches & targets */
+	for (t = xtables_targets; t; t = t->next) {
+		if (strcmp(t->name, "CLASSIFY") && strcmp(t->name, "mangle"))
+			continue;
+		printf("\n");
+		t->help();
+	}
+}
+
+static int
+check_inverse(const char option[], int *invert, int *optidx, int argc)
+{
+	if (option && strcmp(option, "!") == 0) {
+		if (*invert)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Multiple `!' flags not allowed");
+		*invert = true;
+		if (optidx) {
+			*optidx = *optidx+1;
+			if (argc && *optidx > argc)
+				xtables_error(PARAMETER_PROBLEM,
+					      "no argument following `!'");
+		}
+
+		return true;
+	}
+	return false;
+}
+
+static void
+set_option(unsigned int *options, unsigned int option, u_int16_t *invflg,
+	   int invert)
+{
+	if (*options & option)
+		xtables_error(PARAMETER_PROBLEM, "multiple -%c flags not allowed",
+			      opt2char(option));
+	*options |= option;
+
+	if (invert) {
+		unsigned int i;
+		for (i = 0; 1 << i != option; i++);
+
+		if (!inverse_for_options[i])
+			xtables_error(PARAMETER_PROBLEM,
+				      "cannot have ! before -%c",
+				      opt2char(option));
+		*invflg |= inverse_for_options[i];
+	}
+}
+
+static int
+list_entries(struct nft_handle *h, const char *chain, const char *table,
+	     int rulenum, int verbose, int numeric, int expanded,
+	     int linenumbers)
+{
+	unsigned int format;
+
+	format = FMT_OPTIONS;
+	if (!verbose)
+		format |= FMT_NOCOUNTS;
+	else
+		format |= FMT_VIA;
+
+	if (numeric)
+		format |= FMT_NUMERIC;
+
+	if (!expanded)
+		format |= FMT_KILOMEGAGIGA;
+
+	if (linenumbers)
+		format |= FMT_LINENUMBERS;
+
+	return nft_cmd_rule_list(h, chain, table, rulenum, format);
+}
+
+static int
+append_entry(struct nft_handle *h,
+	     const char *chain,
+	     const char *table,
+	     struct iptables_command_state *cs,
+	     int rulenum,
+	     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, bool append)
+{
+	unsigned int i, j;
+	int ret = 1;
+
+	for (i = 0; i < nsaddrs; i++) {
+		cs->arp.arp.src.s_addr = saddrs[i].s_addr;
+		cs->arp.arp.smsk.s_addr = smasks[i].s_addr;
+		for (j = 0; j < ndaddrs; j++) {
+			cs->arp.arp.tgt.s_addr = daddrs[j].s_addr;
+			cs->arp.arp.tmsk.s_addr = dmasks[j].s_addr;
+			if (append) {
+				ret = nft_cmd_rule_append(h, chain, table, cs, NULL,
+						      verbose);
+			} else {
+				ret = nft_cmd_rule_insert(h, chain, table, cs,
+						      rulenum, verbose);
+			}
+		}
+	}
+
+	return ret;
+}
+
+static int
+replace_entry(const char *chain,
+	      const char *table,
+	      struct iptables_command_state *cs,
+	      unsigned int rulenum,
+	      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->arp.arp.src.s_addr = saddr->s_addr;
+	cs->arp.arp.tgt.s_addr = daddr->s_addr;
+	cs->arp.arp.smsk.s_addr = smask->s_addr;
+	cs->arp.arp.tmsk.s_addr = dmask->s_addr;
+
+	return nft_cmd_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->arp.arp.src.s_addr = saddrs[i].s_addr;
+		cs->arp.arp.smsk.s_addr = smasks[i].s_addr;
+		for (j = 0; j < ndaddrs; j++) {
+			cs->arp.arp.tgt.s_addr = daddrs[j].s_addr;
+			cs->arp.arp.tmsk.s_addr = dmasks[j].s_addr;
+			ret = nft_cmd_rule_delete(h, chain, table, cs, verbose);
+		}
+	}
+
+	return ret;
+}
+
+int nft_init_arp(struct nft_handle *h, const char *pname)
+{
+	arptables_globals.program_name = pname;
+	if (xtables_init_all(&arptables_globals, NFPROTO_ARP) < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize arptables-compat\n",
+			arptables_globals.program_name,
+			arptables_globals.program_version);
+		exit(1);
+	}
+
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+	init_extensionsa();
+#endif
+
+	if (nft_init(h, NFPROTO_ARP, xtables_arp) < 0)
+		xtables_error(OTHER_PROBLEM,
+			      "Could not initialize nftables layer.");
+
+	return 0;
+}
+
+int do_commandarp(struct nft_handle *h, int argc, char *argv[], char **table,
+		  bool restore)
+{
+	struct iptables_command_state cs = {
+		.jumpto = "",
+		.arp.arp = {
+			.arhln = 6,
+			.arhln_mask = 255,
+			.arhrd = htons(ARPHRD_ETHER),
+			.arhrd_mask = 65535,
+		},
+	};
+	int invert = 0;
+	unsigned int nsaddrs = 0, ndaddrs = 0;
+	struct in_addr *saddrs = NULL, *smasks = NULL;
+	struct in_addr *daddrs = NULL, *dmasks = NULL;
+
+	int c, verbose = 0;
+	const char *chain = NULL;
+	const char *shostnetworkmask = NULL, *dhostnetworkmask = NULL;
+	const char *policy = NULL, *newname = NULL;
+	unsigned int rulenum = 0, options = 0, command = 0;
+	const char *pcnt = NULL, *bcnt = NULL;
+	int ret = 1;
+	struct xtables_target *t;
+
+	/* re-set optind to 0 in case do_command gets called
+	 * a second time */
+	optind = 0;
+
+	for (t = xtables_targets; t; t = t->next) {
+		t->tflags = 0;
+		t->used = 0;
+	}
+
+	/* Suppress error messages: we may add new options if we
+	    demand-load a protocol. */
+	opterr = 0;
+
+	opts = xt_params->orig_opts;
+	while ((c = getopt_long(argc, argv,
+	   "-A:D:R:I:L::M:F::Z::N:X::E:P:Vh::o:p:s:d:j:l:i:vnt:m:c:",
+					   opts, NULL)) != -1) {
+		switch (c) {
+			/*
+			 * Command selection
+			 */
+		case 'A':
+			add_command(&command, CMD_APPEND, CMD_NONE,
+				    invert);
+			chain = optarg;
+			break;
+
+		case 'D':
+			add_command(&command, CMD_DELETE, CMD_NONE,
+				    invert);
+			chain = optarg;
+			if (xs_has_arg(argc, argv)) {
+				rulenum = parse_rulenumber(argv[optind++]);
+				command = CMD_DELETE_NUM;
+			}
+			break;
+
+		case 'R':
+			add_command(&command, CMD_REPLACE, CMD_NONE,
+				    invert);
+			chain = optarg;
+			if (xs_has_arg(argc, argv))
+				rulenum = parse_rulenumber(argv[optind++]);
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					      "-%c requires a rule number",
+					      cmd2char(CMD_REPLACE));
+			break;
+
+		case 'I':
+			add_command(&command, CMD_INSERT, CMD_NONE,
+				    invert);
+			chain = optarg;
+			if (xs_has_arg(argc, argv))
+				rulenum = parse_rulenumber(argv[optind++]);
+			else rulenum = 1;
+			break;
+
+		case 'L':
+			add_command(&command, CMD_LIST, CMD_ZERO,
+				    invert);
+			if (optarg) chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				chain = argv[optind++];
+			break;
+
+		case 'F':
+			add_command(&command, CMD_FLUSH, CMD_NONE,
+				    invert);
+			if (optarg) chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				chain = argv[optind++];
+			break;
+
+		case 'Z':
+			add_command(&command, CMD_ZERO, CMD_LIST,
+				    invert);
+			if (optarg) chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				chain = argv[optind++];
+			break;
+
+		case 'N':
+			if (optarg && *optarg == '-')
+				xtables_error(PARAMETER_PROBLEM,
+					      "chain name not allowed to start "
+					      "with `-'\n");
+			if (xtables_find_target(optarg, XTF_TRY_LOAD))
+				xtables_error(PARAMETER_PROBLEM,
+						"chain name may not clash "
+						"with target name\n");
+			add_command(&command, CMD_NEW_CHAIN, CMD_NONE,
+				    invert);
+			chain = optarg;
+			break;
+
+		case 'X':
+			add_command(&command, CMD_DELETE_CHAIN, CMD_NONE,
+				    invert);
+			if (optarg) chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				chain = argv[optind++];
+			break;
+
+		case 'E':
+			add_command(&command, CMD_RENAME_CHAIN, CMD_NONE,
+				    invert);
+			chain = optarg;
+			if (xs_has_arg(argc, argv))
+				newname = argv[optind++];
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					      "-%c requires old-chain-name and "
+					      "new-chain-name",
+					      cmd2char(CMD_RENAME_CHAIN));
+			break;
+
+		case 'P':
+			add_command(&command, CMD_SET_POLICY, CMD_NONE,
+				    invert);
+			chain = optarg;
+			if (xs_has_arg(argc, argv))
+				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];
+
+			printhelp();
+			command = CMD_NONE;
+			break;
+		case 's':
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_SOURCE, &cs.arp.arp.invflags,
+				   invert);
+			shostnetworkmask = argv[optind-1];
+			break;
+
+		case 'd':
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_DESTINATION, &cs.arp.arp.invflags,
+				   invert);
+			dhostnetworkmask = argv[optind-1];
+			break;
+
+		case 2:/* src-mac */
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_S_MAC, &cs.arp.arp.invflags,
+				   invert);
+			if (xtables_parse_mac_and_mask(argv[optind - 1],
+			    cs.arp.arp.src_devaddr.addr, cs.arp.arp.src_devaddr.mask))
+				xtables_error(PARAMETER_PROBLEM, "Problem with specified "
+						"source mac");
+			break;
+
+		case 3:/* dst-mac */
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_D_MAC, &cs.arp.arp.invflags,
+				   invert);
+
+			if (xtables_parse_mac_and_mask(argv[optind - 1],
+			    cs.arp.arp.tgt_devaddr.addr, cs.arp.arp.tgt_devaddr.mask))
+				xtables_error(PARAMETER_PROBLEM, "Problem with specified "
+						"destination mac");
+			break;
+
+		case 'l':/* hardware length */
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_H_LENGTH, &cs.arp.arp.invflags,
+				   invert);
+			getlength_and_mask(argv[optind - 1], &cs.arp.arp.arhln,
+					   &cs.arp.arp.arhln_mask);
+
+			if (cs.arp.arp.arhln != 6) {
+				xtables_error(PARAMETER_PROBLEM,
+					      "Only harware address length of"
+					      " 6 is supported currently.");
+			}
+
+			break;
+
+		case 8: /* was never supported, not even in arptables-legacy */
+			xtables_error(PARAMETER_PROBLEM, "not supported");
+		case 4:/* opcode */
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_OPCODE, &cs.arp.arp.invflags,
+				   invert);
+			if (get16_and_mask(argv[optind - 1], &cs.arp.arp.arpop,
+					   &cs.arp.arp.arpop_mask, 10)) {
+				int i;
+
+				for (i = 0; i < NUMOPCODES; i++)
+					if (!strcasecmp(arp_opcodes[i], optarg))
+						break;
+				if (i == NUMOPCODES)
+					xtables_error(PARAMETER_PROBLEM, "Problem with specified opcode");
+				cs.arp.arp.arpop = htons(i+1);
+			}
+			break;
+
+		case 5:/* h-type */
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_H_TYPE, &cs.arp.arp.invflags,
+				   invert);
+			if (get16_and_mask(argv[optind - 1], &cs.arp.arp.arhrd,
+					   &cs.arp.arp.arhrd_mask, 16)) {
+				if (strcasecmp(argv[optind-1], "Ethernet"))
+					xtables_error(PARAMETER_PROBLEM, "Problem with specified hardware type");
+				cs.arp.arp.arhrd = htons(1);
+			}
+			break;
+
+		case 6:/* proto-type */
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_P_TYPE, &cs.arp.arp.invflags,
+				   invert);
+			if (get16_and_mask(argv[optind - 1], &cs.arp.arp.arpro,
+					   &cs.arp.arp.arpro_mask, 0)) {
+				if (strcasecmp(argv[optind-1], "ipv4"))
+					xtables_error(PARAMETER_PROBLEM, "Problem with specified protocol type");
+				cs.arp.arp.arpro = htons(0x800);
+			}
+			break;
+
+		case 'j':
+			set_option(&options, OPT_JUMP, &cs.arp.arp.invflags,
+				   invert);
+			command_jump(&cs, optarg);
+			break;
+
+		case 'i':
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_VIANAMEIN, &cs.arp.arp.invflags,
+				   invert);
+			xtables_parse_interface(argv[optind-1],
+						cs.arp.arp.iniface,
+						cs.arp.arp.iniface_mask);
+			break;
+
+		case 'o':
+			check_inverse(optarg, &invert, &optind, argc);
+			set_option(&options, OPT_VIANAMEOUT, &cs.arp.arp.invflags,
+				   invert);
+			xtables_parse_interface(argv[optind-1],
+						cs.arp.arp.outiface,
+						cs.arp.arp.outiface_mask);
+			break;
+
+		case 'v':
+			if (!verbose)
+				set_option(&options, OPT_VERBOSE,
+					   &cs.arp.arp.invflags, invert);
+			verbose++;
+			break;
+
+		case 'm': /* ignored by arptables-legacy */
+			break;
+		case 'n':
+			set_option(&options, OPT_NUMERIC, &cs.arp.arp.invflags,
+				   invert);
+			break;
+
+		case 't':
+			if (invert)
+				xtables_error(PARAMETER_PROBLEM,
+					      "unexpected ! flag before --table");
+			/* ignore this option.
+			 * arptables-legacy parses it, but libarptc doesn't use it.
+			 * arptables only has a 'filter' table anyway.
+			 */
+			break;
+
+		case 'V':
+			if (invert)
+				printf("Not %s ;-)\n", arptables_globals.program_version);
+			else
+				printf("%s v%s (nf_tables)\n",
+				       arptables_globals.program_name,
+				       arptables_globals.program_version);
+			exit(0);
+
+		case '0':
+			set_option(&options, OPT_LINENUMBERS, &cs.arp.arp.invflags,
+				   invert);
+			break;
+
+		case 'M':
+			//modprobe = optarg;
+			break;
+
+		case 'c':
+
+			set_option(&options, OPT_COUNTERS, &cs.arp.arp.invflags,
+				   invert);
+			pcnt = optarg;
+			if (xs_has_arg(argc, argv))
+				bcnt = argv[optind++];
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					      "-%c requires packet and byte counter",
+					      opt2char(OPT_COUNTERS));
+
+			if (sscanf(pcnt, "%llu", &cs.arp.counters.pcnt) != 1)
+			xtables_error(PARAMETER_PROBLEM,
+				"-%c packet counter not numeric",
+				opt2char(OPT_COUNTERS));
+
+			if (sscanf(bcnt, "%llu", &cs.arp.counters.bcnt) != 1)
+				xtables_error(PARAMETER_PROBLEM,
+					      "-%c byte counter not numeric",
+					      opt2char(OPT_COUNTERS));
+
+			break;
+
+
+		case 1: /* non option */
+			if (optarg[0] == '!' && optarg[1] == '\0') {
+				if (invert)
+					xtables_error(PARAMETER_PROBLEM,
+						      "multiple consecutive ! not"
+						      " allowed");
+				invert = true;
+				optarg[0] = '\0';
+				continue;
+			}
+			printf("Bad argument `%s'\n", optarg);
+			exit_tryhelp(2);
+
+		default:
+			if (cs.target) {
+				xtables_option_tpcall(c, argv,
+						      invert, cs.target, &cs.arp);
+			}
+			break;
+		}
+		invert = false;
+	}
+
+	if (cs.target)
+		xtables_option_tfcall(cs.target);
+
+	if (optind < argc)
+		xtables_error(PARAMETER_PROBLEM,
+			      "unknown arguments found on commandline");
+	if (invert)
+		xtables_error(PARAMETER_PROBLEM,
+			      "nothing appropriate following !");
+
+	if (command & (CMD_REPLACE | CMD_INSERT | CMD_DELETE | CMD_APPEND)) {
+		if (!(options & OPT_DESTINATION))
+			dhostnetworkmask = "0.0.0.0/0";
+		if (!(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.arp.arp.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");
+
+	if (chain && strlen(chain) > ARPT_FUNCTION_MAXNAMELEN)
+		xtables_error(PARAMETER_PROBLEM,
+				"chain name `%s' too long (must be under %i chars)",
+				chain, ARPT_FUNCTION_MAXNAMELEN);
+
+	if (command == CMD_APPEND
+	    || command == CMD_DELETE
+	    || command == CMD_INSERT
+	    || command == CMD_REPLACE) {
+		if (strcmp(chain, "PREROUTING") == 0
+		    || strcmp(chain, "INPUT") == 0) {
+			/* -o not valid with incoming packets. */
+			if (options & OPT_VIANAMEOUT)
+				xtables_error(PARAMETER_PROBLEM,
+					      "Can't use -%c with %s\n",
+					      opt2char(OPT_VIANAMEOUT),
+					      chain);
+		}
+
+		if (strcmp(chain, "POSTROUTING") == 0
+		    || strcmp(chain, "OUTPUT") == 0) {
+			/* -i not valid with outgoing packets */
+			if (options & OPT_VIANAMEIN)
+				xtables_error(PARAMETER_PROBLEM,
+						"Can't use -%c with %s\n",
+						opt2char(OPT_VIANAMEIN),
+						chain);
+		}
+	}
+
+	switch (command) {
+	case CMD_APPEND:
+		ret = append_entry(h, chain, *table, &cs, 0,
+				   nsaddrs, saddrs, smasks,
+				   ndaddrs, daddrs, dmasks,
+				   options&OPT_VERBOSE, true);
+		break;
+	case CMD_DELETE:
+		ret = delete_entry(chain, *table, &cs,
+				   nsaddrs, saddrs, smasks,
+				   ndaddrs, daddrs, dmasks,
+				   options&OPT_VERBOSE, h);
+		break;
+	case CMD_DELETE_NUM:
+		ret = nft_cmd_rule_delete_num(h, chain, *table, rulenum - 1, verbose);
+		break;
+	case CMD_REPLACE:
+		ret = replace_entry(chain, *table, &cs, rulenum - 1,
+				    saddrs, smasks, daddrs, dmasks,
+				    options&OPT_VERBOSE, h);
+		break;
+	case CMD_INSERT:
+		ret = append_entry(h, chain, *table, &cs, rulenum - 1,
+				   nsaddrs, saddrs, smasks,
+				   ndaddrs, daddrs, dmasks,
+				   options&OPT_VERBOSE, false);
+		break;
+	case CMD_LIST:
+		ret = list_entries(h, chain, *table,
+				   rulenum,
+				   options&OPT_VERBOSE,
+				   options&OPT_NUMERIC,
+				   /*options&OPT_EXPANDED*/0,
+				   options&OPT_LINENUMBERS);
+		break;
+	case CMD_FLUSH:
+		ret = nft_cmd_rule_flush(h, chain, *table, options & OPT_VERBOSE);
+		break;
+	case CMD_ZERO:
+		ret = nft_cmd_chain_zero_counters(h, chain, *table,
+					      options & OPT_VERBOSE);
+		break;
+	case CMD_LIST|CMD_ZERO:
+		ret = list_entries(h, chain, *table, rulenum,
+				   options&OPT_VERBOSE,
+				   options&OPT_NUMERIC,
+				   /*options&OPT_EXPANDED*/0,
+				   options&OPT_LINENUMBERS);
+		if (ret)
+			ret = nft_cmd_chain_zero_counters(h, chain, *table,
+						      options & OPT_VERBOSE);
+		break;
+	case CMD_NEW_CHAIN:
+		ret = nft_cmd_chain_user_add(h, chain, *table);
+		break;
+	case CMD_DELETE_CHAIN:
+		ret = nft_cmd_chain_user_del(h, chain, *table,
+					 options & OPT_VERBOSE);
+		break;
+	case CMD_RENAME_CHAIN:
+		ret = nft_cmd_chain_user_rename(h, chain, *table, newname);
+		break;
+	case CMD_SET_POLICY:
+		ret = nft_cmd_chain_set(h, *table, chain, policy, NULL);
+		if (ret < 0)
+			xtables_error(PARAMETER_PROBLEM, "Wrong policy `%s'\n",
+				      policy);
+		break;
+	case CMD_NONE:
+		break;
+	default:
+		/* We should never reach this... */
+		exit_tryhelp(2);
+	}
+
+	free(saddrs);
+	free(smasks);
+	free(daddrs);
+	free(dmasks);
+
+	nft_clear_iptables_command_state(&cs);
+	xtables_free_opts(1);
+
+/*	if (verbose > 1)
+		dump_entries(*handle);*/
+
+	return ret;
+}
diff --git a/iptables/xtables-eb-standalone.c b/iptables/xtables-eb-standalone.c
new file mode 100644
index 0000000..181cf2d
--- /dev/null
+++ b/iptables/xtables-eb-standalone.c
@@ -0,0 +1,62 @@
+/*
+ * Author: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au
+ *
+ * Based on the ipchains code by Paul Russell and Michael Neuling
+ *
+ * (C) 2000-2002 by the netfilter coreteam <coreteam@netfilter.org>:
+ * 		    Paul 'Rusty' Russell <rusty@rustcorp.com.au>
+ * 		    Marc Boucher <marc+nf@mbsi.ca>
+ * 		    James Morris <jmorris@intercode.com.au>
+ * 		    Harald Welte <laforge@gnumonks.org>
+ * 		    Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ *
+ *	arptables -- IP firewall administration for kernels with
+ *	firewall table (aimed for the 2.3 kernels)
+ *
+ *	See the accompanying manual page arptables(8) for information
+ *	about proper usage of this program.
+ *
+ *	This program is free software; you can redistribute it and/or modify
+ *	it under the terms of the GNU General Public License as published by
+ *	the Free Software Foundation; either version 2 of the License, or
+ *	(at your option) any later version.
+ *
+ *	This program is distributed in the hope that it will be useful,
+ *	but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *	GNU General Public License for more details.
+ *
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program; if not, write to the Free Software
+ *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <xtables.h>
+#include <iptables.h>
+#include "nft.h"
+
+#include "xtables-multi.h"
+
+int xtables_eb_main(int argc, char *argv[])
+{
+	int ret;
+	char *table = "filter";
+	struct nft_handle h;
+
+	nft_init_eb(&h, "ebtables");
+
+	ret = do_commandeb(&h, argc, argv, &table, false);
+	if (ret)
+		ret = nft_bridge_commit(&h);
+
+	nft_fini_eb(&h);
+
+	if (!ret)
+		fprintf(stderr, "ebtables: %s\n", nft_strerror(errno));
+
+	exit(!ret);
+}
diff --git a/iptables/xtables-eb-translate.c b/iptables/xtables-eb-translate.c
new file mode 100644
index 0000000..83ae77c
--- /dev/null
+++ b/iptables/xtables-eb-translate.c
@@ -0,0 +1,580 @@
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <string.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <iptables.h>
+#include <xtables.h>
+
+#include <netinet/ether.h>
+
+#include <linux/netfilter_bridge.h>
+#include <linux/netfilter/nf_tables.h>
+#include <libiptc/libxtc.h>
+
+#include "xshared.h"
+#include "xtables-multi.h"
+#include "nft-bridge.h"
+#include "nft.h"
+#include "nft-shared.h"
+/*
+ * From include/ebtables_u.h
+ */
+#define EXEC_STYLE_PRG    0
+#define EXEC_STYLE_DAEMON 1
+
+#define ebt_check_option2(flags, mask) EBT_CHECK_OPTION(flags, mask)
+
+extern int ebt_invert;
+
+static int ebt_check_inverse2(const char option[], int argc, char **argv)
+{
+	if (!option)
+		return ebt_invert;
+	if (strcmp(option, "!") == 0) {
+		if (ebt_invert == 1)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Double use of '!' not allowed");
+		if (optind >= argc)
+			optarg = NULL;
+		else
+			optarg = argv[optind];
+		optind++;
+		ebt_invert = 1;
+		return 1;
+	}
+	return ebt_invert;
+}
+
+/*
+ * Glue code to use libxtables
+ */
+static int parse_rule_number(const char *rule)
+{
+	unsigned int rule_nr;
+
+	if (!xtables_strtoui(rule, NULL, &rule_nr, 1, INT_MAX))
+		xtables_error(PARAMETER_PROBLEM,
+			      "Invalid rule number `%s'", rule);
+
+	return rule_nr;
+}
+
+static int get_current_chain(const char *chain)
+{
+	if (strcmp(chain, "PREROUTING") == 0)
+		return NF_BR_PRE_ROUTING;
+	else if (strcmp(chain, "INPUT") == 0)
+		return NF_BR_LOCAL_IN;
+	else if (strcmp(chain, "FORWARD") == 0)
+		return NF_BR_FORWARD;
+	else if (strcmp(chain, "OUTPUT") == 0)
+		return NF_BR_LOCAL_OUT;
+	else if (strcmp(chain, "POSTROUTING") == 0)
+		return NF_BR_POST_ROUTING;
+
+	return -1;
+}
+
+/*
+ * The original ebtables parser
+ */
+
+/* Checks whether a command has already been specified */
+#define OPT_COMMANDS (flags & OPT_COMMAND || flags & OPT_ZERO)
+
+#define OPT_COMMAND	0x01
+#define OPT_TABLE	0x02
+#define OPT_IN		0x04
+#define OPT_OUT		0x08
+#define OPT_JUMP	0x10
+#define OPT_PROTOCOL	0x20
+#define OPT_SOURCE	0x40
+#define OPT_DEST	0x80
+#define OPT_ZERO	0x100
+#define OPT_LOGICALIN	0x200
+#define OPT_LOGICALOUT	0x400
+#define OPT_COUNT	0x1000 /* This value is also defined in libebtc.c */
+
+/* Default command line options. Do not mess around with the already
+ * assigned numbers unless you know what you are doing */
+extern struct option ebt_original_options[];
+extern struct xtables_globals ebtables_globals;
+#define opts ebtables_globals.opts
+#define prog_name ebtables_globals.program_name
+#define prog_vers ebtables_globals.program_version
+
+static void print_help(void)
+{
+	fprintf(stderr, "%s: Translate ebtables command to nft syntax\n"
+			"no side effects occur, the translated command is written "
+			"to standard output.\n"
+			"A '#' followed by input means no translation "
+			"is available.\n", prog_name);
+	exit(0);
+}
+
+static int parse_rule_range(const char *argv, int *rule_nr, int *rule_nr_end)
+{
+	char *colon = strchr(argv, ':'), *buffer;
+
+	if (colon) {
+		*colon = '\0';
+		if (*(colon + 1) == '\0')
+			*rule_nr_end = -1; /* Until the last rule */
+		else {
+			*rule_nr_end = strtol(colon + 1, &buffer, 10);
+			if (*buffer != '\0' || *rule_nr_end == 0)
+				return -1;
+		}
+	}
+	if (colon == argv)
+		*rule_nr = 1; /* Beginning with the first rule */
+	else {
+		*rule_nr = strtol(argv, &buffer, 10);
+		if (*buffer != '\0' || *rule_nr == 0)
+			return -1;
+	}
+	if (!colon)
+		*rule_nr_end = *rule_nr;
+	return 0;
+}
+
+static void ebtables_parse_interface(const char *arg, char *vianame)
+{
+	unsigned char mask[IFNAMSIZ];
+	char *c;
+
+	xtables_parse_interface(arg, vianame, mask);
+
+	if ((c = strchr(vianame, '+'))) {
+		if (*(c + 1) != '\0')
+			xtables_error(PARAMETER_PROBLEM,
+				      "Spurious characters after '+' wildcard");
+	}
+}
+
+static void print_ebt_cmd(int argc, char *argv[])
+{
+	int i;
+
+	printf("# ");
+	for (i = 1; i < argc; i++)
+		printf("%s ", argv[i]);
+
+	printf("\n");
+}
+
+static int nft_rule_eb_xlate_add(struct nft_handle *h, const struct nft_xt_cmd_parse *p,
+				 const struct iptables_command_state *cs, bool append)
+{
+	struct xt_xlate *xl = xt_xlate_alloc(10240);
+	int ret;
+
+	if (append) {
+		xt_xlate_add(xl, "add rule bridge %s %s ", p->table, p->chain);
+	} else {
+		xt_xlate_add(xl, "insert rule bridge %s %s ", p->table, p->chain);
+	}
+
+	ret = h->ops->xlate(cs, xl);
+	if (ret)
+		printf("%s\n", xt_xlate_get(xl));
+
+	xt_xlate_free(xl);
+	return ret;
+}
+
+/* We use exec_style instead of #ifdef's because ebtables.so is a shared object. */
+static int do_commandeb_xlate(struct nft_handle *h, int argc, char *argv[], char **table)
+{
+	char *buffer;
+	int c, i;
+	int rule_nr = 0;
+	int rule_nr_end = 0;
+	int ret = 0;
+	unsigned int flags = 0;
+	struct iptables_command_state cs = {
+		.argv		= argv,
+		.eb.bitmask	= EBT_NOPROTO,
+	};
+	char command = 'h';
+	const char *chain = NULL;
+	int exec_style = EXEC_STYLE_PRG;
+	int selected_chain = -1;
+	struct xtables_rule_match *xtrm_i;
+	struct ebt_match *match;
+	struct nft_xt_cmd_parse p = {
+		.table          = *table,
+        };
+
+	/* prevent getopt to spoil our error reporting */
+	opterr = false;
+
+	printf("nft ");
+	/* Getopt saves the day */
+	while ((c = getopt_long(argc, argv,
+	   "-A:D:I:N:E:X::L::Z::F::P:Vhi:o:j:c:p:s:d:t:M:", opts, NULL)) != -1) {
+		cs.c = c;
+		cs.invert = ebt_invert;
+		switch (c) {
+		case 'A': /* Add a rule */
+		case 'D': /* Delete a rule */
+		case 'P': /* Define policy */
+		case 'I': /* Insert a rule */
+		case 'N': /* Make a user defined chain */
+		case 'E': /* Rename chain */
+		case 'X': /* Delete chain */
+			/* We allow -N chainname -P policy */
+			/* XXX: Not in ebtables-compat */
+			if (command == 'N' && c == 'P') {
+				command = c;
+				optind--; /* No table specified */
+				break;
+			}
+			if (OPT_COMMANDS)
+				xtables_error(PARAMETER_PROBLEM,
+					      "Multiple commands are not allowed");
+			command = c;
+			chain = optarg;
+			selected_chain = get_current_chain(chain);
+			p.chain = chain;
+			flags |= OPT_COMMAND;
+
+			if (c == 'N') {
+				printf("add chain bridge %s %s\n", p.table, p.chain);
+				ret = 1;
+				break;
+			} else if (c == 'X') {
+				printf("delete chain bridge %s %s\n", p.table, p.chain);
+				ret = 1;
+				break;
+			}
+
+			if (c == 'E') {
+				break;
+			} else if (c == 'D' && optind < argc && (argv[optind][0] != '-' || (argv[optind][1] >= '0' && argv[optind][1] <= '9'))) {
+				if (optind != argc - 1)
+					xtables_error(PARAMETER_PROBLEM,
+							 "No extra options allowed with -D start_nr[:end_nr]");
+				if (parse_rule_range(argv[optind], &rule_nr, &rule_nr_end))
+					xtables_error(PARAMETER_PROBLEM,
+							 "Problem with the specified rule number(s) '%s'", argv[optind]);
+				optind++;
+			} else if (c == 'I') {
+				if (optind >= argc || (argv[optind][0] == '-' && (argv[optind][1] < '0' || argv[optind][1] > '9')))
+					rule_nr = 1;
+				else {
+					rule_nr = parse_rule_number(argv[optind]);
+					optind++;
+				}
+				p.rulenum = rule_nr;
+			} else if (c == 'P') {
+				break;
+			}
+			break;
+		case 'L': /* List */
+			printf("list table bridge %s\n", p.table);
+			ret = 1;
+			break;
+		case 'F': /* Flush */
+			if (p.chain) {
+				printf("flush chain bridge %s %s\n", p.table, p.chain);
+			} else {
+				printf("flush table bridge %s\n", p.table);
+			}
+			ret = 1;
+			break;
+		case 'Z': /* Zero counters */
+			if (c == 'Z') {
+				if ((flags & OPT_ZERO) || (flags & OPT_COMMAND && command != 'L'))
+print_zero:
+					xtables_error(PARAMETER_PROBLEM,
+						      "Command -Z only allowed together with command -L");
+				flags |= OPT_ZERO;
+			} else {
+				if (flags & OPT_COMMAND)
+					xtables_error(PARAMETER_PROBLEM,
+						      "Multiple commands are not allowed");
+				command = c;
+				flags |= OPT_COMMAND;
+				if (flags & OPT_ZERO && c != 'L')
+					goto print_zero;
+			}
+			break;
+		case 'V': /* Version */
+			if (OPT_COMMANDS)
+				xtables_error(PARAMETER_PROBLEM,
+					      "Multiple commands are not allowed");
+			if (exec_style == EXEC_STYLE_DAEMON)
+				xtables_error(PARAMETER_PROBLEM,
+					      "%s %s\n", prog_name, prog_vers);
+			printf("%s %s\n", prog_name, prog_vers);
+			exit(0);
+		case 'h':
+			if (OPT_COMMANDS)
+				xtables_error(PARAMETER_PROBLEM,
+					      "Multiple commands are not allowed");
+			print_help();
+			break;
+		case 't': /* Table */
+			if (OPT_COMMANDS)
+				xtables_error(PARAMETER_PROBLEM,
+					      "Please put the -t option first");
+			ebt_check_option2(&flags, OPT_TABLE);
+			if (strlen(optarg) > EBT_TABLE_MAXNAMELEN - 1)
+				xtables_error(PARAMETER_PROBLEM,
+					      "Table name length cannot exceed %d characters",
+					      EBT_TABLE_MAXNAMELEN - 1);
+			*table = optarg;
+			p.table = optarg;
+			break;
+		case 'i': /* Input interface */
+		case 2  : /* Logical input interface */
+		case 'o': /* Output interface */
+		case 3  : /* Logical output interface */
+		case 'j': /* Target */
+		case 'p': /* Net family protocol */
+		case 's': /* Source mac */
+		case 'd': /* Destination mac */
+		case 'c': /* Set counters */
+			if (!OPT_COMMANDS)
+				xtables_error(PARAMETER_PROBLEM,
+					      "No command specified");
+			if (command != 'A' && command != 'D' && command != 'I')
+				xtables_error(PARAMETER_PROBLEM,
+					      "Command and option do not match");
+			if (c == 'i') {
+				ebt_check_option2(&flags, OPT_IN);
+				if (selected_chain > 2 && selected_chain < NF_BR_BROUTING)
+					xtables_error(PARAMETER_PROBLEM,
+						      "Use -i only in INPUT, FORWARD, PREROUTING and BROUTING chains");
+				if (ebt_check_inverse2(optarg, argc, argv))
+					cs.eb.invflags |= EBT_IIN;
+
+				ebtables_parse_interface(optarg, cs.eb.in);
+				break;
+			} else if (c == 2) {
+				ebt_check_option2(&flags, OPT_LOGICALIN);
+				if (selected_chain > 2 && selected_chain < NF_BR_BROUTING)
+					xtables_error(PARAMETER_PROBLEM,
+						      "Use --logical-in only in INPUT, FORWARD, PREROUTING and BROUTING chains");
+				if (ebt_check_inverse2(optarg, argc, argv))
+					cs.eb.invflags |= EBT_ILOGICALIN;
+
+				ebtables_parse_interface(optarg, cs.eb.logical_in);
+				break;
+			} else if (c == 'o') {
+				ebt_check_option2(&flags, OPT_OUT);
+				if (selected_chain < 2 || selected_chain == NF_BR_BROUTING)
+					xtables_error(PARAMETER_PROBLEM,
+						      "Use -o only in OUTPUT, FORWARD and POSTROUTING chains");
+				if (ebt_check_inverse2(optarg, argc, argv))
+					cs.eb.invflags |= EBT_IOUT;
+
+				ebtables_parse_interface(optarg, cs.eb.out);
+				break;
+			} else if (c == 3) {
+				ebt_check_option2(&flags, OPT_LOGICALOUT);
+				if (selected_chain < 2 || selected_chain == NF_BR_BROUTING)
+					xtables_error(PARAMETER_PROBLEM,
+						      "Use --logical-out only in OUTPUT, FORWARD and POSTROUTING chains");
+				if (ebt_check_inverse2(optarg, argc, argv))
+					cs.eb.invflags |= EBT_ILOGICALOUT;
+
+				ebtables_parse_interface(optarg, cs.eb.logical_out);
+				break;
+			} else if (c == 'j') {
+				ebt_check_option2(&flags, OPT_JUMP);
+				command_jump(&cs, optarg);
+				break;
+			} else if (c == 's') {
+				ebt_check_option2(&flags, OPT_SOURCE);
+				if (ebt_check_inverse2(optarg, argc, argv))
+					cs.eb.invflags |= EBT_ISOURCE;
+
+				if (xtables_parse_mac_and_mask(optarg,
+							       cs.eb.sourcemac,
+							       cs.eb.sourcemsk))
+					xtables_error(PARAMETER_PROBLEM, "Problem with specified source mac '%s'", optarg);
+				cs.eb.bitmask |= EBT_SOURCEMAC;
+				break;
+			} else if (c == 'd') {
+				ebt_check_option2(&flags, OPT_DEST);
+				if (ebt_check_inverse2(optarg, argc, argv))
+					cs.eb.invflags |= EBT_IDEST;
+
+				if (xtables_parse_mac_and_mask(optarg,
+							       cs.eb.destmac,
+							       cs.eb.destmsk))
+					xtables_error(PARAMETER_PROBLEM, "Problem with specified destination mac '%s'", optarg);
+				cs.eb.bitmask |= EBT_DESTMAC;
+				break;
+			} else if (c == 'c') {
+				ebt_check_option2(&flags, OPT_COUNT);
+				if (ebt_check_inverse2(optarg, argc, argv))
+					xtables_error(PARAMETER_PROBLEM,
+						      "Unexpected '!' after -c");
+				if (optind >= argc || optarg[0] == '-' || argv[optind][0] == '-')
+					xtables_error(PARAMETER_PROBLEM,
+						      "Option -c needs 2 arguments");
+
+				cs.counters.pcnt = strtoull(optarg, &buffer, 10);
+				if (*buffer != '\0')
+					xtables_error(PARAMETER_PROBLEM,
+						      "Packet counter '%s' invalid",
+						      optarg);
+				cs.counters.bcnt = strtoull(argv[optind], &buffer, 10);
+				if (*buffer != '\0')
+					xtables_error(PARAMETER_PROBLEM,
+						      "Packet counter '%s' invalid",
+						      argv[optind]);
+				optind++;
+				break;
+			}
+			ebt_check_option2(&flags, OPT_PROTOCOL);
+			if (ebt_check_inverse2(optarg, argc, argv))
+				cs.eb.invflags |= EBT_IPROTO;
+
+			cs.eb.bitmask &= ~((unsigned int)EBT_NOPROTO);
+			i = strtol(optarg, &buffer, 16);
+			if (*buffer == '\0' && (i < 0 || i > 0xFFFF))
+				xtables_error(PARAMETER_PROBLEM,
+					      "Problem with the specified protocol");
+			if (*buffer != '\0') {
+				struct xt_ethertypeent *ent;
+
+				if (!strcasecmp(optarg, "LENGTH")) {
+					cs.eb.bitmask |= EBT_802_3;
+					break;
+				}
+				ent = xtables_getethertypebyname(optarg);
+				if (!ent)
+					xtables_error(PARAMETER_PROBLEM,
+						      "Problem with the specified Ethernet protocol '%s', perhaps "XT_PATH_ETHERTYPES " is missing", optarg);
+				cs.eb.ethproto = ent->e_ethertype;
+			} else
+				cs.eb.ethproto = i;
+
+			if (cs.eb.ethproto < 0x0600)
+				xtables_error(PARAMETER_PROBLEM,
+					      "Sorry, protocols have values above or equal to 0x0600");
+			break;
+		case 4  : /* Lc */
+			ebt_check_option2(&flags, LIST_C);
+			if (command != 'L')
+				xtables_error(PARAMETER_PROBLEM,
+					      "Use --Lc with -L");
+			flags |= LIST_C;
+			break;
+		case 5  : /* Ln */
+			ebt_check_option2(&flags, LIST_N);
+			if (command != 'L')
+				xtables_error(PARAMETER_PROBLEM,
+					      "Use --Ln with -L");
+			if (flags & LIST_X)
+				xtables_error(PARAMETER_PROBLEM,
+					      "--Lx is not compatible with --Ln");
+			flags |= LIST_N;
+			break;
+		case 6  : /* Lx */
+			ebt_check_option2(&flags, LIST_X);
+			if (command != 'L')
+				xtables_error(PARAMETER_PROBLEM,
+					      "Use --Lx with -L");
+			if (flags & LIST_N)
+				xtables_error(PARAMETER_PROBLEM,
+					      "--Lx is not compatible with --Ln");
+			flags |= LIST_X;
+			break;
+		case 12 : /* Lmac2 */
+			ebt_check_option2(&flags, LIST_MAC2);
+			if (command != 'L')
+				xtables_error(PARAMETER_PROBLEM,
+					       "Use --Lmac2 with -L");
+			flags |= LIST_MAC2;
+			break;
+		case 1 :
+			if (!strcmp(optarg, "!"))
+				ebt_check_inverse2(optarg, argc, argv);
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					      "Bad argument : '%s'", optarg);
+			/* ebt_ebt_check_inverse2() did optind++ */
+			optind--;
+			continue;
+		default:
+			ebt_check_inverse2(optarg, argc, argv);
+
+			if (ebt_command_default(&cs))
+				xtables_error(PARAMETER_PROBLEM,
+					      "Unknown argument: '%s'",
+					      argv[optind - 1]);
+
+			if (command != 'A' && command != 'I' &&
+			    command != 'D')
+				xtables_error(PARAMETER_PROBLEM,
+					      "Extensions only for -A, -I, -D");
+		}
+		ebt_invert = 0;
+	}
+
+	/* Do the final checks */
+	if (command == 'A' || command == 'I' || command == 'D') {
+		for (xtrm_i = cs.matches; xtrm_i; xtrm_i = xtrm_i->next)
+			xtables_option_mfcall(xtrm_i->match);
+
+		for (match = cs.match_list; match; match = match->next) {
+			if (match->ismatch)
+				continue;
+
+			xtables_option_tfcall(match->u.watcher);
+		}
+
+		if (cs.target != NULL)
+			xtables_option_tfcall(cs.target);
+	}
+
+	cs.eb.ethproto = htons(cs.eb.ethproto);
+
+	if (command == 'P') {
+		return 0;
+	} else if (command == 'A') {
+		ret = nft_rule_eb_xlate_add(h, &p, &cs, true);
+		if (!ret)
+			print_ebt_cmd(argc, argv);
+	} else if (command == 'I') {
+		ret = nft_rule_eb_xlate_add(h, &p, &cs, false);
+		if (!ret)
+			print_ebt_cmd(argc, argv);
+	}
+
+	ebt_cs_clean(&cs);
+	return ret;
+}
+
+static int dummy_compat_rev(const char *name, uint8_t rev, int opt)
+{
+	return 1;
+}
+
+int xtables_eb_xlate_main(int argc, char *argv[])
+{
+	int ret;
+	char *table = "filter";
+	struct nft_handle h;
+
+	nft_init_eb(&h, argv[0]);
+	ebtables_globals.compat_rev = dummy_compat_rev;
+
+	ret = do_commandeb_xlate(&h, argc, argv, &table);
+	if (!ret)
+		fprintf(stderr, "Translation not implemented\n");
+
+	exit(!ret);
+}
+
diff --git a/iptables/xtables-eb.c b/iptables/xtables-eb.c
new file mode 100644
index 0000000..cfa9317
--- /dev/null
+++ b/iptables/xtables-eb.c
@@ -0,0 +1,1238 @@
+/*
+ * ebtables.c, v2.0 July 2002
+ *
+ * Author: Bart De Schuymer
+ *
+ *  This code was stongly inspired on the iptables code which is
+ *  Copyright (C) 1999 Paul `Rusty' Russell & Michael J. Neuling
+ *
+ * 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 "config.h"
+#include <ctype.h>
+#include <errno.h>
+#include <getopt.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <net/if.h>
+#include <netinet/ether.h>
+#include <iptables.h>
+#include <xtables.h>
+
+#include <linux/netfilter_bridge.h>
+#include <linux/netfilter/nf_tables.h>
+#include <libiptc/libxtc.h>
+#include "xshared.h"
+#include "nft.h"
+#include "nft-bridge.h"
+
+/*
+ * From include/ebtables_u.h
+ */
+#define ebt_check_option2(flags, mask) EBT_CHECK_OPTION(flags, mask)
+
+/*
+ * From useful_functions.c
+ */
+
+/* 0: default
+ * 1: the inverse '!' of the option has already been specified */
+int ebt_invert = 0;
+
+static int ebt_check_inverse2(const char option[], int argc, char **argv)
+{
+	if (!option)
+		return ebt_invert;
+	if (strcmp(option, "!") == 0) {
+		if (ebt_invert == 1)
+			xtables_error(PARAMETER_PROBLEM,
+				      "Double use of '!' not allowed");
+		if (optind >= argc)
+			optarg = NULL;
+		else
+			optarg = argv[optind];
+		optind++;
+		ebt_invert = 1;
+		return 1;
+	}
+	return ebt_invert;
+}
+
+/*
+ * Glue code to use libxtables
+ */
+static int parse_rule_number(const char *rule)
+{
+	unsigned int rule_nr;
+
+	if (!xtables_strtoui(rule, NULL, &rule_nr, 1, INT_MAX))
+		xtables_error(PARAMETER_PROBLEM,
+			      "Invalid rule number `%s'", rule);
+
+	return rule_nr;
+}
+
+static int
+append_entry(struct nft_handle *h,
+	     const char *chain,
+	     const char *table,
+	     struct iptables_command_state *cs,
+	     int rule_nr,
+	     bool verbose, bool append)
+{
+	int ret = 1;
+
+	if (append)
+		ret = nft_cmd_rule_append(h, chain, table, cs, NULL, verbose);
+	else
+		ret = nft_cmd_rule_insert(h, chain, table, cs, rule_nr, verbose);
+
+	return ret;
+}
+
+static int
+delete_entry(struct nft_handle *h,
+	     const char *chain,
+	     const char *table,
+	     struct iptables_command_state *cs,
+	     int rule_nr,
+	     int rule_nr_end,
+	     bool verbose)
+{
+	int ret = 1;
+
+	if (rule_nr == -1)
+		ret = nft_cmd_rule_delete(h, chain, table, cs, verbose);
+	else {
+		do {
+			ret = nft_cmd_rule_delete_num(h, chain, table,
+						  rule_nr, verbose);
+			rule_nr++;
+		} while (rule_nr < rule_nr_end);
+	}
+
+	return ret;
+}
+
+int ebt_get_current_chain(const char *chain)
+{
+	if (!chain)
+		return -1;
+
+	if (strcmp(chain, "PREROUTING") == 0)
+		return NF_BR_PRE_ROUTING;
+	else if (strcmp(chain, "INPUT") == 0)
+		return NF_BR_LOCAL_IN;
+	else if (strcmp(chain, "FORWARD") == 0)
+		return NF_BR_FORWARD;
+	else if (strcmp(chain, "OUTPUT") == 0)
+		return NF_BR_LOCAL_OUT;
+	else if (strcmp(chain, "POSTROUTING") == 0)
+		return NF_BR_POST_ROUTING;
+
+	/* placeholder for user defined chain */
+	return NF_BR_NUMHOOKS;
+}
+
+/*
+ * The original ebtables parser
+ */
+
+/* Checks whether a command has already been specified */
+#define OPT_COMMANDS (flags & OPT_COMMAND || flags & OPT_ZERO)
+
+#define OPT_COMMAND	0x01
+#define OPT_TABLE	0x02
+#define OPT_IN		0x04
+#define OPT_OUT		0x08
+#define OPT_JUMP	0x10
+#define OPT_PROTOCOL	0x20
+#define OPT_SOURCE	0x40
+#define OPT_DEST	0x80
+#define OPT_ZERO	0x100
+#define OPT_LOGICALIN	0x200
+#define OPT_LOGICALOUT	0x400
+#define OPT_KERNELDATA	0x800 /* This value is also defined in ebtablesd.c */
+#define OPT_COUNT	0x1000 /* This value is also defined in libebtc.c */
+#define OPT_CNT_INCR	0x2000 /* This value is also defined in libebtc.c */
+#define OPT_CNT_DECR	0x4000 /* This value is also defined in libebtc.c */
+
+/* Default command line options. Do not mess around with the already
+ * assigned numbers unless you know what you are doing */
+struct option ebt_original_options[] =
+{
+	{ "append"         , required_argument, 0, 'A' },
+	{ "insert"         , required_argument, 0, 'I' },
+	{ "delete"         , required_argument, 0, 'D' },
+	{ "list"           , optional_argument, 0, 'L' },
+	{ "Lc"             , no_argument      , 0, 4   },
+	{ "Ln"             , no_argument      , 0, 5   },
+	{ "Lx"             , no_argument      , 0, 6   },
+	{ "Lmac2"          , no_argument      , 0, 12  },
+	{ "zero"           , optional_argument, 0, 'Z' },
+	{ "flush"          , optional_argument, 0, 'F' },
+	{ "policy"         , required_argument, 0, 'P' },
+	{ "in-interface"   , required_argument, 0, 'i' },
+	{ "in-if"          , required_argument, 0, 'i' },
+	{ "logical-in"     , required_argument, 0, 2   },
+	{ "logical-out"    , required_argument, 0, 3   },
+	{ "out-interface"  , required_argument, 0, 'o' },
+	{ "out-if"         , required_argument, 0, 'o' },
+	{ "version"        , no_argument      , 0, 'V' },
+	{ "help"           , no_argument      , 0, 'h' },
+	{ "jump"           , required_argument, 0, 'j' },
+	{ "set-counters"   , required_argument, 0, 'c' },
+	{ "change-counters", required_argument, 0, 'C' },
+	{ "proto"          , required_argument, 0, 'p' },
+	{ "protocol"       , required_argument, 0, 'p' },
+	{ "db"             , required_argument, 0, 'b' },
+	{ "source"         , required_argument, 0, 's' },
+	{ "src"            , required_argument, 0, 's' },
+	{ "destination"    , required_argument, 0, 'd' },
+	{ "dst"            , required_argument, 0, 'd' },
+	{ "table"          , required_argument, 0, 't' },
+	{ "modprobe"       , required_argument, 0, 'M' },
+	{ "new-chain"      , required_argument, 0, 'N' },
+	{ "rename-chain"   , required_argument, 0, 'E' },
+	{ "delete-chain"   , optional_argument, 0, 'X' },
+	{ "atomic-init"    , no_argument      , 0, 7   },
+	{ "atomic-commit"  , no_argument      , 0, 8   },
+	{ "atomic-file"    , required_argument, 0, 9   },
+	{ "atomic-save"    , no_argument      , 0, 10  },
+	{ "init-table"     , no_argument      , 0, 11  },
+	{ "concurrent"     , no_argument      , 0, 13  },
+	{ 0 }
+};
+
+extern void xtables_exit_error(enum xtables_exittype status, const char *msg, ...) __attribute__((noreturn, format(printf,2,3)));
+struct xtables_globals ebtables_globals = {
+	.option_offset 		= 0,
+	.program_version	= PACKAGE_VERSION,
+	.orig_opts		= ebt_original_options,
+	.exit_err		= xtables_exit_error,
+	.compat_rev		= nft_compatible_revision,
+};
+
+#define opts ebtables_globals.opts
+#define prog_name ebtables_globals.program_name
+#define prog_vers ebtables_globals.program_version
+
+/*
+ * From libebtc.c
+ */
+
+/* Prints all registered extensions */
+static void ebt_list_extensions(const struct xtables_target *t,
+				const struct xtables_rule_match *m)
+{
+	printf("%s v%s\n", prog_name, prog_vers);
+	printf("Loaded userspace extensions:\n");
+	/*printf("\nLoaded tables:\n");
+        while (tbl) {
+		printf("%s\n", tbl->name);
+                tbl = tbl->next;
+	}*/
+	printf("\nLoaded targets:\n");
+        for (t = xtables_targets; t; t = t->next) {
+		printf("%s\n", t->name);
+	}
+	printf("\nLoaded matches:\n");
+        for (; m != NULL; m = m->next)
+		printf("%s\n", m->match->name);
+	/*printf("\nLoaded watchers:\n");
+        while (w) {
+		printf("%s\n", w->name);
+                w = w->next;
+	}*/
+}
+
+#define OPTION_OFFSET 256
+static struct option *merge_options(struct option *oldopts,
+				    const struct option *newopts,
+				    unsigned int *options_offset)
+{
+	unsigned int num_old, num_new, i;
+	struct option *merge;
+
+	if (!newopts || !oldopts || !options_offset)
+		return oldopts;
+	for (num_old = 0; oldopts[num_old].name; num_old++);
+	for (num_new = 0; newopts[num_new].name; num_new++);
+
+	ebtables_globals.option_offset += OPTION_OFFSET;
+	*options_offset = ebtables_globals.option_offset;
+
+	merge = malloc(sizeof(struct option) * (num_new + num_old + 1));
+	if (!merge)
+		return NULL;
+	memcpy(merge, oldopts, num_old * sizeof(struct option));
+	for (i = 0; i < num_new; i++) {
+		merge[num_old + i] = newopts[i];
+		merge[num_old + i].val += *options_offset;
+	}
+	memset(merge + num_old + num_new, 0, sizeof(struct option));
+	/* Only free dynamically allocated stuff */
+	if (oldopts != ebt_original_options)
+		free(oldopts);
+
+	return merge;
+}
+
+static void print_help(const struct xtables_target *t,
+		       const struct xtables_rule_match *m, const char *table)
+{
+	printf("%s %s\n", prog_name, prog_vers);
+	printf(
+"Usage:\n"
+"ebtables -[ADI] chain rule-specification [options]\n"
+"ebtables -P chain target\n"
+"ebtables -[LFZ] [chain]\n"
+"ebtables -[NX] [chain]\n"
+"ebtables -E old-chain-name new-chain-name\n\n"
+"Commands:\n"
+"--append -A chain             : append to chain\n"
+"--delete -D chain             : delete matching rule from chain\n"
+"--delete -D chain rulenum     : delete rule at position rulenum from chain\n"
+"--change-counters -C chain\n"
+"          [rulenum] pcnt bcnt : change counters of existing rule\n"
+"--insert -I chain rulenum     : insert rule at position rulenum in chain\n"
+"--list   -L [chain]           : list the rules in a chain or in all chains\n"
+"--flush  -F [chain]           : delete all rules in chain or in all chains\n"
+"--init-table                  : replace the kernel table with the initial table\n"
+"--zero   -Z [chain]           : put counters on zero in chain or in all chains\n"
+"--policy -P chain target      : change policy on chain to target\n"
+"--new-chain -N chain          : create a user defined chain\n"
+"--rename-chain -E old new     : rename a chain\n"
+"--delete-chain -X [chain]     : delete a user defined chain\n"
+"--atomic-commit               : update the kernel w/t table contained in <FILE>\n"
+"--atomic-init                 : put the initial kernel table into <FILE>\n"
+"--atomic-save                 : put the current kernel table into <FILE>\n"
+"--atomic-file file            : set <FILE> to file\n\n"
+"Options:\n"
+"--proto  -p [!] proto         : protocol hexadecimal, by name or LENGTH\n"
+"--src    -s [!] address[/mask]: source mac address\n"
+"--dst    -d [!] address[/mask]: destination mac address\n"
+"--in-if  -i [!] name[+]       : network input interface name\n"
+"--out-if -o [!] name[+]       : network output interface name\n"
+"--logical-in  [!] name[+]     : logical bridge input interface name\n"
+"--logical-out [!] name[+]     : logical bridge output interface name\n"
+"--set-counters -c chain\n"
+"          pcnt bcnt           : set the counters of the to be added rule\n"
+"--modprobe -M program         : try to insert modules using this program\n"
+"--concurrent                  : use a file lock to support concurrent scripts\n"
+"--version -V                  : print package version\n\n"
+"Environment variable:\n"
+/*ATOMIC_ENV_VARIABLE "          : if set <FILE> (see above) will equal its value"*/
+"\n\n");
+	for (; m != NULL; m = m->next) {
+		printf("\n");
+		m->match->help();
+	}
+	if (t != NULL) {
+		printf("\n");
+		t->help();
+	}
+
+//	if (table->help)
+//		table->help(ebt_hooknames);
+}
+
+/* Execute command L */
+static int list_rules(struct nft_handle *h, const char *chain, const char *table,
+		      int rule_nr, int verbose, int numeric, int expanded,
+		      int linenumbers, int counters)
+{
+	unsigned int format;
+
+	format = FMT_OPTIONS | FMT_C_COUNTS;
+	if (verbose)
+		format |= FMT_VIA;
+
+	if (numeric)
+		format |= FMT_NUMERIC;
+
+	if (!expanded)
+		format |= FMT_KILOMEGAGIGA;
+
+	if (linenumbers)
+		format |= FMT_LINENUMBERS;
+
+	if (!counters)
+		format |= FMT_NOCOUNTS;
+
+	return nft_cmd_rule_list(h, chain, table, rule_nr, format);
+}
+
+static int parse_rule_range(const char *argv, int *rule_nr, int *rule_nr_end)
+{
+	char *colon = strchr(argv, ':'), *buffer;
+
+	if (colon) {
+		*colon = '\0';
+		if (*(colon + 1) == '\0')
+			*rule_nr_end = -1; /* Until the last rule */
+		else {
+			*rule_nr_end = strtol(colon + 1, &buffer, 10);
+			if (*buffer != '\0' || *rule_nr_end == 0)
+				return -1;
+		}
+	}
+	if (colon == argv)
+		*rule_nr = 1; /* Beginning with the first rule */
+	else {
+		*rule_nr = strtol(argv, &buffer, 10);
+		if (*buffer != '\0' || *rule_nr == 0)
+			return -1;
+	}
+	if (!colon)
+		*rule_nr_end = *rule_nr;
+	return 0;
+}
+
+/* Incrementing or decrementing rules in daemon mode is not supported as the
+ * involved code overload is not worth it (too annoying to take the increased
+ * counters in the kernel into account). */
+static int parse_change_counters_rule(int argc, char **argv, int *rule_nr, int *rule_nr_end, struct iptables_command_state *cs)
+{
+	char *buffer;
+	int ret = 0;
+
+	if (optind + 1 >= argc || argv[optind][0] == '-' || argv[optind + 1][0] == '-')
+		xtables_error(PARAMETER_PROBLEM,
+			      "The command -C needs at least 2 arguments");
+	if (optind + 2 < argc && (argv[optind + 2][0] != '-' || (argv[optind + 2][1] >= '0' && argv[optind + 2][1] <= '9'))) {
+		if (optind + 3 != argc)
+			xtables_error(PARAMETER_PROBLEM,
+				      "No extra options allowed with -C start_nr[:end_nr] pcnt bcnt");
+		if (parse_rule_range(argv[optind], rule_nr, rule_nr_end))
+			xtables_error(PARAMETER_PROBLEM,
+				      "Something is wrong with the rule number specification '%s'", argv[optind]);
+		optind++;
+	}
+
+	if (argv[optind][0] == '+') {
+		ret += 1;
+		cs->counters.pcnt = strtoull(argv[optind] + 1, &buffer, 10);
+	} else if (argv[optind][0] == '-') {
+		ret += 2;
+		cs->counters.pcnt = strtoull(argv[optind] + 1, &buffer, 10);
+	} else
+		cs->counters.pcnt = strtoull(argv[optind], &buffer, 10);
+
+	if (*buffer != '\0')
+		goto invalid;
+	optind++;
+	if (argv[optind][0] == '+') {
+		ret += 3;
+		cs->counters.bcnt = strtoull(argv[optind] + 1, &buffer, 10);
+	} else if (argv[optind][0] == '-') {
+		ret += 6;
+		cs->counters.bcnt = strtoull(argv[optind] + 1, &buffer, 10);
+	} else
+		cs->counters.bcnt = strtoull(argv[optind], &buffer, 10);
+
+	if (*buffer != '\0')
+		goto invalid;
+	optind++;
+	return ret;
+invalid:
+	xtables_error(PARAMETER_PROBLEM,"Packet counter '%s' invalid", argv[optind]);
+}
+
+static void ebtables_parse_interface(const char *arg, char *vianame)
+{
+	unsigned char mask[IFNAMSIZ];
+	char *c;
+
+	xtables_parse_interface(arg, vianame, mask);
+
+	if ((c = strchr(vianame, '+'))) {
+		if (*(c + 1) != '\0')
+			xtables_error(PARAMETER_PROBLEM,
+				      "Spurious characters after '+' wildcard");
+	}
+}
+
+/* This code is very similar to iptables/xtables.c:command_match() */
+static void ebt_load_match(const char *name)
+{
+	struct xtables_match *m;
+	size_t size;
+
+	m = xtables_find_match(name, XTF_TRY_LOAD, NULL);
+	if (m == NULL) {
+		fprintf(stderr, "Unable to load %s match\n", name);
+		return;
+	}
+
+	size = XT_ALIGN(sizeof(struct xt_entry_match)) + m->size;
+	m->m = xtables_calloc(1, size);
+	m->m->u.match_size = size;
+	strcpy(m->m->u.user.name, m->name);
+	m->m->u.user.revision = m->revision;
+	xs_init_match(m);
+
+	opts = merge_options(opts, m->extra_opts, &m->option_offset);
+	if (opts == NULL)
+		xtables_error(OTHER_PROBLEM, "Can't alloc memory");
+}
+
+static void __ebt_load_watcher(const char *name, const char *typename)
+{
+	struct xtables_target *watcher;
+	size_t size;
+
+	watcher = xtables_find_target(name, XTF_TRY_LOAD);
+	if (!watcher) {
+		fprintf(stderr, "Unable to load %s %s\n", name, typename);
+		return;
+	}
+
+	size = XT_ALIGN(sizeof(struct xt_entry_target)) + watcher->size;
+
+	watcher->t = xtables_calloc(1, size);
+	watcher->t->u.target_size = size;
+	snprintf(watcher->t->u.user.name,
+		sizeof(watcher->t->u.user.name), "%s", name);
+	watcher->t->u.user.name[sizeof(watcher->t->u.user.name)-1] = '\0';
+	watcher->t->u.user.revision = watcher->revision;
+
+	xs_init_target(watcher);
+
+	opts = merge_options(opts, watcher->extra_opts,
+			     &watcher->option_offset);
+	if (opts == NULL)
+		xtables_error(OTHER_PROBLEM, "Can't alloc memory");
+}
+
+static void ebt_load_watcher(const char *name)
+{
+	return __ebt_load_watcher(name, "watcher");
+}
+
+static void ebt_load_target(const char *name)
+{
+	return __ebt_load_watcher(name, "target");
+}
+
+void ebt_load_match_extensions(void)
+{
+	opts = ebt_original_options;
+	ebt_load_match("802_3");
+	ebt_load_match("arp");
+	ebt_load_match("ip");
+	ebt_load_match("ip6");
+	ebt_load_match("mark_m");
+	ebt_load_match("limit");
+	ebt_load_match("pkttype");
+	ebt_load_match("vlan");
+	ebt_load_match("stp");
+	ebt_load_match("among");
+
+	ebt_load_watcher("log");
+	ebt_load_watcher("nflog");
+
+	ebt_load_target("mark");
+	ebt_load_target("dnat");
+	ebt_load_target("snat");
+	ebt_load_target("arpreply");
+	ebt_load_target("redirect");
+	ebt_load_target("standard");
+}
+
+void ebt_add_match(struct xtables_match *m,
+		   struct iptables_command_state *cs)
+{
+	struct xtables_rule_match **rule_matches = &cs->matches;
+	struct xtables_match *newm;
+	struct ebt_match *newnode, **matchp;
+	struct xt_entry_match *m2;
+
+	newm = xtables_find_match(m->name, XTF_LOAD_MUST_SUCCEED, rule_matches);
+	if (newm == NULL)
+		xtables_error(OTHER_PROBLEM,
+			      "Unable to add match %s", m->name);
+
+	m2 = xtables_calloc(1, newm->m->u.match_size);
+	memcpy(m2, newm->m, newm->m->u.match_size);
+	memset(newm->m->data, 0, newm->size);
+	xs_init_match(newm);
+	newm->m = m2;
+
+	newm->mflags = m->mflags;
+	m->mflags = 0;
+
+	/* glue code for watchers */
+	newnode = calloc(1, sizeof(struct ebt_match));
+	if (newnode == NULL)
+		xtables_error(OTHER_PROBLEM, "Unable to alloc memory");
+
+	newnode->ismatch = true;
+	newnode->u.match = newm;
+
+	for (matchp = &cs->match_list; *matchp; matchp = &(*matchp)->next)
+		;
+	*matchp = newnode;
+}
+
+void ebt_add_watcher(struct xtables_target *watcher,
+		     struct iptables_command_state *cs)
+{
+	struct ebt_match *newnode, **matchp;
+	struct xtables_target *clone;
+
+	clone = xtables_malloc(sizeof(struct xtables_target));
+	memcpy(clone, watcher, sizeof(struct xtables_target));
+	clone->udata = NULL;
+	clone->tflags = watcher->tflags;
+	clone->next = clone;
+
+	clone->t = xtables_calloc(1, watcher->t->u.target_size);
+	memcpy(clone->t, watcher->t, watcher->t->u.target_size);
+
+	memset(watcher->t->data, 0, watcher->size);
+	xs_init_target(watcher);
+	watcher->tflags = 0;
+
+
+	newnode = calloc(1, sizeof(struct ebt_match));
+	if (newnode == NULL)
+		xtables_error(OTHER_PROBLEM, "Unable to alloc memory");
+
+	newnode->u.watcher = clone;
+
+	for (matchp = &cs->match_list; *matchp; matchp = &(*matchp)->next)
+		;
+	*matchp = newnode;
+}
+
+int ebt_command_default(struct iptables_command_state *cs)
+{
+	struct xtables_target *t = cs->target;
+	struct xtables_match *m;
+	struct ebt_match *matchp;
+
+	/* Is it a target option? */
+	if (t && t->parse) {
+		if (t->parse(cs->c - t->option_offset, cs->argv,
+			     ebt_invert, &t->tflags, NULL, &t->t))
+			return 0;
+	}
+
+	/* check previously added matches/watchers to this rule first */
+	for (matchp = cs->match_list; matchp; matchp = matchp->next) {
+		if (matchp->ismatch) {
+			m = matchp->u.match;
+			if (m->parse &&
+			    m->parse(cs->c - m->option_offset, cs->argv,
+				     ebt_invert, &m->mflags, NULL, &m->m))
+				return 0;
+		} else {
+			t = matchp->u.watcher;
+			if (t->parse &&
+			    t->parse(cs->c - t->option_offset, cs->argv,
+				     ebt_invert, &t->tflags, NULL, &t->t))
+				return 0;
+		}
+	}
+
+	/* Is it a match_option? */
+	for (m = xtables_matches; m; m = m->next) {
+		if (m->parse &&
+		    m->parse(cs->c - m->option_offset, cs->argv,
+			     ebt_invert, &m->mflags, NULL, &m->m)) {
+			ebt_add_match(m, cs);
+			return 0;
+		}
+	}
+
+	/* Is it a watcher option? */
+	for (t = xtables_targets; t; t = t->next) {
+		if (t->parse &&
+		    t->parse(cs->c - t->option_offset, cs->argv,
+			     ebt_invert, &t->tflags, NULL, &t->t)) {
+			ebt_add_watcher(t, cs);
+			return 0;
+		}
+	}
+	return 1;
+}
+
+int nft_init_eb(struct nft_handle *h, const char *pname)
+{
+	ebtables_globals.program_name = pname;
+	if (xtables_init_all(&ebtables_globals, NFPROTO_BRIDGE) < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize ebtables-compat\n",
+			ebtables_globals.program_name,
+			ebtables_globals.program_version);
+		exit(1);
+	}
+
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+	init_extensionsb();
+#endif
+
+	if (nft_init(h, NFPROTO_BRIDGE, xtables_bridge) < 0)
+		xtables_error(OTHER_PROBLEM,
+			      "Could not initialize nftables layer.");
+
+	/* manually registering ebt matches, given the original ebtables parser
+	 * don't use '-m matchname' and the match can't be loaded dynamically when
+	 * the user calls it.
+	 */
+	ebt_load_match_extensions();
+
+	return 0;
+}
+
+void nft_fini_eb(struct nft_handle *h)
+{
+	struct xtables_match *match;
+	struct xtables_target *target;
+
+	for (match = xtables_matches; match; match = match->next) {
+		free(match->m);
+	}
+	for (target = xtables_targets; target; target = target->next) {
+		free(target->t);
+	}
+
+	free(opts);
+
+	nft_fini(h);
+	xtables_fini();
+}
+
+int do_commandeb(struct nft_handle *h, int argc, char *argv[], char **table,
+		 bool restore)
+{
+	char *buffer;
+	int c, i;
+	int chcounter = 0; /* Needed for -C */
+	int rule_nr = 0;
+	int rule_nr_end = 0;
+	int ret = 0;
+	unsigned int flags = 0;
+	struct xtables_target *t;
+	struct iptables_command_state cs = {
+		.argv = argv,
+		.jumpto	= "",
+		.eb.bitmask = EBT_NOPROTO,
+	};
+	char command = 'h';
+	const char *chain = NULL;
+	const char *policy = NULL;
+	int selected_chain = -1;
+	struct xtables_rule_match *xtrm_i;
+	struct ebt_match *match;
+	bool table_set = false;
+
+	/* prevent getopt to spoil our error reporting */
+	optind = 0;
+	opterr = false;
+
+	/* Getopt saves the day */
+	while ((c = getopt_long(argc, argv,
+	   "-A:D:C:I:N:E:X::L::Z::F::P:Vhi:o:j:c:p:s:d:t:M:", opts, NULL)) != -1) {
+		cs.c = c;
+		cs.invert = ebt_invert;
+		switch (c) {
+
+		case 'A': /* Add a rule */
+		case 'D': /* Delete a rule */
+		case 'C': /* Change counters */
+		case 'P': /* Define policy */
+		case 'I': /* Insert a rule */
+		case 'N': /* Make a user defined chain */
+		case 'E': /* Rename chain */
+		case 'X': /* Delete chain */
+			/* We allow -N chainname -P policy */
+			if (command == 'N' && c == 'P') {
+				command = c;
+				optind--; /* No table specified */
+				goto handle_P;
+			}
+			if (OPT_COMMANDS)
+				xtables_error(PARAMETER_PROBLEM,
+					      "Multiple commands are not allowed");
+
+			command = c;
+			if (optarg && (optarg[0] == '-' || !strcmp(optarg, "!")))
+				xtables_error(PARAMETER_PROBLEM, "No chain name specified");
+			chain = optarg;
+			selected_chain = ebt_get_current_chain(chain);
+			flags |= OPT_COMMAND;
+
+			if (c == 'N') {
+				ret = nft_cmd_chain_user_add(h, chain, *table);
+				break;
+			} else if (c == 'X') {
+				/* X arg is optional, optarg is NULL */
+				if (!chain && optind < argc && argv[optind][0] != '-') {
+					chain = argv[optind];
+					optind++;
+				}
+				ret = nft_cmd_chain_user_del(h, chain, *table, 0);
+				break;
+			}
+
+			if (c == 'E') {
+				if (optind >= argc)
+					xtables_error(PARAMETER_PROBLEM, "No new chain name specified");
+				else if (optind < argc - 1)
+					xtables_error(PARAMETER_PROBLEM, "No extra options allowed with -E");
+				else if (strlen(argv[optind]) >= NFT_CHAIN_MAXNAMELEN)
+					xtables_error(PARAMETER_PROBLEM, "Chain name length can't exceed %d"" characters", NFT_CHAIN_MAXNAMELEN - 1);
+				else if (strchr(argv[optind], ' ') != NULL)
+					xtables_error(PARAMETER_PROBLEM, "Use of ' ' not allowed in chain names");
+
+				errno = 0;
+				ret = nft_cmd_chain_user_rename(h, chain, *table,
+							    argv[optind]);
+				if (ret != 0 && errno == ENOENT)
+					xtables_error(PARAMETER_PROBLEM, "Chain '%s' doesn't exists", chain);
+
+				optind++;
+				break;
+			} else if (c == 'D' && optind < argc && (argv[optind][0] != '-' || (argv[optind][1] >= '0' && argv[optind][1] <= '9'))) {
+				if (optind != argc - 1)
+					xtables_error(PARAMETER_PROBLEM,
+							 "No extra options allowed with -D start_nr[:end_nr]");
+				if (parse_rule_range(argv[optind], &rule_nr, &rule_nr_end))
+					xtables_error(PARAMETER_PROBLEM,
+							 "Problem with the specified rule number(s) '%s'", argv[optind]);
+				optind++;
+			} else if (c == 'C') {
+				if ((chcounter = parse_change_counters_rule(argc, argv, &rule_nr, &rule_nr_end, &cs)) == -1)
+					return -1;
+			} else if (c == 'I') {
+				if (optind >= argc || (argv[optind][0] == '-' && (argv[optind][1] < '0' || argv[optind][1] > '9')))
+					rule_nr = 1;
+				else {
+					rule_nr = parse_rule_number(argv[optind]);
+					optind++;
+				}
+			} else if (c == 'P') {
+handle_P:
+				if (optind >= argc)
+					xtables_error(PARAMETER_PROBLEM,
+						      "No policy specified");
+				for (i = 0; i < NUM_STANDARD_TARGETS; i++)
+					if (!strcmp(argv[optind], nft_ebt_standard_target(i))) {
+						policy = argv[optind];
+						if (-i-1 == EBT_CONTINUE)
+							xtables_error(PARAMETER_PROBLEM,
+								      "Wrong policy '%s'",
+								      argv[optind]);
+						break;
+					}
+				if (i == NUM_STANDARD_TARGETS)
+					xtables_error(PARAMETER_PROBLEM,
+						      "Unknown policy '%s'", argv[optind]);
+				optind++;
+			}
+			break;
+		case 'L': /* List */
+		case 'F': /* Flush */
+		case 'Z': /* Zero counters */
+			if (c == 'Z') {
+				if ((flags & OPT_ZERO) || (flags & OPT_COMMAND && command != 'L'))
+print_zero:
+					xtables_error(PARAMETER_PROBLEM,
+						      "Command -Z only allowed together with command -L");
+				flags |= OPT_ZERO;
+			} else {
+				if (flags & OPT_COMMAND)
+					xtables_error(PARAMETER_PROBLEM,
+						      "Multiple commands are not allowed");
+				command = c;
+				flags |= OPT_COMMAND;
+				if (flags & OPT_ZERO && c != 'L')
+					goto print_zero;
+			}
+
+			if (optind < argc && argv[optind][0] != '-') {
+				chain = argv[optind];
+				optind++;
+			}
+			break;
+		case 'V': /* Version */
+			if (OPT_COMMANDS)
+				xtables_error(PARAMETER_PROBLEM,
+					      "Multiple commands are not allowed");
+			printf("%s %s (nf_tables)\n", prog_name, prog_vers);
+			exit(0);
+		case 'h': /* Help */
+			if (OPT_COMMANDS)
+				xtables_error(PARAMETER_PROBLEM,
+					      "Multiple commands are not allowed");
+			command = 'h';
+
+			/* All other arguments should be extension names */
+			while (optind < argc) {
+				/*struct ebt_u_match *m;
+				struct ebt_u_watcher *w;*/
+
+				if (!strcasecmp("list_extensions", argv[optind])) {
+					ebt_list_extensions(xtables_targets, cs.matches);
+					exit(0);
+				}
+				/*if ((m = ebt_find_match(argv[optind])))
+					ebt_add_match(new_entry, m);
+				else if ((w = ebt_find_watcher(argv[optind])))
+					ebt_add_watcher(new_entry, w);
+				else {*/
+					if (!(t = xtables_find_target(argv[optind], XTF_TRY_LOAD)))
+						xtables_error(PARAMETER_PROBLEM,"Extension '%s' not found", argv[optind]);
+					if (flags & OPT_JUMP)
+						xtables_error(PARAMETER_PROBLEM,"Sorry, you can only see help for one target extension at a time");
+					flags |= OPT_JUMP;
+					cs.target = t;
+				//}
+				optind++;
+			}
+			break;
+		case 't': /* Table */
+			ebt_check_option2(&flags, OPT_TABLE);
+			if (restore && table_set)
+				xtables_error(PARAMETER_PROBLEM,
+					      "The -t option (seen in line %u) cannot be used in %s.\n",
+					      line, xt_params->program_name);
+			if (strlen(optarg) > EBT_TABLE_MAXNAMELEN - 1)
+				xtables_error(PARAMETER_PROBLEM,
+					      "Table name length cannot exceed %d characters",
+					      EBT_TABLE_MAXNAMELEN - 1);
+			*table = optarg;
+			table_set = true;
+			break;
+		case 'i': /* Input interface */
+		case 2  : /* Logical input interface */
+		case 'o': /* Output interface */
+		case 3  : /* Logical output interface */
+		case 'j': /* Target */
+		case 'p': /* Net family protocol */
+		case 's': /* Source mac */
+		case 'd': /* Destination mac */
+		case 'c': /* Set counters */
+			if (!OPT_COMMANDS)
+				xtables_error(PARAMETER_PROBLEM,
+					      "No command specified");
+			if (command != 'A' && command != 'D' && command != 'I' && command != 'C')
+				xtables_error(PARAMETER_PROBLEM,
+					      "Command and option do not match");
+			if (c == 'i') {
+				ebt_check_option2(&flags, OPT_IN);
+				if (selected_chain > 2 && selected_chain < NF_BR_BROUTING)
+					xtables_error(PARAMETER_PROBLEM,
+						      "Use -i only in INPUT, FORWARD, PREROUTING and BROUTING chains");
+				if (ebt_check_inverse2(optarg, argc, argv))
+					cs.eb.invflags |= EBT_IIN;
+
+				ebtables_parse_interface(optarg, cs.eb.in);
+				break;
+			} else if (c == 2) {
+				ebt_check_option2(&flags, OPT_LOGICALIN);
+				if (selected_chain > 2 && selected_chain < NF_BR_BROUTING)
+					xtables_error(PARAMETER_PROBLEM,
+						      "Use --logical-in only in INPUT, FORWARD, PREROUTING and BROUTING chains");
+				if (ebt_check_inverse2(optarg, argc, argv))
+					cs.eb.invflags |= EBT_ILOGICALIN;
+
+				ebtables_parse_interface(optarg, cs.eb.logical_in);
+				break;
+			} else if (c == 'o') {
+				ebt_check_option2(&flags, OPT_OUT);
+				if (selected_chain < 2 || selected_chain == NF_BR_BROUTING)
+					xtables_error(PARAMETER_PROBLEM,
+						      "Use -o only in OUTPUT, FORWARD and POSTROUTING chains");
+				if (ebt_check_inverse2(optarg, argc, argv))
+					cs.eb.invflags |= EBT_IOUT;
+
+				ebtables_parse_interface(optarg, cs.eb.out);
+				break;
+			} else if (c == 3) {
+				ebt_check_option2(&flags, OPT_LOGICALOUT);
+				if (selected_chain < 2 || selected_chain == NF_BR_BROUTING)
+					xtables_error(PARAMETER_PROBLEM,
+						      "Use --logical-out only in OUTPUT, FORWARD and POSTROUTING chains");
+				if (ebt_check_inverse2(optarg, argc, argv))
+					cs.eb.invflags |= EBT_ILOGICALOUT;
+
+				ebtables_parse_interface(optarg, cs.eb.logical_out);
+				break;
+			} else if (c == 'j') {
+				ebt_check_option2(&flags, OPT_JUMP);
+				if (strcmp(optarg, "CONTINUE") != 0) {
+					command_jump(&cs, optarg);
+				}
+				break;
+			} else if (c == 's') {
+				ebt_check_option2(&flags, OPT_SOURCE);
+				if (ebt_check_inverse2(optarg, argc, argv))
+					cs.eb.invflags |= EBT_ISOURCE;
+
+				if (xtables_parse_mac_and_mask(optarg,
+							       cs.eb.sourcemac,
+							       cs.eb.sourcemsk))
+					xtables_error(PARAMETER_PROBLEM, "Problem with specified source mac '%s'", optarg);
+				cs.eb.bitmask |= EBT_SOURCEMAC;
+				break;
+			} else if (c == 'd') {
+				ebt_check_option2(&flags, OPT_DEST);
+				if (ebt_check_inverse2(optarg, argc, argv))
+					cs.eb.invflags |= EBT_IDEST;
+
+				if (xtables_parse_mac_and_mask(optarg,
+							       cs.eb.destmac,
+							       cs.eb.destmsk))
+					xtables_error(PARAMETER_PROBLEM, "Problem with specified destination mac '%s'", optarg);
+				cs.eb.bitmask |= EBT_DESTMAC;
+				break;
+			} else if (c == 'c') {
+				ebt_check_option2(&flags, OPT_COUNT);
+				if (ebt_check_inverse2(optarg, argc, argv))
+					xtables_error(PARAMETER_PROBLEM,
+						      "Unexpected '!' after -c");
+				if (optind >= argc || optarg[0] == '-' || argv[optind][0] == '-')
+					xtables_error(PARAMETER_PROBLEM,
+						      "Option -c needs 2 arguments");
+
+				cs.counters.pcnt = strtoull(optarg, &buffer, 10);
+				if (*buffer != '\0')
+					xtables_error(PARAMETER_PROBLEM,
+						      "Packet counter '%s' invalid",
+						      optarg);
+				cs.counters.bcnt = strtoull(argv[optind], &buffer, 10);
+				if (*buffer != '\0')
+					xtables_error(PARAMETER_PROBLEM,
+						      "Packet counter '%s' invalid",
+						      argv[optind]);
+				optind++;
+				break;
+			}
+			ebt_check_option2(&flags, OPT_PROTOCOL);
+			if (ebt_check_inverse2(optarg, argc, argv))
+				cs.eb.invflags |= EBT_IPROTO;
+
+			cs.eb.bitmask &= ~((unsigned int)EBT_NOPROTO);
+			i = strtol(optarg, &buffer, 16);
+			if (*buffer == '\0' && (i < 0 || i > 0xFFFF))
+				xtables_error(PARAMETER_PROBLEM,
+					      "Problem with the specified protocol");
+			if (*buffer != '\0') {
+				struct xt_ethertypeent *ent;
+
+				if (!strcasecmp(optarg, "LENGTH")) {
+					cs.eb.bitmask |= EBT_802_3;
+					break;
+				}
+				ent = xtables_getethertypebyname(optarg);
+				if (!ent)
+					xtables_error(PARAMETER_PROBLEM,
+						      "Problem with the specified Ethernet protocol '%s', perhaps "XT_PATH_ETHERTYPES " is missing", optarg);
+				cs.eb.ethproto = ent->e_ethertype;
+			} else
+				cs.eb.ethproto = i;
+
+			if (cs.eb.ethproto < 0x0600)
+				xtables_error(PARAMETER_PROBLEM,
+					      "Sorry, protocols have values above or equal to 0x0600");
+			break;
+		case 4  : /* Lc */
+			ebt_check_option2(&flags, LIST_C);
+			if (command != 'L')
+				xtables_error(PARAMETER_PROBLEM,
+					      "Use --Lc with -L");
+			flags |= LIST_C;
+			break;
+		case 5  : /* Ln */
+			ebt_check_option2(&flags, LIST_N);
+			if (command != 'L')
+				xtables_error(PARAMETER_PROBLEM,
+					      "Use --Ln with -L");
+			if (flags & LIST_X)
+				xtables_error(PARAMETER_PROBLEM,
+					      "--Lx is not compatible with --Ln");
+			flags |= LIST_N;
+			break;
+		case 6  : /* Lx */
+			ebt_check_option2(&flags, LIST_X);
+			if (command != 'L')
+				xtables_error(PARAMETER_PROBLEM,
+					      "Use --Lx with -L");
+			if (flags & LIST_N)
+				xtables_error(PARAMETER_PROBLEM,
+					      "--Lx is not compatible with --Ln");
+			flags |= LIST_X;
+			break;
+		case 12 : /* Lmac2 */
+			ebt_check_option2(&flags, LIST_MAC2);
+			if (command != 'L')
+				xtables_error(PARAMETER_PROBLEM,
+					       "Use --Lmac2 with -L");
+			flags |= LIST_MAC2;
+			break;
+		case 8 : /* atomic-commit */
+/*
+			replace->command = c;
+			if (OPT_COMMANDS)
+				ebt_print_error2("Multiple commands are not allowed");
+			replace->flags |= OPT_COMMAND;
+			if (!replace->filename)
+				ebt_print_error2("No atomic file specified");*/
+			/* Get the information from the file */
+			/*ebt_get_table(replace, 0);*/
+			/* We don't want the kernel giving us its counters,
+			 * they would overwrite the counters extracted from
+			 * the file */
+			/*replace->num_counters = 0;*/
+			/* Make sure the table will be written to the kernel */
+			/*free(replace->filename);
+			replace->filename = NULL;
+			break;*/
+		/*case 7 :*/ /* atomic-init */
+		/*case 10:*/ /* atomic-save */
+		case 11: /* init-table */
+			nft_cmd_table_flush(h, *table, false);
+			return 1;
+		/*
+			replace->command = c;
+			if (OPT_COMMANDS)
+				ebt_print_error2("Multiple commands are not allowed");
+			if (c != 11 && !replace->filename)
+				ebt_print_error2("No atomic file specified");
+			replace->flags |= OPT_COMMAND;
+			{
+				char *tmp = replace->filename;*/
+
+				/* Get the kernel table */
+				/*replace->filename = NULL;
+				ebt_get_kernel_table(replace, c == 10 ? 0 : 1);
+				replace->filename = tmp;
+			}
+			break;
+		case 9 :*/ /* atomic */
+			/*
+			if (OPT_COMMANDS)
+				ebt_print_error2("--atomic has to come before the command");*/
+			/* A possible memory leak here, but this is not
+			 * executed in daemon mode */
+			/*replace->filename = (char *)malloc(strlen(optarg) + 1);
+			strcpy(replace->filename, optarg);
+			break; */
+		case 13 :
+			break;
+		case 1 :
+			if (!strcmp(optarg, "!"))
+				ebt_check_inverse2(optarg, argc, argv);
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					      "Bad argument : '%s'", optarg);
+			/* ebt_ebt_check_inverse2() did optind++ */
+			optind--;
+			continue;
+		default:
+			ebt_check_inverse2(optarg, argc, argv);
+
+			if (ebt_command_default(&cs))
+				xtables_error(PARAMETER_PROBLEM,
+					      "Unknown argument: '%s'",
+					      argv[optind]);
+
+			if (command != 'A' && command != 'I' &&
+			    command != 'D' && command != 'C')
+				xtables_error(PARAMETER_PROBLEM,
+					      "Extensions only for -A, -I, -D and -C");
+		}
+		ebt_invert = 0;
+	}
+
+	/* Just in case we didn't catch an error */
+	/*if (ebt_errormsg[0] != '\0')
+		return -1;
+
+	if (!(table = ebt_find_table(replace->name)))
+		ebt_print_error2("Bad table name");*/
+
+	if (command == 'h' && !(flags & OPT_ZERO)) {
+		print_help(cs.target, cs.matches, *table);
+		ret = 1;
+	}
+
+	/* Do the final checks */
+	if (command == 'A' || command == 'I' ||
+	    command == 'D' || command == 'C') {
+		for (xtrm_i = cs.matches; xtrm_i; xtrm_i = xtrm_i->next)
+			xtables_option_mfcall(xtrm_i->match);
+
+		for (match = cs.match_list; match; match = match->next) {
+			if (match->ismatch)
+				continue;
+
+			xtables_option_tfcall(match->u.watcher);
+		}
+
+		if (cs.target != NULL)
+			xtables_option_tfcall(cs.target);
+	}
+	/* So, the extensions can work with the host endian.
+	 * The kernel does not have to do this of course */
+	cs.eb.ethproto = htons(cs.eb.ethproto);
+
+	if (command == 'P') {
+		if (selected_chain >= NF_BR_NUMHOOKS) {
+			ret = ebt_cmd_user_chain_policy(h, *table, chain, policy);
+		} else {
+			if (strcmp(policy, "RETURN") == 0) {
+				xtables_error(PARAMETER_PROBLEM,
+					      "Policy RETURN only allowed for user defined chains");
+			}
+			ret = nft_cmd_chain_set(h, *table, chain, policy, NULL);
+			if (ret < 0)
+				xtables_error(PARAMETER_PROBLEM, "Wrong policy");
+		}
+	} else if (command == 'L') {
+		ret = list_rules(h, chain, *table, rule_nr,
+				 0,
+				 0,
+				 /*flags&OPT_EXPANDED*/0,
+				 flags&LIST_N,
+				 flags&LIST_C);
+	}
+	if (flags & OPT_ZERO) {
+		ret = nft_cmd_chain_zero_counters(h, chain, *table, 0);
+	} else if (command == 'F') {
+		ret = nft_cmd_rule_flush(h, chain, *table, 0);
+	} else if (command == 'A') {
+		ret = append_entry(h, chain, *table, &cs, 0, 0, true);
+	} else if (command == 'I') {
+		ret = append_entry(h, chain, *table, &cs, rule_nr - 1,
+				   0, false);
+	} else if (command == 'D') {
+		ret = delete_entry(h, chain, *table, &cs, rule_nr - 1,
+				   rule_nr_end, 0);
+	} /*else if (replace->command == 'C') {
+		ebt_change_counters(replace, new_entry, rule_nr, rule_nr_end, &(new_entry->cnt_surplus), chcounter);
+		if (ebt_errormsg[0] != '\0')
+			return -1;
+	}*/
+
+	ebt_cs_clean(&cs);
+	return ret;
+}
diff --git a/iptables/xtables-legacy-multi.c b/iptables/xtables-legacy-multi.c
new file mode 100644
index 0000000..3b7905f
--- /dev/null
+++ b/iptables/xtables-legacy-multi.c
@@ -0,0 +1,54 @@
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "xshared.h"
+
+#include "xtables-multi.h"
+
+#ifdef ENABLE_IPV4
+#include "iptables-multi.h"
+#endif
+
+#ifdef ENABLE_IPV6
+#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},
+	{"main4",               iptables_main},
+	{"iptables-save",       iptables_save_main},
+	{"save4",               iptables_save_main},
+	{"iptables-restore",    iptables_restore_main},
+	{"restore4",            iptables_restore_main},
+	{"iptables-legacy",     iptables_main},
+	{"iptables-legacy-save",iptables_save_main},
+	{"iptables-legacy-restore",iptables_restore_main},
+
+
+#endif
+	{"iptables-xml",        iptables_xml_main},
+	{"xml",                 iptables_xml_main},
+#ifdef ENABLE_IPV6
+	{"ip6tables",           ip6tables_main},
+	{"main6",               ip6tables_main},
+	{"ip6tables-save",      ip6tables_save_main},
+	{"save6",               ip6tables_save_main},
+	{"ip6tables-restore",   ip6tables_restore_main},
+	{"restore6",            ip6tables_restore_main},
+	{"ip6tables-legacy",    ip6tables_main},
+	{"ip6tables-legacy-save",ip6tables_save_main},
+	{"ip6tables-legacy-restore",ip6tables_restore_main},
+#endif
+	{NULL},
+};
+
+int main(int argc, char **argv)
+{
+	return subcmd_main(argc, argv, multi_subcommands);
+}
diff --git a/iptables/xtables-legacy.8 b/iptables/xtables-legacy.8
new file mode 100644
index 0000000..6db7d2c
--- /dev/null
+++ b/iptables/xtables-legacy.8
@@ -0,0 +1,78 @@
+.\"
+.\" (C) Copyright 2016-2017, Arturo Borrero Gonzalez <arturo@netfilter.org>
+.\"
+.\" %%%LICENSE_START(GPLv2+_DOC_FULL)
+.\" This is free documentation; 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.
+.\"
+.\" The GNU General Public License's references to "object code"
+.\" and "executables" are to be interpreted as the output of any
+.\" document formatting or typesetting system, including
+.\" intermediate and printed output.
+.\"
+.\" This manual 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 manual; if not, see
+.\" <http://www.gnu.org/licenses/>.
+.\" %%%LICENSE_END
+.\"
+.TH XTABLES-LEGACY 8 "June 2018"
+
+.SH NAME
+xtables-legacy \(em iptables using old getsockopt/setsockopt-based kernel api
+
+.SH DESCRIPTION
+\fBxtables-legacy\fP are the original versions of iptables that use
+old getsockopt/setsockopt-based kernel interface.
+This kernel interface has some limitations, therefore iptables can also
+be used with the newer nf_tables based API.
+See
+.B xtables\-nft(8)
+for information about the xtables-nft variants of iptables.
+
+.SH USAGE
+The xtables-legacy-multi binary can be linked to the traditional names:
+
+.nf
+	/sbin/iptables -> /sbin/iptables\-legacy\-multi
+	/sbin/ip6tables -> /sbin/ip6tables\-legacy\-multi
+	/sbin/iptables\-save -> /sbin/ip6tables\-legacy\-multi
+	/sbin/iptables\-restore -> /sbin/ip6tables\-legacy\-multi
+.fi
+
+The iptables version string will indicate whether the legacy API (get/setsockopt) or
+the new nf_tables API is used:
+.nf
+	iptables \-V
+	iptables v1.7 (legacy)
+.fi
+
+.SH LIMITATIONS
+
+When inserting a rule using
+iptables \-A or iptables \-I, iptables first needs to retrieve the current active
+ruleset, change it to include the new rule, and then commit back the result.
+This means that if two instances of iptables are running concurrently, one of the
+updates might be lost.  This can be worked around partially with the \-\-wait option.
+
+There is also no method to monitor changes to the ruleset, except periodically calling
+iptables-legacy-save and checking for any differences in output.
+
+.B xtables\-monitor(8)
+will need the
+.B xtables\-nft(8)
+versions to work, it cannot display changes made using the
+.B iptables-legacy
+tools.
+
+.SH SEE ALSO
+\fBxtables\-nft(8)\fP, \fBxtables\-translate(8)\fP
+
+.SH AUTHORS
+Rusty Russell originally wrote iptables, in early consultation with Michael Neuling.
diff --git a/iptables/xtables-monitor.8.in b/iptables/xtables-monitor.8.in
new file mode 100644
index 0000000..b647a79
--- /dev/null
+++ b/iptables/xtables-monitor.8.in
@@ -0,0 +1,93 @@
+.TH XTABLES\-MONITOR 8 "" "@PACKAGE_STRING@" "@PACKAGE_STRING@"
+.SH NAME
+xtables-monitor \(em show changes to rule set and trace-events
+.SH SYNOPSIS
+\fBxtables\-monitor\fP [\fB\-t\fP] [\fB\-e\fP] [\fB\-4\fP|\fB|\-6\fB]
+.PP
+\
+.SH DESCRIPTION
+.PP
+.B xtables-monitor
+is used to monitor changes to the ruleset or to show rule evaluation events
+for packets tagged using the TRACE target.
+.B xtables-monitor
+will run until the user aborts execution, typically by using CTRL-C.
+.RE
+.SH OPTIONS
+\fB\-e\fP, \fB\-\-event\fP
+.TP
+Watch for updates to the rule set.
+Updates include creation of new tables, chains and rules and
+the name of the program that caused the rule update.
+.TP
+\fB\-t\fP, \fB\-\-trace\fP
+Watch for trace events generated by packets that have been tagged
+using the TRACE target.
+.TP
+\fB\-4\fP
+Restrict output to IPv4.
+.TP
+\fB\-6\fP
+Restrict output to IPv6.
+.SH EXAMPLE OUTPUT
+.TP
+.B xtables-monitor \-\-trace
+
+ 1 TRACE: 2 fc475095 raw:PREROUTING:rule:0x3:CONTINUE \-4 \-t raw \-A PREROUTING \-p icmp \-j TRACE
+ 2 PACKET: 0 fc475095 IN=lo LL=0x304 0000000000000000000000000800 SRC=127.0.0.1 DST=127.0.0.1 LEN=84 TOS=0x0 TTL=64 ID=38349DF
+ 3 TRACE: 2 fc475095 raw:PREROUTING:return:
+ 4 TRACE: 2 fc475095 raw:PREROUTING:policy:ACCEPT
+ 5 TRACE: 2 fc475095 filter:INPUT:return:
+ 6 TRACE: 2 fc475095 filter:INPUT:policy:DROP
+ 7 TRACE: 2 0df9d3d8 raw:PREROUTING:rule:0x3:CONTINUE \-4 \-t raw \-A PREROUTING \-p icmp \-j TRACE
+.PP
+The first line shows a packet entering rule set evaluation.
+The protocol number is shown (AF_INET in this case), then a packet
+identifier number that allows to correlate messages coming from rule set evaluation of
+this packet.  After this, the rule that was matched by the packet is shown.
+This is the TRACE rule that turns on tracing events for this packet.
+
+The second line dumps information about the packet. Incoming interface
+and packet headers such as source and destination addresses are shown.
+
+The third line shows that the packet completed traversal of the raw table
+PREROUTING chain, and is returning, followed by use the chain policy to make accept/drop
+decision (the example shows accept being applied).
+The fifth line shows that the packet leaves the filter INPUT chain, i.e., no rules in the filter tables
+INPUT chain matched the packet.
+It then got DROPPED by the policy of the INPUT table, as shown by line six.
+The last line shows another packet arriving \-\- the packet id is different.
+
+When using the TRACE target, it is usually a good idea to only select packets
+that are relevant, for example via
+.nf
+iptables \-t raw \-A PREROUTING \-p tcp \-\-dport 80 \-\-syn \-m limit \-\-limit 1/s \-j TRACE
+.fi
+.TP
+.B xtables-monitor \-\-event
+  1 EVENT: nft: NEW table: table filter ip flags 0 use 4 handle 444
+  2 EVENT: # nft: ip filter INPUT use 2 type filter hook input prio 0 policy drop packets 0 bytes 0
+  3 EVENT: # nft: ip filter FORWARD use 0 type filter hook forward prio 0 policy accept packets 0 bytes 0
+  4 EVENT: # nft: ip filter OUTPUT use 0 type filter hook output prio 0 policy accept packets 0 bytes 0
+  5 EVENT: \-4 \-t filter \-N TCP
+  6 EVENT: \-4 \-t filter \-A TCP \-s 192.168.0.0/16 \-p tcp \-m tcp \-\-dport 22 \-j ACCEPT
+  7 EVENT: \-4 \-t filter \-A TCP \-p tcp \-m multiport \-\-dports 80,443 \-j ACCEPT
+  8 EVENT: \-4 \-t filter \-A INPUT \-p tcp \-j TCP
+  9 EVENT: \-4 \-t filter \-A INPUT \-m conntrack \-\-ctstate RELATED,ESTABLISHED \-j ACCEPT
+ 10 NEWGEN: GENID=13904 PID=25167 NAME=iptables-nftables-restore
+.PP
+This example shows event monitoring.  Line one shows creation of a table (filter in this case), followed
+by three base hooks INPUT, FORWARD and OUTPUT.  The iptables-nftables tools all create tables and base
+chains automatically when needed, so this is expected when a table was not yet initialized or when it is
+re-created from scratch by iptables-nftables-restore.  Line five shows a new user-defined chain (TCP)
+being added, followed by addition a few rules. the last line shows that a new ruleset generation has
+become active, i.e., the rule set changes are now active.  This also lists the process id and the programs name.
+.SH LIMITATIONS
+.B xtables-monitor
+only works with rules added using iptables-nftables, rules added using
+iptables-legacy cannot be monitored.
+.SH BUGS
+Should be reported or by sending email to netfilter-devel@vger.kernel.org or
+by filing a report on https://bugzilla.netfilter.org/.
+.SH SEE ALSO
+\fBiptables\fP(8), \fBxtables\fP(8), \fBnft\fP(8)
diff --git a/iptables/xtables-monitor.c b/iptables/xtables-monitor.c
new file mode 100644
index 0000000..4b98098
--- /dev/null
+++ b/iptables/xtables-monitor.c
@@ -0,0 +1,710 @@
+/*
+ * (C) 2012-2013 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software has been sponsored by Sophos Astaro <http://www.sophos.com>
+ */
+
+#define _GNU_SOURCE
+#include "config.h"
+#include <errno.h>
+#include <stdlib.h>
+#include <time.h>
+#include <string.h>
+#include <netinet/ether.h>
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+#include <net/if_arp.h>
+#include <getopt.h>
+
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <linux/netfilter/nfnetlink.h>
+#include <linux/netfilter/nf_tables.h>
+
+#include <libmnl/libmnl.h>
+#include <libnftnl/table.h>
+#include <libnftnl/trace.h>
+#include <libnftnl/chain.h>
+#include <libnftnl/rule.h>
+
+#include <include/xtables.h>
+#include "iptables.h" /* for xtables_globals */
+#include "xtables-multi.h"
+#include "nft.h"
+#include "nft-arp.h"
+
+struct cb_arg {
+	uint32_t nfproto;
+	bool is_event;
+	struct nft_handle *h;
+};
+
+static int table_cb(const struct nlmsghdr *nlh, void *data)
+{
+	uint32_t type = nlh->nlmsg_type & 0xFF;
+	const struct cb_arg *arg = data;
+	struct nftnl_table *t;
+	char buf[4096];
+
+	t = nftnl_table_alloc();
+	if (t == NULL)
+		goto err;
+
+	if (nftnl_table_nlmsg_parse(nlh, t) < 0)
+		goto err_free;
+
+	if (arg->nfproto && arg->nfproto != nftnl_table_get_u32(t, NFTNL_TABLE_FAMILY))
+		goto err_free;
+	nftnl_table_snprintf(buf, sizeof(buf), t, NFTNL_OUTPUT_DEFAULT, 0);
+	printf(" EVENT: ");
+	printf("nft: %s table: %s\n", type == NFT_MSG_NEWTABLE ? "NEW" : "DEL", buf);
+
+err_free:
+	nftnl_table_free(t);
+err:
+	return MNL_CB_OK;
+}
+
+static bool counters;
+static bool trace;
+static bool events;
+
+static int rule_cb(const struct nlmsghdr *nlh, void *data)
+{
+	uint32_t type = nlh->nlmsg_type & 0xFF;
+	const struct cb_arg *arg = data;
+	struct nftnl_rule *r;
+	uint8_t family;
+
+	r = nftnl_rule_alloc();
+	if (r == NULL)
+		goto err;
+
+	if (nftnl_rule_nlmsg_parse(nlh, r) < 0)
+		goto err_free;
+
+	family = nftnl_rule_get_u32(r, NFTNL_RULE_FAMILY);
+	if (arg->nfproto && arg->nfproto != family)
+		goto err_free;
+
+	arg->h->ops = nft_family_ops_lookup(family);
+
+	if (arg->is_event)
+		printf(" EVENT: ");
+	switch (family) {
+	case AF_INET:
+	case AF_INET6:
+		printf("-%c ", family == AF_INET ? '4' : '6');
+		break;
+	case NFPROTO_ARP:
+		printf("-0 ");
+		break;
+	default:
+		puts("");
+		goto err_free;
+	}
+
+	printf("-t %s ", nftnl_rule_get_str(r, NFTNL_RULE_TABLE));
+	nft_rule_print_save(arg->h, r, type == NFT_MSG_NEWRULE ? NFT_RULE_APPEND :
+							   NFT_RULE_DEL,
+			    counters ? 0 : FMT_NOCOUNTS);
+err_free:
+	nftnl_rule_free(r);
+err:
+	return MNL_CB_OK;
+}
+
+static int chain_cb(const struct nlmsghdr *nlh, void *data)
+{
+	uint32_t type = nlh->nlmsg_type & 0xFF;
+	const struct cb_arg *arg = data;
+	struct nftnl_chain *c;
+	char buf[4096];
+	int family;
+
+	c = nftnl_chain_alloc();
+	if (c == NULL)
+		goto err;
+
+	if (nftnl_chain_nlmsg_parse(nlh, c) < 0)
+		goto err_free;
+
+	family = nftnl_chain_get_u32(c, NFTNL_CHAIN_FAMILY);
+	if (arg->nfproto && arg->nfproto != family)
+		goto err_free;
+
+	if (nftnl_chain_is_set(c, NFTNL_CHAIN_PRIO))
+		family = -1;
+
+	printf(" EVENT: ");
+	switch (family) {
+	case NFPROTO_IPV4:
+		family = 4;
+		break;
+	case NFPROTO_IPV6:
+		family = 6;
+		break;
+	default:
+		nftnl_chain_snprintf(buf, sizeof(buf), c, NFTNL_OUTPUT_DEFAULT, 0);
+		printf("# nft: %s\n", buf);
+		goto err_free;
+	}
+
+	printf("-%d -t %s -%c %s\n",
+			family,
+			nftnl_chain_get_str(c, NFTNL_CHAIN_TABLE),
+			type == NFT_MSG_NEWCHAIN ? 'N' : 'X',
+			nftnl_chain_get_str(c, NFTNL_CHAIN_NAME));
+err_free:
+	nftnl_chain_free(c);
+err:
+	return MNL_CB_OK;
+}
+
+static int newgen_cb(const struct nlmsghdr *nlh, void *data)
+{
+	uint32_t genid = 0, pid = 0;
+	const struct nlattr *attr;
+	const char *name = NULL;
+
+	mnl_attr_for_each(attr, nlh, sizeof(struct nfgenmsg)) {
+		switch (mnl_attr_get_type(attr)) {
+		case NFTA_GEN_ID:
+			if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0)
+				break;
+		        genid = ntohl(mnl_attr_get_u32(attr));
+			break;
+		case NFTA_GEN_PROC_NAME:
+			if (mnl_attr_validate(attr, MNL_TYPE_NUL_STRING) < 0)
+				break;
+			name = mnl_attr_get_str(attr);
+			break;
+		case NFTA_GEN_PROC_PID:
+			if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0)
+				break;
+			pid = ntohl(mnl_attr_get_u32(attr));
+			break;
+		}
+	}
+
+	if (name)
+		printf("NEWGEN: GENID=%u PID=%u NAME=%s\n", genid, pid, name);
+
+	return MNL_CB_OK;
+}
+
+static void trace_print_return(const struct nftnl_trace *nlt)
+{
+	const char *chain = NULL;
+
+	if (nftnl_trace_is_set(nlt, NFTNL_TRACE_JUMP_TARGET)) {
+		chain = nftnl_trace_get_str(nlt, NFTNL_TRACE_JUMP_TARGET);
+		printf("%s", chain);
+	}
+}
+
+static void trace_print_rule(const struct nftnl_trace *nlt, struct cb_arg *args)
+{
+	uint64_t handle = nftnl_trace_get_u64(nlt, NFTNL_TRACE_RULE_HANDLE);
+	uint32_t family = nftnl_trace_get_u32(nlt, NFTNL_TRACE_FAMILY);
+	const char *table = nftnl_trace_get_str(nlt, NFTNL_TRACE_TABLE);
+	const char *chain = nftnl_trace_get_str(nlt, NFTNL_TRACE_CHAIN);
+        struct nftnl_rule *r;
+	struct mnl_socket *nl;
+	struct nlmsghdr *nlh;
+	uint32_t portid;
+	char buf[16536];
+	int ret;
+
+        r = nftnl_rule_alloc();
+	if (r == NULL) {
+		perror("OOM");
+		exit(EXIT_FAILURE);
+	}
+
+	nlh = nftnl_chain_nlmsg_build_hdr(buf, NFT_MSG_GETRULE, family, 0, 0);
+
+        nftnl_rule_set_u32(r, NFTNL_RULE_FAMILY, family);
+	nftnl_rule_set_str(r, NFTNL_RULE_CHAIN, chain);
+	nftnl_rule_set_str(r, NFTNL_RULE_TABLE, table);
+	nftnl_rule_set_u64(r, NFTNL_RULE_HANDLE, handle);
+	nftnl_rule_nlmsg_build_payload(nlh, r);
+	nftnl_rule_free(r);
+
+	nl = mnl_socket_open(NETLINK_NETFILTER);
+	if (nl == NULL) {
+		perror("mnl_socket_open");
+		exit(EXIT_FAILURE);
+	}
+
+	if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+		perror("mnl_socket_bind");
+		exit(EXIT_FAILURE);
+	}
+
+	portid = mnl_socket_get_portid(nl);
+	if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
+		perror("mnl_socket_send");
+		exit(EXIT_FAILURE);
+	}
+
+	ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+	if (ret > 0) {
+		args->is_event = false;
+		ret = mnl_cb_run(buf, ret, 0, portid, rule_cb, args);
+	}
+	if (ret == -1) {
+		perror("error");
+		exit(EXIT_FAILURE);
+	}
+	mnl_socket_close(nl);
+}
+
+static void trace_print_packet(const struct nftnl_trace *nlt, struct cb_arg *args)
+{
+	struct list_head stmts = LIST_HEAD_INIT(stmts);
+	uint32_t nfproto, family;
+	uint16_t l4proto = 0;
+	uint32_t mark;
+	char name[IFNAMSIZ];
+
+	family = nftnl_trace_get_u32(nlt, NFTNL_TRACE_FAMILY);
+	printf("PACKET: %d %08x ", family, nftnl_trace_get_u32(nlt, NFTNL_TRACE_ID));
+
+	if (nftnl_trace_is_set(nlt, NFTNL_TRACE_IIF))
+		printf("IN=%s ", if_indextoname(nftnl_trace_get_u32(nlt, NFTNL_TRACE_IIF), name));
+	if (nftnl_trace_is_set(nlt, NFTNL_TRACE_OIF))
+		printf("OUT=%s ", if_indextoname(nftnl_trace_get_u32(nlt, NFTNL_TRACE_OIF), name));
+
+	nfproto = family;
+	if (nftnl_trace_is_set(nlt, NFTNL_TRACE_NFPROTO)) {
+		nfproto = nftnl_trace_get_u32(nlt, NFTNL_TRACE_NFPROTO);
+
+		if (family != nfproto)
+			printf("NFPROTO=%d ", nfproto);
+	}
+
+	if (nftnl_trace_is_set(nlt, NFTNL_TRACE_LL_HEADER)) {
+		const struct ethhdr *eh;
+		const char *linklayer;
+		uint32_t i, len;
+		uint16_t type = nftnl_trace_get_u16(nlt, NFTNL_TRACE_IIFTYPE);
+
+		linklayer = nftnl_trace_get_data(nlt, NFTNL_TRACE_LL_HEADER, &len);
+		switch (type) {
+		case ARPHRD_ETHER:
+			if (len < sizeof(*eh))
+			       break;
+			eh = (const void *)linklayer;
+			printf("MACSRC=%s ", ether_ntoa((const void *)eh->h_source));
+			printf("MACDST=%s ", ether_ntoa((const void *)eh->h_dest));
+			printf("MACPROTO=%04x ", ntohs(eh->h_proto));
+			break;
+		case ARPHRD_LOOPBACK:
+			printf("LOOPBACK ");
+			break;
+		default:
+			printf("LL=0x%x ", type);
+			for (i = 0 ; i < len; i++)
+				printf("%02x", linklayer[i]);
+			printf(" ");
+			break;
+		}
+	}
+
+	if (nftnl_trace_is_set(nlt, NFTNL_TRACE_NETWORK_HEADER)) {
+		const struct ip6_hdr *ip6h;
+		const struct iphdr *iph;
+		uint32_t i, len;
+		const char *nh;
+
+		ip6h = nftnl_trace_get_data(nlt, NFTNL_TRACE_NETWORK_HEADER, &len);
+
+		switch (nfproto) {
+		case NFPROTO_IPV4: {
+			char addrbuf[INET_ADDRSTRLEN];
+
+			if (len < sizeof(*iph))
+				break;
+			iph = (const void *)ip6h;
+
+
+			inet_ntop(AF_INET, &iph->saddr, addrbuf, sizeof(addrbuf));
+			printf("SRC=%s ", addrbuf);
+			inet_ntop(AF_INET, &iph->daddr, addrbuf, sizeof(addrbuf));
+			printf("DST=%s ", addrbuf);
+
+			printf("LEN=%d TOS=0x%x TTL=%d ID=%d", ntohs(iph->tot_len), iph->tos, iph->ttl, ntohs(iph->id));
+			if (iph->frag_off & htons(0x8000))
+				printf("CE ");
+			if (iph->frag_off & htons(IP_DF))
+				printf("DF ");
+			if (iph->frag_off & htons(IP_MF))
+				printf("MF ");
+
+			if (ntohs(iph->frag_off) & 0x1fff)
+				printf("FRAG:%u ", ntohs(iph->frag_off) & 0x1fff);
+
+			l4proto = iph->protocol;
+			if (iph->ihl * 4 > sizeof(*iph)) {
+				unsigned int optsize;
+				const char *op;
+
+				optsize = iph->ihl * 4 - sizeof(*iph);
+				op = (const char *)iph;
+				op += sizeof(*iph);
+
+				printf("OPT (");
+				for (i = 0; i < optsize; i++)
+					printf("%02X", op[i]);
+				printf(")");
+			}
+			break;
+		}
+		case NFPROTO_IPV6: {
+			uint32_t flowlabel = ntohl(*(uint32_t *)ip6h);
+			char addrbuf[INET6_ADDRSTRLEN];
+
+			if (len < sizeof(*ip6h))
+				break;
+
+			inet_ntop(AF_INET6, &ip6h->ip6_src, addrbuf, sizeof(addrbuf));
+			printf("SRC=%s ", addrbuf);
+			inet_ntop(AF_INET6, &ip6h->ip6_dst, addrbuf, sizeof(addrbuf));
+			printf("DST=%s ", addrbuf);
+
+			printf("LEN=%zu TC=%u HOPLIMIT=%u FLOWLBL=%u ",
+				ntohs(ip6h->ip6_plen) + sizeof(*iph),
+				(flowlabel & 0x0ff00000) >> 20,
+				ip6h->ip6_hops,
+				flowlabel & 0x000fffff);
+
+			l4proto = ip6h->ip6_nxt;
+			break;
+		}
+		default:
+			nh = (const char *)ip6h;
+			printf("NH=");
+			for (i = 0 ; i < len; i++)
+				printf("%02x", nh[i]);
+			printf(" ");
+		}
+	}
+
+	if (nftnl_trace_is_set(nlt, NFTNL_TRACE_TRANSPORT_HEADER)) {
+		const struct tcphdr *tcph;
+		uint32_t len;
+
+		tcph = nftnl_trace_get_data(nlt, NFTNL_TRACE_TRANSPORT_HEADER, &len);
+
+		switch (l4proto) {
+		case IPPROTO_DCCP:
+		case IPPROTO_SCTP:
+		case IPPROTO_UDPLITE:
+		case IPPROTO_UDP:
+			if (len < 4)
+				break;
+			printf("SPORT=%d DPORT=%d ", ntohs(tcph->source), ntohs(tcph->dest));
+			break;
+		case IPPROTO_TCP:
+			if (len < sizeof(*tcph))
+				break;
+			printf("SPORT=%d DPORT=%d ", ntohs(tcph->source), ntohs(tcph->dest));
+			if (tcph->syn)
+				printf("SYN ");
+			if (tcph->ack)
+				printf("ACK ");
+			if (tcph->fin)
+				printf("FIN ");
+			if (tcph->rst)
+				printf("RST ");
+			if (tcph->psh)
+				printf("PSH ");
+			if (tcph->urg)
+				printf("URG ");
+			break;
+		default:
+			break;
+		}
+	}
+
+	mark = nftnl_trace_get_u32(nlt, NFTNL_TRACE_MARK);
+	if (mark)
+		printf("MARK=0x%x ", mark);
+	puts("");
+}
+
+static void trace_print_hdr(const struct nftnl_trace *nlt)
+{
+	printf(" TRACE: %d %08x %s:%s", nftnl_trace_get_u32(nlt, NFTNL_TABLE_FAMILY),
+					nftnl_trace_get_u32(nlt, NFTNL_TRACE_ID),
+					nftnl_trace_get_str(nlt, NFTNL_TRACE_TABLE),
+					nftnl_trace_get_str(nlt, NFTNL_TRACE_CHAIN));
+}
+
+static void print_verdict(const struct nftnl_trace *nlt, uint32_t verdict)
+{
+	const char *chain;
+
+	switch (verdict) {
+	case NF_ACCEPT:
+		printf("ACCEPT");
+		break;
+	case NF_DROP:
+		printf("DROP");
+		break;
+	case NF_QUEUE:
+		printf("QUEUE");
+		break;
+	case NF_STOLEN:
+		printf("STOLEN");
+		break;
+	case NFT_BREAK:
+		printf("BREAK");
+		break;
+	case NFT_CONTINUE:
+		printf("CONTINUE");
+		break;
+	case NFT_GOTO:
+		printf("GOTO");
+		if (nftnl_trace_is_set(nlt, NFTNL_TRACE_JUMP_TARGET)) {
+			chain = nftnl_trace_get_str(nlt, NFTNL_TRACE_JUMP_TARGET);
+			printf(":%s", chain);
+		}
+		break;
+	case NFT_JUMP:
+		printf("JUMP");
+		if (nftnl_trace_is_set(nlt, NFTNL_TRACE_JUMP_TARGET)) {
+			chain = nftnl_trace_get_str(nlt, NFTNL_TRACE_JUMP_TARGET);
+			printf(":%s", chain);
+		}
+		break;
+	default:
+		printf("0x%x", verdict);
+		break;
+	}
+
+	printf(" ");
+}
+
+static int trace_cb(const struct nlmsghdr *nlh, struct cb_arg *arg)
+{
+	struct nftnl_trace *nlt;
+	uint32_t verdict;
+
+	nlt = nftnl_trace_alloc();
+	if (nlt == NULL)
+		goto err;
+
+	if (nftnl_trace_nlmsg_parse(nlh, nlt) < 0)
+		goto err_free;
+
+	if (arg->nfproto &&
+	    arg->nfproto != nftnl_trace_get_u32(nlt, NFTNL_TABLE_FAMILY))
+		goto err_free;
+
+	switch (nftnl_trace_get_u32(nlt, NFTNL_TRACE_TYPE)) {
+	case NFT_TRACETYPE_RULE:
+		verdict = nftnl_trace_get_u32(nlt, NFTNL_TRACE_VERDICT);
+
+		if (nftnl_trace_is_set(nlt, NFTNL_TRACE_LL_HEADER) ||
+		    nftnl_trace_is_set(nlt, NFTNL_TRACE_NETWORK_HEADER))
+			trace_print_packet(nlt, arg);
+
+		if (nftnl_trace_is_set(nlt, NFTNL_TRACE_RULE_HANDLE)) {
+			trace_print_hdr(nlt);
+			printf(":rule:0x%" PRIx64":", nftnl_trace_get_u64(nlt, NFTNL_TRACE_RULE_HANDLE));
+			print_verdict(nlt, verdict);
+			printf(" ");
+			trace_print_rule(nlt, arg);
+		}
+		break;
+	case NFT_TRACETYPE_POLICY:
+		trace_print_hdr(nlt);
+		printf(":policy:");
+		verdict = nftnl_trace_get_u32(nlt, NFTNL_TRACE_POLICY);
+
+		print_verdict(nlt, verdict);
+		puts("");
+		break;
+	case NFT_TRACETYPE_RETURN:
+		trace_print_hdr(nlt);
+		printf(":return:");
+		trace_print_return(nlt);
+		puts("");
+		break;
+	}
+err_free:
+	nftnl_trace_free(nlt);
+err:
+	fflush(stdout);
+	return MNL_CB_OK;
+}
+
+static int monitor_cb(const struct nlmsghdr *nlh, void *data)
+{
+	uint32_t type = nlh->nlmsg_type & 0xFF;
+	struct cb_arg *arg = data;
+	int ret = MNL_CB_OK;
+
+	switch(type) {
+	case NFT_MSG_NEWTABLE:
+	case NFT_MSG_DELTABLE:
+		ret = table_cb(nlh, data);
+		break;
+	case NFT_MSG_NEWCHAIN:
+	case NFT_MSG_DELCHAIN:
+		ret = chain_cb(nlh, data);
+		break;
+	case NFT_MSG_NEWRULE:
+	case NFT_MSG_DELRULE:
+		arg->is_event = true;
+		ret = rule_cb(nlh, data);
+		break;
+	case NFT_MSG_NEWGEN:
+		ret = newgen_cb(nlh, data);
+		break;
+	case NFT_MSG_TRACE:
+		ret = trace_cb(nlh, data);
+		break;
+	}
+
+	return ret;
+}
+
+static const struct option options[] = {
+	{.name = "counters", .has_arg = false, .val = 'c'},
+	{.name = "trace", .has_arg = false, .val = 't'},
+	{.name = "event", .has_arg = false, .val = 'e'},
+	{.name = "ipv4", .has_arg = false, .val = '4'},
+	{.name = "ipv6", .has_arg = false, .val = '6'},
+	{.name = "version", .has_arg = false, .val = 'V'},
+	{.name = "help", .has_arg = false, .val = 'h'},
+	{NULL},
+};
+
+static void print_usage(void)
+{
+	printf("%s %s\n", xtables_globals.program_name,
+			  xtables_globals.program_version);
+	printf("Usage: %s [ -t | -e ]\n"
+	       "        --trace    -t    trace ruleset traversal of packets tagged via -j TRACE rule\n"
+	       "        --event    -e    show events that modify the ruleset\n"
+	       "Optional arguments:\n"
+	       "        --ipv4     -4    only monitor IPv4\n"
+	       "        --ipv6     -6    only monitor IPv6\n"
+	       "	--counters -c    show counters in rules\n"
+
+	       , xtables_globals.program_name);
+	exit(EXIT_FAILURE);
+}
+
+int xtables_monitor_main(int argc, char *argv[])
+{
+	struct mnl_socket *nl;
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	uint32_t nfgroup = 0;
+	struct nft_handle h = {};
+	struct cb_arg cb_arg = {
+		.h = &h,
+	};
+	int ret, c;
+
+	xtables_globals.program_name = "xtables-monitor";
+	/* 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
+
+	if (nft_init(&h, AF_INET, xtables_ipv4)) {
+		fprintf(stderr, "%s/%s Failed to initialize nft: %s\n",
+			xtables_globals.program_name,
+			xtables_globals.program_version,
+			strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+
+	opterr = 0;
+	while ((c = getopt_long(argc, argv, "ceht46V", options, NULL)) != -1) {
+		switch (c) {
+	        case 'c':
+			counters = true;
+			break;
+	        case 't':
+			trace = true;
+			break;
+	        case 'e':
+			events = true;
+			break;
+	        case 'h':
+			print_usage();
+			exit(0);
+		case '4':
+			cb_arg.nfproto = NFPROTO_IPV4;
+			break;
+		case '6':
+			cb_arg.nfproto = NFPROTO_IPV6;
+			break;
+		case 'V':
+			printf("xtables-monitor %s\n", PACKAGE_VERSION);
+			exit(0);
+		default:
+			fprintf(stderr, "xtables-monitor %s: Bad argument.\n", PACKAGE_VERSION);
+			fprintf(stderr, "Try `xtables-monitor -h' for more information.\n");
+			exit(PARAMETER_PROBLEM);
+		}
+	}
+
+	if (trace)
+		nfgroup |= 1 << (NFNLGRP_NFTRACE - 1);
+	if (events)
+		nfgroup |= 1 << (NFNLGRP_NFTABLES - 1);
+
+	if (nfgroup == 0) {
+		print_usage();
+		exit(EXIT_FAILURE);
+	}
+
+	nl = mnl_socket_open(NETLINK_NETFILTER);
+	if (nl == NULL) {
+		perror("cannot open nfnetlink socket");
+		exit(EXIT_FAILURE);
+	}
+
+	if (mnl_socket_bind(nl, nfgroup, MNL_SOCKET_AUTOPID) < 0) {
+		perror("cannot bind to nfnetlink socket");
+		exit(EXIT_FAILURE);
+	}
+
+	ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+	while (ret > 0) {
+		ret = mnl_cb_run(buf, ret, 0, 0, monitor_cb, &cb_arg);
+		if (ret <= 0)
+			break;
+		ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+	}
+	if (ret == -1) {
+		perror("cannot receive from nfnetlink socket");
+		exit(EXIT_FAILURE);
+	}
+	mnl_socket_close(nl);
+
+	xtables_fini();
+
+	return EXIT_SUCCESS;
+}
+
diff --git a/iptables/xtables-multi.c b/iptables/xtables-multi.c
deleted file mode 100644
index 8014d5f..0000000
--- a/iptables/xtables-multi.c
+++ /dev/null
@@ -1,41 +0,0 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include "xshared.h"
-
-#include "xtables-multi.h"
-
-#ifdef ENABLE_IPV4
-#include "iptables-multi.h"
-#endif
-
-#ifdef ENABLE_IPV6
-#include "ip6tables-multi.h"
-#endif
-
-static const struct subcommand multi_subcommands[] = {
-#ifdef ENABLE_IPV4
-	{"iptables",            iptables_main},
-	{"main4",               iptables_main},
-	{"iptables-save",       iptables_save_main},
-	{"save4",               iptables_save_main},
-	{"iptables-restore",    iptables_restore_main},
-	{"restore4",            iptables_restore_main},
-#endif
-	{"iptables-xml",        iptables_xml_main},
-	{"xml",                 iptables_xml_main},
-#ifdef ENABLE_IPV6
-	{"ip6tables",           ip6tables_main},
-	{"main6",               ip6tables_main},
-	{"ip6tables-save",      ip6tables_save_main},
-	{"save6",               ip6tables_save_main},
-	{"ip6tables-restore",   ip6tables_restore_main},
-	{"restore6",            ip6tables_restore_main},
-#endif
-	{NULL},
-};
-
-int main(int argc, char **argv)
-{
-	return subcmd_main(argc, argv, multi_subcommands);
-}
diff --git a/iptables/xtables-multi.h b/iptables/xtables-multi.h
index 615724b..0fedb43 100644
--- a/iptables/xtables-multi.h
+++ b/iptables/xtables-multi.h
@@ -2,5 +2,26 @@
 #define _XTABLES_MULTI_H 1
 
 extern int iptables_xml_main(int, char **);
+#ifdef ENABLE_NFTABLES
+extern int xtables_ip4_main(int, char **);
+extern int xtables_ip4_save_main(int, char **);
+extern int xtables_ip4_restore_main(int, char **);
+extern int xtables_ip6_main(int, char **);
+extern int xtables_ip6_save_main(int, char **);
+extern int xtables_ip6_restore_main(int, char **);
+extern int xtables_ip4_xlate_main(int, char **);
+extern int xtables_ip6_xlate_main(int, char **);
+extern int xtables_eb_xlate_main(int, char **);
+extern int xtables_ip4_xlate_restore_main(int, char **);
+extern int xtables_ip6_xlate_restore_main(int, char **);
+extern int xtables_arp_main(int, char **);
+extern int xtables_arp_restore_main(int, char **);
+extern int xtables_arp_save_main(int, char **);
+extern int xtables_eb_main(int, char **);
+extern int xtables_eb_restore_main(int, char **);
+extern int xtables_eb_save_main(int, char **);
+extern int xtables_config_main(int, char **);
+extern int xtables_monitor_main(int, char **);
+#endif
 
 #endif /* _XTABLES_MULTI_H */
diff --git a/iptables/xtables-nft-multi.c b/iptables/xtables-nft-multi.c
new file mode 100644
index 0000000..e2b7c64
--- /dev/null
+++ b/iptables/xtables-nft-multi.c
@@ -0,0 +1,53 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "xshared.h"
+
+#include "xtables-multi.h"
+
+static const struct subcommand multi_subcommands[] = {
+	{"iptables-xml",		iptables_xml_main},
+	{"xml",				iptables_xml_main},
+	{"iptables",			xtables_ip4_main},
+	{"iptables-nft",		xtables_ip4_main},
+	{"main4",			xtables_ip4_main},
+	{"save4",			xtables_ip4_save_main},
+	{"restore4",			xtables_ip4_restore_main},
+	{"iptables-save",		xtables_ip4_save_main},
+	{"iptables-restore",		xtables_ip4_restore_main},
+	{"iptables-nft-save",	xtables_ip4_save_main},
+	{"iptables-nft-restore",	xtables_ip4_restore_main},
+	{"ip6tables",			xtables_ip6_main},
+	{"ip6tables-nft",		xtables_ip6_main},
+	{"main6",			xtables_ip6_main},
+	{"save6",			xtables_ip6_save_main},
+	{"restore6",			xtables_ip6_restore_main},
+	{"ip6tables-save",		xtables_ip6_save_main},
+	{"ip6tables-restore",		xtables_ip6_restore_main},
+	{"ip6tables-nft-save",	xtables_ip6_save_main},
+	{"ip6tables-nft-restore",	xtables_ip6_restore_main},
+	{"iptables-translate",		xtables_ip4_xlate_main},
+	{"ip6tables-translate",		xtables_ip6_xlate_main},
+	{"iptables-restore-translate",	xtables_ip4_xlate_restore_main},
+	{"ip6tables-restore-translate",	xtables_ip6_xlate_restore_main},
+	{"arptables",			xtables_arp_main},
+	{"arptables-nft",		xtables_arp_main},
+	{"arptables-restore",		xtables_arp_restore_main},
+	{"arptables-nft-restore",	xtables_arp_restore_main},
+	{"arptables-save",		xtables_arp_save_main},
+	{"arptables-nft-save",		xtables_arp_save_main},
+	{"ebtables-translate",		xtables_eb_xlate_main},
+	{"ebtables",			xtables_eb_main},
+	{"ebtables-restore",		xtables_eb_restore_main},
+	{"ebtables-save",		xtables_eb_save_main},
+	{"ebtables-nft",		xtables_eb_main},
+	{"ebtables-nft-restore",	xtables_eb_restore_main},
+	{"ebtables-nft-save",		xtables_eb_save_main},
+	{"xtables-monitor",		xtables_monitor_main},
+	{NULL},
+};
+
+int main(int argc, char **argv)
+{
+	return subcmd_main(argc, argv, multi_subcommands);
+}
diff --git a/iptables/xtables-nft.8 b/iptables/xtables-nft.8
new file mode 100644
index 0000000..702bf95
--- /dev/null
+++ b/iptables/xtables-nft.8
@@ -0,0 +1,208 @@
+.\"
+.\" (C) Copyright 2016-2017, Arturo Borrero Gonzalez <arturo@netfilter.org>
+.\"
+.\" %%%LICENSE_START(GPLv2+_DOC_FULL)
+.\" This is free documentation; 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.
+.\"
+.\" The GNU General Public License's references to "object code"
+.\" and "executables" are to be interpreted as the output of any
+.\" document formatting or typesetting system, including
+.\" intermediate and printed output.
+.\"
+.\" This manual 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 manual; if not, see
+.\" <http://www.gnu.org/licenses/>.
+.\" %%%LICENSE_END
+.\"
+.TH XTABLES-NFT 8 "June 2018"
+
+.SH NAME
+xtables-nft \(em iptables using nftables kernel api
+
+.SH DESCRIPTION
+\fBxtables-nft\fP are versions of iptables that use the nftables API.
+This is a set of tools to help the system administrator migrate the
+ruleset from \fBiptables(8)\fP, \fBip6tables(8)\fP, \fBarptables(8)\fP, and
+\fBebtables(8)\fP to \fBnftables(8)\fP.
+
+The \fBxtables-nft\fP set is composed of several commands:
+.IP \[bu] 2
+iptables\-nft
+.IP \[bu]
+iptables\-nft\-save
+.IP \[bu]
+iptables\-nft\-restore
+.IP \[bu]
+ip6tables\-nft
+.IP \[bu]
+ip6tables\-nft\-save
+.IP \[bu]
+ip6tables\-nft\-restore
+.IP \[bu]
+arptables\-nft
+.IP \[bu]
+ebtables\-nft
+
+These tools use the libxtables framework extensions and hook to the nf_tables
+kernel subsystem using the \fBnft_compat\fP module.
+
+.SH USAGE
+The xtables-nft tools allow you to manage the nf_tables backend using the
+native syntax of \fBiptables(8)\fP, \fBip6tables(8)\fP, \fBarptables(8)\fP, and
+\fBebtables(8)\fP.
+
+You should use the xtables-nft tools exactly the same way as you would use the
+corresponding original tools.
+
+Adding a rule will result in that rule being added to the nf_tables kernel
+subsystem instead.
+Listing the ruleset will use the nf_tables backend as well.
+
+When these tools were designed, the main idea was to replace each legacy binary
+with a symlink to the xtables-nft program, for example:
+
+.nf
+	/sbin/iptables -> /usr/sbin/iptables\-nft\-multi
+	/sbin/ip6tables -> /usr/sbin/ip6tables\-nft\-multi
+	/sbin/arptables -> /usr/sbin/arptables\-nft\-multi
+	/sbin/ebtables -> /usr/sbin/ebtables\-nft\-multi
+.fi
+
+The iptables version string will indicate whether the legacy API (get/setsockopt) or
+the new nf_tables api is used:
+.nf
+	iptables \-V
+	iptables v1.7 (nf_tables)
+.fi
+
+.SH DIFFERENCES TO LEGACY IPTABLES
+
+Because the xtables-nft tools use the nf_tables kernel API, rule additions
+and deletions are always atomic.  Unlike iptables-legacy, iptables-nft \-A ..
+will NOT need to retrieve the current ruleset from the kernel, change it, and
+re-load the altered ruleset.  Instead, iptables-nft will tell the kernel to add
+one rule.  For this reason, the iptables-legacy \-\-wait option is a no-op in
+iptables-nft.
+
+Use of the xtables-nft tools allow monitoring ruleset changes using the
+.B xtables\-monitor(8)
+command.
+
+When using \-j TRACE to debug packet traversal to the ruleset, note that you will need to use
+.B xtables\-monitor(8)
+in \-\-trace mode to obtain monitoring trace events.
+
+.SH EXAMPLES
+One basic example is creating the skeleton ruleset in nf_tables from the
+xtables-nft tools, in a fresh machine:
+
+.nf
+	root@machine:~# iptables\-nft \-L
+	[...]
+	root@machine:~# ip6tables\-nft \-L
+	[...]
+	root@machine:~# arptables\-nft \-L
+	[...]
+	root@machine:~# ebtables\-nft \-L
+	[...]
+	root@machine:~# nft list ruleset
+	table ip filter {
+		chain INPUT {
+			type filter hook input priority 0; policy accept;
+		}
+
+		chain FORWARD {
+			type filter hook forward priority 0; policy accept;
+		}
+
+		chain OUTPUT {
+			type filter hook output priority 0; policy accept;
+		}
+	}
+	table ip6 filter {
+		chain INPUT {
+			type filter hook input priority 0; policy accept;
+		}
+
+		chain FORWARD {
+			type filter hook forward priority 0; policy accept;
+		}
+
+		chain OUTPUT {
+			type filter hook output priority 0; policy accept;
+		}
+	}
+	table bridge filter {
+		chain INPUT {
+			type filter hook input priority \-200; policy accept;
+		}
+
+		chain FORWARD {
+			type filter hook forward priority \-200; policy accept;
+		}
+
+		chain OUTPUT {
+			type filter hook output priority \-200; policy accept;
+		}
+	}
+	table arp filter {
+		chain INPUT {
+			type filter hook input priority 0; policy accept;
+		}
+
+		chain FORWARD {
+			type filter hook forward priority 0; policy accept;
+		}
+
+		chain OUTPUT {
+			type filter hook output priority 0; policy accept;
+		}
+	}
+.fi
+
+(please note that in fresh machines, listing the ruleset for the first time
+results in all tables an chain being created).
+
+To migrate your complete filter ruleset, in the case of \fBiptables(8)\fP,
+you would use:
+
+.nf
+	root@machine:~# iptables\-legacy\-save > myruleset # reads from x_tables
+	root@machine:~# iptables\-nft\-restore myruleset   # writes to nf_tables
+.fi
+or
+.nf
+	root@machine:~# iptables\-legacy\-save | iptables-translate-restore | less
+.fi
+
+to see how rules would look like in the nft
+\fBnft(8)\fP
+syntax.
+
+.SH LIMITATIONS
+You should use \fBLinux kernel >= 4.17\fP.
+
+The CLUSTERIP target is not supported.
+
+To get up-to-date information about this, please head to
+\fBhttp://wiki.nftables.org/\fP.
+
+.SH SEE ALSO
+\fBnft(8)\fP, \fBxtables\-translate(8)\fP, \fBxtables\-monitor(8)\fP
+
+.SH AUTHORS
+The nftables framework is written by the Netfilter project
+(https://www.netfilter.org).
+
+This manual page was written by Arturo Borrero Gonzalez
+<arturo@debian.org> for the Debian project, but may be used by others.
+
+This documentation is free/libre under the terms of the GPLv2+.
diff --git a/iptables/xtables-restore.c b/iptables/xtables-restore.c
new file mode 100644
index 0000000..d273949
--- /dev/null
+++ b/iptables/xtables-restore.c
@@ -0,0 +1,477 @@
+/* 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 "config.h"
+#include <getopt.h>
+#include <errno.h>
+#include <libgen.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "iptables.h"
+#include "xtables.h"
+#include "libiptc/libiptc.h"
+#include "xtables-multi.h"
+#include "nft.h"
+#include "nft-bridge.h"
+#include "nft-cache.h"
+#include <libnftnl/chain.h>
+
+static int counters, verbose;
+
+/* Keeping track of external matches and targets.  */
+static const struct option options[] = {
+	{.name = "counters", .has_arg = false, .val = 'c'},
+	{.name = "verbose",  .has_arg = false, .val = 'v'},
+	{.name = "version",       .has_arg = 0, .val = 'V'},
+	{.name = "test",     .has_arg = false, .val = 't'},
+	{.name = "help",     .has_arg = false, .val = 'h'},
+	{.name = "noflush",  .has_arg = false, .val = 'n'},
+	{.name = "modprobe", .has_arg = true,  .val = 'M'},
+	{.name = "table",    .has_arg = true,  .val = 'T'},
+	{.name = "ipv4",     .has_arg = false, .val = '4'},
+	{.name = "ipv6",     .has_arg = false, .val = '6'},
+	{.name = "wait",          .has_arg = 2, .val = 'w'},
+	{.name = "wait-interval", .has_arg = 2, .val = 'W'},
+	{NULL},
+};
+
+#define prog_name xtables_globals.program_name
+#define prog_vers xtables_globals.program_version
+
+static void print_usage(const char *name, const char *version)
+{
+	fprintf(stderr, "Usage: %s [-c] [-v] [-V] [-t] [-h] [-n] [-T table] [-M command] [-4] [-6] [file]\n"
+			"	   [ --counters ]\n"
+			"	   [ --verbose ]\n"
+			"	   [ --version]\n"
+			"	   [ --test ]\n"
+			"	   [ --help ]\n"
+			"	   [ --noflush ]\n"
+			"	   [ --table=<TABLE> ]\n"
+			"	   [ --modprobe=<command> ]\n"
+			"	   [ --ipv4 ]\n"
+			"	   [ --ipv6 ]\n", name);
+}
+
+static const struct nft_xt_restore_cb restore_cb = {
+	.commit		= nft_commit,
+	.abort		= nft_abort,
+	.table_flush	= nft_cmd_table_flush,
+	.do_command	= do_commandx,
+	.chain_set	= nft_cmd_chain_set,
+	.chain_restore  = nft_cmd_chain_restore,
+};
+
+struct nft_xt_restore_state {
+	const struct builtin_table *curtable;
+	struct argv_store av_store;
+	bool in_table;
+};
+
+static void xtables_restore_parse_line(struct nft_handle *h,
+				       const struct nft_xt_restore_parse *p,
+				       struct nft_xt_restore_state *state,
+				       char *buffer)
+{
+	const struct nft_xt_restore_cb *cb = p->cb;
+	int ret = 0;
+
+	if (buffer[0] == '\n')
+		return;
+	else if (buffer[0] == '#') {
+		if (verbose) {
+			fputs(buffer, stdout);
+			fflush(stdout);
+		}
+		return;
+	} else if (state->in_table &&
+		   (strncmp(buffer, "COMMIT", 6) == 0) &&
+		   (buffer[6] == '\0' || buffer[6] == '\n')) {
+		if (!p->testing) {
+			/* Commit per table, although we support
+			 * global commit at once, stick by now to
+			 * the existing behaviour.
+			 */
+			DEBUGP("Calling commit\n");
+			if (cb->commit)
+				ret = cb->commit(h);
+		} else {
+			DEBUGP("Not calling commit, testing\n");
+			if (cb->abort)
+				ret = cb->abort(h);
+		}
+		state->in_table = false;
+
+	} else if ((buffer[0] == '*') && (!state->in_table || !p->commit)) {
+		/* 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);
+
+		state->curtable = nft_table_builtin_find(h, table);
+		if (!state->curtable)
+			xtables_error(PARAMETER_PROBLEM,
+				"%s: line %u table name '%s' invalid\n",
+				xt_params->program_name, line, table);
+
+		if (p->tablename && (strcmp(p->tablename, table) != 0))
+			return;
+
+		/* implicit commit if no explicit COMMIT supported */
+		if (!p->commit)
+			cb->commit(h);
+
+		if (h->noflush == 0) {
+			DEBUGP("Cleaning all chains of table '%s'\n", table);
+			if (cb->table_flush)
+				cb->table_flush(h, table, verbose);
+		}
+
+		ret = 1;
+		state->in_table = true;
+
+		if (cb->table_new)
+			cb->table_new(h, table);
+
+	} else if ((buffer[0] == ':') && state->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);
+
+		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);
+
+		if (nft_chain_builtin_find(state->curtable, chain)) {
+			if (counters) {
+				char *ctrs;
+				ctrs = strtok(NULL, " \t\n");
+
+				if (!ctrs || !parse_counters(ctrs, &count))
+					xtables_error(PARAMETER_PROBLEM,
+						   "invalid policy counters for chain '%s'\n",
+						   chain);
+
+			}
+			if (cb->chain_set &&
+			    cb->chain_set(h, state->curtable->name,
+					  chain, policy, &count) < 0) {
+				xtables_error(OTHER_PROBLEM,
+					      "Can't set policy `%s' on `%s' line %u: %s\n",
+					      policy, chain, line,
+					      strerror(errno));
+			}
+			DEBUGP("Setting policy of chain %s to %s\n",
+			       chain, policy);
+		} else if (cb->chain_restore(h, chain, state->curtable->name) < 0 &&
+			   errno != EEXIST) {
+			xtables_error(PARAMETER_PROBLEM,
+				      "cannot create chain '%s' (%s)\n",
+				      chain, strerror(errno));
+		} else if (h->family == NFPROTO_BRIDGE &&
+			   !ebt_cmd_user_chain_policy(h, state->curtable->name,
+						      chain, policy)) {
+			xtables_error(OTHER_PROBLEM,
+				      "Can't set policy `%s' on `%s' line %u: %s\n",
+				      policy, chain, line,
+				      strerror(errno));
+		}
+		ret = 1;
+	} else if (state->in_table) {
+		char *pcnt = NULL;
+		char *bcnt = NULL;
+		char *parsestart = buffer;
+
+		add_argv(&state->av_store, xt_params->program_name, 0);
+		add_argv(&state->av_store, "-t", 0);
+		add_argv(&state->av_store, state->curtable->name, 0);
+
+		tokenize_rule_counters(&parsestart, &pcnt, &bcnt, line);
+		if (counters && pcnt && bcnt) {
+			add_argv(&state->av_store, "--set-counters", 0);
+			add_argv(&state->av_store, pcnt, 0);
+			add_argv(&state->av_store, bcnt, 0);
+		}
+
+		add_param_to_argv(&state->av_store, parsestart, line);
+
+		DEBUGP("calling do_command4(%u, argv, &%s, handle):\n",
+		       state->av_store.argc, state->curtable->name);
+		debug_print_argv(&state->av_store);
+
+		ret = cb->do_command(h, state->av_store.argc,
+				     state->av_store.argv,
+				     &state->av_store.argv[2], true);
+		if (ret < 0) {
+			if (cb->abort)
+				ret = cb->abort(h);
+			else
+				ret = 0;
+
+			if (ret < 0) {
+				fprintf(stderr,
+					"failed to abort commit operation\n");
+			}
+			exit(1);
+		}
+
+		free_argv(&state->av_store);
+		fflush(stdout);
+	}
+	if (p->tablename && state->curtable &&
+	    (strcmp(p->tablename, state->curtable->name) != 0))
+		return;
+	if (!ret) {
+		fprintf(stderr, "%s: line %u failed\n",
+				xt_params->program_name, line);
+		exit(1);
+	}
+}
+
+void xtables_restore_parse(struct nft_handle *h,
+			   const struct nft_xt_restore_parse *p)
+{
+	struct nft_xt_restore_state state = {};
+	char buffer[10240] = {};
+
+	if (!verbose && !h->noflush)
+		nft_cache_level_set(h, NFT_CL_FAKE, NULL);
+
+	line = 0;
+	while (fgets(buffer, sizeof(buffer), p->in)) {
+		h->error.lineno = ++line;
+		DEBUGP("%s: input line %d: '%s'\n", __func__, line, buffer);
+		xtables_restore_parse_line(h, p, &state, buffer);
+	}
+	if (state.in_table && p->commit) {
+		fprintf(stderr, "%s: COMMIT expected at line %u\n",
+				xt_params->program_name, line + 1);
+		exit(1);
+	} else if (state.in_table && p->cb->commit && !p->cb->commit(h)) {
+		xtables_error(OTHER_PROBLEM, "%s: final implicit COMMIT failed",
+			      xt_params->program_name);
+	}
+}
+
+static int
+xtables_restore_main(int family, const char *progname, int argc, char *argv[])
+{
+	const struct builtin_table *tables;
+	struct nft_xt_restore_parse p = {
+		.commit = true,
+		.cb = &restore_cb,
+	};
+	bool noflush = false;
+	struct nft_handle h;
+	int c;
+
+	line = 0;
+
+	xtables_globals.program_name = progname;
+	c = xtables_init_all(&xtables_globals, family);
+	if (c < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
+				xtables_globals.program_name,
+				xtables_globals.program_version);
+		exit(1);
+	}
+
+	while ((c = getopt_long(argc, argv, "bcvVthnM:T:wW", options, NULL)) != -1) {
+		switch (c) {
+			case 'b':
+				fprintf(stderr, "-b/--binary option is not implemented\n");
+				break;
+			case 'c':
+				counters = 1;
+				break;
+			case 'v':
+				verbose = 1;
+				break;
+			case 'V':
+				printf("%s v%s (nf_tables)\n", prog_name, prog_vers);
+				exit(0);
+			case 't':
+				p.testing = 1;
+				break;
+			case 'h':
+				print_usage(prog_name, PACKAGE_VERSION);
+				exit(0);
+			case 'n':
+				noflush = true;
+				break;
+			case 'M':
+				xtables_modprobe_program = optarg;
+				break;
+			case 'T':
+				p.tablename = optarg;
+				break;
+			case 'w': /* fallthrough.  Ignored by xt-restore */
+			case 'W':
+				if (!optarg && xs_has_arg(argc, argv))
+					optind++;
+				break;
+			default:
+				fprintf(stderr,
+					"Try `%s -h' for more information.\n",
+					prog_name);
+				exit(1);
+		}
+	}
+
+	if (optind == argc - 1) {
+		p.in = fopen(argv[optind], "re");
+		if (!p.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 {
+		p.in = stdin;
+	}
+
+	switch (family) {
+	case NFPROTO_IPV4:
+	case NFPROTO_IPV6: /* fallthough, same table */
+		tables = xtables_ipv4;
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+		init_extensions();
+		init_extensions4();
+#endif
+		break;
+	case NFPROTO_ARP:
+		tables = xtables_arp;
+		break;
+	case NFPROTO_BRIDGE:
+		tables = xtables_bridge;
+		break;
+	default:
+		fprintf(stderr, "Unknown family %d\n", family);
+		return 1;
+	}
+
+	if (nft_init(&h, family, tables) < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize nft: %s\n",
+				xtables_globals.program_name,
+				xtables_globals.program_version,
+				strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+	h.noflush = noflush;
+	h.restore = true;
+
+	xtables_restore_parse(&h, &p);
+
+	nft_fini(&h);
+	xtables_fini();
+	fclose(p.in);
+	return 0;
+}
+
+int xtables_ip4_restore_main(int argc, char *argv[])
+{
+	return xtables_restore_main(NFPROTO_IPV4, basename(*argv),
+				    argc, argv);
+}
+
+int xtables_ip6_restore_main(int argc, char *argv[])
+{
+	return xtables_restore_main(NFPROTO_IPV6, basename(*argv),
+				    argc, argv);
+}
+
+static const struct nft_xt_restore_cb ebt_restore_cb = {
+	.commit		= nft_bridge_commit,
+	.table_flush	= nft_cmd_table_flush,
+	.do_command	= do_commandeb,
+	.chain_set	= nft_cmd_chain_set,
+	.chain_restore  = nft_cmd_chain_restore,
+};
+
+static const struct option ebt_restore_options[] = {
+	{.name = "noflush", .has_arg = 0, .val = 'n'},
+	{ 0 }
+};
+
+int xtables_eb_restore_main(int argc, char *argv[])
+{
+	struct nft_xt_restore_parse p = {
+		.in = stdin,
+		.cb = &ebt_restore_cb,
+	};
+	bool noflush = false;
+	struct nft_handle h;
+	int c;
+
+	while ((c = getopt_long(argc, argv, "n",
+				ebt_restore_options, NULL)) != -1) {
+		switch(c) {
+		case 'n':
+			noflush = 1;
+			break;
+		default:
+			fprintf(stderr,
+				"Usage: ebtables-restore [ --noflush ]\n");
+			exit(1);
+			break;
+		}
+	}
+
+	nft_init_eb(&h, "ebtables-restore");
+	h.noflush = noflush;
+	xtables_restore_parse(&h, &p);
+	nft_fini_eb(&h);
+
+	return 0;
+}
+
+static const struct nft_xt_restore_cb arp_restore_cb = {
+	.commit		= nft_commit,
+	.table_flush	= nft_cmd_table_flush,
+	.do_command	= do_commandarp,
+	.chain_set	= nft_cmd_chain_set,
+	.chain_restore  = nft_cmd_chain_restore,
+};
+
+int xtables_arp_restore_main(int argc, char *argv[])
+{
+	struct nft_xt_restore_parse p = {
+		.in = stdin,
+		.cb = &arp_restore_cb,
+	};
+	struct nft_handle h;
+
+	nft_init_arp(&h, "arptables-restore");
+	xtables_restore_parse(&h, &p);
+	nft_fini(&h);
+	xtables_fini();
+
+	return 0;
+}
diff --git a/iptables/xtables-save.c b/iptables/xtables-save.c
new file mode 100644
index 0000000..d7901c6
--- /dev/null
+++ b/iptables/xtables-save.c
@@ -0,0 +1,272 @@
+/* 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 "config.h"
+#include <getopt.h>
+#include <errno.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <netdb.h>
+#include <unistd.h>
+#include "libiptc/libiptc.h"
+#include "iptables.h"
+#include "xtables-multi.h"
+#include "nft.h"
+#include "nft-cache.h"
+
+#include <libnftnl/chain.h>
+
+#ifndef NO_SHARED_LIBS
+#include <dlfcn.h>
+#endif
+
+#define prog_name xtables_globals.program_name
+#define prog_vers xtables_globals.program_version
+
+static const char *ipt_save_optstring = "bcdt:M:f:V";
+static const struct option ipt_save_options[] = {
+	{.name = "counters", .has_arg = false, .val = 'c'},
+	{.name = "version",  .has_arg = false, .val = 'V'},
+	{.name = "dump",     .has_arg = false, .val = 'd'},
+	{.name = "table",    .has_arg = true,  .val = 't'},
+	{.name = "modprobe", .has_arg = true,  .val = 'M'},
+	{.name = "file",     .has_arg = true,  .val = 'f'},
+	{NULL},
+};
+
+static const char *arp_save_optstring = "cM:V";
+static const struct option arp_save_options[] = {
+	{.name = "counters", .has_arg = false, .val = 'c'},
+	{.name = "version",  .has_arg = false, .val = 'V'},
+	{.name = "modprobe", .has_arg = true,  .val = 'M'},
+	{NULL},
+};
+
+static const char *ebt_save_optstring = "ct:M:V";
+static const struct option ebt_save_options[] = {
+	{.name = "counters", .has_arg = false, .val = 'c'},
+	{.name = "version",  .has_arg = false, .val = 'V'},
+	{.name = "table",    .has_arg = true,  .val = 't'},
+	{.name = "modprobe", .has_arg = true,  .val = 'M'},
+	{NULL},
+};
+
+struct do_output_data {
+	unsigned int format;
+	bool commit;
+};
+
+static int
+__do_output(struct nft_handle *h, const char *tablename, void *data)
+{
+	struct do_output_data *d = data;
+	time_t now;
+
+	if (!nft_table_builtin_find(h, tablename))
+		return 0;
+
+	if (!nft_is_table_compatible(h, tablename, NULL)) {
+		printf("# Table `%s' is incompatible, use 'nft' tool.\n",
+		       tablename);
+		return 0;
+	}
+
+	now = time(NULL);
+	printf("# Generated by %s v%s on %s", prog_name,
+	       prog_vers, ctime(&now));
+
+	printf("*%s\n", tablename);
+	/* Dump out chain names first,
+	 * thereby preventing dependency conflicts */
+	nft_chain_foreach(h, tablename, nft_chain_save, h);
+	nft_rule_save(h, tablename, d->format);
+	if (d->commit)
+		printf("COMMIT\n");
+
+	now = time(NULL);
+	printf("# Completed on %s", ctime(&now));
+	return 0;
+}
+
+static int
+do_output(struct nft_handle *h, const char *tablename, struct do_output_data *d)
+{
+	int ret;
+
+	if (!tablename) {
+		ret = nft_for_each_table(h, __do_output, d);
+		nft_check_xt_legacy(h->family, true);
+		return !!ret;
+	}
+
+	if (!nft_table_find(h, tablename) &&
+	    !nft_table_builtin_find(h, tablename)) {
+		fprintf(stderr, "Table `%s' does not exist\n", tablename);
+		return 1;
+	}
+
+	ret = __do_output(h, tablename, d);
+	nft_check_xt_legacy(h->family, true);
+	return ret;
+}
+
+/* Format:
+ * :Chain name POLICY packets bytes
+ * rule
+ */
+static int
+xtables_save_main(int family, int argc, char *argv[],
+		  const char *optstring, const struct option *longopts)
+{
+	const struct builtin_table *tables;
+	const char *tablename = NULL;
+	struct do_output_data d = {
+		.format = FMT_NOCOUNTS,
+	};
+	struct nft_handle h;
+	bool dump = false;
+	FILE *file = NULL;
+	int ret, c;
+
+	xtables_globals.program_name = basename(*argv);;
+	c = xtables_init_all(&xtables_globals, family);
+	if (c < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
+				xtables_globals.program_name,
+				xtables_globals.program_version);
+		exit(1);
+	}
+
+	while ((c = getopt_long(argc, argv, optstring, longopts, NULL)) != -1) {
+		switch (c) {
+		case 'b':
+			fprintf(stderr, "-b/--binary option is not implemented\n");
+			break;
+		case 'c':
+			d.format &= ~FMT_NOCOUNTS;
+			break;
+
+		case 't':
+			/* Select specific table. */
+			tablename = optarg;
+			break;
+		case 'M':
+			xtables_modprobe_program = optarg;
+			break;
+		case 'f':
+			file = fopen(optarg, "w");
+			if (file == NULL) {
+				fprintf(stderr, "Failed to open file, error: %s\n",
+					strerror(errno));
+				exit(1);
+			}
+			ret = dup2(fileno(file), STDOUT_FILENO);
+			if (ret == -1) {
+				fprintf(stderr, "Failed to redirect stdout, error: %s\n",
+					strerror(errno));
+				exit(1);
+			}
+			fclose(file);
+			break;
+		case 'd':
+			dump = true;
+			break;
+		case 'V':
+			printf("%s v%s (nf_tables)\n", prog_name, prog_vers);
+			exit(0);
+		default:
+			fprintf(stderr,
+				"Look at manual page `%s.8' for more information.\n",
+				prog_name);
+			exit(1);
+		}
+	}
+
+	if (optind < argc) {
+		fprintf(stderr, "Unknown arguments found on commandline\n");
+		exit(1);
+	}
+
+	switch (family) {
+	case NFPROTO_IPV4:
+	case NFPROTO_IPV6: /* fallthough, same table */
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+		init_extensions();
+		init_extensions4();
+#endif
+		tables = xtables_ipv4;
+		d.commit = true;
+		break;
+	case NFPROTO_ARP:
+		tables = xtables_arp;
+		break;
+	case NFPROTO_BRIDGE: {
+		const char *ctr = getenv("EBTABLES_SAVE_COUNTER");
+
+		if (!(d.format & FMT_NOCOUNTS)) {
+			d.format |= FMT_EBT_SAVE;
+		} else if (ctr && !strcmp(ctr, "yes")) {
+			d.format &= ~FMT_NOCOUNTS;
+			d.format |= FMT_C_COUNTS | FMT_EBT_SAVE;
+		}
+		tables = xtables_bridge;
+		break;
+	}
+	default:
+		fprintf(stderr, "Unknown family %d\n", family);
+		return 1;
+	}
+
+	if (nft_init(&h, family, tables) < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize nft: %s\n",
+				xtables_globals.program_name,
+				xtables_globals.program_version,
+				strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+
+	nft_cache_level_set(&h, NFT_CL_RULES, NULL);
+	nft_cache_build(&h);
+	nft_xt_fake_builtin_chains(&h, tablename, NULL);
+
+	ret = do_output(&h, tablename, &d);
+	nft_fini(&h);
+	xtables_fini();
+	if (dump)
+		exit(0);
+
+	return ret;
+}
+
+int xtables_ip4_save_main(int argc, char *argv[])
+{
+	return xtables_save_main(NFPROTO_IPV4, argc, argv,
+				 ipt_save_optstring, ipt_save_options);
+}
+
+int xtables_ip6_save_main(int argc, char *argv[])
+{
+	return xtables_save_main(NFPROTO_IPV6, argc, argv,
+				 ipt_save_optstring, ipt_save_options);
+}
+
+int xtables_eb_save_main(int argc, char *argv[])
+{
+	return xtables_save_main(NFPROTO_BRIDGE, argc, argv,
+				 ebt_save_optstring, ebt_save_options);
+}
+
+int xtables_arp_save_main(int argc, char *argv[])
+{
+	return xtables_save_main(NFPROTO_ARP, argc, argv,
+				 arp_save_optstring, arp_save_options);
+}
diff --git a/iptables/xtables-standalone.c b/iptables/xtables-standalone.c
new file mode 100644
index 0000000..7b71db6
--- /dev/null
+++ b/iptables/xtables-standalone.c
@@ -0,0 +1,97 @@
+/*
+ * Author: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au
+ *
+ * Based on the ipchains code by Paul Russell and Michael Neuling
+ *
+ * (C) 2000-2002 by the netfilter coreteam <coreteam@netfilter.org>:
+ * 		    Paul 'Rusty' Russell <rusty@rustcorp.com.au>
+ * 		    Marc Boucher <marc+nf@mbsi.ca>
+ * 		    James Morris <jmorris@intercode.com.au>
+ * 		    Harald Welte <laforge@gnumonks.org>
+ * 		    Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ *
+ *	iptables -- IP firewall administration for kernels with
+ *	firewall table (aimed for the 2.3 kernels)
+ *
+ *	See the accompanying manual page iptables(8) for information
+ *	about proper usage of this program.
+ *
+ *	This program is free software; you can redistribute it and/or modify
+ *	it under the terms of the GNU General Public License as published by
+ *	the Free Software Foundation; either version 2 of the License, or
+ *	(at your option) any later version.
+ *
+ *	This program is distributed in the hope that it will be useful,
+ *	but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *	GNU General Public License for more details.
+ *
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program; if not, write to the Free Software
+ *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <iptables.h>
+#include "xtables-multi.h"
+#include "nft.h"
+
+static int
+xtables_main(int family, const char *progname, int argc, char *argv[])
+{
+	int ret;
+	char *table = "filter";
+	struct nft_handle h;
+
+	xtables_globals.program_name = progname;
+	ret = xtables_init_all(&xtables_globals, family);
+	if (ret < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
+				xtables_globals.program_name,
+				xtables_globals.program_version);
+				exit(1);
+	}
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+	init_extensions();
+	init_extensions4();
+#endif
+
+	if (nft_init(&h, family, xtables_ipv4) < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize nft: %s\n",
+				xtables_globals.program_name,
+				xtables_globals.program_version,
+				strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+
+	ret = do_commandx(&h, argc, argv, &table, false);
+	if (ret)
+		ret = nft_commit(&h);
+
+	nft_fini(&h);
+	xtables_fini();
+
+	if (!ret) {
+		fprintf(stderr, "%s: %s.%s\n", progname, nft_strerror(errno),
+			(errno == EINVAL ?
+			 " Run `dmesg' for more information." : ""));
+
+		if (errno == EAGAIN)
+			exit(RESOURCE_PROBLEM);
+	}
+
+	exit(!ret);
+}
+
+int xtables_ip4_main(int argc, char *argv[])
+{
+	return xtables_main(NFPROTO_IPV4, "iptables", argc, argv);
+}
+
+int xtables_ip6_main(int argc, char *argv[])
+{
+	return xtables_main(NFPROTO_IPV6, "ip6tables", argc, argv);
+}
diff --git a/iptables/xtables-translate.8 b/iptables/xtables-translate.8
new file mode 100644
index 0000000..3dc7276
--- /dev/null
+++ b/iptables/xtables-translate.8
@@ -0,0 +1,136 @@
+.\"
+.\" (C) Copyright 2018, Arturo Borrero Gonzalez <arturo@netfilter.org>
+.\"
+.\" %%%LICENSE_START(GPLv2+_DOC_FULL)
+.\" This is free documentation; 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.
+.\"
+.\" The GNU General Public License's references to "object code"
+.\" and "executables" are to be interpreted as the output of any
+.\" document formatting or typesetting system, including
+.\" intermediate and printed output.
+.\"
+.\" This manual 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 manual; if not, see
+.\" <http://www.gnu.org/licenses/>.
+.\" %%%LICENSE_END
+.\"
+.TH IPTABLES-TRANSLATE 8 "May 14, 2019"
+
+.SH NAME
+iptables-translate \(em translation tool to migrate from iptables to nftables
+.P
+ip6tables-translate \(em translation tool to migrate from ip6tables to nftables
+.SH DESCRIPTION
+There is a set of tools to help the system administrator translate a given
+ruleset from \fBiptables(8)\fP and \fBip6tables(8)\fP to \fBnftables(8)\fP.
+
+The available commands are:
+
+.IP \[bu] 2
+iptables-translate
+.IP \[bu]
+iptables-restore-translate
+.IP \[bu] 2
+ip6tables-translate
+.IP \[bu]
+ip6tables-restore-translate
+
+.SH USAGE
+They take as input the original \fBiptables(8)\fP/\fBip6tables(8)\fP syntax and
+output the native \fBnftables(8)\fP syntax.
+
+The \fBiptables-restore-translate\fP tool reads a ruleset in the syntax
+produced by \fBiptables-save(8)\fP. Likewise, the
+\fBip6tables-restore-translate\fP tool reads one produced by
+\fBip6tables-save(8)\fP.  No ruleset modifications occur, these tools are
+text converters only.
+
+The \fBiptables-translate\fP reads a command line as if it was entered to
+\fBiptables(8)\fP, and \fBip6tables-translate\fP reads a command like as if it
+was entered to \fBip6tables(8)\fP.
+
+.SH EXAMPLES
+Basic operation examples.
+
+Single command translation:
+
+.nf
+root@machine:~# iptables-translate -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
+nft add rule ip filter INPUT tcp dport 22 ct state new counter accept
+
+root@machine:~# ip6tables-translate -A FORWARD -i eth0 -o eth3 -p udp -m multiport --dports 111,222 -j ACCEPT
+nft add rule ip6 filter FORWARD iifname eth0 oifname eth3 meta l4proto udp udp dport { 111,222} counter accept
+.fi
+
+Whole ruleset translation:
+
+.nf
+root@machine:~# iptables-save > save.txt
+root@machine:~# cat save.txt
+# Generated by iptables-save v1.6.0 on Sat Dec 24 14:26:40 2016
+*filter
+:INPUT ACCEPT [5166:1752111]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [5058:628693]
+-A FORWARD -p tcp -m tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
+COMMIT
+# Completed on Sat Dec 24 14:26:40 2016
+
+root@machine:~# iptables-restore-translate -f save.txt
+# Translated by iptables-restore-translate v1.6.0 on Sat Dec 24 14:26:59 2016
+add table ip filter
+add chain ip filter INPUT { type filter hook input priority 0; }
+add chain ip filter FORWARD { type filter hook forward priority 0; }
+add chain ip filter OUTPUT { type filter hook output priority 0; }
+add rule ip filter FORWARD tcp dport 22 ct state new counter accept
+
+root@machine:~# iptables-restore-translate -f save.txt > ruleset.nft
+root@machine:~# nft -f ruleset.nft
+root@machine:~# nft list ruleset
+table ip filter {
+	chain INPUT {
+		type filter hook input priority 0; policy accept;
+	}
+
+	chain FORWARD {
+		type filter hook forward priority 0; policy accept;
+		tcp dport ssh ct state new counter packets 0 bytes 0 accept
+	}
+
+	chain OUTPUT {
+		type filter hook output priority 0; policy accept;
+	}
+}
+.fi
+
+
+.SH LIMITATIONS
+Some (few) extensions may be not supported (or fully-supported) for whatever
+reason (for example, they were considered obsolete, or we didn't have the time
+to work on them).
+
+There are no translations available for \fBebtables(8)\fP and
+\fBarptables(8)\fP.
+
+To get up-to-date information about this, please head to
+\fBhttps://wiki.nftables.org/\fP.
+
+.SH SEE ALSO
+\fBnft(8)\fP, \fBiptables(8)\fP
+
+.SH AUTHORS
+The nftables framework is written by the Netfilter project
+(https://www.netfilter.org).
+
+This manual page was written by Arturo Borrero Gonzalez
+<arturo@netfilter.org>.
+
+This documentation is free/libre under the terms of the GPLv2+.
diff --git a/iptables/xtables-translate.c b/iptables/xtables-translate.c
new file mode 100644
index 0000000..575fb32
--- /dev/null
+++ b/iptables/xtables-translate.c
@@ -0,0 +1,599 @@
+/*
+ * (C) 2014 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.
+ */
+#include "config.h"
+#include <time.h>
+#include "xtables-multi.h"
+#include "nft.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 <libiptc/libxtc.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include "xshared.h"
+#include "nft-shared.h"
+
+void xlate_ifname(struct xt_xlate *xl, const char *nftmeta, const char *ifname,
+		  bool invert)
+{
+	int ifaclen = strlen(ifname), i, j;
+	char iface[IFNAMSIZ * 2];
+
+	if (ifaclen < 1 || ifaclen >= IFNAMSIZ)
+		return;
+
+	for (i = 0, j = 0; i < ifaclen + 1; i++, j++) {
+		switch (ifname[i]) {
+		case '*':
+			iface[j++] = '\\';
+			/* fall through */
+		default:
+			iface[j] = ifname[i];
+			break;
+		}
+	}
+
+	if (ifaclen == 1 && ifname[0] == '+') {
+		/* Nftables does not support wildcard only string. Workaround
+		 * is easy, given that this will match always or never
+		 * depending on 'invert' value. To match always, simply don't
+		 * generate an expression. To match never, use an invalid
+		 * interface name (kernel doesn't accept '/' in names) to match
+		 * against. */
+		if (!invert)
+			return;
+		strcpy(iface, "INVAL/D");
+		invert = false;
+	}
+
+	if (iface[j - 2] == '+')
+		iface[j - 2] = '*';
+
+	xt_xlate_add(xl, "%s %s\"%s\" ", nftmeta, invert ? "!= " : "", iface);
+}
+
+int xlate_action(const struct iptables_command_state *cs, bool goto_set,
+		 struct xt_xlate *xl)
+{
+	int ret = 1, numeric = cs->options & OPT_NUMERIC;
+
+	/* If no target at all, add nothing (default to continue) */
+	if (cs->target != NULL) {
+		/* Standard target? */
+		if (strcmp(cs->jumpto, XTC_LABEL_ACCEPT) == 0)
+			xt_xlate_add(xl, " accept");
+		else if (strcmp(cs->jumpto, XTC_LABEL_DROP) == 0)
+			xt_xlate_add(xl, " drop");
+		else if (strcmp(cs->jumpto, XTC_LABEL_RETURN) == 0)
+			xt_xlate_add(xl, " return");
+		else if (cs->target->xlate) {
+			xt_xlate_add(xl, " ");
+			struct xt_xlate_tg_params params = {
+				.ip		= (const void *)&cs->fw,
+				.target		= cs->target->t,
+				.numeric	= numeric,
+				.escape_quotes	= !cs->restore,
+			};
+			ret = cs->target->xlate(xl, &params);
+		}
+		else
+			return 0;
+	} else if (strlen(cs->jumpto) > 0) {
+		/* Not standard, then it's a go / jump to chain */
+		if (goto_set)
+			xt_xlate_add(xl, " goto %s", cs->jumpto);
+		else
+			xt_xlate_add(xl, " jump %s", cs->jumpto);
+	}
+
+	return ret;
+}
+
+int xlate_matches(const struct iptables_command_state *cs, struct xt_xlate *xl)
+{
+	struct xtables_rule_match *matchp;
+	int ret = 1, numeric = cs->options & OPT_NUMERIC;
+
+	for (matchp = cs->matches; matchp; matchp = matchp->next) {
+		struct xt_xlate_mt_params params = {
+			.ip		= (const void *)&cs->fw,
+			.match		= matchp->match->m,
+			.numeric	= numeric,
+			.escape_quotes	= !cs->restore,
+		};
+
+		if (!matchp->match->xlate)
+			return 0;
+
+		ret = matchp->match->xlate(xl, &params);
+
+		if (strcmp(matchp->match->name, "comment") != 0)
+			xt_xlate_add(xl, " ");
+
+		if (!ret)
+			break;
+	}
+	return ret;
+}
+
+bool xlate_find_match(const struct iptables_command_state *cs, const char *p_name)
+{
+	struct xtables_rule_match *matchp;
+
+	/* Skip redundant protocol, eg. ip protocol tcp tcp dport */
+	for (matchp = cs->matches; matchp; matchp = matchp->next) {
+		if (strcmp(matchp->match->name, p_name) == 0)
+			return true;
+	}
+	return false;
+}
+
+const char *family2str[] = {
+	[NFPROTO_IPV4]	= "ip",
+	[NFPROTO_IPV6]	= "ip6",
+};
+
+static int nft_rule_xlate_add(struct nft_handle *h,
+			      const struct nft_xt_cmd_parse *p,
+			      const struct iptables_command_state *cs,
+			      bool append)
+{
+	struct xt_xlate *xl = xt_xlate_alloc(10240);
+	int ret;
+
+	if (append) {
+		xt_xlate_add(xl, "add rule %s %s %s ",
+			   family2str[h->family], p->table, p->chain);
+	} else {
+		xt_xlate_add(xl, "insert rule %s %s %s ",
+			   family2str[h->family], p->table, p->chain);
+	}
+
+	ret = h->ops->xlate(cs, xl);
+	if (ret)
+		printf("%s\n", xt_xlate_get(xl));
+
+	xt_xlate_free(xl);
+	return ret;
+}
+
+static int xlate(struct nft_handle *h, struct nft_xt_cmd_parse *p,
+		 struct iptables_command_state *cs,
+		 struct xtables_args *args, bool append,
+		 int (*cb)(struct nft_handle *h,
+			   const struct nft_xt_cmd_parse *p,
+			   const struct iptables_command_state *cs,
+			   bool append))
+{
+	unsigned int i, j;
+	int ret = 1;
+
+	for (i = 0; i < args->s.naddrs; i++) {
+		switch (h->family) {
+		case AF_INET:
+			cs->fw.ip.src.s_addr = args->s.addr.v4[i].s_addr;
+			cs->fw.ip.smsk.s_addr = args->s.mask.v4[i].s_addr;
+			for (j = 0; j < args->d.naddrs; j++) {
+				cs->fw.ip.dst.s_addr =
+					args->d.addr.v4[j].s_addr;
+				cs->fw.ip.dmsk.s_addr =
+					args->d.mask.v4[j].s_addr;
+				ret = cb(h, p, cs, append);
+			}
+			break;
+		case AF_INET6:
+			memcpy(&cs->fw6.ipv6.src,
+			       &args->s.addr.v6[i], sizeof(struct in6_addr));
+			memcpy(&cs->fw6.ipv6.smsk,
+			       &args->s.mask.v6[i], sizeof(struct in6_addr));
+			for (j = 0; j < args->d.naddrs; j++) {
+				memcpy(&cs->fw6.ipv6.dst,
+				       &args->d.addr.v6[j],
+				       sizeof(struct in6_addr));
+				memcpy(&cs->fw6.ipv6.dmsk,
+				       &args->d.mask.v6[j],
+				       sizeof(struct in6_addr));
+				ret = cb(h, p, cs, append);
+			}
+			break;
+		}
+		if (!cs->restore && i < args->s.naddrs - 1)
+			printf("nft ");
+	}
+
+	return ret;
+}
+
+static void print_ipt_cmd(int argc, char *argv[])
+{
+	int i;
+
+	printf("# ");
+	for (i = 1; i < argc; i++)
+		printf("%s ", argv[i]);
+
+	printf("\n");
+}
+
+static int do_command_xlate(struct nft_handle *h, int argc, char *argv[],
+			    char **table, bool restore)
+{
+	int ret = 0;
+	struct nft_xt_cmd_parse p = {
+		.table		= *table,
+		.restore	= restore,
+		.xlate		= true,
+	};
+	struct iptables_command_state cs;
+	struct xtables_args args = {
+		.family = h->family,
+	};
+
+	do_parse(h, argc, argv, &p, &cs, &args);
+
+	cs.restore = restore;
+
+	if (!restore && p.command != CMD_NONE)
+		printf("nft ");
+
+	switch (p.command) {
+	case CMD_APPEND:
+		ret = 1;
+		if (!xlate(h, &p, &cs, &args, true, nft_rule_xlate_add))
+			print_ipt_cmd(argc, argv);
+		break;
+	case CMD_DELETE:
+		break;
+	case CMD_DELETE_NUM:
+		break;
+	case CMD_CHECK:
+		break;
+	case CMD_REPLACE:
+		break;
+	case CMD_INSERT:
+		ret = 1;
+		if (!xlate(h, &p, &cs, &args, false, nft_rule_xlate_add))
+			print_ipt_cmd(argc, argv);
+		break;
+	case CMD_FLUSH:
+		if (p.chain) {
+			printf("flush chain %s %s %s\n",
+				family2str[h->family], p.table, p.chain);
+		} else {
+			printf("flush table %s %s\n",
+				family2str[h->family], p.table);
+		}
+		ret = 1;
+		break;
+	case CMD_ZERO:
+		break;
+	case CMD_ZERO_NUM:
+		break;
+	case CMD_LIST:
+	case CMD_LIST|CMD_ZERO:
+	case CMD_LIST|CMD_ZERO_NUM:
+		printf("list table %s %s\n",
+		       family2str[h->family], p.table);
+		ret = 1;
+		break;
+	case CMD_LIST_RULES:
+	case CMD_LIST_RULES|CMD_ZERO:
+	case CMD_LIST_RULES|CMD_ZERO_NUM:
+		break;
+	case CMD_NEW_CHAIN:
+		printf("add chain %s %s %s\n",
+		       family2str[h->family], p.table, p.chain);
+		ret = 1;
+		break;
+	case CMD_DELETE_CHAIN:
+		printf("delete chain %s %s %s\n",
+		       family2str[h->family], p.table, p.chain);
+		ret = 1;
+		break;
+	case CMD_RENAME_CHAIN:
+		break;
+	case CMD_SET_POLICY:
+		break;
+	case CMD_NONE:
+		ret = 1;
+		break;
+	default:
+		/* We should never reach this... */
+		printf("Unsupported command?\n");
+		exit(1);
+	}
+
+	nft_clear_iptables_command_state(&cs);
+
+	if (h->family == AF_INET) {
+		free(args.s.addr.v4);
+		free(args.s.mask.v4);
+		free(args.d.addr.v4);
+		free(args.d.mask.v4);
+	} else if (h->family == AF_INET6) {
+		free(args.s.addr.v6);
+		free(args.s.mask.v6);
+		free(args.d.addr.v6);
+		free(args.d.mask.v6);
+	}
+	xtables_free_opts(1);
+
+	return ret;
+}
+
+static void print_usage(const char *name, const char *version)
+{
+	fprintf(stderr, "%s %s "
+			"(c) 2014 by Pablo Neira Ayuso <pablo@netfilter.org>\n"
+			"Usage: %s [-h] [-f]\n"
+                        "	[ --help ]\n"
+                        "	[ --file=<FILE> ]\n", name, version, name);
+        exit(1);
+}
+
+static const struct option options[] = {
+	{ .name = "help",	.has_arg = false,	.val = 'h' },
+	{ .name = "file",	.has_arg = true,	.val = 'f' },
+	{ .name = "version",	.has_arg = false,	.val = 'V' },
+	{ NULL },
+};
+
+static int xlate_chain_user_restore(struct nft_handle *h, const char *chain,
+				    const char *table)
+{
+	printf("add chain %s %s %s\n", family2str[h->family], table, chain);
+	return 0;
+}
+
+static int commit(struct nft_handle *h)
+{
+	return 1;
+}
+
+static void xlate_table_new(struct nft_handle *h, const char *table)
+{
+	printf("add table %s %s\n", family2str[h->family], table);
+}
+
+static int get_hook_prio(const char *table, const char *chain)
+{
+	int prio = 0;
+
+	if (strcmp("nat", table) == 0) {
+		if (strcmp(chain, "PREROUTING") == 0)
+			prio = NF_IP_PRI_NAT_DST;
+		if (strcmp(chain, "INPUT") == 0)
+			prio = NF_IP_PRI_NAT_SRC;
+		if (strcmp(chain, "OUTPUT") == 0)
+			prio = NF_IP_PRI_NAT_DST;
+		if (strcmp(chain, "POSTROUTING") == 0)
+			prio = NF_IP_PRI_NAT_SRC;
+	} else if (strcmp("mangle", table) == 0) {
+		prio = NF_IP_PRI_MANGLE;
+	} else if (strcmp("raw", table) == 0) {
+		prio = NF_IP_PRI_RAW;
+	} else if (strcmp(chain, "security") == 0) {
+		prio = NF_IP_PRI_SECURITY;
+	}
+
+	return prio;
+}
+
+static int xlate_chain_set(struct nft_handle *h, const char *table,
+			   const char *chain, const char *policy,
+			   const struct xt_counters *counters)
+{
+	const char *type = "filter";
+	int prio;
+
+	if (strcmp(table, "nat") == 0)
+		type = "nat";
+	else if (strcmp(table, "mangle") == 0 && strcmp(chain, "OUTPUT") == 0)
+		type = "route";
+
+	printf("add chain %s %s %s { type %s ",
+	       family2str[h->family], table, chain, type);
+	prio = get_hook_prio(table, chain);
+	if (strcmp(chain, "PREROUTING") == 0)
+		printf("hook prerouting priority %d; ", prio);
+	else if (strcmp(chain, "INPUT") == 0)
+		printf("hook input priority %d; ", prio);
+	else if (strcmp(chain, "FORWARD") == 0)
+		printf("hook forward priority %d; ", prio);
+	else if (strcmp(chain, "OUTPUT") == 0)
+		printf("hook output priority %d; ", prio);
+	else if (strcmp(chain, "POSTROUTING") == 0)
+		printf("hook postrouting priority %d; ", prio);
+
+	if (strcmp(policy, "ACCEPT") == 0)
+		printf("policy accept; ");
+	else if (strcmp(policy, "DROP") == 0)
+		printf("policy drop; ");
+
+	printf("}\n");
+	return 1;
+}
+
+static int dummy_compat_rev(const char *name, uint8_t rev, int opt)
+{
+	/* Avoid querying the kernel - it's not needed when just translating
+	 * rules and not even possible when running as unprivileged user.
+	 */
+	return 1;
+}
+
+static const struct nft_xt_restore_cb cb_xlate = {
+	.table_new	= xlate_table_new,
+	.chain_set	= xlate_chain_set,
+	.chain_restore	= xlate_chain_user_restore,
+	.do_command	= do_command_xlate,
+	.commit		= commit,
+	.abort		= commit,
+};
+
+static int xtables_xlate_main_common(struct nft_handle *h,
+				     int family,
+				     const char *progname)
+{
+	const struct builtin_table *tables;
+	int ret;
+
+	xtables_globals.program_name = progname;
+	xtables_globals.compat_rev = dummy_compat_rev;
+	ret = xtables_init_all(&xtables_globals, family);
+	if (ret < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
+			xtables_globals.program_name,
+			xtables_globals.program_version);
+		return 1;
+	}
+	switch (family) {
+	case NFPROTO_IPV4:
+	case NFPROTO_IPV6: /* fallthrough: same table */
+#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
+	init_extensions();
+	init_extensions4();
+#endif
+		tables = xtables_ipv4;
+		break;
+	case NFPROTO_ARP:
+		tables = xtables_arp;
+		break;
+	case NFPROTO_BRIDGE:
+		tables = xtables_bridge;
+		break;
+	default:
+		fprintf(stderr, "Unknown family %d\n", family);
+		return 1;
+	}
+
+	if (nft_init(h, family, tables) < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize nft: %s\n",
+				xtables_globals.program_name,
+				xtables_globals.program_version,
+				strerror(errno));
+		return 1;
+	}
+
+	return 0;
+}
+
+static int xtables_xlate_main(int family, const char *progname, int argc,
+			      char *argv[])
+{
+	int ret;
+	char *table = "filter";
+	struct nft_handle h = {
+		.family = family,
+	};
+
+	ret = xtables_xlate_main_common(&h, family, progname);
+	if (ret < 0)
+		exit(EXIT_FAILURE);
+
+	ret = do_command_xlate(&h, argc, argv, &table, false);
+	if (!ret)
+		fprintf(stderr, "Translation not implemented\n");
+
+	nft_fini(&h);
+	xtables_fini();
+	exit(!ret);
+}
+
+static int xtables_restore_xlate_main(int family, const char *progname,
+				      int argc, char *argv[])
+{
+	int ret;
+	struct nft_handle h = {
+		.family = family,
+	};
+	const char *file = NULL;
+	struct nft_xt_restore_parse p = {
+		.cb = &cb_xlate,
+	};
+	time_t now = time(NULL);
+	int c;
+
+	ret = xtables_xlate_main_common(&h, family, progname);
+	if (ret < 0)
+		exit(EXIT_FAILURE);
+
+	opterr = 0;
+	while ((c = getopt_long(argc, argv, "hf:V", options, NULL)) != -1) {
+		switch (c) {
+		case 'h':
+			print_usage(argv[0], PACKAGE_VERSION);
+			exit(0);
+		case 'f':
+			file = optarg;
+			break;
+		case 'V':
+			printf("%s v%s\n", argv[0], PACKAGE_VERSION);
+			exit(0);
+		}
+	}
+
+	if (file == NULL) {
+		fprintf(stderr, "ERROR: missing file name\n");
+		print_usage(argv[0], PACKAGE_VERSION);
+		exit(0);
+	}
+
+	p.in = fopen(file, "r");
+	if (p.in == NULL) {
+		fprintf(stderr, "Cannot open file %s\n", file);
+		exit(1);
+	}
+
+	printf("# Translated by %s v%s on %s",
+	       argv[0], PACKAGE_VERSION, ctime(&now));
+	xtables_restore_parse(&h, &p);
+	printf("# Completed on %s", ctime(&now));
+
+	nft_fini(&h);
+	xtables_fini();
+	fclose(p.in);
+	exit(0);
+}
+
+int xtables_ip4_xlate_main(int argc, char *argv[])
+{
+	return xtables_xlate_main(NFPROTO_IPV4, "iptables-translate",
+				  argc, argv);
+}
+
+int xtables_ip6_xlate_main(int argc, char *argv[])
+{
+	return xtables_xlate_main(NFPROTO_IPV6, "ip6tables-translate",
+				  argc, argv);
+}
+
+int xtables_ip4_xlate_restore_main(int argc, char *argv[])
+{
+	return xtables_restore_xlate_main(NFPROTO_IPV4,
+					  "iptables-translate-restore",
+					  argc, argv);
+}
+
+int xtables_ip6_xlate_restore_main(int argc, char *argv[])
+{
+	return xtables_restore_xlate_main(NFPROTO_IPV6,
+					  "ip6tables-translate-restore",
+					  argc, argv);
+}
diff --git a/iptables/xtables.c b/iptables/xtables.c
index acfcf8b..9779bd8 100644
--- a/iptables/xtables.c
+++ b/iptables/xtables.c
@@ -1,5 +1,14 @@
+/* Code to take an iptables-style command line and do it. */
+
 /*
- * (C) 2000-2006 by the netfilter coreteam <coreteam@netfilter.org>:
+ * 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
@@ -15,1818 +24,1055 @@
  *	along with this program; if not, write to the Free Software
  *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
-#include <ctype.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
+#include "config.h"
+#include <getopt.h>
+#include <string.h>
 #include <netdb.h>
-#include <stdarg.h>
+#include <errno.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <string.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <limits.h>
 #include <unistd.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/statfs.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <arpa/inet.h>
-#include <linux/magic.h> /* for PROC_SUPER_MAGIC */
-
+#include <iptables.h>
 #include <xtables.h>
-#include <limits.h> /* INT_MAX in ip_tables.h/ip6_tables.h */
-#include <linux/netfilter_ipv4/ip_tables.h>
-#include <linux/netfilter_ipv6/ip6_tables.h>
-#include <libiptc/libxtc.h>
-
-#ifndef NO_SHARED_LIBS
-#include <dlfcn.h>
-#endif
-#ifndef IPT_SO_GET_REVISION_MATCH /* Old kernel source. */
-#	define IPT_SO_GET_REVISION_MATCH	(IPT_BASE_CTL + 2)
-#	define IPT_SO_GET_REVISION_TARGET	(IPT_BASE_CTL + 3)
-#endif
-#ifndef IP6T_SO_GET_REVISION_MATCH /* Old kernel source. */
-#	define IP6T_SO_GET_REVISION_MATCH	68
-#	define IP6T_SO_GET_REVISION_TARGET	69
-#endif
-#include <getopt.h>
-#include "iptables/internal.h"
+#include <fcntl.h>
 #include "xshared.h"
+#include "nft-shared.h"
+#include "nft.h"
 
-#define NPROTO	255
+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 = "wait",	  .has_arg = 2, .val = 'w'},
+	{.name = "wait-interval", .has_arg = 2, .val = 'W'},
+	{.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},
+};
 
-#ifndef PROC_SYS_MODPROBE
-#define PROC_SYS_MODPROBE "/proc/sys/kernel/modprobe"
+void xtables_exit_error(enum xtables_exittype status, const char *msg, ...) __attribute__((noreturn, format(printf,2,3)));
+
+struct xtables_globals xtables_globals = {
+	.option_offset = 0,
+	.program_version = PACKAGE_VERSION,
+	.orig_opts = original_opts,
+	.exit_err = xtables_exit_error,
+	.compat_rev = nft_compatible_revision,
+};
+
+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 xt_params->opts
+#define prog_name xt_params->program_name
+#define prog_vers xt_params->program_version
+
+static void __attribute__((noreturn))
+exit_tryhelp(int status)
+{
+	if (line != -1)
+		fprintf(stderr, "Error occurred at line: %d\n", line);
+	fprintf(stderr, "Try `%s -h' or '%s --help' for more information.\n",
+			prog_name, prog_name);
+	xtables_free_opts(1);
+	exit(status);
+}
+
+static void
+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"
+"  --wait	-w [seconds]	maximum wait to acquire xtables lock before give up\n"
+"  --wait-interval -W [usecs]	wait time to try to acquire xtables lock\n"
+"				default is 1 second\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");
 
-/* we need this for ip6?tables-restore.  ip6?tables-restore.c sets line to the
- * current line of the input file, in order  to give a more precise error
- * message.  ip6?tables itself doesn't need this, so it is initialized to the
- * magic number of -1 */
-int line = -1;
+	print_extension_helps(xtables_targets, matches);
+}
 
-void basic_exit_err(enum xtables_exittype status, const char *msg, ...) __attribute__((noreturn, format(printf,2,3)));
-
-struct xtables_globals *xt_params = NULL;
-
-void basic_exit_err(enum xtables_exittype status, const char *msg, ...)
+void
+xtables_exit_error(enum xtables_exittype status, const char *msg, ...)
 {
 	va_list args;
 
 	va_start(args, msg);
-	fprintf(stderr, "%s v%s: ", xt_params->program_name, xt_params->program_version);
+	fprintf(stderr, "%s v%s (nf_tables): ", prog_name, prog_vers);
 	vfprintf(stderr, msg, args);
 	va_end(args);
 	fprintf(stderr, "\n");
+	if (status == PARAMETER_PROBLEM)
+		exit_tryhelp(status);
+	if (status == VERSION_PROBLEM)
+		fprintf(stderr,
+			"Perhaps iptables or your kernel needs to be upgraded.\n");
+	/* On error paths, make sure that we don't leak memory */
+	xtables_free_opts(1);
 	exit(status);
 }
 
-void xtables_free_opts(int unused)
+/*
+ *	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'.  */
+
+static void
+set_option(unsigned int *options, unsigned int option, uint8_t *invflg,
+	   int invert)
 {
-	if (xt_params->opts != xt_params->orig_opts) {
-		free(xt_params->opts);
-		xt_params->opts = NULL;
+	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];
 	}
 }
 
-struct option *xtables_merge_options(struct option *orig_opts,
-				     struct option *oldopts,
-				     const struct option *newopts,
-				     unsigned int *option_offset)
+static int
+add_entry(const char *chain,
+	  const char *table,
+	  struct iptables_command_state *cs,
+	  int rulenum, int family,
+	  const struct addr_mask s,
+	  const struct addr_mask d,
+	  bool verbose, struct nft_handle *h, bool append)
 {
-	unsigned int num_oold = 0, num_old = 0, num_new = 0, i;
-	struct option *merge, *mp;
+	unsigned int i, j;
+	int ret = 1;
 
-	if (newopts == NULL)
-		return oldopts;
+	for (i = 0; i < s.naddrs; i++) {
+		if (family == AF_INET) {
+			cs->fw.ip.src.s_addr = s.addr.v4[i].s_addr;
+			cs->fw.ip.smsk.s_addr = s.mask.v4[i].s_addr;
+			for (j = 0; j < d.naddrs; j++) {
+				cs->fw.ip.dst.s_addr = d.addr.v4[j].s_addr;
+				cs->fw.ip.dmsk.s_addr = d.mask.v4[j].s_addr;
 
-	for (num_oold = 0; orig_opts[num_oold].name; num_oold++) ;
-	if (oldopts != NULL)
-		for (num_old = 0; oldopts[num_old].name; num_old++) ;
-	for (num_new = 0; newopts[num_new].name; num_new++) ;
-
-	/*
-	 * Since @oldopts also has @orig_opts already (and does so at the
-	 * start), skip these entries.
-	 */
-	oldopts += num_oold;
-	num_old -= num_oold;
-
-	merge = malloc(sizeof(*mp) * (num_oold + num_old + num_new + 1));
-	if (merge == NULL)
-		return NULL;
-
-	/* Let the base options -[ADI...] have precedence over everything */
-	memcpy(merge, orig_opts, sizeof(*mp) * num_oold);
-	mp = merge + num_oold;
-
-	/* Second, the new options */
-	xt_params->option_offset += XT_OPTION_OFFSET_SCALE;
-	*option_offset = xt_params->option_offset;
-	memcpy(mp, newopts, sizeof(*mp) * num_new);
-
-	for (i = 0; i < num_new; ++i, ++mp)
-		mp->val += *option_offset;
-
-	/* Third, the old options */
-	memcpy(mp, oldopts, sizeof(*mp) * num_old);
-	mp += num_old;
-	xtables_free_opts(0);
-
-	/* Clear trailing entry */
-	memset(mp, 0, sizeof(*mp));
-	return merge;
-}
-
-static const struct xtables_afinfo afinfo_ipv4 = {
-	.kmod          = "ip_tables",
-	.proc_exists   = "/proc/net/ip_tables_names",
-	.libprefix     = "libipt_",
-	.family	       = NFPROTO_IPV4,
-	.ipproto       = IPPROTO_IP,
-	.so_rev_match  = IPT_SO_GET_REVISION_MATCH,
-	.so_rev_target = IPT_SO_GET_REVISION_TARGET,
-};
-
-static const struct xtables_afinfo afinfo_ipv6 = {
-	.kmod          = "ip6_tables",
-	.proc_exists   = "/proc/net/ip6_tables_names",
-	.libprefix     = "libip6t_",
-	.family        = NFPROTO_IPV6,
-	.ipproto       = IPPROTO_IPV6,
-	.so_rev_match  = IP6T_SO_GET_REVISION_MATCH,
-	.so_rev_target = IP6T_SO_GET_REVISION_TARGET,
-};
-
-const struct xtables_afinfo *afinfo;
-
-/* Search path for Xtables .so files */
-static const char *xtables_libdir;
-
-/* the path to command to load kernel module */
-const char *xtables_modprobe_program;
-
-/* Keep track of matches/targets pending full registration: linked lists. */
-struct xtables_match *xtables_pending_matches;
-struct xtables_target *xtables_pending_targets;
-
-/* Keep track of fully registered external matches/targets: linked lists. */
-struct xtables_match *xtables_matches;
-struct xtables_target *xtables_targets;
-
-/* Fully register a match/target which was previously partially registered. */
-static void xtables_fully_register_pending_match(struct xtables_match *me);
-static void xtables_fully_register_pending_target(struct xtables_target *me);
-
-void xtables_init(void)
-{
-	xtables_libdir = getenv("XTABLES_LIBDIR");
-	if (xtables_libdir != NULL)
-		return;
-	xtables_libdir = getenv("IPTABLES_LIB_DIR");
-	if (xtables_libdir != NULL) {
-		fprintf(stderr, "IPTABLES_LIB_DIR is deprecated, "
-		        "use XTABLES_LIBDIR.\n");
-		return;
-	}
-	/*
-	 * Well yes, IP6TABLES_LIB_DIR is of lower priority over
-	 * IPTABLES_LIB_DIR since this moved to libxtables; I think that is ok
-	 * for these env vars are deprecated anyhow, and in light of the
-	 * (shared) libxt_*.so files, makes less sense to have
-	 * IPTABLES_LIB_DIR != IP6TABLES_LIB_DIR.
-	 */
-	xtables_libdir = getenv("IP6TABLES_LIB_DIR");
-	if (xtables_libdir != NULL) {
-		fprintf(stderr, "IP6TABLES_LIB_DIR is deprecated, "
-		        "use XTABLES_LIBDIR.\n");
-		return;
-	}
-	xtables_libdir = XTABLES_LIBDIR;
-}
-
-void xtables_set_nfproto(uint8_t nfproto)
-{
-	switch (nfproto) {
-	case NFPROTO_IPV4:
-		afinfo = &afinfo_ipv4;
-		break;
-	case NFPROTO_IPV6:
-		afinfo = &afinfo_ipv6;
-		break;
-	default:
-		fprintf(stderr, "libxtables: unhandled NFPROTO in %s\n",
-		        __func__);
-	}
-}
-
-/**
- * xtables_set_params - set the global parameters used by xtables
- * @xtp:	input xtables_globals structure
- *
- * The app is expected to pass a valid xtables_globals data-filled
- * with proper values
- * @xtp cannot be NULL
- *
- * Returns -1 on failure to set and 0 on success
- */
-int xtables_set_params(struct xtables_globals *xtp)
-{
-	if (!xtp) {
-		fprintf(stderr, "%s: Illegal global params\n",__func__);
-		return -1;
-	}
-
-	xt_params = xtp;
-
-	if (!xt_params->exit_err)
-		xt_params->exit_err = basic_exit_err;
-
-	return 0;
-}
-
-int xtables_init_all(struct xtables_globals *xtp, uint8_t nfproto)
-{
-	xtables_init();
-	xtables_set_nfproto(nfproto);
-	return xtables_set_params(xtp);
-}
-
-/**
- * xtables_*alloc - wrappers that exit on failure
- */
-void *xtables_calloc(size_t count, size_t size)
-{
-	void *p;
-
-	if ((p = calloc(count, size)) == NULL) {
-		perror("ip[6]tables: calloc failed");
-		exit(1);
-	}
-
-	return p;
-}
-
-void *xtables_malloc(size_t size)
-{
-	void *p;
-
-	if ((p = malloc(size)) == NULL) {
-		perror("ip[6]tables: malloc failed");
-		exit(1);
-	}
-
-	return p;
-}
-
-void *xtables_realloc(void *ptr, size_t size)
-{
-	void *p;
-
-	if ((p = realloc(ptr, size)) == NULL) {
-		perror("ip[6]tables: realloc failed");
-		exit(1);
-	}
-
-	return p;
-}
-
-static char *get_modprobe(void)
-{
-	int procfile;
-	char *ret;
-
-#define PROCFILE_BUFSIZ	1024
-	procfile = open(PROC_SYS_MODPROBE, O_RDONLY);
-	if (procfile < 0)
-		return NULL;
-	if (fcntl(procfile, F_SETFD, FD_CLOEXEC) == -1) {
-		fprintf(stderr, "Could not set close on exec: %s\n",
-			strerror(errno));
-		exit(1);
-	}
-
-	ret = malloc(PROCFILE_BUFSIZ);
-	if (ret) {
-		memset(ret, 0, PROCFILE_BUFSIZ);
-		switch (read(procfile, ret, PROCFILE_BUFSIZ)) {
-		case -1: goto fail;
-		case PROCFILE_BUFSIZ: goto fail; /* Partial read.  Wierd */
+				if (append) {
+					ret = nft_cmd_rule_append(h, chain, table,
+							      cs, NULL,
+							      verbose);
+				} else {
+					ret = nft_cmd_rule_insert(h, chain, table,
+							      cs, rulenum,
+							      verbose);
+				}
+			}
+		} else if (family == AF_INET6) {
+			memcpy(&cs->fw6.ipv6.src,
+			       &s.addr.v6[i], sizeof(struct in6_addr));
+			memcpy(&cs->fw6.ipv6.smsk,
+			       &s.mask.v6[i], sizeof(struct in6_addr));
+			for (j = 0; j < d.naddrs; j++) {
+				memcpy(&cs->fw6.ipv6.dst,
+				       &d.addr.v6[j], sizeof(struct in6_addr));
+				memcpy(&cs->fw6.ipv6.dmsk,
+				       &d.mask.v6[j], sizeof(struct in6_addr));
+				if (append) {
+					ret = nft_cmd_rule_append(h, chain, table,
+							      cs, NULL,
+							      verbose);
+				} else {
+					ret = nft_cmd_rule_insert(h, chain, table,
+							      cs, rulenum,
+							      verbose);
+				}
+			}
 		}
-		if (ret[strlen(ret)-1]=='\n') 
-			ret[strlen(ret)-1]=0;
-		close(procfile);
-		return ret;
 	}
- fail:
-	free(ret);
-	close(procfile);
-	return NULL;
+
+	return ret;
 }
 
-int xtables_insmod(const char *modname, const char *modprobe, bool quiet)
+static int
+replace_entry(const char *chain, const char *table,
+	      struct iptables_command_state *cs,
+	      unsigned int rulenum,
+	      int family,
+	      const struct addr_mask s,
+	      const struct addr_mask d,
+	      bool verbose, struct nft_handle *h)
 {
-	char *buf = NULL;
-	char *argv[4];
-	int status;
+	if (family == AF_INET) {
+		cs->fw.ip.src.s_addr = s.addr.v4->s_addr;
+		cs->fw.ip.dst.s_addr = d.addr.v4->s_addr;
+		cs->fw.ip.smsk.s_addr = s.mask.v4->s_addr;
+		cs->fw.ip.dmsk.s_addr = d.mask.v4->s_addr;
+	} else if (family == AF_INET6) {
+		memcpy(&cs->fw6.ipv6.src, s.addr.v6, sizeof(struct in6_addr));
+		memcpy(&cs->fw6.ipv6.dst, d.addr.v6, sizeof(struct in6_addr));
+		memcpy(&cs->fw6.ipv6.smsk, s.mask.v6, sizeof(struct in6_addr));
+		memcpy(&cs->fw6.ipv6.dmsk, d.mask.v6, sizeof(struct in6_addr));
+	} else
+		return 1;
 
-	/* If they don't explicitly set it, read out of kernel */
-	if (!modprobe) {
-		buf = get_modprobe();
-		if (!buf)
-			return -1;
-		modprobe = buf;
-	}
+	return nft_cmd_rule_replace(h, chain, table, cs, rulenum, verbose);
+}
 
-	/*
-	 * Need to flush the buffer, or the child may output it again
-	 * when switching the program thru execv.
-	 */
-	fflush(stdout);
+static int
+delete_entry(const char *chain, const char *table,
+	     struct iptables_command_state *cs,
+	     int family,
+	     const struct addr_mask s,
+	     const struct addr_mask d,
+	     bool verbose,
+	     struct nft_handle *h)
+{
+	unsigned int i, j;
+	int ret = 1;
 
-	switch (vfork()) {
-	case 0:
-		argv[0] = (char *)modprobe;
-		argv[1] = (char *)modname;
-		if (quiet) {
-			argv[2] = "-q";
-			argv[3] = NULL;
-		} else {
-			argv[2] = NULL;
-			argv[3] = NULL;
+	for (i = 0; i < s.naddrs; i++) {
+		if (family == AF_INET) {
+			cs->fw.ip.src.s_addr = s.addr.v4[i].s_addr;
+			cs->fw.ip.smsk.s_addr = s.mask.v4[i].s_addr;
+			for (j = 0; j < d.naddrs; j++) {
+				cs->fw.ip.dst.s_addr = d.addr.v4[j].s_addr;
+				cs->fw.ip.dmsk.s_addr = d.mask.v4[j].s_addr;
+				ret = nft_cmd_rule_delete(h, chain,
+						      table, cs, verbose);
+			}
+		} else if (family == AF_INET6) {
+			memcpy(&cs->fw6.ipv6.src,
+			       &s.addr.v6[i], sizeof(struct in6_addr));
+			memcpy(&cs->fw6.ipv6.smsk,
+			       &s.mask.v6[i], sizeof(struct in6_addr));
+			for (j = 0; j < d.naddrs; j++) {
+				memcpy(&cs->fw6.ipv6.dst,
+				       &d.addr.v6[j], sizeof(struct in6_addr));
+				memcpy(&cs->fw6.ipv6.dmsk,
+				       &d.mask.v6[j], sizeof(struct in6_addr));
+				ret = nft_cmd_rule_delete(h, chain,
+						      table, cs, verbose);
+			}
 		}
-		execv(argv[0], argv);
-
-		/* not usually reached */
-		exit(1);
-	case -1:
-		return -1;
-
-	default: /* parent */
-		wait(&status);
 	}
 
-	free(buf);
-	if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
-		return 0;
-	return -1;
+	return ret;
 }
 
-/* return true if a given file exists within procfs */
-static bool proc_file_exists(const char *filename)
+static int
+check_entry(const char *chain, const char *table,
+	    struct iptables_command_state *cs,
+	    int family,
+	    const struct addr_mask s,
+	    const struct addr_mask d,
+	    bool verbose, struct nft_handle *h)
 {
-	struct stat s;
-	struct statfs f;
+	unsigned int i, j;
+	int ret = 1;
 
-	if (lstat(filename, &s))
-		return false;
-	if (!S_ISREG(s.st_mode))
-		return false;
-	if (statfs(filename, &f))
-		return false;
-	if (f.f_type != PROC_SUPER_MAGIC)
-		return false;
-	return true;
+	for (i = 0; i < s.naddrs; i++) {
+		if (family == AF_INET) {
+			cs->fw.ip.src.s_addr = s.addr.v4[i].s_addr;
+			cs->fw.ip.smsk.s_addr = s.mask.v4[i].s_addr;
+			for (j = 0; j < d.naddrs; j++) {
+				cs->fw.ip.dst.s_addr = d.addr.v4[j].s_addr;
+				cs->fw.ip.dmsk.s_addr = d.mask.v4[j].s_addr;
+				ret = nft_cmd_rule_check(h, chain,
+						     table, cs, verbose);
+			}
+		} else if (family == AF_INET6) {
+			memcpy(&cs->fw6.ipv6.src,
+			       &s.addr.v6[i], sizeof(struct in6_addr));
+			memcpy(&cs->fw6.ipv6.smsk,
+			       &s.mask.v6[i], sizeof(struct in6_addr));
+			for (j = 0; j < d.naddrs; j++) {
+				memcpy(&cs->fw6.ipv6.dst,
+				       &d.addr.v6[j], sizeof(struct in6_addr));
+				memcpy(&cs->fw6.ipv6.dmsk,
+				       &d.mask.v6[j], sizeof(struct in6_addr));
+				ret = nft_cmd_rule_check(h, chain,
+						     table, cs, verbose);
+			}
+		}
+	}
+
+	return ret;
 }
 
-int xtables_load_ko(const char *modprobe, bool quiet)
+static int
+list_entries(struct nft_handle *h, const char *chain, const char *table,
+	     int rulenum, int verbose, int numeric, int expanded,
+	     int linenumbers)
 {
-	static bool loaded = false;
-	int ret;
+	unsigned int format;
 
-	if (loaded)
-		return 0;
+	format = FMT_OPTIONS;
+	if (!verbose)
+		format |= FMT_NOCOUNTS;
+	else
+		format |= FMT_VIA;
 
-	if (proc_file_exists(afinfo->proc_exists)) {
-		loaded = true;
-		return 0;
+	if (numeric)
+		format |= FMT_NUMERIC;
+
+	if (!expanded)
+		format |= FMT_KILOMEGAGIGA;
+
+	if (linenumbers)
+		format |= FMT_LINENUMBERS;
+
+	return nft_cmd_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 */
+
+	return nft_cmd_rule_list_save(h, chain, table, rulenum, counters);
+}
+
+void do_parse(struct nft_handle *h, int argc, char *argv[],
+	      struct nft_xt_cmd_parse *p, struct iptables_command_state *cs,
+	      struct xtables_args *args)
+{
+	struct xtables_match *m;
+	struct xtables_rule_match *matchp;
+	bool wait_interval_set = false;
+	struct timeval wait_interval;
+	struct xtables_target *t;
+	bool table_set = false;
+	int wait = 0;
+
+	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:fbvw::W::nt:m:xc:g:46",
+					   opts, NULL)) != -1) {
+		switch (cs->c) {
+			/*
+			 * Command selection
+			 */
+		case 'A':
+			add_command(&p->command, CMD_APPEND, CMD_NONE,
+				    cs->invert);
+			p->chain = optarg;
+			break;
+
+		case 'C':
+			add_command(&p->command, CMD_CHECK, CMD_NONE,
+				    cs->invert);
+			p->chain = optarg;
+			break;
+
+		case 'D':
+			add_command(&p->command, CMD_DELETE, CMD_NONE,
+				    cs->invert);
+			p->chain = optarg;
+			if (xs_has_arg(argc, argv)) {
+				p->rulenum = parse_rulenumber(argv[optind++]);
+				p->command = CMD_DELETE_NUM;
+			}
+			break;
+
+		case 'R':
+			add_command(&p->command, CMD_REPLACE, CMD_NONE,
+				    cs->invert);
+			p->chain = optarg;
+			if (xs_has_arg(argc, argv))
+				p->rulenum = parse_rulenumber(argv[optind++]);
+			else
+				xtables_error(PARAMETER_PROBLEM,
+					   "-%c requires a rule number",
+					   cmd2char(CMD_REPLACE));
+			break;
+
+		case 'I':
+			add_command(&p->command, CMD_INSERT, CMD_NONE,
+				    cs->invert);
+			p->chain = optarg;
+			if (xs_has_arg(argc, argv))
+				p->rulenum = parse_rulenumber(argv[optind++]);
+			else
+				p->rulenum = 1;
+			break;
+
+		case 'L':
+			add_command(&p->command, CMD_LIST,
+				    CMD_ZERO | CMD_ZERO_NUM, cs->invert);
+			if (optarg)
+				p->chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				p->chain = argv[optind++];
+			if (xs_has_arg(argc, argv))
+				p->rulenum = parse_rulenumber(argv[optind++]);
+			break;
+
+		case 'S':
+			add_command(&p->command, CMD_LIST_RULES,
+				    CMD_ZERO|CMD_ZERO_NUM, cs->invert);
+			if (optarg)
+				p->chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				p->chain = argv[optind++];
+			if (xs_has_arg(argc, argv))
+				p->rulenum = parse_rulenumber(argv[optind++]);
+			break;
+
+		case 'F':
+			add_command(&p->command, CMD_FLUSH, CMD_NONE,
+				    cs->invert);
+			if (optarg)
+				p->chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				p->chain = argv[optind++];
+			break;
+
+		case 'Z':
+			add_command(&p->command, CMD_ZERO,
+				    CMD_LIST|CMD_LIST_RULES, cs->invert);
+			if (optarg)
+				p->chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				p->chain = argv[optind++];
+			if (xs_has_arg(argc, argv)) {
+				p->rulenum = parse_rulenumber(argv[optind++]);
+				p->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(&p->command, CMD_NEW_CHAIN, CMD_NONE,
+				    cs->invert);
+			p->chain = optarg;
+			break;
+
+		case 'X':
+			add_command(&p->command, CMD_DELETE_CHAIN, CMD_NONE,
+				    cs->invert);
+			if (optarg)
+				p->chain = optarg;
+			else if (xs_has_arg(argc, argv))
+				p->chain = argv[optind++];
+			break;
+
+		case 'E':
+			add_command(&p->command, CMD_RENAME_CHAIN, CMD_NONE,
+				    cs->invert);
+			p->chain = optarg;
+			if (xs_has_arg(argc, argv))
+				p->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(&p->command, CMD_SET_POLICY, CMD_NONE,
+				    cs->invert);
+			p->chain = optarg;
+			if (xs_has_arg(argc, argv))
+				p->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);
+
+			printhelp(cs->matches);
+			p->command = CMD_NONE;
+			return;
+
+			/*
+			 * Option selection
+			 */
+		case 'p':
+			set_option(&cs->options, OPT_PROTOCOL,
+				   &args->invflags, cs->invert);
+
+			/* Canonicalize into lower case */
+			for (cs->protocol = optarg; *cs->protocol; cs->protocol++)
+				*cs->protocol = tolower(*cs->protocol);
+
+			cs->protocol = optarg;
+			args->proto = xtables_parse_protocol(cs->protocol);
+
+			if (args->proto == 0 &&
+			    (args->invflags & XT_INV_PROTO))
+				xtables_error(PARAMETER_PROBLEM,
+					   "rule would never match protocol");
+
+			/* This needs to happen here to parse extensions */
+			h->ops->proto_parse(cs, args);
+			break;
+
+		case 's':
+			set_option(&cs->options, OPT_SOURCE,
+				   &args->invflags, cs->invert);
+			args->shostnetworkmask = optarg;
+			break;
+
+		case 'd':
+			set_option(&cs->options, OPT_DESTINATION,
+				   &args->invflags, cs->invert);
+			args->dhostnetworkmask = optarg;
+			break;
+
+#ifdef IPT_F_GOTO
+		case 'g':
+			set_option(&cs->options, OPT_JUMP, &args->invflags,
+				   cs->invert);
+			args->goto_set = true;
+			cs->jumpto = xt_parse_target(optarg);
+			break;
+#endif
+
+		case 'j':
+			set_option(&cs->options, OPT_JUMP, &cs->fw.ip.invflags,
+				   cs->invert);
+			command_jump(cs, optarg);
+			break;
+
+
+		case 'i':
+			if (*optarg == '\0')
+				xtables_error(PARAMETER_PROBLEM,
+					"Empty interface is likely to be "
+					"undesired");
+			set_option(&cs->options, OPT_VIANAMEIN,
+				   &args->invflags, cs->invert);
+			xtables_parse_interface(optarg,
+						args->iniface,
+						args->iniface_mask);
+			break;
+
+		case 'o':
+			if (*optarg == '\0')
+				xtables_error(PARAMETER_PROBLEM,
+					"Empty interface is likely to be "
+					"undesired");
+			set_option(&cs->options, OPT_VIANAMEOUT,
+				   &args->invflags, cs->invert);
+			xtables_parse_interface(optarg,
+						args->outiface,
+						args->outiface_mask);
+			break;
+
+		case 'f':
+			if (args->family == AF_INET6) {
+				xtables_error(PARAMETER_PROBLEM,
+					"`-f' is not supported in IPv6, "
+					"use -m frag instead");
+			}
+			set_option(&cs->options, OPT_FRAGMENT, &args->invflags,
+				   cs->invert);
+			args->flags |= IPT_F_FRAG;
+			break;
+
+		case 'v':
+			if (!p->verbose)
+				set_option(&cs->options, OPT_VERBOSE,
+					   &args->invflags, cs->invert);
+			p->verbose++;
+			break;
+
+		case 'm':
+			command_match(cs);
+			break;
+
+		case 'n':
+			set_option(&cs->options, OPT_NUMERIC, &args->invflags,
+				   cs->invert);
+			break;
+
+		case 't':
+			if (cs->invert)
+				xtables_error(PARAMETER_PROBLEM,
+					   "unexpected ! flag before --table");
+			if (p->restore && table_set)
+				xtables_error(PARAMETER_PROBLEM,
+					      "The -t option (seen in line %u) cannot be used in %s.\n",
+					      line, xt_params->program_name);
+			if (!nft_table_builtin_find(h, optarg))
+				xtables_error(VERSION_PROBLEM,
+					      "table '%s' does not exist",
+					      optarg);
+			p->table = optarg;
+			table_set = true;
+			break;
+
+		case 'x':
+			set_option(&cs->options, OPT_EXPANDED, &args->invflags,
+				   cs->invert);
+			break;
+
+		case 'V':
+			if (cs->invert)
+				printf("Not %s ;-)\n", prog_vers);
+			else
+				printf("%s v%s (nf_tables)\n",
+				       prog_name, prog_vers);
+			exit(0);
+
+		case 'w':
+			if (p->restore) {
+				xtables_error(PARAMETER_PROBLEM,
+					      "You cannot use `-w' from "
+					      "iptables-restore");
+			}
+
+			wait = parse_wait_time(argc, argv);
+			break;
+
+		case 'W':
+			if (p->restore) {
+				xtables_error(PARAMETER_PROBLEM,
+					      "You cannot use `-W' from "
+					      "iptables-restore");
+			}
+
+			parse_wait_interval(argc, argv, &wait_interval);
+			wait_interval_set = true;
+			break;
+
+		case '0':
+			set_option(&cs->options, OPT_LINENUMBERS,
+				   &args->invflags, cs->invert);
+			break;
+
+		case 'M':
+			xtables_modprobe_program = optarg;
+			break;
+
+		case 'c':
+			set_option(&cs->options, OPT_COUNTERS, &args->invflags,
+				   cs->invert);
+			args->pcnt = optarg;
+			args->bcnt = strchr(args->pcnt + 1, ',');
+			if (args->bcnt)
+			    args->bcnt++;
+			if (!args->bcnt && xs_has_arg(argc, argv))
+				args->bcnt = argv[optind++];
+			if (!args->bcnt)
+				xtables_error(PARAMETER_PROBLEM,
+					"-%c requires packet and byte counter",
+					opt2char(OPT_COUNTERS));
+
+			if (sscanf(args->pcnt, "%llu", &args->pcnt_cnt) != 1)
+				xtables_error(PARAMETER_PROBLEM,
+					"-%c packet counter not numeric",
+					opt2char(OPT_COUNTERS));
+
+			if (sscanf(args->bcnt, "%llu", &args->bcnt_cnt) != 1)
+				xtables_error(PARAMETER_PROBLEM,
+					"-%c byte counter not numeric",
+					opt2char(OPT_COUNTERS));
+			break;
+
+		case '4':
+			if (args->family == AF_INET)
+				break;
+
+			if (p->restore && args->family == AF_INET6)
+				return;
+
+			exit_tryhelp(2);
+
+		case '6':
+			if (args->family == AF_INET6)
+				break;
+
+			if (p->restore && args->family == AF_INET)
+				return;
+
+			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, &xtables_globals) == 1)
+				/* cf. ip6tables.c */
+				continue;
+			break;
+		}
+		cs->invert = false;
+	}
+
+	if (strcmp(p->table, "nat") == 0 &&
+	    ((p->policy != NULL && strcmp(p->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");
+
+	if (!wait && wait_interval_set)
+		xtables_error(PARAMETER_PROBLEM,
+			      "--wait-interval only makes sense with --wait\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 (!p->command)
+		xtables_error(PARAMETER_PROBLEM, "no command specified");
+	if (cs->invert)
+		xtables_error(PARAMETER_PROBLEM,
+			   "nothing appropriate following !");
+
+	/* Set only if required, needed by xtables-restore */
+	if (h->family == AF_UNSPEC)
+		h->family = args->family;
+
+	h->ops->post_parse(p->command, cs, args);
+
+	if (p->command == CMD_REPLACE &&
+	    (args->s.naddrs != 1 || args->d.naddrs != 1))
+		xtables_error(PARAMETER_PROBLEM, "Replacement rule does not "
+			   "specify a unique address");
+
+	generic_opt_check(p->command, cs->options);
+
+	if (p->chain != NULL && strlen(p->chain) >= XT_EXTENSION_MAXNAMELEN)
+		xtables_error(PARAMETER_PROBLEM,
+			   "chain name `%s' too long (must be under %u chars)",
+			   p->chain, XT_EXTENSION_MAXNAMELEN);
+
+	if (p->command == CMD_APPEND ||
+	    p->command == CMD_DELETE ||
+	    p->command == CMD_DELETE_NUM ||
+	    p->command == CMD_CHECK ||
+	    p->command == CMD_INSERT ||
+	    p->command == CMD_REPLACE) {
+		if (strcmp(p->chain, "PREROUTING") == 0
+		    || strcmp(p->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),
+					   p->chain);
+		}
+
+		if (strcmp(p->chain, "POSTROUTING") == 0
+		    || strcmp(p->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),
+					   p->chain);
+		}
+	}
+}
+
+int do_commandx(struct nft_handle *h, int argc, char *argv[], char **table,
+		bool restore)
+{
+	int ret = 1;
+	struct nft_xt_cmd_parse p = {
+		.table		= *table,
+		.restore	= restore,
+	};
+	struct iptables_command_state cs;
+	struct xtables_args args = {
+		.family = h->family,
 	};
 
-	ret = xtables_insmod(afinfo->kmod, modprobe, quiet);
-	if (ret == 0)
-		loaded = true;
+	do_parse(h, argc, argv, &p, &cs, &args);
 
-	return ret;
-}
-
-/**
- * xtables_strtou{i,l} - string to number conversion
- * @s:	input string
- * @end:	like strtoul's "end" pointer
- * @value:	pointer for result
- * @min:	minimum accepted value
- * @max:	maximum accepted value
- *
- * If @end is NULL, we assume the caller wants a "strict strtoul", and hence
- * "15a" is rejected.
- * In either case, the value obtained is compared for min-max compliance.
- * Base is always 0, i.e. autodetect depending on @s.
- *
- * Returns true/false whether number was accepted. On failure, *value has
- * undefined contents.
- */
-bool xtables_strtoul(const char *s, char **end, uintmax_t *value,
-                     uintmax_t min, uintmax_t max)
-{
-	uintmax_t v;
-	const char *p;
-	char *my_end;
-
-	errno = 0;
-	/* Since strtoul allows leading minus, we have to check for ourself. */
-	for (p = s; isspace(*p); ++p)
-		;
-	if (*p == '-')
-		return false;
-	v = strtoumax(s, &my_end, 0);
-	if (my_end == s)
-		return false;
-	if (end != NULL)
-		*end = my_end;
-
-	if (errno != ERANGE && min <= v && (max == 0 || v <= max)) {
-		if (value != NULL)
-			*value = v;
-		if (end == NULL)
-			return *my_end == '\0';
-		return true;
-	}
-
-	return false;
-}
-
-bool xtables_strtoui(const char *s, char **end, unsigned int *value,
-                     unsigned int min, unsigned int max)
-{
-	uintmax_t v;
-	bool ret;
-
-	ret = xtables_strtoul(s, end, &v, min, max);
-	if (value != NULL)
-		*value = v;
-	return ret;
-}
-
-int xtables_service_to_port(const char *name, const char *proto)
-{
-	struct servent *service;
-
-	if ((service = getservbyname(name, proto)) != NULL)
-		return ntohs((unsigned short) service->s_port);
-
-	return -1;
-}
-
-uint16_t xtables_parse_port(const char *port, const char *proto)
-{
-	unsigned int portnum;
-
-	if (xtables_strtoui(port, NULL, &portnum, 0, UINT16_MAX) ||
-	    (portnum = xtables_service_to_port(port, proto)) != (unsigned)-1)
-		return portnum;
-
-	xt_params->exit_err(PARAMETER_PROBLEM,
-		   "invalid port/service `%s' specified", port);
-}
-
-void xtables_parse_interface(const char *arg, char *vianame,
-			     unsigned char *mask)
-{
-	unsigned int vialen = strlen(arg);
-	unsigned int i;
-
-	memset(mask, 0, IFNAMSIZ);
-	memset(vianame, 0, IFNAMSIZ);
-
-	if (vialen + 1 > IFNAMSIZ)
-		xt_params->exit_err(PARAMETER_PROBLEM,
-			   "interface name `%s' must be shorter than IFNAMSIZ"
-			   " (%i)", arg, IFNAMSIZ-1);
-
-	strcpy(vianame, arg);
-	if (vialen == 0)
-		memset(mask, 0, IFNAMSIZ);
-	else if (vianame[vialen - 1] == '+') {
-		memset(mask, 0xFF, vialen - 1);
-		memset(mask + vialen - 1, 0, IFNAMSIZ - vialen + 1);
-		/* Don't remove `+' here! -HW */
-	} else {
-		/* Include nul-terminator in match */
-		memset(mask, 0xFF, vialen + 1);
-		memset(mask + vialen + 1, 0, IFNAMSIZ - vialen - 1);
-		for (i = 0; vianame[i]; i++) {
-			if (vianame[i] == '/' ||
-			    vianame[i] == ' ') {
-				fprintf(stderr,
-					"Warning: weird character in interface"
-					" `%s' ('/' and ' ' are not allowed by the kernel).\n",
-					vianame);
-				break;
-			}
-		}
-	}
-}
-
-#ifndef NO_SHARED_LIBS
-static void *load_extension(const char *search_path, const char *af_prefix,
-    const char *name, bool is_target)
-{
-	const char *all_prefixes[] = {"libxt_", af_prefix, NULL};
-	const char **prefix;
-	const char *dir = search_path, *next;
-	void *ptr = NULL;
-	struct stat sb;
-	char path[256];
-
-	do {
-		next = strchr(dir, ':');
-		if (next == NULL)
-			next = dir + strlen(dir);
-
-		for (prefix = all_prefixes; *prefix != NULL; ++prefix) {
-			snprintf(path, sizeof(path), "%.*s/%s%s.so",
-			         (unsigned int)(next - dir), dir,
-			         *prefix, name);
-
-			if (stat(path, &sb) != 0) {
-				if (errno == ENOENT)
-					continue;
-				fprintf(stderr, "%s: %s\n", path,
-					strerror(errno));
-				return NULL;
-			}
-			if (dlopen(path, RTLD_NOW) == NULL) {
-				fprintf(stderr, "%s: %s\n", path, dlerror());
-				break;
-			}
-
-			if (is_target)
-				ptr = xtables_find_target(name, XTF_DONT_LOAD);
-			else
-				ptr = xtables_find_match(name,
-				      XTF_DONT_LOAD, NULL);
-
-			if (ptr != NULL)
-				return ptr;
-
-			fprintf(stderr, "%s: no \"%s\" extension found for "
-				"this protocol\n", path, name);
-			errno = ENOENT;
-			return NULL;
-		}
-		dir = next + 1;
-	} while (*next != '\0');
-
-	return NULL;
-}
-#endif
-
-struct xtables_match *
-xtables_find_match(const char *name, enum xtables_tryload tryload,
-		   struct xtables_rule_match **matches)
-{
-	struct xtables_match **dptr;
-	struct xtables_match *ptr;
-	const char *icmp6 = "icmp6";
-
-	if (strlen(name) >= XT_EXTENSION_MAXNAMELEN)
-		xtables_error(PARAMETER_PROBLEM,
-			   "Invalid match name \"%s\" (%u chars max)",
-			   name, XT_EXTENSION_MAXNAMELEN - 1);
-
-	/* This is ugly as hell. Nonetheless, there is no way of changing
-	 * this without hurting backwards compatibility */
-	if ( (strcmp(name,"icmpv6") == 0) ||
-	     (strcmp(name,"ipv6-icmp") == 0) ||
-	     (strcmp(name,"icmp6") == 0) )
-		name = icmp6;
-
-	/* Trigger delayed initialization */
-	for (dptr = &xtables_pending_matches; *dptr; ) {
-		if (strcmp(name, (*dptr)->name) == 0) {
-			ptr = *dptr;
-			*dptr = (*dptr)->next;
-			ptr->next = NULL;
-			xtables_fully_register_pending_match(ptr);
-		} else {
-			dptr = &((*dptr)->next);
-		}
-	}
-
-	for (ptr = xtables_matches; ptr; ptr = ptr->next) {
-		if (strcmp(name, ptr->name) == 0) {
-			struct xtables_match *clone;
-
-			/* First match of this type: */
-			if (ptr->m == NULL)
-				break;
-
-			/* Second and subsequent clones */
-			clone = xtables_malloc(sizeof(struct xtables_match));
-			memcpy(clone, ptr, sizeof(struct xtables_match));
-			clone->mflags = 0;
-			/* This is a clone: */
-			clone->next = clone;
-
-			ptr = clone;
-			break;
-		}
-	}
-
-#ifndef NO_SHARED_LIBS
-	if (!ptr && tryload != XTF_DONT_LOAD && tryload != XTF_DURING_LOAD) {
-		ptr = load_extension(xtables_libdir, afinfo->libprefix,
-		      name, false);
-
-		if (ptr == NULL && tryload == XTF_LOAD_MUST_SUCCEED)
-			xt_params->exit_err(PARAMETER_PROBLEM,
-				   "Couldn't load match `%s':%s\n",
-				   name, strerror(errno));
-	}
-#else
-	if (ptr && !ptr->loaded) {
-		if (tryload != XTF_DONT_LOAD)
-			ptr->loaded = 1;
-		else
-			ptr = NULL;
-	}
-	if(!ptr && (tryload == XTF_LOAD_MUST_SUCCEED)) {
-		xt_params->exit_err(PARAMETER_PROBLEM,
-			   "Couldn't find match `%s'\n", name);
-	}
-#endif
-
-	if (ptr && matches) {
-		struct xtables_rule_match **i;
-		struct xtables_rule_match *newentry;
-
-		newentry = xtables_malloc(sizeof(struct xtables_rule_match));
-
-		for (i = matches; *i; i = &(*i)->next) {
-			if (strcmp(name, (*i)->match->name) == 0)
-				(*i)->completed = true;
-		}
-		newentry->match = ptr;
-		newentry->completed = false;
-		newentry->next = NULL;
-		*i = newentry;
-	}
-
-	return ptr;
-}
-
-struct xtables_target *
-xtables_find_target(const char *name, enum xtables_tryload tryload)
-{
-	struct xtables_target **dptr;
-	struct xtables_target *ptr;
-
-	/* Standard target? */
-	if (strcmp(name, "") == 0
-	    || strcmp(name, XTC_LABEL_ACCEPT) == 0
-	    || strcmp(name, XTC_LABEL_DROP) == 0
-	    || strcmp(name, XTC_LABEL_QUEUE) == 0
-	    || strcmp(name, XTC_LABEL_RETURN) == 0)
-		name = "standard";
-
-	/* Trigger delayed initialization */
-	for (dptr = &xtables_pending_targets; *dptr; ) {
-		if (strcmp(name, (*dptr)->name) == 0) {
-			ptr = *dptr;
-			*dptr = (*dptr)->next;
-			ptr->next = NULL;
-			xtables_fully_register_pending_target(ptr);
-		} else {
-			dptr = &((*dptr)->next);
-		}
-	}
-
-	for (ptr = xtables_targets; ptr; ptr = ptr->next) {
-		if (strcmp(name, ptr->name) == 0)
-			break;
-	}
-
-#ifndef NO_SHARED_LIBS
-	if (!ptr && tryload != XTF_DONT_LOAD && tryload != XTF_DURING_LOAD) {
-		ptr = load_extension(xtables_libdir, afinfo->libprefix,
-		      name, true);
-
-		if (ptr == NULL && tryload == XTF_LOAD_MUST_SUCCEED)
-			xt_params->exit_err(PARAMETER_PROBLEM,
-				   "Couldn't load target `%s':%s\n",
-				   name, strerror(errno));
-	}
-#else
-	if (ptr && !ptr->loaded) {
-		if (tryload != XTF_DONT_LOAD)
-			ptr->loaded = 1;
-		else
-			ptr = NULL;
-	}
-	if (ptr == NULL && tryload == XTF_LOAD_MUST_SUCCEED) {
-		xt_params->exit_err(PARAMETER_PROBLEM,
-			   "Couldn't find target `%s'\n", name);
-	}
-#endif
-
-	if (ptr)
-		ptr->used = 1;
-
-	return ptr;
-}
-
-static int compatible_revision(const char *name, uint8_t revision, int opt)
-{
-	struct xt_get_revision rev;
-	socklen_t s = sizeof(rev);
-	int max_rev, sockfd;
-
-	sockfd = socket(afinfo->family, SOCK_RAW, IPPROTO_RAW);
-	if (sockfd < 0) {
-		if (errno == EPERM) {
-			/* revision 0 is always supported. */
-			if (revision != 0)
-				fprintf(stderr, "%s: Could not determine whether "
-						"revision %u is supported, "
-						"assuming it is.\n",
-					name, revision);
-			return 1;
-		}
-		fprintf(stderr, "Could not open socket to kernel: %s\n",
-			strerror(errno));
-		exit(1);
-	}
-
-	if (fcntl(sockfd, F_SETFD, FD_CLOEXEC) == -1) {
-		fprintf(stderr, "Could not set close on exec: %s\n",
-			strerror(errno));
-		exit(1);
-	}
-
-	xtables_load_ko(xtables_modprobe_program, true);
-
-	strcpy(rev.name, name);
-	rev.revision = revision;
-
-	max_rev = getsockopt(sockfd, afinfo->ipproto, opt, &rev, &s);
-	if (max_rev < 0) {
-		/* Definitely don't support this? */
-		if (errno == ENOENT || errno == EPROTONOSUPPORT) {
-			close(sockfd);
-			return 0;
-		} else if (errno == ENOPROTOOPT) {
-			close(sockfd);
-			/* Assume only revision 0 support (old kernel) */
-			return (revision == 0);
-		} else {
-			fprintf(stderr, "getsockopt failed strangely: %s\n",
-				strerror(errno));
-			exit(1);
-		}
-	}
-	close(sockfd);
-	return 1;
-}
-
-
-static int compatible_match_revision(const char *name, uint8_t revision)
-{
-	return compatible_revision(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);
-}
-
-static void xtables_check_options(const char *name, const struct option *opt)
-{
-	for (; opt->name != NULL; ++opt)
-		if (opt->val < 0 || opt->val >= XT_OPTION_OFFSET_SCALE) {
-			fprintf(stderr, "%s: Extension %s uses invalid "
-			        "option value %d\n",xt_params->program_name,
-			        name, opt->val);
-			exit(1);
-		}
-}
-
-void xtables_register_match(struct xtables_match *me)
-{
-	if (me->version == NULL) {
-		fprintf(stderr, "%s: match %s<%u> is missing a version\n",
-		        xt_params->program_name, me->name, me->revision);
-		exit(1);
-	}
-	if (strcmp(me->version, XTABLES_VERSION) != 0) {
-		fprintf(stderr, "%s: match \"%s\" has version \"%s\", "
-		        "but \"%s\" is required.\n",
-			xt_params->program_name, me->name,
-			me->version, XTABLES_VERSION);
-		exit(1);
-	}
-
-	if (strlen(me->name) >= XT_EXTENSION_MAXNAMELEN) {
-		fprintf(stderr, "%s: match `%s' has invalid name\n",
-			xt_params->program_name, me->name);
-		exit(1);
-	}
-
-	if (me->family >= NPROTO) {
-		fprintf(stderr,
-			"%s: BUG: match %s has invalid protocol family\n",
-			xt_params->program_name, me->name);
-		exit(1);
-	}
-
-	if (me->x6_options != NULL)
-		xtables_option_metavalidate(me->name, me->x6_options);
-	if (me->extra_opts != NULL)
-		xtables_check_options(me->name, me->extra_opts);
-
-	/* ignore not interested match */
-	if (me->family != afinfo->family && me->family != AF_UNSPEC)
-		return;
-
-	/* place on linked list of matches pending full registration */
-	me->next = xtables_pending_matches;
-	xtables_pending_matches = me;
-}
-
-static void xtables_fully_register_pending_match(struct xtables_match *me)
-{
-	struct xtables_match **i, *old;
-
-	old = xtables_find_match(me->name, XTF_DURING_LOAD, NULL);
-	if (old) {
-		if (old->revision == me->revision &&
-		    old->family == me->family) {
-			fprintf(stderr,
-				"%s: match `%s' already registered.\n",
-				xt_params->program_name, me->name);
-			exit(1);
-		}
-
-		/* Now we have two (or more) options, check compatibility. */
-		if (compatible_match_revision(old->name, old->revision)
-		    && old->revision > me->revision)
-			return;
-
-		/* See if new match can be used. */
-		if (!compatible_match_revision(me->name, me->revision))
-			return;
-
-		/* Prefer !AF_UNSPEC over AF_UNSPEC for same revision. */
-		if (old->revision == me->revision && me->family == AF_UNSPEC)
-			return;
-
-		/* Delete old one. */
-		for (i = &xtables_matches; *i!=old; i = &(*i)->next);
-		*i = old->next;
-	}
-
-	if (me->size != XT_ALIGN(me->size)) {
-		fprintf(stderr, "%s: match `%s' has invalid size %u.\n",
-		        xt_params->program_name, me->name,
-		        (unsigned int)me->size);
-		exit(1);
-	}
-
-	/* Append to list. */
-	for (i = &xtables_matches; *i; i = &(*i)->next);
-	me->next = NULL;
-	*i = me;
-
-	me->m = NULL;
-	me->mflags = 0;
-}
-
-void xtables_register_matches(struct xtables_match *match, unsigned int n)
-{
-	do {
-		xtables_register_match(&match[--n]);
-	} while (n > 0);
-}
-
-void xtables_register_target(struct xtables_target *me)
-{
-	if (me->version == NULL) {
-		fprintf(stderr, "%s: target %s<%u> is missing a version\n",
-		        xt_params->program_name, me->name, me->revision);
-		exit(1);
-	}
-	if (strcmp(me->version, XTABLES_VERSION) != 0) {
-		fprintf(stderr, "%s: target \"%s\" has version \"%s\", "
-		        "but \"%s\" is required.\n",
-			xt_params->program_name, me->name,
-			me->version, XTABLES_VERSION);
-		exit(1);
-	}
-
-	if (strlen(me->name) >= XT_EXTENSION_MAXNAMELEN) {
-		fprintf(stderr, "%s: target `%s' has invalid name\n",
-			xt_params->program_name, me->name);
-		exit(1);
-	}
-
-	if (me->family >= NPROTO) {
-		fprintf(stderr,
-			"%s: BUG: target %s has invalid protocol family\n",
-			xt_params->program_name, me->name);
-		exit(1);
-	}
-
-	if (me->x6_options != NULL)
-		xtables_option_metavalidate(me->name, me->x6_options);
-	if (me->extra_opts != NULL)
-		xtables_check_options(me->name, me->extra_opts);
-
-	/* ignore not interested target */
-	if (me->family != afinfo->family && me->family != AF_UNSPEC)
-		return;
-
-	/* place on linked list of targets pending full registration */
-	me->next = xtables_pending_targets;
-	xtables_pending_targets = me;
-}
-
-static void xtables_fully_register_pending_target(struct xtables_target *me)
-{
-	struct xtables_target *old;
-
-	old = xtables_find_target(me->name, XTF_DURING_LOAD);
-	if (old) {
-		struct xtables_target **i;
-
-		if (old->revision == me->revision &&
-		    old->family == me->family) {
-			fprintf(stderr,
-				"%s: target `%s' already registered.\n",
-				xt_params->program_name, me->name);
-			exit(1);
-		}
-
-		/* Now we have two (or more) options, check compatibility. */
-		if (compatible_target_revision(old->name, old->revision)
-		    && old->revision > me->revision)
-			return;
-
-		/* See if new target can be used. */
-		if (!compatible_target_revision(me->name, me->revision))
-			return;
-
-		/* Prefer !AF_UNSPEC over AF_UNSPEC for same revision. */
-		if (old->revision == me->revision && me->family == AF_UNSPEC)
-			return;
-
-		/* Delete old one. */
-		for (i = &xtables_targets; *i!=old; i = &(*i)->next);
-		*i = old->next;
-	}
-
-	if (me->size != XT_ALIGN(me->size)) {
-		fprintf(stderr, "%s: target `%s' has invalid size %u.\n",
-		        xt_params->program_name, me->name,
-		        (unsigned int)me->size);
-		exit(1);
-	}
-
-	/* Prepend to list. */
-	me->next = xtables_targets;
-	xtables_targets = me;
-	me->t = NULL;
-	me->tflags = 0;
-}
-
-void xtables_register_targets(struct xtables_target *target, unsigned int n)
-{
-	do {
-		xtables_register_target(&target[--n]);
-	} while (n > 0);
-}
-
-/**
- * xtables_param_act - act on condition
- * @status:	a constant from enum xtables_exittype
- *
- * %XTF_ONLY_ONCE: print error message that option may only be used once.
- * @p1:		module name (e.g. "mark")
- * @p2(...):	option in conflict (e.g. "--mark")
- * @p3(...):	condition to match on (see extensions/ for examples)
- *
- * %XTF_NO_INVERT: option does not support inversion
- * @p1:		module name
- * @p2:		option in conflict
- * @p3:		condition to match on
- *
- * %XTF_BAD_VALUE: bad value for option
- * @p1:		module name
- * @p2:		option with which the problem occured (e.g. "--mark")
- * @p3:		string the user passed in (e.g. "99999999999999")
- *
- * %XTF_ONE_ACTION: two mutually exclusive actions have been specified
- * @p1:		module name
- *
- * Displays an error message and exits the program.
- */
-void xtables_param_act(unsigned int status, const char *p1, ...)
-{
-	const char *p2, *p3;
-	va_list args;
-	bool b;
-
-	va_start(args, p1);
-
-	switch (status) {
-	case XTF_ONLY_ONCE:
-		p2 = va_arg(args, const char *);
-		b  = va_arg(args, unsigned int);
-		if (!b)
-			return;
-		xt_params->exit_err(PARAMETER_PROBLEM,
-		           "%s: \"%s\" option may only be specified once",
-		           p1, p2);
+	switch (p.command) {
+	case CMD_APPEND:
+		ret = add_entry(p.chain, p.table, &cs, 0, h->family,
+				args.s, args.d,
+				cs.options & OPT_VERBOSE, h, true);
 		break;
-	case XTF_NO_INVERT:
-		p2 = va_arg(args, const char *);
-		b  = va_arg(args, unsigned int);
-		if (!b)
-			return;
-		xt_params->exit_err(PARAMETER_PROBLEM,
-		           "%s: \"%s\" option cannot be inverted", p1, p2);
+	case CMD_DELETE:
+		ret = delete_entry(p.chain, p.table, &cs, h->family,
+				   args.s, args.d,
+				   cs.options & OPT_VERBOSE, h);
 		break;
-	case XTF_BAD_VALUE:
-		p2 = va_arg(args, const char *);
-		p3 = va_arg(args, const char *);
-		xt_params->exit_err(PARAMETER_PROBLEM,
-		           "%s: Bad value for \"%s\" option: \"%s\"",
-		           p1, p2, p3);
+	case CMD_DELETE_NUM:
+		ret = nft_cmd_rule_delete_num(h, p.chain, p.table,
+					      p.rulenum - 1, p.verbose);
 		break;
-	case XTF_ONE_ACTION:
-		b = va_arg(args, unsigned int);
-		if (!b)
-			return;
-		xt_params->exit_err(PARAMETER_PROBLEM,
-		           "%s: At most one action is possible", p1);
+	case CMD_CHECK:
+		ret = check_entry(p.chain, p.table, &cs, h->family,
+				  args.s, args.d,
+				  cs.options & OPT_VERBOSE, h);
+		break;
+	case CMD_REPLACE:
+		ret = replace_entry(p.chain, p.table, &cs, p.rulenum - 1,
+				    h->family, args.s, args.d,
+				    cs.options & OPT_VERBOSE, h);
+		break;
+	case CMD_INSERT:
+		ret = add_entry(p.chain, p.table, &cs, p.rulenum - 1,
+				h->family, args.s, args.d,
+				cs.options&OPT_VERBOSE, h, false);
+		break;
+	case CMD_FLUSH:
+		ret = nft_cmd_rule_flush(h, p.chain, p.table,
+					 cs.options & OPT_VERBOSE);
+		break;
+	case CMD_ZERO:
+		ret = nft_cmd_chain_zero_counters(h, p.chain, p.table,
+						  cs.options & OPT_VERBOSE);
+		break;
+	case CMD_ZERO_NUM:
+		ret = nft_cmd_rule_zero_counters(h, p.chain, p.table,
+					     p.rulenum - 1);
+		break;
+	case CMD_LIST:
+	case CMD_LIST|CMD_ZERO:
+	case CMD_LIST|CMD_ZERO_NUM:
+		ret = list_entries(h, p.chain, p.table, p.rulenum,
+				   cs.options & OPT_VERBOSE,
+				   cs.options & OPT_NUMERIC,
+				   cs.options & OPT_EXPANDED,
+				   cs.options & OPT_LINENUMBERS);
+		if (ret && (p.command & CMD_ZERO)) {
+			ret = nft_cmd_chain_zero_counters(h, p.chain, p.table,
+						      cs.options & OPT_VERBOSE);
+		}
+		if (ret && (p.command & CMD_ZERO_NUM)) {
+			ret = nft_cmd_rule_zero_counters(h, p.chain, p.table,
+						     p.rulenum - 1);
+		}
+		nft_check_xt_legacy(h->family, false);
+		break;
+	case CMD_LIST_RULES:
+	case CMD_LIST_RULES|CMD_ZERO:
+	case CMD_LIST_RULES|CMD_ZERO_NUM:
+		ret = list_rules(h, p.chain, p.table, p.rulenum,
+				 cs.options & OPT_VERBOSE);
+		if (ret && (p.command & CMD_ZERO)) {
+			ret = nft_cmd_chain_zero_counters(h, p.chain, p.table,
+						      cs.options & OPT_VERBOSE);
+		}
+		if (ret && (p.command & CMD_ZERO_NUM)) {
+			ret = nft_cmd_rule_zero_counters(h, p.chain, p.table,
+						     p.rulenum - 1);
+		}
+		nft_check_xt_legacy(h->family, false);
+		break;
+	case CMD_NEW_CHAIN:
+		ret = nft_cmd_chain_user_add(h, p.chain, p.table);
+		break;
+	case CMD_DELETE_CHAIN:
+		ret = nft_cmd_chain_user_del(h, p.chain, p.table,
+					 cs.options & OPT_VERBOSE);
+		break;
+	case CMD_RENAME_CHAIN:
+		ret = nft_cmd_chain_user_rename(h, p.chain, p.table, p.newname);
+		break;
+	case CMD_SET_POLICY:
+		ret = nft_cmd_chain_set(h, p.table, p.chain, p.policy, NULL);
+		break;
+	case CMD_NONE:
+	/* do_parse ignored the line (eg: -4 with ip6tables-restore) */
 		break;
 	default:
-		xt_params->exit_err(status, p1, args);
-		break;
+		/* We should never reach this... */
+		exit_tryhelp(2);
 	}
 
-	va_end(args);
-}
+	*table = p.table;
 
-const char *xtables_ipaddr_to_numeric(const struct in_addr *addrp)
-{
-	static char buf[20];
-	const unsigned char *bytep = (const void *)&addrp->s_addr;
+	nft_clear_iptables_command_state(&cs);
 
-	sprintf(buf, "%u.%u.%u.%u", bytep[0], bytep[1], bytep[2], bytep[3]);
-	return buf;
-}
-
-static const char *ipaddr_to_host(const struct in_addr *addr)
-{
-	struct hostent *host;
-
-	host = gethostbyaddr(addr, sizeof(struct in_addr), AF_INET);
-	if (host == NULL)
-		return NULL;
-
-	return host->h_name;
-}
-
-static const char *ipaddr_to_network(const struct in_addr *addr)
-{
-	struct netent *net;
-
-	if ((net = getnetbyaddr(ntohl(addr->s_addr), AF_INET)) != NULL)
-		return net->n_name;
-
-	return NULL;
-}
-
-const char *xtables_ipaddr_to_anyname(const struct in_addr *addr)
-{
-	const char *name;
-
-	if ((name = ipaddr_to_host(addr)) != NULL ||
-	    (name = ipaddr_to_network(addr)) != NULL)
-		return name;
-
-	return xtables_ipaddr_to_numeric(addr);
-}
-
-const char *xtables_ipmask_to_numeric(const struct in_addr *mask)
-{
-	static char buf[20];
-	uint32_t maskaddr, bits;
-	int i;
-
-	maskaddr = ntohl(mask->s_addr);
-
-	if (maskaddr == 0xFFFFFFFFL)
-		/* we don't want to see "/32" */
-		return "";
-
-	i = 32;
-	bits = 0xFFFFFFFEL;
-	while (--i >= 0 && maskaddr != bits)
-		bits <<= 1;
-	if (i >= 0)
-		sprintf(buf, "/%d", i);
-	else
-		/* mask was not a decent combination of 1's and 0's */
-		sprintf(buf, "/%s", xtables_ipaddr_to_numeric(mask));
-
-	return buf;
-}
-
-static struct in_addr *__numeric_to_ipaddr(const char *dotted, bool is_mask)
-{
-	static struct in_addr addr;
-	unsigned char *addrp;
-	unsigned int onebyte;
-	char buf[20], *p, *q;
-	int i;
-
-	/* copy dotted string, because we need to modify it */
-	strncpy(buf, dotted, sizeof(buf) - 1);
-	buf[sizeof(buf) - 1] = '\0';
-	addrp = (void *)&addr.s_addr;
-
-	p = buf;
-	for (i = 0; i < 3; ++i) {
-		if ((q = strchr(p, '.')) == NULL) {
-			if (is_mask)
-				return NULL;
-
-			/* autocomplete, this is a network address */
-			if (!xtables_strtoui(p, NULL, &onebyte, 0, UINT8_MAX))
-				return NULL;
-
-			addrp[i] = onebyte;
-			while (i < 3)
-				addrp[++i] = 0;
-
-			return &addr;
-		}
-
-		*q = '\0';
-		if (!xtables_strtoui(p, NULL, &onebyte, 0, UINT8_MAX))
-			return NULL;
-
-		addrp[i] = onebyte;
-		p = q + 1;
+	if (h->family == AF_INET) {
+		free(args.s.addr.v4);
+		free(args.s.mask.v4);
+		free(args.d.addr.v4);
+		free(args.d.mask.v4);
+	} else if (h->family == AF_INET6) {
+		free(args.s.addr.v6);
+		free(args.s.mask.v6);
+		free(args.d.addr.v6);
+		free(args.d.mask.v6);
 	}
+	xtables_free_opts(1);
 
-	/* we have checked 3 bytes, now we check the last one */
-	if (!xtables_strtoui(p, NULL, &onebyte, 0, UINT8_MAX))
-		return NULL;
-
-	addrp[3] = onebyte;
-	return &addr;
-}
-
-struct in_addr *xtables_numeric_to_ipaddr(const char *dotted)
-{
-	return __numeric_to_ipaddr(dotted, false);
-}
-
-struct in_addr *xtables_numeric_to_ipmask(const char *dotted)
-{
-	return __numeric_to_ipaddr(dotted, true);
-}
-
-static struct in_addr *network_to_ipaddr(const char *name)
-{
-	static struct in_addr addr;
-	struct netent *net;
-
-	if ((net = getnetbyname(name)) != NULL) {
-		if (net->n_addrtype != AF_INET)
-			return NULL;
-		addr.s_addr = htonl(net->n_net);
-		return &addr;
-	}
-
-	return NULL;
-}
-
-static struct in_addr *host_to_ipaddr(const char *name, unsigned int *naddr)
-{
-	struct hostent *host;
-	struct in_addr *addr;
-	unsigned int i;
-
-	*naddr = 0;
-	if ((host = gethostbyname(name)) != NULL) {
-		if (host->h_addrtype != AF_INET ||
-		    host->h_length != sizeof(struct in_addr))
-			return NULL;
-
-		while (host->h_addr_list[*naddr] != NULL)
-			++*naddr;
-		addr = xtables_calloc(*naddr, sizeof(struct in_addr));
-		for (i = 0; i < *naddr; i++)
-			memcpy(&addr[i], host->h_addr_list[i],
-			       sizeof(struct in_addr));
-		return addr;
-	}
-
-	return NULL;
-}
-
-static struct in_addr *
-ipparse_hostnetwork(const char *name, unsigned int *naddrs)
-{
-	struct in_addr *addrptmp, *addrp;
-
-	if ((addrptmp = xtables_numeric_to_ipaddr(name)) != NULL ||
-	    (addrptmp = network_to_ipaddr(name)) != NULL) {
-		addrp = xtables_malloc(sizeof(struct in_addr));
-		memcpy(addrp, addrptmp, sizeof(*addrp));
-		*naddrs = 1;
-		return addrp;
-	}
-	if ((addrptmp = host_to_ipaddr(name, naddrs)) != NULL)
-		return addrptmp;
-
-	xt_params->exit_err(PARAMETER_PROBLEM, "host/network `%s' not found", name);
-}
-
-static struct in_addr *parse_ipmask(const char *mask)
-{
-	static struct in_addr maskaddr;
-	struct in_addr *addrp;
-	unsigned int bits;
-
-	if (mask == NULL) {
-		/* no mask at all defaults to 32 bits */
-		maskaddr.s_addr = 0xFFFFFFFF;
-		return &maskaddr;
-	}
-	if ((addrp = xtables_numeric_to_ipmask(mask)) != NULL)
-		/* dotted_to_addr already returns a network byte order addr */
-		return addrp;
-	if (!xtables_strtoui(mask, NULL, &bits, 0, 32))
-		xt_params->exit_err(PARAMETER_PROBLEM,
-			   "invalid mask `%s' specified", mask);
-	if (bits != 0) {
-		maskaddr.s_addr = htonl(0xFFFFFFFF << (32 - bits));
-		return &maskaddr;
-	}
-
-	maskaddr.s_addr = 0U;
-	return &maskaddr;
-}
-
-void xtables_ipparse_multiple(const char *name, struct in_addr **addrpp,
-                              struct in_addr **maskpp, unsigned int *naddrs)
-{
-	struct in_addr *addrp;
-	char buf[256], *p;
-	unsigned int len, i, j, n, count = 1;
-	const char *loop = name;
-
-	while ((loop = strchr(loop, ',')) != NULL) {
-		++count;
-		++loop; /* skip ',' */
-	}
-
-	*addrpp = xtables_malloc(sizeof(struct in_addr) * count);
-	*maskpp = xtables_malloc(sizeof(struct in_addr) * count);
-
-	loop = name;
-
-	for (i = 0; i < count; ++i) {
-		if (loop == NULL)
-			break;
-		if (*loop == ',')
-			++loop;
-		if (*loop == '\0')
-			break;
-		p = strchr(loop, ',');
-		if (p != NULL)
-			len = p - loop;
-		else
-			len = strlen(loop);
-		if (len == 0 || sizeof(buf) - 1 < len)
-			break;
-
-		strncpy(buf, loop, len);
-		buf[len] = '\0';
-		loop += len;
-		if ((p = strrchr(buf, '/')) != NULL) {
-			*p = '\0';
-			addrp = parse_ipmask(p + 1);
-		} else {
-			addrp = parse_ipmask(NULL);
-		}
-		memcpy(*maskpp + i, addrp, sizeof(*addrp));
-
-		/* if a null mask is given, the name is ignored, like in "any/0" */
-		if ((*maskpp + i)->s_addr == 0)
-			/*
-			 * A bit pointless to process multiple addresses
-			 * in this case...
-			 */
-			strcpy(buf, "0.0.0.0");
-
-		addrp = ipparse_hostnetwork(buf, &n);
-		if (n > 1) {
-			count += n - 1;
-			*addrpp = xtables_realloc(*addrpp,
-			          sizeof(struct in_addr) * count);
-			*maskpp = xtables_realloc(*maskpp,
-			          sizeof(struct in_addr) * count);
-			for (j = 0; j < n; ++j)
-				/* for each new addr */
-				memcpy(*addrpp + i + j, addrp + j,
-				       sizeof(*addrp));
-			for (j = 1; j < n; ++j)
-				/* for each new mask */
-				memcpy(*maskpp + i + j, *maskpp + i,
-				       sizeof(*addrp));
-			i += n - 1;
-		} else {
-			memcpy(*addrpp + i, addrp, sizeof(*addrp));
-		}
-		/* free what ipparse_hostnetwork had allocated: */
-		free(addrp);
-	}
-	*naddrs = count;
-	for (i = 0; i < count; ++i)
-		(*addrpp+i)->s_addr &= (*maskpp+i)->s_addr;
-}
-
-
-/**
- * xtables_ipparse_any - transform arbitrary name to in_addr
- *
- * Possible inputs (pseudo regex):
- * 	m{^($hostname|$networkname|$ipaddr)(/$mask)?}
- * "1.2.3.4/5", "1.2.3.4", "hostname", "networkname"
- */
-void xtables_ipparse_any(const char *name, struct in_addr **addrpp,
-                         struct in_addr *maskp, unsigned int *naddrs)
-{
-	unsigned int i, j, k, n;
-	struct in_addr *addrp;
-	char buf[256], *p;
-
-	strncpy(buf, name, sizeof(buf) - 1);
-	buf[sizeof(buf) - 1] = '\0';
-	if ((p = strrchr(buf, '/')) != NULL) {
-		*p = '\0';
-		addrp = parse_ipmask(p + 1);
-	} else {
-		addrp = parse_ipmask(NULL);
-	}
-	memcpy(maskp, addrp, sizeof(*maskp));
-
-	/* if a null mask is given, the name is ignored, like in "any/0" */
-	if (maskp->s_addr == 0U)
-		strcpy(buf, "0.0.0.0");
-
-	addrp = *addrpp = ipparse_hostnetwork(buf, naddrs);
-	n = *naddrs;
-	for (i = 0, j = 0; i < n; ++i) {
-		addrp[j++].s_addr &= maskp->s_addr;
-		for (k = 0; k < j - 1; ++k)
-			if (addrp[k].s_addr == addrp[j-1].s_addr) {
-				/*
-				 * Nuke the dup by copying an address from the
-				 * tail here, and check the current position
-				 * again (--j).
-				 */
-				memcpy(&addrp[--j], &addrp[--*naddrs],
-				       sizeof(struct in_addr));
-				break;
-			}
-	}
-}
-
-const char *xtables_ip6addr_to_numeric(const struct in6_addr *addrp)
-{
-	/* 0000:0000:0000:0000:0000:0000:000.000.000.000
-	 * 0000:0000:0000:0000:0000:0000:0000:0000 */
-	static char buf[50+1];
-	return inet_ntop(AF_INET6, addrp, buf, sizeof(buf));
-}
-
-static const char *ip6addr_to_host(const struct in6_addr *addr)
-{
-	static char hostname[NI_MAXHOST];
-	struct sockaddr_in6 saddr;
-	int err;
-
-	memset(&saddr, 0, sizeof(struct sockaddr_in6));
-	memcpy(&saddr.sin6_addr, addr, sizeof(*addr));
-	saddr.sin6_family = AF_INET6;
-
-	err = getnameinfo((const void *)&saddr, sizeof(struct sockaddr_in6),
-	      hostname, sizeof(hostname) - 1, NULL, 0, 0);
-	if (err != 0) {
-#ifdef DEBUG
-		fprintf(stderr,"IP2Name: %s\n",gai_strerror(err));
-#endif
-		return NULL;
-	}
-
-#ifdef DEBUG
-	fprintf (stderr, "\naddr2host: %s\n", hostname);
-#endif
-	return hostname;
-}
-
-const char *xtables_ip6addr_to_anyname(const struct in6_addr *addr)
-{
-	const char *name;
-
-	if ((name = ip6addr_to_host(addr)) != NULL)
-		return name;
-
-	return xtables_ip6addr_to_numeric(addr);
-}
-
-static int ip6addr_prefix_length(const struct in6_addr *k)
-{
-	unsigned int bits = 0;
-	uint32_t a, b, c, d;
-
-	a = ntohl(k->s6_addr32[0]);
-	b = ntohl(k->s6_addr32[1]);
-	c = ntohl(k->s6_addr32[2]);
-	d = ntohl(k->s6_addr32[3]);
-	while (a & 0x80000000U) {
-		++bits;
-		a <<= 1;
-		a  |= (b >> 31) & 1;
-		b <<= 1;
-		b  |= (c >> 31) & 1;
-		c <<= 1;
-		c  |= (d >> 31) & 1;
-		d <<= 1;
-	}
-	if (a != 0 || b != 0 || c != 0 || d != 0)
-		return -1;
-	return bits;
-}
-
-const char *xtables_ip6mask_to_numeric(const struct in6_addr *addrp)
-{
-	static char buf[50+2];
-	int l = ip6addr_prefix_length(addrp);
-
-	if (l == -1) {
-		strcpy(buf, "/");
-		strcat(buf, xtables_ip6addr_to_numeric(addrp));
-		return buf;
-	}
-	sprintf(buf, "/%d", l);
-	return buf;
-}
-
-struct in6_addr *xtables_numeric_to_ip6addr(const char *num)
-{
-	static struct in6_addr ap;
-	int err;
-
-	if ((err = inet_pton(AF_INET6, num, &ap)) == 1)
-		return &ap;
-#ifdef DEBUG
-	fprintf(stderr, "\nnumeric2addr: %d\n", err);
-#endif
-	return NULL;
-}
-
-static struct in6_addr *
-host_to_ip6addr(const char *name, unsigned int *naddr)
-{
-	struct in6_addr *addr;
-	struct addrinfo hints;
-	struct addrinfo *res, *p;
-	int err;
-	unsigned int i;
-
-	memset(&hints, 0, sizeof(hints));
-	hints.ai_flags    = AI_CANONNAME;
-	hints.ai_family   = AF_INET6;
-	hints.ai_socktype = SOCK_RAW;
-
-	*naddr = 0;
-	if ((err = getaddrinfo(name, NULL, &hints, &res)) != 0) {
-#ifdef DEBUG
-		fprintf(stderr,"Name2IP: %s\n",gai_strerror(err));
-#endif
-		return NULL;
-	} else {
-		/* Find length of address chain */
-		for (p = res; p != NULL; p = p->ai_next)
-			++*naddr;
-#ifdef DEBUG
-		fprintf(stderr, "resolved: len=%d  %s ", res->ai_addrlen,
-		        xtables_ip6addr_to_numeric(&((struct sockaddr_in6 *)res->ai_addr)->sin6_addr));
-#endif
-		/* Copy each element of the address chain */
-		addr = xtables_calloc(*naddr, sizeof(struct in6_addr));
-		for (i = 0, p = res; p != NULL; p = p->ai_next)
-			memcpy(&addr[i++],
-			       &((const struct sockaddr_in6 *)p->ai_addr)->sin6_addr,
-			       sizeof(struct in6_addr));
-		freeaddrinfo(res);
-		return addr;
-	}
-
-	return NULL;
-}
-
-static struct in6_addr *network_to_ip6addr(const char *name)
-{
-	/*	abort();*/
-	/* TODO: not implemented yet, but the exception breaks the
-	 *       name resolvation */
-	return NULL;
-}
-
-static struct in6_addr *
-ip6parse_hostnetwork(const char *name, unsigned int *naddrs)
-{
-	struct in6_addr *addrp, *addrptmp;
-
-	if ((addrptmp = xtables_numeric_to_ip6addr(name)) != NULL ||
-	    (addrptmp = network_to_ip6addr(name)) != NULL) {
-		addrp = xtables_malloc(sizeof(struct in6_addr));
-		memcpy(addrp, addrptmp, sizeof(*addrp));
-		*naddrs = 1;
-		return addrp;
-	}
-	if ((addrp = host_to_ip6addr(name, naddrs)) != NULL)
-		return addrp;
-
-	xt_params->exit_err(PARAMETER_PROBLEM, "host/network `%s' not found", name);
-}
-
-static struct in6_addr *parse_ip6mask(char *mask)
-{
-	static struct in6_addr maskaddr;
-	struct in6_addr *addrp;
-	unsigned int bits;
-
-	if (mask == NULL) {
-		/* no mask at all defaults to 128 bits */
-		memset(&maskaddr, 0xff, sizeof maskaddr);
-		return &maskaddr;
-	}
-	if ((addrp = xtables_numeric_to_ip6addr(mask)) != NULL)
-		return addrp;
-	if (!xtables_strtoui(mask, NULL, &bits, 0, 128))
-		xt_params->exit_err(PARAMETER_PROBLEM,
-			   "invalid mask `%s' specified", mask);
-	if (bits != 0) {
-		char *p = (void *)&maskaddr;
-		memset(p, 0xff, bits / 8);
-		memset(p + (bits / 8) + 1, 0, (128 - bits) / 8);
-		p[bits/8] = 0xff << (8 - (bits & 7));
-		return &maskaddr;
-	}
-
-	memset(&maskaddr, 0, sizeof(maskaddr));
-	return &maskaddr;
-}
-
-void
-xtables_ip6parse_multiple(const char *name, struct in6_addr **addrpp,
-		      struct in6_addr **maskpp, unsigned int *naddrs)
-{
-	static const struct in6_addr zero_addr;
-	struct in6_addr *addrp;
-	char buf[256], *p;
-	unsigned int len, i, j, n, count = 1;
-	const char *loop = name;
-
-	while ((loop = strchr(loop, ',')) != NULL) {
-		++count;
-		++loop; /* skip ',' */
-	}
-
-	*addrpp = xtables_malloc(sizeof(struct in6_addr) * count);
-	*maskpp = xtables_malloc(sizeof(struct in6_addr) * count);
-
-	loop = name;
-
-	for (i = 0; i < count /*NB: count can grow*/; ++i) {
-		if (loop == NULL)
-			break;
-		if (*loop == ',')
-			++loop;
-		if (*loop == '\0')
-			break;
-		p = strchr(loop, ',');
-		if (p != NULL)
-			len = p - loop;
-		else
-			len = strlen(loop);
-		if (len == 0 || sizeof(buf) - 1 < len)
-			break;
-
-		strncpy(buf, loop, len);
-		buf[len] = '\0';
-		loop += len;
-		if ((p = strrchr(buf, '/')) != NULL) {
-			*p = '\0';
-			addrp = parse_ip6mask(p + 1);
-		} else {
-			addrp = parse_ip6mask(NULL);
-		}
-		memcpy(*maskpp + i, addrp, sizeof(*addrp));
-
-		/* if a null mask is given, the name is ignored, like in "any/0" */
-		if (memcmp(*maskpp + i, &zero_addr, sizeof(zero_addr)) == 0)
-			strcpy(buf, "::");
-
-		addrp = ip6parse_hostnetwork(buf, &n);
-		if (n > 1) {
-			count += n - 1;
-			*addrpp = xtables_realloc(*addrpp,
-			          sizeof(struct in6_addr) * count);
-			*maskpp = xtables_realloc(*maskpp,
-			          sizeof(struct in6_addr) * count);
-			for (j = 0; j < n; ++j)
-				/* for each new addr */
-				memcpy(*addrpp + i + j, addrp + j,
-				       sizeof(*addrp));
-			for (j = 1; j < n; ++j)
-				/* for each new mask */
-				memcpy(*maskpp + i + j, *maskpp + i,
-				       sizeof(*addrp));
-			i += n - 1;
-		} else {
-			memcpy(*addrpp + i, addrp, sizeof(*addrp));
-		}
-		/* free what ip6parse_hostnetwork had allocated: */
-		free(addrp);
-	}
-	*naddrs = count;
-	for (i = 0; i < count; ++i)
-		for (j = 0; j < 4; ++j)
-			(*addrpp+i)->s6_addr32[j] &= (*maskpp+i)->s6_addr32[j];
-}
-
-void xtables_ip6parse_any(const char *name, struct in6_addr **addrpp,
-                          struct in6_addr *maskp, unsigned int *naddrs)
-{
-	static const struct in6_addr zero_addr;
-	struct in6_addr *addrp;
-	unsigned int i, j, k, n;
-	char buf[256], *p;
-
-	strncpy(buf, name, sizeof(buf) - 1);
-	buf[sizeof(buf)-1] = '\0';
-	if ((p = strrchr(buf, '/')) != NULL) {
-		*p = '\0';
-		addrp = parse_ip6mask(p + 1);
-	} else {
-		addrp = parse_ip6mask(NULL);
-	}
-	memcpy(maskp, addrp, sizeof(*maskp));
-
-	/* if a null mask is given, the name is ignored, like in "any/0" */
-	if (memcmp(maskp, &zero_addr, sizeof(zero_addr)) == 0)
-		strcpy(buf, "::");
-
-	addrp = *addrpp = ip6parse_hostnetwork(buf, naddrs);
-	n = *naddrs;
-	for (i = 0, j = 0; i < n; ++i) {
-		for (k = 0; k < 4; ++k)
-			addrp[j].s6_addr32[k] &= maskp->s6_addr32[k];
-		++j;
-		for (k = 0; k < j - 1; ++k)
-			if (IN6_ARE_ADDR_EQUAL(&addrp[k], &addrp[j - 1])) {
-				/*
-				 * Nuke the dup by copying an address from the
-				 * tail here, and check the current position
-				 * again (--j).
-				 */
-				memcpy(&addrp[--j], &addrp[--*naddrs],
-				       sizeof(struct in_addr));
-				break;
-			}
-	}
-}
-
-void xtables_save_string(const char *value)
-{
-	static const char no_quote_chars[] = "_-0123456789"
-		"abcdefghijklmnopqrstuvwxyz"
-		"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
-	static const char escape_chars[] = "\"\\'";
-	size_t length;
-	const char *p;
-
-	length = strspn(value, no_quote_chars);
-	if (length > 0 && value[length] == 0) {
-		/* no quoting required */
-		putchar(' ');
-		fputs(value, stdout);
-	} else {
-		/* there is at least one dangerous character in the
-		   value, which we have to quote.  Write double quotes
-		   around the value and escape special characters with
-		   a backslash */
-		printf(" \"");
-
-		for (p = strpbrk(value, escape_chars); p != NULL;
-		     p = strpbrk(value, escape_chars)) {
-			if (p > value)
-				fwrite(value, 1, p - value, stdout);
-			putchar('\\');
-			putchar(*p);
-			value = p + 1;
-		}
-
-		/* print the rest and finish the double quoted
-		   string */
-		fputs(value, stdout);
-		putchar('\"');
-	}
-}
-
-/**
- * Check for option-intrapositional negation.
- * Do not use in new code.
- */
-int xtables_check_inverse(const char option[], int *invert,
-			  int *my_optind, int argc, char **argv)
-{
-	if (option == NULL || strcmp(option, "!") != 0)
-		return false;
-
-	fprintf(stderr, "Using intrapositioned negation "
-	        "(`--option ! this`) is deprecated in favor of "
-	        "extrapositioned (`! --option this`).\n");
-
-	if (*invert)
-		xt_params->exit_err(PARAMETER_PROBLEM,
-			   "Multiple `!' flags not allowed");
-	*invert = true;
-	if (my_optind != NULL) {
-		optarg = argv[*my_optind];
-		++*my_optind;
-		if (argc && *my_optind > argc)
-			xt_params->exit_err(PARAMETER_PROBLEM,
-				   "no argument following `!'");
-	}
-
-	return true;
-}
-
-const struct xtables_pprot xtables_chain_protos[] = {
-	{"tcp",       IPPROTO_TCP},
-	{"sctp",      IPPROTO_SCTP},
-	{"udp",       IPPROTO_UDP},
-	{"udplite",   IPPROTO_UDPLITE},
-	{"icmp",      IPPROTO_ICMP},
-	{"icmpv6",    IPPROTO_ICMPV6},
-	{"ipv6-icmp", IPPROTO_ICMPV6},
-	{"esp",       IPPROTO_ESP},
-	{"ah",        IPPROTO_AH},
-	{"ipv6-mh",   IPPROTO_MH},
-	{"mh",        IPPROTO_MH},
-	{"all",       0},
-	{NULL},
-};
-
-uint16_t
-xtables_parse_protocol(const char *s)
-{
-	const struct protoent *pent;
-	unsigned int proto, i;
-
-	if (xtables_strtoui(s, NULL, &proto, 0, UINT8_MAX))
-		return proto;
-
-	/* first deal with the special case of 'all' to prevent
-	 * people from being able to redefine 'all' in nsswitch
-	 * and/or provoke expensive [not working] ldap/nis/...
-	 * lookups */
-	if (strcmp(s, "all") == 0)
-		return 0;
-
-	pent = getprotobyname(s);
-	if (pent != NULL)
-		return pent->p_proto;
-
-	for (i = 0; i < ARRAY_SIZE(xtables_chain_protos); ++i) {
-		if (xtables_chain_protos[i].name == NULL)
-			continue;
-		if (strcmp(s, xtables_chain_protos[i].name) == 0)
-			return xtables_chain_protos[i].num;
-	}
-	xt_params->exit_err(PARAMETER_PROBLEM,
-		"unknown protocol \"%s\" specified", s);
-	return -1;
+	return ret;
 }
diff --git a/iptables/xtables.lock b/iptables/xtables.lock
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/iptables/xtables.lock
diff --git a/iptables/xtoptions.c b/iptables/xtoptions.c
deleted file mode 100644
index ac0601f..0000000
--- a/iptables/xtoptions.c
+++ /dev/null
@@ -1,1155 +0,0 @@
-/*
- *	Argument parser
- *	Copyright © Jan Engelhardt, 2011
- *
- *	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.
- */
-#include <ctype.h>
-#include <errno.h>
-#include <getopt.h>
-#include <limits.h>
-#include <netdb.h>
-#include <stdbool.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <syslog.h>
-#include <arpa/inet.h>
-#include <netinet/ip.h>
-#include "xtables.h"
-#include "xshared.h"
-#ifndef IPTOS_NORMALSVC
-#	define IPTOS_NORMALSVC 0
-#endif
-
-#define XTOPT_MKPTR(cb) \
-	((void *)((char *)(cb)->data + (cb)->entry->ptroff))
-
-/**
- * Simple key-value pairs for syslog levels
- */
-struct syslog_level {
-	char name[8];
-	uint8_t level;
-};
-
-struct tos_value_mask {
-	uint8_t value, mask;
-};
-
-static const size_t xtopt_psize[] = {
-	/*
-	 * All types not listed here, and thus essentially being initialized to
-	 * zero have zero on purpose.
-	 */
-	[XTTYPE_UINT8]       = sizeof(uint8_t),
-	[XTTYPE_UINT16]      = sizeof(uint16_t),
-	[XTTYPE_UINT32]      = sizeof(uint32_t),
-	[XTTYPE_UINT64]      = sizeof(uint64_t),
-	[XTTYPE_UINT8RC]     = sizeof(uint8_t[2]),
-	[XTTYPE_UINT16RC]    = sizeof(uint16_t[2]),
-	[XTTYPE_UINT32RC]    = sizeof(uint32_t[2]),
-	[XTTYPE_UINT64RC]    = sizeof(uint64_t[2]),
-	[XTTYPE_DOUBLE]      = sizeof(double),
-	[XTTYPE_STRING]      = -1,
-	[XTTYPE_SYSLOGLEVEL] = sizeof(uint8_t),
-	[XTTYPE_HOST]        = sizeof(union nf_inet_addr),
-	[XTTYPE_HOSTMASK]    = sizeof(union nf_inet_addr),
-	[XTTYPE_PROTOCOL]    = sizeof(uint8_t),
-	[XTTYPE_PORT]        = sizeof(uint16_t),
-	[XTTYPE_PORTRC]      = sizeof(uint16_t[2]),
-	[XTTYPE_PLENMASK]    = sizeof(union nf_inet_addr),
-	[XTTYPE_ETHERMAC]    = sizeof(uint8_t[6]),
-};
-
-/**
- * Creates getopt options from the x6-style option map, and assigns each a
- * getopt id.
- */
-struct option *
-xtables_options_xfrm(struct option *orig_opts, struct option *oldopts,
-		     const struct xt_option_entry *entry, unsigned int *offset)
-{
-	unsigned int num_orig, num_old = 0, num_new, i;
-	struct option *merge, *mp;
-
-	if (entry == NULL)
-		return oldopts;
-	for (num_orig = 0; orig_opts[num_orig].name != NULL; ++num_orig)
-		;
-	if (oldopts != NULL)
-		for (num_old = 0; oldopts[num_old].name != NULL; ++num_old)
-			;
-	for (num_new = 0; entry[num_new].name != NULL; ++num_new)
-		;
-
-	/*
-	 * Since @oldopts also has @orig_opts already (and does so at the
-	 * start), skip these entries.
-	 */
-	oldopts += num_orig;
-	num_old -= num_orig;
-
-	merge = malloc(sizeof(*mp) * (num_orig + num_old + num_new + 1));
-	if (merge == NULL)
-		return NULL;
-
-	/* Let the base options -[ADI...] have precedence over everything */
-	memcpy(merge, orig_opts, sizeof(*mp) * num_orig);
-	mp = merge + num_orig;
-
-	/* Second, the new options */
-	xt_params->option_offset += XT_OPTION_OFFSET_SCALE;
-	*offset = xt_params->option_offset;
-
-	for (i = 0; i < num_new; ++i, ++mp, ++entry) {
-		mp->name         = entry->name;
-		mp->has_arg      = entry->type != XTTYPE_NONE;
-		mp->flag         = NULL;
-		mp->val          = entry->id + *offset;
-	}
-
-	/* Third, the old options */
-	memcpy(mp, oldopts, sizeof(*mp) * num_old);
-	mp += num_old;
-	xtables_free_opts(0);
-
-	/* Clear trailing entry */
-	memset(mp, 0, sizeof(*mp));
-	return merge;
-}
-
-/**
- * Give the upper limit for a certain type.
- */
-static uintmax_t xtopt_max_by_type(enum xt_option_type type)
-{
-	switch (type) {
-	case XTTYPE_UINT8:
-	case XTTYPE_UINT8RC:
-		return UINT8_MAX;
-	case XTTYPE_UINT16:
-	case XTTYPE_UINT16RC:
-		return UINT16_MAX;
-	case XTTYPE_UINT32:
-	case XTTYPE_UINT32RC:
-		return UINT32_MAX;
-	case XTTYPE_UINT64:
-	case XTTYPE_UINT64RC:
-		return UINT64_MAX;
-	default:
-		return 0;
-	}
-}
-
-/**
- * Return the size of a single entity based upon a type - predominantly an
- * XTTYPE_UINT*RC type.
- */
-static size_t xtopt_esize_by_type(enum xt_option_type type)
-{
-	switch (type) {
-	case XTTYPE_UINT8RC:
-		return xtopt_psize[XTTYPE_UINT8];
-	case XTTYPE_UINT16RC:
-		return xtopt_psize[XTTYPE_UINT16];
-	case XTTYPE_UINT32RC:
-		return xtopt_psize[XTTYPE_UINT32];
-	case XTTYPE_UINT64RC:
-		return xtopt_psize[XTTYPE_UINT64];
-	default:
-		return xtopt_psize[type];
-	}
-}
-
-/**
- * Require a simple integer.
- */
-static void xtopt_parse_int(struct xt_option_call *cb)
-{
-	const struct xt_option_entry *entry = cb->entry;
-	uintmax_t lmin = 0, lmax = xtopt_max_by_type(entry->type);
-	uintmax_t value;
-
-	if (cb->entry->min != 0)
-		lmin = cb->entry->min;
-	if (cb->entry->max != 0)
-		lmax = cb->entry->max;
-
-	if (!xtables_strtoul(cb->arg, NULL, &value, lmin, lmax))
-		xt_params->exit_err(PARAMETER_PROBLEM,
-			"%s: bad value for option \"--%s\", "
-			"or out of range (%ju-%ju).\n",
-			cb->ext_name, entry->name, lmin, lmax);
-
-	if (entry->type == XTTYPE_UINT8) {
-		cb->val.u8 = value;
-		if (entry->flags & XTOPT_PUT)
-			*(uint8_t *)XTOPT_MKPTR(cb) = cb->val.u8;
-	} else if (entry->type == XTTYPE_UINT16) {
-		cb->val.u16 = value;
-		if (entry->flags & XTOPT_PUT)
-			*(uint16_t *)XTOPT_MKPTR(cb) = cb->val.u16;
-	} else if (entry->type == XTTYPE_UINT32) {
-		cb->val.u32 = value;
-		if (entry->flags & XTOPT_PUT)
-			*(uint32_t *)XTOPT_MKPTR(cb) = cb->val.u32;
-	} else if (entry->type == XTTYPE_UINT64) {
-		cb->val.u64 = value;
-		if (entry->flags & XTOPT_PUT)
-			*(uint64_t *)XTOPT_MKPTR(cb) = cb->val.u64;
-	}
-}
-
-/**
- * Require a simple floating point number.
- */
-static void xtopt_parse_float(struct xt_option_call *cb)
-{
-	const struct xt_option_entry *entry = cb->entry;
-	double value;
-	char *end;
-
-	value = strtod(cb->arg, &end);
-	if (end == cb->arg || *end != '\0' ||
-	    (entry->min != entry->max &&
-	    (value < entry->min || value > entry->max)))
-		xt_params->exit_err(PARAMETER_PROBLEM,
-			"%s: bad value for option \"--%s\", "
-			"or out of range (%u-%u).\n",
-			cb->ext_name, entry->name, entry->min, entry->max);
-
-	cb->val.dbl = value;
-	if (entry->flags & XTOPT_PUT)
-		*(double *)XTOPT_MKPTR(cb) = cb->val.dbl;
-}
-
-/**
- * Copy the parsed value to the appropriate entry in cb->val.
- */
-static void xtopt_mint_value_to_cb(struct xt_option_call *cb, uintmax_t value)
-{
-	const struct xt_option_entry *entry = cb->entry;
-
-	if (cb->nvals >= ARRAY_SIZE(cb->val.u32_range))
-		return;
-	if (entry->type == XTTYPE_UINT8RC)
-		cb->val.u8_range[cb->nvals] = value;
-	else if (entry->type == XTTYPE_UINT16RC)
-		cb->val.u16_range[cb->nvals] = value;
-	else if (entry->type == XTTYPE_UINT32RC)
-		cb->val.u32_range[cb->nvals] = value;
-	else if (entry->type == XTTYPE_UINT64RC)
-		cb->val.u64_range[cb->nvals] = value;
-}
-
-/**
- * Copy the parsed value to the data area, using appropriate type access.
- */
-static void xtopt_mint_value_to_ptr(struct xt_option_call *cb, void **datap,
-				    uintmax_t value)
-{
-	const struct xt_option_entry *entry = cb->entry;
-	void *data = *datap;
-
-	if (!(entry->flags & XTOPT_PUT))
-		return;
-	if (entry->type == XTTYPE_UINT8RC)
-		*(uint8_t *)data = value;
-	else if (entry->type == XTTYPE_UINT16RC)
-		*(uint16_t *)data = value;
-	else if (entry->type == XTTYPE_UINT32RC)
-		*(uint32_t *)data = value;
-	else if (entry->type == XTTYPE_UINT64RC)
-		*(uint64_t *)data = value;
-	data += xtopt_esize_by_type(entry->type);
-	*datap = data;
-}
-
-/**
- * Multiple integer parse routine.
- *
- * This function is capable of parsing any number of fields. Only the first
- * two values from the string will be put into @cb however (and as such,
- * @cb->val.uXX_range is just that large) to cater for the few extensions that
- * do not have a range[2] field, but {min, max}, and which cannot use
- * XTOPT_POINTER.
- */
-static void xtopt_parse_mint(struct xt_option_call *cb)
-{
-	const struct xt_option_entry *entry = cb->entry;
-	const char *arg = cb->arg;
-	size_t esize = xtopt_esize_by_type(entry->type);
-	const uintmax_t lmax = xtopt_max_by_type(entry->type);
-	void *put = XTOPT_MKPTR(cb);
-	unsigned int maxiter;
-	uintmax_t value;
-	char *end = "";
-	char sep = ':';
-
-	maxiter = entry->size / esize;
-	if (maxiter == 0)
-		maxiter = ARRAY_SIZE(cb->val.u32_range);
-	if (entry->size % esize != 0)
-		xt_params->exit_err(OTHER_PROBLEM, "%s: memory block does "
-			"not have proper size\n", __func__);
-
-	cb->nvals = 0;
-	for (arg = cb->arg, end = (char *)arg; ; arg = end + 1) {
-		if (cb->nvals == maxiter)
-			xt_params->exit_err(PARAMETER_PROBLEM, "%s: Too many "
-				"components for option \"--%s\" (max: %u)\n",
-				cb->ext_name, entry->name, maxiter);
-		if (*arg == '\0' || *arg == sep) {
-			/* Default range components when field not spec'd. */
-			end = (char *)arg;
-			value = (cb->nvals == 1) ? lmax : 0;
-		} else {
-			if (!xtables_strtoul(arg, &end, &value, 0, lmax))
-				xt_params->exit_err(PARAMETER_PROBLEM,
-					"%s: bad value for option \"--%s\" near "
-					"\"%s\", or out of range (0-%ju).\n",
-					cb->ext_name, entry->name, arg, lmax);
-			if (*end != '\0' && *end != sep)
-				xt_params->exit_err(PARAMETER_PROBLEM,
-					"%s: Argument to \"--%s\" has "
-					"unexpected characters near \"%s\".\n",
-					cb->ext_name, entry->name, end);
-		}
-		xtopt_mint_value_to_cb(cb, value);
-		++cb->nvals;
-		xtopt_mint_value_to_ptr(cb, &put, value);
-		if (*end == '\0')
-			break;
-	}
-}
-
-static void xtopt_parse_string(struct xt_option_call *cb)
-{
-	const struct xt_option_entry *entry = cb->entry;
-	size_t z = strlen(cb->arg);
-	char *p;
-
-	if (entry->min != 0 && z < entry->min)
-		xt_params->exit_err(PARAMETER_PROBLEM,
-			"Argument must have a minimum length of "
-			"%u characters\n", entry->min);
-	if (entry->max != 0 && z > entry->max)
-		xt_params->exit_err(PARAMETER_PROBLEM,
-			"Argument must have a maximum length of "
-			"%u characters\n", entry->max);
-	if (!(entry->flags & XTOPT_PUT))
-		return;
-	if (z >= entry->size)
-		z = entry->size - 1;
-	p = XTOPT_MKPTR(cb);
-	strncpy(p, cb->arg, z);
-	p[z] = '\0';
-}
-
-static const struct tos_symbol_info {
-	unsigned char value;
-	const char *name;
-} tos_symbol_names[] = {
-	{IPTOS_LOWDELAY,    "Minimize-Delay"},
-	{IPTOS_THROUGHPUT,  "Maximize-Throughput"},
-	{IPTOS_RELIABILITY, "Maximize-Reliability"},
-	{IPTOS_MINCOST,     "Minimize-Cost"},
-	{IPTOS_NORMALSVC,   "Normal-Service"},
-	{},
-};
-
-/*
- * tos_parse_numeric - parse a string like "15/255"
- *
- * @str:	input string
- * @tvm:	(value/mask) tuple
- * @max:	maximum allowed value (must be pow(2,some_int)-1)
- */
-static bool tos_parse_numeric(const char *str, struct xt_option_call *cb,
-                              unsigned int max)
-{
-	unsigned int value;
-	char *end;
-
-	xtables_strtoui(str, &end, &value, 0, max);
-	cb->val.tos_value = value;
-	cb->val.tos_mask  = max;
-
-	if (*end == '/') {
-		const char *p = end + 1;
-
-		if (!xtables_strtoui(p, &end, &value, 0, max))
-			xtables_error(PARAMETER_PROBLEM, "Illegal value: \"%s\"",
-			           str);
-		cb->val.tos_mask = value;
-	}
-
-	if (*end != '\0')
-		xtables_error(PARAMETER_PROBLEM, "Illegal value: \"%s\"", str);
-	return true;
-}
-
-/**
- * @str:	input string
- * @tvm:	(value/mask) tuple
- * @def_mask:	mask to force when a symbolic name is used
- */
-static void xtopt_parse_tosmask(struct xt_option_call *cb)
-{
-	const struct tos_symbol_info *symbol;
-	char *tmp;
-
-	if (xtables_strtoui(cb->arg, &tmp, NULL, 0, UINT8_MAX)) {
-		tos_parse_numeric(cb->arg, cb, UINT8_MAX);
-		return;
-	}
-	/*
-	 * This is our way we deal with different defaults
-	 * for different revisions.
-	 */
-	cb->val.tos_mask = cb->entry->max;
-	for (symbol = tos_symbol_names; symbol->name != NULL; ++symbol)
-		if (strcasecmp(cb->arg, symbol->name) == 0) {
-			cb->val.tos_value = symbol->value;
-			return;
-		}
-
-	xtables_error(PARAMETER_PROBLEM, "Symbolic name \"%s\" is unknown",
-		      cb->arg);
-}
-
-/**
- * Validate the input for being conformant to "mark[/mask]".
- */
-static void xtopt_parse_markmask(struct xt_option_call *cb)
-{
-	unsigned int mark = 0, mask = ~0U;
-	char *end;
-
-	if (!xtables_strtoui(cb->arg, &end, &mark, 0, UINT32_MAX))
-		xt_params->exit_err(PARAMETER_PROBLEM,
-			"%s: bad mark value for option \"--%s\", "
-			"or out of range.\n",
-			cb->ext_name, cb->entry->name);
-	if (*end == '/' &&
-	    !xtables_strtoui(end + 1, &end, &mask, 0, UINT32_MAX))
-		xt_params->exit_err(PARAMETER_PROBLEM,
-			"%s: bad mask value for option \"--%s\", "
-			"or out of range.\n",
-			cb->ext_name, cb->entry->name);
-	if (*end != '\0')
-		xt_params->exit_err(PARAMETER_PROBLEM,
-			"%s: trailing garbage after value "
-			"for option \"--%s\".\n",
-			cb->ext_name, cb->entry->name);
-	cb->val.mark = mark;
-	cb->val.mask = mask;
-}
-
-static int xtopt_sysloglvl_compare(const void *a, const void *b)
-{
-	const char *name = a;
-	const struct syslog_level *entry = b;
-
-	return strcmp(name, entry->name);
-}
-
-static void xtopt_parse_sysloglevel(struct xt_option_call *cb)
-{
-	static const struct syslog_level log_names[] = { /* must be sorted */
-		{"alert",   LOG_ALERT},
-		{"crit",    LOG_CRIT},
-		{"debug",   LOG_DEBUG},
-		{"emerg",   LOG_EMERG},
-		{"error",   LOG_ERR}, /* deprecated */
-		{"info",    LOG_INFO},
-		{"notice",  LOG_NOTICE},
-		{"panic",   LOG_EMERG}, /* deprecated */
-		{"warning", LOG_WARNING},
-	};
-	const struct syslog_level *e;
-	unsigned int num = 0;
-
-	if (!xtables_strtoui(cb->arg, NULL, &num, 0, 7)) {
-		e = bsearch(cb->arg, log_names, ARRAY_SIZE(log_names),
-			    sizeof(*log_names), xtopt_sysloglvl_compare);
-		if (e == NULL)
-			xt_params->exit_err(PARAMETER_PROBLEM,
-				"log level \"%s\" unknown\n", cb->arg);
-		num = e->level;
-	}
-	cb->val.syslog_level = num;
-	if (cb->entry->flags & XTOPT_PUT)
-		*(uint8_t *)XTOPT_MKPTR(cb) = num;
-}
-
-static void *xtables_sa_host(const void *sa, unsigned int afproto)
-{
-	if (afproto == AF_INET6)
-		return &((struct sockaddr_in6 *)sa)->sin6_addr;
-	else if (afproto == AF_INET)
-		return &((struct sockaddr_in *)sa)->sin_addr;
-	return (void *)sa;
-}
-
-static socklen_t xtables_sa_hostlen(unsigned int afproto)
-{
-	if (afproto == AF_INET6)
-		return sizeof(struct in6_addr);
-	else if (afproto == AF_INET)
-		return sizeof(struct in_addr);
-	return 0;
-}
-
-/**
- * Accepts: a hostname (DNS), or a single inetaddr - without any mask. The
- * result is stored in @cb->val.haddr. Additionally, @cb->val.hmask and
- * @cb->val.hlen are set for completeness to the appropriate values.
- */
-static void xtopt_parse_host(struct xt_option_call *cb)
-{
-	struct addrinfo hints = {.ai_family = afinfo->family};
-	unsigned int adcount = 0;
-	struct addrinfo *res, *p;
-	int ret;
-
-	ret = getaddrinfo(cb->arg, NULL, &hints, &res);
-	if (ret < 0)
-		xt_params->exit_err(PARAMETER_PROBLEM,
-			"getaddrinfo: %s\n", gai_strerror(ret));
-
-	memset(&cb->val.hmask, 0xFF, sizeof(cb->val.hmask));
-	cb->val.hlen = (afinfo->family == NFPROTO_IPV4) ? 32 : 128;
-
-	for (p = res; p != NULL; p = p->ai_next) {
-		if (adcount == 0) {
-			memset(&cb->val.haddr, 0, sizeof(cb->val.haddr));
-			memcpy(&cb->val.haddr,
-			       xtables_sa_host(p->ai_addr, p->ai_family),
-			       xtables_sa_hostlen(p->ai_family));
-			++adcount;
-			continue;
-		}
-		if (memcmp(&cb->val.haddr,
-		    xtables_sa_host(p->ai_addr, p->ai_family),
-		    xtables_sa_hostlen(p->ai_family)) != 0)
-			xt_params->exit_err(PARAMETER_PROBLEM,
-				"%s resolves to more than one address\n",
-				cb->arg);
-	}
-
-	freeaddrinfo(res);
-	if (cb->entry->flags & XTOPT_PUT)
-		/* Validation in xtables_option_metavalidate */
-		memcpy(XTOPT_MKPTR(cb), &cb->val.haddr,
-		       sizeof(cb->val.haddr));
-}
-
-/**
- * @name:	port name, or number as a string (e.g. "http" or "80")
- *
- * Resolve a port name to a number. Returns the port number in integral
- * form on success, or <0 on error. (errno will not be set.)
- */
-static int xtables_getportbyname(const char *name)
-{
-	struct addrinfo *res = NULL, *p;
-	int ret;
-
-	ret = getaddrinfo(NULL, name, NULL, &res);
-	if (ret < 0)
-		return -1;
-	ret = -1;
-	for (p = res; p != NULL; p = p->ai_next) {
-		if (p->ai_family == AF_INET6) {
-			ret = ((struct sockaddr_in6 *)p->ai_addr)->sin6_port;
-			break;
-		} else if (p->ai_family == AF_INET) {
-			ret = ((struct sockaddr_in *)p->ai_addr)->sin_port;
-			break;
-		}
-	}
-	freeaddrinfo(res);
-	if (ret < 0)
-		return ret;
-	return ntohs(ret);
-}
-
-/**
- * Validate and parse a protocol specification (number or name) by use of
- * /etc/protocols and put the result into @cb->val.protocol.
- */
-static void xtopt_parse_protocol(struct xt_option_call *cb)
-{
-	cb->val.protocol = xtables_parse_protocol(cb->arg);
-	if (cb->entry->flags & XTOPT_PUT)
-		*(uint8_t *)XTOPT_MKPTR(cb) = cb->val.protocol;
-}
-
-/**
- * Validate and parse a port specification and put the result into
- * @cb->val.port.
- */
-static void xtopt_parse_port(struct xt_option_call *cb)
-{
-	const struct xt_option_entry *entry = cb->entry;
-	int ret;
-
-	ret = xtables_getportbyname(cb->arg);
-	if (ret < 0)
-		xt_params->exit_err(PARAMETER_PROBLEM,
-			"Port \"%s\" does not resolve to anything.\n",
-			cb->arg);
-	if (entry->flags & XTOPT_NBO)
-		ret = htons(ret);
-	cb->val.port = ret;
-	if (entry->flags & XTOPT_PUT)
-		*(uint16_t *)XTOPT_MKPTR(cb) = cb->val.port;
-}
-
-static void xtopt_parse_mport(struct xt_option_call *cb)
-{
-	static const size_t esize = sizeof(uint16_t);
-	const struct xt_option_entry *entry = cb->entry;
-	char *lo_arg, *wp_arg, *arg;
-	unsigned int maxiter;
-	int value;
-
-	wp_arg = lo_arg = strdup(cb->arg);
-	if (lo_arg == NULL)
-		xt_params->exit_err(RESOURCE_PROBLEM, "strdup");
-
-	maxiter = entry->size / esize;
-	if (maxiter == 0)
-		maxiter = 2; /* ARRAY_SIZE(cb->val.port_range) */
-	if (entry->size % esize != 0)
-		xt_params->exit_err(OTHER_PROBLEM, "%s: memory block does "
-			"not have proper size\n", __func__);
-
-	cb->val.port_range[0] = 0;
-	cb->val.port_range[1] = UINT16_MAX;
-	cb->nvals = 0;
-
-	while ((arg = strsep(&wp_arg, ":")) != NULL) {
-		if (cb->nvals == maxiter)
-			xt_params->exit_err(PARAMETER_PROBLEM, "%s: Too many "
-				"components for option \"--%s\" (max: %u)\n",
-				cb->ext_name, entry->name, maxiter);
-		if (*arg == '\0') {
-			++cb->nvals;
-			continue;
-		}
-
-		value = xtables_getportbyname(arg);
-		if (value < 0)
-			xt_params->exit_err(PARAMETER_PROBLEM,
-				"Port \"%s\" does not resolve to "
-				"anything.\n", arg);
-		if (entry->flags & XTOPT_NBO)
-			value = htons(value);
-		if (cb->nvals < ARRAY_SIZE(cb->val.port_range))
-			cb->val.port_range[cb->nvals] = value;
-		++cb->nvals;
-	}
-
-	if (cb->nvals == 1) {
-		cb->val.port_range[1] = cb->val.port_range[0];
-		++cb->nvals;
-	}
-	if (entry->flags & XTOPT_PUT)
-		memcpy(XTOPT_MKPTR(cb), cb->val.port_range, sizeof(uint16_t) *
-		       (cb->nvals <= maxiter ? cb->nvals : maxiter));
-	free(lo_arg);
-}
-
-/**
- * Parse an integer and ensure it is within the address family's prefix length
- * limits. The result is stored in @cb->val.hlen.
- */
-static void xtopt_parse_plen(struct xt_option_call *cb)
-{
-	const struct xt_option_entry *entry = cb->entry;
-	unsigned int prefix_len = 128; /* happiness is a warm gcc */
-
-	cb->val.hlen = (afinfo->family == NFPROTO_IPV4) ? 32 : 128;
-	if (!xtables_strtoui(cb->arg, NULL, &prefix_len, 0, cb->val.hlen))
-		xt_params->exit_err(PARAMETER_PROBLEM,
-			"%s: bad value for option \"--%s\", "
-			"or out of range (%u-%u).\n",
-			cb->ext_name, entry->name, 0, cb->val.hlen);
-
-	cb->val.hlen = prefix_len;
-}
-
-/**
- * Reuse xtopt_parse_plen for testing the integer. Afterwards convert this to
- * a bitmask, and make it available through @cb->val.hmask (hlen remains
- * valid). If %XTOPT_PUT is used, hmask will be copied to the target area.
- */
-static void xtopt_parse_plenmask(struct xt_option_call *cb)
-{
-	const struct xt_option_entry *entry = cb->entry;
-	uint32_t *mask = cb->val.hmask.all;
-
-	xtopt_parse_plen(cb);
-
-	memset(mask, 0xFF, sizeof(union nf_inet_addr));
-	/* This shifting is AF-independent. */
-	if (cb->val.hlen == 0) {
-		mask[0] = mask[1] = mask[2] = mask[3] = 0;
-	} else if (cb->val.hlen <= 32) {
-		mask[0] <<= 32 - cb->val.hlen;
-		mask[1] = mask[2] = mask[3] = 0;
-	} else if (cb->val.hlen <= 64) {
-		mask[1] <<= 32 - (cb->val.hlen - 32);
-		mask[2] = mask[3] = 0;
-	} else if (cb->val.hlen <= 96) {
-		mask[2] <<= 32 - (cb->val.hlen - 64);
-		mask[3] = 0;
-	} else if (cb->val.hlen <= 128) {
-		mask[3] <<= 32 - (cb->val.hlen - 96);
-	}
-	mask[0] = htonl(mask[0]);
-	mask[1] = htonl(mask[1]);
-	mask[2] = htonl(mask[2]);
-	mask[3] = htonl(mask[3]);
-	if (entry->flags & XTOPT_PUT)
-		memcpy(XTOPT_MKPTR(cb), mask, sizeof(union nf_inet_addr));
-}
-
-static void xtopt_parse_hostmask(struct xt_option_call *cb)
-{
-	const char *orig_arg = cb->arg;
-	char *work, *p;
-
-	if (strchr(cb->arg, '/') == NULL) {
-		xtopt_parse_host(cb);
-		return;
-	}
-	work = strdup(orig_arg);
-	if (work == NULL)
-		xt_params->exit_err(PARAMETER_PROBLEM, "strdup");
-	p = strchr(work, '/'); /* by def this can't be NULL now */
-	*p++ = '\0';
-	/*
-	 * Because xtopt_parse_host and xtopt_parse_plenmask would store
-	 * different things in the same target area, XTTYPE_HOSTMASK must
-	 * disallow XTOPT_PUT, which it does by forcing its absence,
-	 * cf. not being listed in xtopt_psize.
-	 */
-	cb->arg = work;
-	xtopt_parse_host(cb);
-	cb->arg = p;
-	xtopt_parse_plenmask(cb);
-	cb->arg = orig_arg;
-}
-
-static void xtopt_parse_ethermac(struct xt_option_call *cb)
-{
-	const char *arg = cb->arg;
-	unsigned int i;
-	char *end;
-
-	for (i = 0; i < ARRAY_SIZE(cb->val.ethermac) - 1; ++i) {
-		cb->val.ethermac[i] = strtoul(arg, &end, 16);
-		if (cb->val.ethermac[i] > UINT8_MAX || *end != ':')
-			goto out;
-		arg = end + 1;
-	}
-	i = ARRAY_SIZE(cb->val.ethermac) - 1;
-	cb->val.ethermac[i] = strtoul(arg, &end, 16);
-	if (cb->val.ethermac[i] > UINT8_MAX || *end != '\0')
-		goto out;
-	if (cb->entry->flags & XTOPT_PUT)
-		memcpy(XTOPT_MKPTR(cb), cb->val.ethermac,
-		       sizeof(cb->val.ethermac));
-	return;
- out:
-	xt_params->exit_err(PARAMETER_PROBLEM, "ether");
-}
-
-static void (*const xtopt_subparse[])(struct xt_option_call *) = {
-	[XTTYPE_UINT8]       = xtopt_parse_int,
-	[XTTYPE_UINT16]      = xtopt_parse_int,
-	[XTTYPE_UINT32]      = xtopt_parse_int,
-	[XTTYPE_UINT64]      = xtopt_parse_int,
-	[XTTYPE_UINT8RC]     = xtopt_parse_mint,
-	[XTTYPE_UINT16RC]    = xtopt_parse_mint,
-	[XTTYPE_UINT32RC]    = xtopt_parse_mint,
-	[XTTYPE_UINT64RC]    = xtopt_parse_mint,
-	[XTTYPE_DOUBLE]      = xtopt_parse_float,
-	[XTTYPE_STRING]      = xtopt_parse_string,
-	[XTTYPE_TOSMASK]     = xtopt_parse_tosmask,
-	[XTTYPE_MARKMASK32]  = xtopt_parse_markmask,
-	[XTTYPE_SYSLOGLEVEL] = xtopt_parse_sysloglevel,
-	[XTTYPE_HOST]        = xtopt_parse_host,
-	[XTTYPE_HOSTMASK]    = xtopt_parse_hostmask,
-	[XTTYPE_PROTOCOL]    = xtopt_parse_protocol,
-	[XTTYPE_PORT]        = xtopt_parse_port,
-	[XTTYPE_PORTRC]      = xtopt_parse_mport,
-	[XTTYPE_PLEN]        = xtopt_parse_plen,
-	[XTTYPE_PLENMASK]    = xtopt_parse_plenmask,
-	[XTTYPE_ETHERMAC]    = xtopt_parse_ethermac,
-};
-
-/**
- * The master option parsing routine. May be used for the ".x6_parse"
- * function pointer in extensions if fully automatic parsing is desired.
- * It may be also called manually from a custom x6_parse function.
- */
-void xtables_option_parse(struct xt_option_call *cb)
-{
-	const struct xt_option_entry *entry = cb->entry;
-	unsigned int eflag = 1 << cb->entry->id;
-
-	/*
-	 * With {.id = P_FOO, .excl = P_FOO} we can have simple double-use
-	 * prevention. Though it turned out that this is too much typing (most
-	 * of the options are one-time use only), so now we also have
-	 * %XTOPT_MULTI.
-	 */
-	if ((!(entry->flags & XTOPT_MULTI) || (entry->excl & eflag)) &&
-	    cb->xflags & eflag)
-		xt_params->exit_err(PARAMETER_PROBLEM,
-			"%s: option \"--%s\" can only be used once.\n",
-			cb->ext_name, cb->entry->name);
-	if (cb->invert && !(entry->flags & XTOPT_INVERT))
-		xt_params->exit_err(PARAMETER_PROBLEM,
-			"%s: option \"--%s\" cannot be inverted.\n",
-			cb->ext_name, entry->name);
-	if (entry->type != XTTYPE_NONE && optarg == NULL)
-		xt_params->exit_err(PARAMETER_PROBLEM,
-			"%s: option \"--%s\" requires an argument.\n",
-			cb->ext_name, entry->name);
-	if (entry->type <= ARRAY_SIZE(xtopt_subparse) &&
-	    xtopt_subparse[entry->type] != NULL)
-		xtopt_subparse[entry->type](cb);
-	/* Exclusion with other flags tested later in finalize. */
-	cb->xflags |= 1 << entry->id;
-}
-
-/**
- * Verifies that an extension's option map descriptor is valid, and ought to
- * be called right after the extension has been loaded, and before option
- * merging/xfrm.
- */
-void xtables_option_metavalidate(const char *name,
-				 const struct xt_option_entry *entry)
-{
-	for (; entry->name != NULL; ++entry) {
-		if (entry->id >= CHAR_BIT * sizeof(unsigned int) ||
-		    entry->id >= XT_OPTION_OFFSET_SCALE)
-			xt_params->exit_err(OTHER_PROBLEM,
-				"Extension %s uses invalid ID %u\n",
-				name, entry->id);
-		if (!(entry->flags & XTOPT_PUT))
-			continue;
-		if (entry->type >= ARRAY_SIZE(xtopt_psize) ||
-		    xtopt_psize[entry->type] == 0)
-			xt_params->exit_err(OTHER_PROBLEM,
-				"%s: entry type of option \"--%s\" cannot be "
-				"combined with XTOPT_PUT\n",
-				name, entry->name);
-		if (xtopt_psize[entry->type] != -1 &&
-		    xtopt_psize[entry->type] != entry->size)
-			xt_params->exit_err(OTHER_PROBLEM,
-				"%s: option \"--%s\" points to a memory block "
-				"of wrong size (expected %zu, got %zu)\n",
-				name, entry->name,
-				xtopt_psize[entry->type], entry->size);
-	}
-}
-
-/**
- * Find an option entry by its id.
- */
-static const struct xt_option_entry *
-xtables_option_lookup(const struct xt_option_entry *entry, unsigned int id)
-{
-	for (; entry->name != NULL; ++entry)
-		if (entry->id == id)
-			return entry;
-	return NULL;
-}
-
-/**
- * @c:		getopt id (i.e. with offset)
- * @fw:		struct ipt_entry or ip6t_entry
- *
- * Dispatch arguments to the appropriate parse function, based upon the
- * extension's choice of API.
- */
-void xtables_option_tpcall(unsigned int c, char **argv, bool invert,
-			   struct xtables_target *t, void *fw)
-{
-	struct xt_option_call cb;
-
-	if (t->x6_parse == NULL) {
-		if (t->parse != NULL)
-			t->parse(c - t->option_offset, argv, invert,
-				 &t->tflags, fw, &t->t);
-		return;
-	}
-
-	c -= t->option_offset;
-	cb.entry = xtables_option_lookup(t->x6_options, c);
-	if (cb.entry == NULL)
-		xtables_error(OTHER_PROBLEM,
-			"Extension does not know id %u\n", c);
-	cb.arg      = optarg;
-	cb.invert   = invert;
-	cb.ext_name = t->name;
-	cb.data     = t->t->data;
-	cb.xflags   = t->tflags;
-	cb.target   = &t->t;
-	cb.xt_entry = fw;
-	t->x6_parse(&cb);
-	t->tflags = cb.xflags;
-}
-
-/**
- * @c:		getopt id (i.e. with offset)
- * @fw:		struct ipt_entry or ip6t_entry
- *
- * Dispatch arguments to the appropriate parse function, based upon the
- * extension's choice of API.
- */
-void xtables_option_mpcall(unsigned int c, char **argv, bool invert,
-			   struct xtables_match *m, void *fw)
-{
-	struct xt_option_call cb;
-
-	if (m->x6_parse == NULL) {
-		if (m->parse != NULL)
-			m->parse(c - m->option_offset, argv, invert,
-				 &m->mflags, fw, &m->m);
-		return;
-	}
-
-	c -= m->option_offset;
-	cb.entry = xtables_option_lookup(m->x6_options, c);
-	if (cb.entry == NULL)
-		xtables_error(OTHER_PROBLEM,
-			"Extension does not know id %u\n", c);
-	cb.arg      = optarg;
-	cb.invert   = invert;
-	cb.ext_name = m->name;
-	cb.data     = m->m->data;
-	cb.xflags   = m->mflags;
-	cb.match    = &m->m;
-	cb.xt_entry = fw;
-	m->x6_parse(&cb);
-	m->mflags = cb.xflags;
-}
-
-/**
- * @name:	name of extension
- * @entry:	current option (from all ext's entries) being validated
- * @xflags:	flags the extension has collected
- * @i:		conflicting option (id) to test for
- */
-static void
-xtables_option_fcheck2(const char *name, const struct xt_option_entry *entry,
-		       const struct xt_option_entry *other,
-		       unsigned int xflags)
-{
-	unsigned int ef = 1 << entry->id, of = 1 << other->id;
-
-	if (entry->also & of && !(xflags & of))
-		xt_params->exit_err(PARAMETER_PROBLEM,
-			"%s: option \"--%s\" also requires \"--%s\".\n",
-			name, entry->name, other->name);
-
-	if (!(entry->excl & of))
-		/* Use of entry does not collide with other option, good. */
-		return;
-	if ((xflags & (ef | of)) != (ef | of))
-		/* Conflicting options were not used. */
-		return;
-
-	xt_params->exit_err(PARAMETER_PROBLEM,
-		"%s: option \"--%s\" cannot be used together with \"--%s\".\n",
-		name, entry->name, other->name);
-}
-
-/**
- * @name:	name of extension
- * @xflags:	accumulated flags
- * @entry:	extension's option table
- *
- * Check that all option constraints have been met. This effectively replaces
- * ->final_check of the older API.
- */
-void xtables_options_fcheck(const char *name, unsigned int xflags,
-			    const struct xt_option_entry *table)
-{
-	const struct xt_option_entry *entry, *other;
-	unsigned int i;
-
-	for (entry = table; entry->name != NULL; ++entry) {
-		if (entry->flags & XTOPT_MAND &&
-		    !(xflags & (1 << entry->id)))
-			xt_params->exit_err(PARAMETER_PROBLEM,
-				"%s: option \"--%s\" must be specified\n",
-				name, entry->name);
-		if (!(xflags & (1 << entry->id)))
-			/* Not required, not specified, thus skip. */
-			continue;
-
-		for (i = 0; i < CHAR_BIT * sizeof(entry->id); ++i) {
-			if (entry->id == i)
-				/*
-				 * Avoid conflict with self. Multi-use check
-				 * was done earlier in xtables_option_parse.
-				 */
-				continue;
-			other = xtables_option_lookup(table, i);
-			if (other == NULL)
-				continue;
-			xtables_option_fcheck2(name, entry, other, xflags);
-		}
-	}
-}
-
-/**
- * Dispatch arguments to the appropriate final_check function, based upon the
- * extension's choice of API.
- */
-void xtables_option_tfcall(struct xtables_target *t)
-{
-	if (t->x6_fcheck != NULL) {
-		struct xt_fcheck_call cb;
-
-		cb.ext_name = t->name;
-		cb.data     = t->t->data;
-		cb.xflags   = t->tflags;
-		t->x6_fcheck(&cb);
-	} else if (t->final_check != NULL) {
-		t->final_check(t->tflags);
-	}
-	if (t->x6_options != NULL)
-		xtables_options_fcheck(t->name, t->tflags, t->x6_options);
-}
-
-/**
- * Dispatch arguments to the appropriate final_check function, based upon the
- * extension's choice of API.
- */
-void xtables_option_mfcall(struct xtables_match *m)
-{
-	if (m->x6_fcheck != NULL) {
-		struct xt_fcheck_call cb;
-
-		cb.ext_name = m->name;
-		cb.data     = m->m->data;
-		cb.xflags   = m->mflags;
-		m->x6_fcheck(&cb);
-	} else if (m->final_check != NULL) {
-		m->final_check(m->mflags);
-	}
-	if (m->x6_options != NULL)
-		xtables_options_fcheck(m->name, m->mflags, m->x6_options);
-}
-
-struct xtables_lmap *xtables_lmap_init(const char *file)
-{
-	struct xtables_lmap *lmap_head = NULL, *lmap_prev = NULL, *lmap_this;
-	char buf[512];
-	FILE *fp;
-	char *cur, *nxt;
-	int id;
-
-	fp = fopen(file, "re");
-	if (fp == NULL)
-		return NULL;
-
-	while (fgets(buf, sizeof(buf), fp) != NULL) {
-		cur = buf;
-		while (isspace(*cur))
-			++cur;
-		if (*cur == '#' || *cur == '\n' || *cur == '\0')
-			continue;
-
-		/* iproute2 allows hex and dec format */
-		errno = 0;
-		id = strtoul(cur, &nxt, strncmp(cur, "0x", 2) == 0 ? 16 : 10);
-		if (nxt == cur || errno != 0)
-			continue;
-
-		/* same boundaries as in iproute2 */
-		if (id < 0 || id > 255)
-			continue;
-		cur = nxt;
-
-		if (!isspace(*cur))
-			continue;
-		while (isspace(*cur))
-			++cur;
-		if (*cur == '#' || *cur == '\n' || *cur == '\0')
-			continue;
-		nxt = cur;
-		while (*nxt != '\0' && !isspace(*nxt))
-			++nxt;
-		if (nxt == cur)
-			continue;
-		*nxt = '\0';
-
-		/* found valid data */
-		lmap_this = malloc(sizeof(*lmap_this));
-		if (lmap_this == NULL) {
-			perror("malloc");
-			goto out;
-		}
-		lmap_this->id   = id;
-		lmap_this->name = strdup(cur);
-		if (lmap_this->name == NULL) {
-			free(lmap_this);
-			goto out;
-		}
-		lmap_this->next = NULL;
-
-		if (lmap_prev != NULL)
-			lmap_prev->next = lmap_this;
-		else
-			lmap_head = lmap_this;
-		lmap_prev = lmap_this;
-	}
-
-	fclose(fp);
-	return lmap_head;
- out:
-	xtables_lmap_free(lmap_head);
-	return NULL;
-}
-
-void xtables_lmap_free(struct xtables_lmap *head)
-{
-	struct xtables_lmap *next;
-
-	for (; head != NULL; head = next) {
-		next = head->next;
-		free(head->name);
-		free(head);
-	}
-}
-
-int xtables_lmap_name2id(const struct xtables_lmap *head, const char *name)
-{
-	for (; head != NULL; head = head->next)
-		if (strcmp(head->name, name) == 0)
-			return head->id;
-	return -1;
-}
-
-const char *xtables_lmap_id2name(const struct xtables_lmap *head, int id)
-{
-	for (; head != NULL; head = head->next)
-		if (head->id == id)
-			return head->name;
-	return NULL;
-}
diff --git a/libipq/.gitignore b/libipq/.gitignore
new file mode 100644
index 0000000..6cb21a3
--- /dev/null
+++ b/libipq/.gitignore
@@ -0,0 +1 @@
+/libipq.pc
diff --git a/libipq/Makefile.am b/libipq/Makefile.am
index 93e5b1c..9e3a2ca 100644
--- a/libipq/Makefile.am
+++ b/libipq/Makefile.am
@@ -9,3 +9,5 @@
                    ipq_get_msgerr.3 ipq_get_packet.3 ipq_message_type.3 \
                    ipq_perror.3 ipq_read.3 ipq_set_mode.3 ipq_set_verdict.3 \
                    libipq.3
+
+pkgconfig_DATA = libipq.pc
diff --git a/libipq/ipq_set_verdict.3 b/libipq/ipq_set_verdict.3
index 7771ed6..a6172b3 100644
--- a/libipq/ipq_set_verdict.3
+++ b/libipq/ipq_set_verdict.3
@@ -30,7 +30,7 @@
 .B ipq_set_verdict
 function issues a verdict on a packet previously obtained with
 .BR ipq_read ,
-specifing the intended disposition of the packet, and optionally
+specifying the intended disposition of the packet, and optionally
 supplying a modified version of the payload data.
 .PP
 The
diff --git a/libipq/libipq.c b/libipq/libipq.c
index e330487..fb65971 100644
--- a/libipq/libipq.c
+++ b/libipq/libipq.c
@@ -231,7 +231,6 @@
         
 	if (h->fd == -1) {
 		ipq_errno = IPQ_ERR_SOCKET;
-		close(h->fd);
 		free(h);
 		return NULL;
 	}
diff --git a/libipq/libipq.pc.in b/libipq/libipq.pc.in
new file mode 100644
index 0000000..ea31ec7
--- /dev/null
+++ b/libipq/libipq.pc.in
@@ -0,0 +1,11 @@
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name:		libipq
+Description:	Interface to the (old) ip_queue mechanism
+Version:	@PACKAGE_VERSION@
+Libs:		-L${libdir} -lipq
+Cflags:		-I${includedir}
diff --git a/libiptc/.gitignore b/libiptc/.gitignore
index 8767550..49ca83d 100644
--- a/libiptc/.gitignore
+++ b/libiptc/.gitignore
@@ -1 +1 @@
-/libiptc.pc
+/*.pc
diff --git a/libiptc/Android.bp b/libiptc/Android.bp
new file mode 100644
index 0000000..d7297a9
--- /dev/null
+++ b/libiptc/Android.bp
@@ -0,0 +1,31 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "external_iptables_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-GPL
+    default_applicable_licenses: ["external_iptables_license"],
+}
+
+cc_defaults {
+    name: "libiptc_defaults",
+    defaults: ["iptables_defaults"],
+
+    cflags: ["-Wno-pointer-sign"],
+}
+
+cc_library_static {
+    name: "libip4tc",
+    defaults: ["libiptc_defaults"],
+
+    srcs: ["libip4tc.c"],
+}
+
+cc_library_static {
+    name: "libip6tc",
+    defaults: ["libiptc_defaults"],
+
+    cflags: ["-Wno-unused-function"],
+
+    srcs: ["libip6tc.c"],
+}
diff --git a/libiptc/Android.mk b/libiptc/Android.mk
index 199672d..b3e8606 100644
--- a/libiptc/Android.mk
+++ b/libiptc/Android.mk
@@ -1,48 +1,34 @@
 LOCAL_PATH:= $(call my-dir)
 
 #----------------------------------------------------------------
-# libip4tc (static library)
+# libip4tc
 
 include $(CLEAR_VARS)
 
 LOCAL_C_INCLUDES:= \
-	$(KERNEL_HEADERS) \
 	$(LOCAL_PATH)/../include/
 LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/../include
 
 # Accommodate arm-eabi-4.4.3 tools that don't set __ANDROID__
 LOCAL_CFLAGS:=-D__ANDROID__
+LOCAL_CFLAGS+=-D_LARGEFILE_SOURCE=1 -D_LARGE_FILES -D_FILE_OFFSET_BITS=64 -D_REENTRANT -DENABLE_IPV4 -DENABLE_IPV6
+LOCAL_CFLAGS += \
+    -Wall -Werror \
+    -Wno-pointer-arith \
+    -Wno-pointer-sign \
+    -Wno-sign-compare \
+    -Wno-unused-parameter \
 
 LOCAL_SRC_FILES:= \
 	libip4tc.c \
 
 
+LOCAL_LDFLAGS:=-version-info 1:0:1
 LOCAL_MODULE_TAGS:=
 LOCAL_MODULE:=libip4tc
 
 include $(BUILD_STATIC_LIBRARY)
 
-#----------------------------------------------------------------
-# libip4tc (shared library)
-#
-include $(CLEAR_VARS)
-
-LOCAL_C_INCLUDES:= \
-	$(KERNEL_HEADERS) \
-	$(LOCAL_PATH)/../include/
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/../include
-
-# Accommodate arm-eabi-4.4.3 tools that don't set __ANDROID__
-LOCAL_CFLAGS:=-D__ANDROID__
-
-LOCAL_SRC_FILES:= \
-	libip4tc.c \
-
-
-LOCAL_MODULE_TAGS:=
-LOCAL_MODULE:=libip4tc
-
-include $(BUILD_SHARED_LIBRARY)
 
 #----------------------------------------------------------------
 # libip6tc
@@ -50,20 +36,28 @@
 include $(CLEAR_VARS)
 
 LOCAL_C_INCLUDES:= \
-	$(KERNEL_HEADERS) \
 	$(LOCAL_PATH)/../include/
 LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/../include
 
 # Accommodate arm-eabi-4.4.3 tools that don't set __ANDROID__
 LOCAL_CFLAGS:=-D__ANDROID__
+LOCAL_CFLAGS+=-D_LARGEFILE_SOURCE=1 -D_LARGE_FILES -D_FILE_OFFSET_BITS=64 -D_REENTRANT -DENABLE_IPV4 -DENABLE_IPV6
+LOCAL_CFLAGS += \
+    -Wall -Werror \
+    -Wno-pointer-arith \
+    -Wno-pointer-sign \
+    -Wno-sign-compare \
+    -Wno-unused-function \
+    -Wno-unused-parameter \
 
 LOCAL_SRC_FILES:= \
 	libip6tc.c \
 
 
+LOCAL_LDFLAGS:=-version-info 1:0:1
 LOCAL_MODULE_TAGS:=
 LOCAL_MODULE:=libip6tc
 
-include $(BUILD_SHARED_LIBRARY)
+include $(BUILD_STATIC_LIBRARY)
 
 #----------------------------------------------------------------
diff --git a/libiptc/Makefile.am b/libiptc/Makefile.am
index 22c920f..464a069 100644
--- a/libiptc/Makefile.am
+++ b/libiptc/Makefile.am
@@ -3,13 +3,10 @@
 AM_CFLAGS        = ${regular_CFLAGS}
 AM_CPPFLAGS      = ${regular_CPPFLAGS} -I${top_builddir}/include -I${top_srcdir}/include ${kinclude_CPPFLAGS}
 
-pkgconfig_DATA      = libiptc.pc
+pkgconfig_DATA      = libiptc.pc libip4tc.pc libip6tc.pc
 
-lib_LTLIBRARIES     = libip4tc.la libip6tc.la libiptc.la
-libiptc_la_SOURCES  =
-libiptc_la_LIBADD   = libip4tc.la libip6tc.la
-libiptc_la_LDFLAGS  = -version-info 0:0:0 ${libiptc_LDFLAGS2}
+lib_LTLIBRARIES     = libip4tc.la libip6tc.la
 libip4tc_la_SOURCES = libip4tc.c
-libip4tc_la_LDFLAGS = -version-info 0:0:0
+libip4tc_la_LDFLAGS = -version-info 2:0:0
 libip6tc_la_SOURCES = libip6tc.c
-libip6tc_la_LDFLAGS = -version-info 0:0:0 ${libiptc_LDFLAGS2}
+libip6tc_la_LDFLAGS = -version-info 2:0:0
diff --git a/libiptc/libip4tc.c b/libiptc/libip4tc.c
index e9e85c5..78a896f 100644
--- a/libiptc/libip4tc.c
+++ b/libiptc/libip4tc.c
@@ -22,7 +22,7 @@
 #define inline
 #endif
 
-#if !defined(__ANDROID__) && (!defined(__GLIBC__) || (__GLIBC__ < 2))
+#if !defined(__BIONIC__) && (!defined(__GLIBC__) || (__GLIBC__ < 2))
 typedef unsigned int socklen_t;
 #endif
 
@@ -36,33 +36,27 @@
 #define HOOK_FORWARD		NF_IP_FORWARD
 #define HOOK_LOCAL_OUT		NF_IP_LOCAL_OUT
 #define HOOK_POST_ROUTING	NF_IP_POST_ROUTING
-#ifdef NF_IP_DROPPING
-#define HOOK_DROPPING		NF_IP_DROPPING
-#endif
 
-#define STRUCT_ENTRY_TARGET	struct ipt_entry_target
+#define STRUCT_ENTRY_TARGET	struct xt_entry_target
 #define STRUCT_ENTRY		struct ipt_entry
-#define STRUCT_ENTRY_MATCH	struct ipt_entry_match
+#define STRUCT_ENTRY_MATCH	struct xt_entry_match
 #define STRUCT_GETINFO		struct ipt_getinfo
 #define STRUCT_GET_ENTRIES	struct ipt_get_entries
-#define STRUCT_COUNTERS		struct ipt_counters
-#define STRUCT_COUNTERS_INFO	struct ipt_counters_info
-#define STRUCT_STANDARD_TARGET	struct ipt_standard_target
+#define STRUCT_COUNTERS		struct xt_counters
+#define STRUCT_COUNTERS_INFO	struct xt_counters_info
+#define STRUCT_STANDARD_TARGET	struct xt_standard_target
 #define STRUCT_REPLACE		struct ipt_replace
 
-#define STRUCT_TC_HANDLE	struct iptc_handle
-#define xtc_handle		iptc_handle
-
 #define ENTRY_ITERATE		IPT_ENTRY_ITERATE
-#define TABLE_MAXNAMELEN	IPT_TABLE_MAXNAMELEN
-#define FUNCTION_MAXNAMELEN	IPT_FUNCTION_MAXNAMELEN
+#define TABLE_MAXNAMELEN	XT_TABLE_MAXNAMELEN
+#define FUNCTION_MAXNAMELEN	XT_FUNCTION_MAXNAMELEN
 
 #define GET_TARGET		ipt_get_target
 
-#define ERROR_TARGET		IPT_ERROR_TARGET
+#define ERROR_TARGET		XT_ERROR_TARGET
 #define NUMHOOKS		NF_IP_NUMHOOKS
 
-#define IPT_CHAINLABEL		ipt_chainlabel
+#define IPT_CHAINLABEL		xt_chainlabel
 
 #define TC_DUMP_ENTRIES		dump_entries
 #define TC_IS_CHAIN		iptc_is_chain
@@ -96,6 +90,7 @@
 #define TC_STRERROR		iptc_strerror
 #define TC_NUM_RULES		iptc_num_rules
 #define TC_GET_RULE		iptc_get_rule
+#define TC_OPS			iptc_ops
 
 #define TC_AF			AF_INET
 #define TC_IPPROTO		IPPROTO_IP
@@ -106,14 +101,14 @@
 #define SO_GET_ENTRIES		IPT_SO_GET_ENTRIES
 #define SO_GET_VERSION		IPT_SO_GET_VERSION
 
-#define STANDARD_TARGET		IPT_STANDARD_TARGET
+#define STANDARD_TARGET		XT_STANDARD_TARGET
 #define LABEL_RETURN		IPTC_LABEL_RETURN
 #define LABEL_ACCEPT		IPTC_LABEL_ACCEPT
 #define LABEL_DROP		IPTC_LABEL_DROP
 #define LABEL_QUEUE		IPTC_LABEL_QUEUE
 
 #define ALIGN			XT_ALIGN
-#define RETURN			IPT_RETURN
+#define RETURN			XT_RETURN
 
 #include "libiptc.c"
 
@@ -126,7 +121,7 @@
 #define IP_PARTS(n) IP_PARTS_NATIVE(ntohl(n))
 
 static int
-dump_entry(struct ipt_entry *e, struct iptc_handle *const handle)
+dump_entry(struct ipt_entry *e, struct xtc_handle *const handle)
 {
 	size_t i;
 	STRUCT_ENTRY_TARGET *t;
@@ -166,7 +161,7 @@
 			       : "UNKNOWN");
 		else
 			printf("verdict=%u\n", pos);
-	} else if (strcmp(t->u.user.name, IPT_ERROR_TARGET) == 0)
+	} else if (strcmp(t->u.user.name, XT_ERROR_TARGET) == 0)
 		printf("error=`%s'\n", t->data);
 
 	printf("\n");
@@ -209,7 +204,7 @@
 	mptr = matchmask + sizeof(STRUCT_ENTRY);
 	if (IPT_MATCH_ITERATE(a, match_different, a->elems, b->elems, &mptr))
 		return NULL;
-	mptr += XT_ALIGN(sizeof(struct ipt_entry_target));
+	mptr += XT_ALIGN(sizeof(struct xt_entry_target));
 
 	return mptr;
 }
@@ -241,7 +236,7 @@
 static inline int
 check_entry(const STRUCT_ENTRY *e, unsigned int *i, unsigned int *off,
 	    unsigned int user_offset, int *was_return,
-	    struct iptc_handle *h)
+	    struct xtc_handle *h)
 {
 	unsigned int toff;
 	STRUCT_STANDARD_TARGET *t;
@@ -277,14 +272,14 @@
 
 			idx = iptcb_entry2index(h, te);
 			assert(strcmp(GET_TARGET(te)->u.user.name,
-				      IPT_ERROR_TARGET)
+				      XT_ERROR_TARGET)
 			       != 0);
 			assert(te != e);
 
 			/* Prior node must be error node, or this node. */
 			assert(t->verdict == iptcb_entry2offset(h, e)+e->next_offset
 			       || strcmp(GET_TARGET(index2entry(h, idx-1))
-					 ->u.user.name, IPT_ERROR_TARGET)
+					 ->u.user.name, XT_ERROR_TARGET)
 			       == 0);
 		}
 
@@ -294,7 +289,7 @@
 			*was_return = 1;
 		else
 			*was_return = 0;
-	} else if (strcmp(t->target.u.user.name, IPT_ERROR_TARGET) == 0) {
+	} else if (strcmp(t->target.u.user.name, XT_ERROR_TARGET) == 0) {
 		assert(t->target.u.target_size
 		       == ALIGN(sizeof(struct ipt_error_target)));
 
@@ -307,193 +302,10 @@
 	else *was_return = 0;
 
 	if (*off == user_offset)
-		assert(strcmp(t->target.u.user.name, IPT_ERROR_TARGET) == 0);
+		assert(strcmp(t->target.u.user.name, XT_ERROR_TARGET) == 0);
 
 	(*off) += e->next_offset;
 	(*i)++;
 	return 0;
 }
-
-#ifdef IPTC_DEBUG
-/* Do every conceivable sanity check on the handle */
-static void
-do_check(struct iptc_handle *h, unsigned int line)
-{
-	unsigned int i, n;
-	unsigned int user_offset; /* Offset of first user chain */
-	int was_return;
-
-	assert(h->changed == 0 || h->changed == 1);
-	if (strcmp(h->info.name, "filter") == 0) {
-		assert(h->info.valid_hooks
-		       == (1 << NF_IP_LOCAL_IN
-			   | 1 << NF_IP_FORWARD
-			   | 1 << NF_IP_LOCAL_OUT));
-
-		/* Hooks should be first three */
-		assert(h->info.hook_entry[NF_IP_LOCAL_IN] == 0);
-
-		n = get_chain_end(h, 0);
-		n += get_entry(h, n)->next_offset;
-		assert(h->info.hook_entry[NF_IP_FORWARD] == n);
-
-		n = get_chain_end(h, n);
-		n += get_entry(h, n)->next_offset;
-		assert(h->info.hook_entry[NF_IP_LOCAL_OUT] == n);
-
-		user_offset = h->info.hook_entry[NF_IP_LOCAL_OUT];
-	} else if (strcmp(h->info.name, "nat") == 0) {
-		assert((h->info.valid_hooks
-		        == (1 << NF_IP_PRE_ROUTING
-			    | 1 << NF_IP_POST_ROUTING
-			    | 1 << NF_IP_LOCAL_OUT)) ||
-		       (h->info.valid_hooks
-			== (1 << NF_IP_PRE_ROUTING
-			    | 1 << NF_IP_LOCAL_IN
-			    | 1 << NF_IP_POST_ROUTING
-			    | 1 << NF_IP_LOCAL_OUT)));
-
-		assert(h->info.hook_entry[NF_IP_PRE_ROUTING] == 0);
-
-		n = get_chain_end(h, 0);
-
-		n += get_entry(h, n)->next_offset;
-		assert(h->info.hook_entry[NF_IP_POST_ROUTING] == n);
-		n = get_chain_end(h, n);
-
-		n += get_entry(h, n)->next_offset;
-		assert(h->info.hook_entry[NF_IP_LOCAL_OUT] == n);
-		user_offset = h->info.hook_entry[NF_IP_LOCAL_OUT];
-
-		if (h->info.valid_hooks & (1 << NF_IP_LOCAL_IN)) {
-			n = get_chain_end(h, n);
-			n += get_entry(h, n)->next_offset;
-			assert(h->info.hook_entry[NF_IP_LOCAL_IN] == n);
-			user_offset = h->info.hook_entry[NF_IP_LOCAL_IN];
-		}
-
-	} else if (strcmp(h->info.name, "mangle") == 0) {
-		/* This code is getting ugly because linux < 2.4.18-pre6 had
-		 * two mangle hooks, linux >= 2.4.18-pre6 has five mangle hooks
-		 * */
-		assert((h->info.valid_hooks
-			== (1 << NF_IP_PRE_ROUTING
-			    | 1 << NF_IP_LOCAL_OUT)) || 
-		       (h->info.valid_hooks
-			== (1 << NF_IP_PRE_ROUTING
-			    | 1 << NF_IP_LOCAL_IN
-			    | 1 << NF_IP_FORWARD
-			    | 1 << NF_IP_LOCAL_OUT
-			    | 1 << NF_IP_POST_ROUTING)));
-
-		/* Hooks should be first five */
-		assert(h->info.hook_entry[NF_IP_PRE_ROUTING] == 0);
-
-		n = get_chain_end(h, 0);
-
-		if (h->info.valid_hooks & (1 << NF_IP_LOCAL_IN)) {
-			n += get_entry(h, n)->next_offset;
-			assert(h->info.hook_entry[NF_IP_LOCAL_IN] == n);
-			n = get_chain_end(h, n);
-		}
-
-		if (h->info.valid_hooks & (1 << NF_IP_FORWARD)) {
-			n += get_entry(h, n)->next_offset;
-			assert(h->info.hook_entry[NF_IP_FORWARD] == n);
-			n = get_chain_end(h, n);
-		}
-
-		n += get_entry(h, n)->next_offset;
-		assert(h->info.hook_entry[NF_IP_LOCAL_OUT] == n);
-		user_offset = h->info.hook_entry[NF_IP_LOCAL_OUT];
-
-		if (h->info.valid_hooks & (1 << NF_IP_POST_ROUTING)) {
-			n = get_chain_end(h, n);
-			n += get_entry(h, n)->next_offset;
-			assert(h->info.hook_entry[NF_IP_POST_ROUTING] == n);
-			user_offset = h->info.hook_entry[NF_IP_POST_ROUTING];
-		}
-	} else if (strcmp(h->info.name, "raw") == 0) {
-		assert(h->info.valid_hooks
-		       == (1 << NF_IP_PRE_ROUTING
-			   | 1 << NF_IP_LOCAL_OUT));
-
-		/* Hooks should be first three */
-		assert(h->info.hook_entry[NF_IP_PRE_ROUTING] == 0);
-
-		n = get_chain_end(h, n);
-		n += get_entry(h, n)->next_offset;
-		assert(h->info.hook_entry[NF_IP_LOCAL_OUT] == n);
-
-		user_offset = h->info.hook_entry[NF_IP_LOCAL_OUT];
-
-#ifdef NF_IP_DROPPING
-	} else if (strcmp(h->info.name, "drop") == 0) {
-		assert(h->info.valid_hooks == (1 << NF_IP_DROPPING));
-
-		/* Hook should be first */
-		assert(h->info.hook_entry[NF_IP_DROPPING] == 0);
-		user_offset = 0;
-#endif
-	} else {
-		fprintf(stderr, "Unknown table `%s'\n", h->info.name);
-		abort();
-	}
-
-	/* User chain == end of last builtin + policy entry */
-	user_offset = get_chain_end(h, user_offset);
-	user_offset += get_entry(h, user_offset)->next_offset;
-
-	/* Overflows should be end of entry chains, and unconditional
-           policy nodes. */
-	for (i = 0; i < NUMHOOKS; i++) {
-		STRUCT_ENTRY *e;
-		STRUCT_STANDARD_TARGET *t;
-
-		if (!(h->info.valid_hooks & (1 << i)))
-			continue;
-		assert(h->info.underflow[i]
-		       == get_chain_end(h, h->info.hook_entry[i]));
-
-		e = get_entry(h, get_chain_end(h, h->info.hook_entry[i]));
-		assert(unconditional(&e->ip));
-		assert(e->target_offset == sizeof(*e));
-		t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e);
-		assert(t->target.u.target_size == ALIGN(sizeof(*t)));
-		assert(e->next_offset == sizeof(*e) + ALIGN(sizeof(*t)));
-
-		assert(strcmp(t->target.u.user.name, STANDARD_TARGET)==0);
-		assert(t->verdict == -NF_DROP-1 || t->verdict == -NF_ACCEPT-1);
-
-		/* Hooks and underflows must be valid entries */
-		entry2index(h, get_entry(h, h->info.hook_entry[i]));
-		entry2index(h, get_entry(h, h->info.underflow[i]));
-	}
-
-	assert(h->info.size
-	       >= h->info.num_entries * (sizeof(STRUCT_ENTRY)
-					 +sizeof(STRUCT_STANDARD_TARGET)));
-
-	assert(h->entries.size
-	       >= (h->new_number
-		   * (sizeof(STRUCT_ENTRY)
-		      + sizeof(STRUCT_STANDARD_TARGET))));
-	assert(strcmp(h->info.name, h->entries.name) == 0);
-
-	i = 0; n = 0;
-	was_return = 0;
-	/* Check all the entries. */
-	ENTRY_ITERATE(h->entries.entrytable, h->entries.size,
-		      check_entry, &i, &n, user_offset, &was_return, h);
-
-	assert(i == h->new_number);
-	assert(n == h->entries.size);
-
-	/* Final entry must be error node */
-	assert(strcmp(GET_TARGET(index2entry(h, h->new_number-1))
-		      ->u.user.name,
-		      ERROR_TARGET) == 0);
-}
-#endif /*IPTC_DEBUG*/
-
 #endif
diff --git a/libiptc/libip4tc.pc.in b/libiptc/libip4tc.pc.in
new file mode 100644
index 0000000..5efa1ca
--- /dev/null
+++ b/libiptc/libip4tc.pc.in
@@ -0,0 +1,10 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name:		libip4tc
+Description:	iptables IPv4 ruleset ADT and kernel interface
+Version:	@PACKAGE_VERSION@
+Libs:		-L${libdir} -lip4tc
+Cflags:		-I${includedir}
diff --git a/libiptc/libip6tc.c b/libiptc/libip6tc.c
index a97f397..06cd623 100644
--- a/libiptc/libip6tc.c
+++ b/libiptc/libip6tc.c
@@ -23,7 +23,7 @@
 #define inline
 #endif
 
-#if !defined(__ANDROID__) && (!defined(__GLIBC__) || (__GLIBC__ < 2))
+#if !defined(__BIONIC__) && (!defined(__GLIBC__) || (__GLIBC__ < 2))
 typedef unsigned int socklen_t;
 #endif
 
@@ -35,29 +35,26 @@
 #define HOOK_LOCAL_OUT		NF_IP6_LOCAL_OUT
 #define HOOK_POST_ROUTING	NF_IP6_POST_ROUTING
 
-#define STRUCT_ENTRY_TARGET	struct ip6t_entry_target
+#define STRUCT_ENTRY_TARGET	struct xt_entry_target
 #define STRUCT_ENTRY		struct ip6t_entry
-#define STRUCT_ENTRY_MATCH	struct ip6t_entry_match
+#define STRUCT_ENTRY_MATCH	struct xt_entry_match
 #define STRUCT_GETINFO		struct ip6t_getinfo
 #define STRUCT_GET_ENTRIES	struct ip6t_get_entries
-#define STRUCT_COUNTERS		struct ip6t_counters
-#define STRUCT_COUNTERS_INFO	struct ip6t_counters_info
-#define STRUCT_STANDARD_TARGET	struct ip6t_standard_target
+#define STRUCT_COUNTERS		struct xt_counters
+#define STRUCT_COUNTERS_INFO	struct xt_counters_info
+#define STRUCT_STANDARD_TARGET	struct xt_standard_target
 #define STRUCT_REPLACE		struct ip6t_replace
 
-#define STRUCT_TC_HANDLE	struct ip6tc_handle
-#define xtc_handle		ip6tc_handle
-
 #define ENTRY_ITERATE		IP6T_ENTRY_ITERATE
-#define TABLE_MAXNAMELEN	IP6T_TABLE_MAXNAMELEN
-#define FUNCTION_MAXNAMELEN	IP6T_FUNCTION_MAXNAMELEN
+#define TABLE_MAXNAMELEN	XT_TABLE_MAXNAMELEN
+#define FUNCTION_MAXNAMELEN	XT_FUNCTION_MAXNAMELEN
 
 #define GET_TARGET		ip6t_get_target
 
-#define ERROR_TARGET		IP6T_ERROR_TARGET
+#define ERROR_TARGET		XT_ERROR_TARGET
 #define NUMHOOKS		NF_IP6_NUMHOOKS
 
-#define IPT_CHAINLABEL		ip6t_chainlabel
+#define IPT_CHAINLABEL		xt_chainlabel
 
 #define TC_DUMP_ENTRIES		dump_entries6
 #define TC_IS_CHAIN		ip6tc_is_chain
@@ -91,6 +88,7 @@
 #define TC_STRERROR		ip6tc_strerror
 #define TC_NUM_RULES		ip6tc_num_rules
 #define TC_GET_RULE		ip6tc_get_rule
+#define TC_OPS			ip6tc_ops
 
 #define TC_AF			AF_INET6
 #define TC_IPPROTO		IPPROTO_IPV6
@@ -101,21 +99,21 @@
 #define SO_GET_ENTRIES		IP6T_SO_GET_ENTRIES
 #define SO_GET_VERSION		IP6T_SO_GET_VERSION
 
-#define STANDARD_TARGET		IP6T_STANDARD_TARGET
+#define STANDARD_TARGET		XT_STANDARD_TARGET
 #define LABEL_RETURN		IP6TC_LABEL_RETURN
 #define LABEL_ACCEPT		IP6TC_LABEL_ACCEPT
 #define LABEL_DROP		IP6TC_LABEL_DROP
 #define LABEL_QUEUE		IP6TC_LABEL_QUEUE
 
 #define ALIGN			XT_ALIGN
-#define RETURN			IP6T_RETURN
+#define RETURN			XT_RETURN
 
 #include "libiptc.c"
 
 #define BIT6(a, l) \
  ((ntohl(a->s6_addr32[(l) / 32]) >> (31 - ((l) & 31))) & 1)
 
-int
+static int
 ipv6_prefix_length(const struct in6_addr *a)
 {
 	int l, i;
@@ -131,12 +129,12 @@
 }
 
 static int
-dump_entry(struct ip6t_entry *e, struct ip6tc_handle *const handle)
+dump_entry(struct ip6t_entry *e, struct xtc_handle *const handle)
 {
 	size_t i;
 	char buf[40];
 	int len;
-	struct ip6t_entry_target *t;
+	struct xt_entry_target *t;
 	
 	printf("Entry %u (%lu):\n", iptcb_entry2index(handle, e),
 	       iptcb_entry2offset(handle, e));
@@ -185,18 +183,18 @@
 
 	t = ip6t_get_target(e);
 	printf("Target name: `%s' [%u]\n", t->u.user.name, t->u.target_size);
-	if (strcmp(t->u.user.name, IP6T_STANDARD_TARGET) == 0) {
+	if (strcmp(t->u.user.name, XT_STANDARD_TARGET) == 0) {
 		const unsigned char *data = t->data;
 		int pos = *(const int *)data;
 		if (pos < 0)
 			printf("verdict=%s\n",
 			       pos == -NF_ACCEPT-1 ? "NF_ACCEPT"
 			       : pos == -NF_DROP-1 ? "NF_DROP"
-			       : pos == IP6T_RETURN ? "RETURN"
+			       : pos == XT_RETURN ? "RETURN"
 			       : "UNKNOWN");
 		else
 			printf("verdict=%u\n", pos);
-	} else if (strcmp(t->u.user.name, IP6T_ERROR_TARGET) == 0)
+	} else if (strcmp(t->u.user.name, XT_ERROR_TARGET) == 0)
 		printf("error=`%s'\n", t->data);
 
 	printf("\n");
@@ -241,11 +239,12 @@
 	mptr = matchmask + sizeof(STRUCT_ENTRY);
 	if (IP6T_MATCH_ITERATE(a, match_different, a->elems, b->elems, &mptr))
 		return NULL;
-	mptr += XT_ALIGN(sizeof(struct ip6t_entry_target));
+	mptr += XT_ALIGN(sizeof(struct xt_entry_target));
 
 	return mptr;
 }
 
+#if 0
 /* All zeroes == unconditional rule. */
 static inline int
 unconditional(const struct ip6t_ip6 *ipv6)
@@ -258,181 +257,4 @@
 
 	return (i == sizeof(*ipv6));
 }
-
-#ifdef IPTC_DEBUG
-/* Do every conceivable sanity check on the handle */
-static void
-do_check(struct xtc_handle *h, unsigned int line)
-{
-	unsigned int i, n;
-	unsigned int user_offset; /* Offset of first user chain */
-	int was_return;
-
-	assert(h->changed == 0 || h->changed == 1);
-	if (strcmp(h->info.name, "filter") == 0) {
-		assert(h->info.valid_hooks
-		       == (1 << NF_IP6_LOCAL_IN
-			   | 1 << NF_IP6_FORWARD
-			   | 1 << NF_IP6_LOCAL_OUT));
-
-		/* Hooks should be first three */
-		assert(h->info.hook_entry[NF_IP6_LOCAL_IN] == 0);
-
-		n = get_chain_end(h, 0);
-		n += get_entry(h, n)->next_offset;
-		assert(h->info.hook_entry[NF_IP6_FORWARD] == n);
-
-		n = get_chain_end(h, n);
-		n += get_entry(h, n)->next_offset;
-		assert(h->info.hook_entry[NF_IP6_LOCAL_OUT] == n);
-
-		user_offset = h->info.hook_entry[NF_IP6_LOCAL_OUT];
-	} else if (strcmp(h->info.name, "nat") == 0) {
-		assert((h->info.valid_hooks
-			== (1 << NF_IP6_PRE_ROUTING
-			    | 1 << NF_IP6_LOCAL_OUT
-			    | 1 << NF_IP6_POST_ROUTING)) ||
-		       (h->info.valid_hooks
-			== (1 << NF_IP6_PRE_ROUTING
-			    | 1 << NF_IP6_LOCAL_IN
-			    | 1 << NF_IP6_LOCAL_OUT
-			    | 1 << NF_IP6_POST_ROUTING)));
-
-		assert(h->info.hook_entry[NF_IP6_PRE_ROUTING] == 0);
-
-		n = get_chain_end(h, 0);
-
-		n += get_entry(h, n)->next_offset;
-		assert(h->info.hook_entry[NF_IP6_POST_ROUTING] == n);
-		n = get_chain_end(h, n);
-
-		n += get_entry(h, n)->next_offset;
-		assert(h->info.hook_entry[NF_IP6_LOCAL_OUT] == n);
-		user_offset = h->info.hook_entry[NF_IP6_LOCAL_OUT];
-
-		if (h->info.valid_hooks & (1 << NF_IP6_LOCAL_IN)) {
-			n = get_chain_end(h, n);
-			n += get_entry(h, n)->next_offset;
-			assert(h->info.hook_entry[NF_IP6_LOCAL_IN] == n);
-			user_offset = h->info.hook_entry[NF_IP6_LOCAL_IN];
-		}
-
-	} else if (strcmp(h->info.name, "mangle") == 0) {
-		/* This code is getting ugly because linux < 2.4.18-pre6 had
-		 * two mangle hooks, linux >= 2.4.18-pre6 has five mangle hooks
-		 * */
-		assert((h->info.valid_hooks
-			== (1 << NF_IP6_PRE_ROUTING
-			    | 1 << NF_IP6_LOCAL_OUT)) ||
-		       (h->info.valid_hooks
-			== (1 << NF_IP6_PRE_ROUTING
-			    | 1 << NF_IP6_LOCAL_IN
-			    | 1 << NF_IP6_FORWARD
-			    | 1 << NF_IP6_LOCAL_OUT
-			    | 1 << NF_IP6_POST_ROUTING)));
-
-		/* Hooks should be first five */
-		assert(h->info.hook_entry[NF_IP6_PRE_ROUTING] == 0);
-
-		n = get_chain_end(h, 0);
-
-		if (h->info.valid_hooks & (1 << NF_IP6_LOCAL_IN)) {
-			n += get_entry(h, n)->next_offset;
-			assert(h->info.hook_entry[NF_IP6_LOCAL_IN] == n);
-			n = get_chain_end(h, n);
-		}
-
-		if (h->info.valid_hooks & (1 << NF_IP6_FORWARD)) {
-			n += get_entry(h, n)->next_offset;
-			assert(h->info.hook_entry[NF_IP6_FORWARD] == n);
-			n = get_chain_end(h, n);
-		}
-
-		n += get_entry(h, n)->next_offset;
-		assert(h->info.hook_entry[NF_IP6_LOCAL_OUT] == n);
-		user_offset = h->info.hook_entry[NF_IP6_LOCAL_OUT];
-
-		if (h->info.valid_hooks & (1 << NF_IP6_POST_ROUTING)) {
-			n = get_chain_end(h, n);
-			n += get_entry(h, n)->next_offset;
-			assert(h->info.hook_entry[NF_IP6_POST_ROUTING] == n);
-			user_offset = h->info.hook_entry[NF_IP6_POST_ROUTING];
-		}
-	} else if (strcmp(h->info.name, "raw") == 0) {
-		assert(h->info.valid_hooks
-		       == (1 << NF_IP6_PRE_ROUTING
-			   | 1 << NF_IP6_LOCAL_OUT));
-
-		/* Hooks should be first three */
-		assert(h->info.hook_entry[NF_IP6_PRE_ROUTING] == 0);
-
-		n = get_chain_end(h, n);
-		n += get_entry(h, n)->next_offset;
-		assert(h->info.hook_entry[NF_IP6_LOCAL_OUT] == n);
-
-		user_offset = h->info.hook_entry[NF_IP6_LOCAL_OUT];
-	} else {
-                fprintf(stderr, "Unknown table `%s'\n", h->info.name);
-		abort();
-	}
-
-	/* User chain == end of last builtin + policy entry */
-	user_offset = get_chain_end(h, user_offset);
-	user_offset += get_entry(h, user_offset)->next_offset;
-
-	/* Overflows should be end of entry chains, and unconditional
-           policy nodes. */
-	for (i = 0; i < NUMHOOKS; i++) {
-		STRUCT_ENTRY *e;
-		STRUCT_STANDARD_TARGET *t;
-
-		if (!(h->info.valid_hooks & (1 << i)))
-			continue;
-		assert(h->info.underflow[i]
-		       == get_chain_end(h, h->info.hook_entry[i]));
-
-		e = get_entry(h, get_chain_end(h, h->info.hook_entry[i]));
-		assert(unconditional(&e->ipv6));
-		assert(e->target_offset == sizeof(*e));
-		t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e);
-		printf("target_size=%u, align=%u\n",
-			t->target.u.target_size, ALIGN(sizeof(*t)));
-		assert(t->target.u.target_size == ALIGN(sizeof(*t)));
-		assert(e->next_offset == sizeof(*e) + ALIGN(sizeof(*t)));
-
-		assert(strcmp(t->target.u.user.name, STANDARD_TARGET)==0);
-		assert(t->verdict == -NF_DROP-1 || t->verdict == -NF_ACCEPT-1);
-
-		/* Hooks and underflows must be valid entries */
-		iptcb_entry2index(h, get_entry(h, h->info.hook_entry[i]));
-		iptcb_entry2index(h, get_entry(h, h->info.underflow[i]));
-	}
-
-	assert(h->info.size
-	       >= h->info.num_entries * (sizeof(STRUCT_ENTRY)
-					 +sizeof(STRUCT_STANDARD_TARGET)));
-
-	assert(h->entries.size
-	       >= (h->new_number
-		   * (sizeof(STRUCT_ENTRY)
-		      + sizeof(STRUCT_STANDARD_TARGET))));
-	assert(strcmp(h->info.name, h->entries.name) == 0);
-
-	i = 0; n = 0;
-	was_return = 0;
-
-#if 0
-	/* Check all the entries. */
-	ENTRY_ITERATE(h->entries.entrytable, h->entries.size,
-		      check_entry, &i, &n, user_offset, &was_return, h);
-
-	assert(i == h->new_number);
-	assert(n == h->entries.size);
-
-	/* Final entry must be error node */
-	assert(strcmp(GET_TARGET(index2entry(h, h->new_number-1))
-		      ->u.user.name,
-		      ERROR_TARGET) == 0);
 #endif
-}
-#endif /*IPTC_DEBUG*/
diff --git a/libiptc/libip6tc.pc.in b/libiptc/libip6tc.pc.in
new file mode 100644
index 0000000..30a61b2
--- /dev/null
+++ b/libiptc/libip6tc.pc.in
@@ -0,0 +1,10 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name:		libip6tc
+Description:	iptables IPv6 ruleset ADT and kernel interface
+Version:	@PACKAGE_VERSION@
+Libs:		-L${libdir} -lip6tc
+Cflags:		-I${includedir}
diff --git a/libiptc/libiptc.c b/libiptc/libiptc.c
index 0b6d5e3..ceeb017 100644
--- a/libiptc/libiptc.c
+++ b/libiptc/libiptc.c
@@ -29,10 +29,13 @@
  * 	- performance work: speedup initial ruleset parsing.
  * 	- sponsored by ComX Networks A/S (http://www.comx.dk/)
  */
+#include <unistd.h>
+#include <fcntl.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <stdbool.h>
 #include <xtables.h>
+#include <libiptc/xtcshared.h>
 
 #include "linux_list.h"
 
@@ -61,18 +64,9 @@
 	[HOOK_FORWARD]		= "FORWARD",
 	[HOOK_LOCAL_OUT]	= "OUTPUT",
 	[HOOK_POST_ROUTING]	= "POSTROUTING",
-#ifdef HOOK_DROPPING
-	[HOOK_DROPPING]		= "DROPPING"
-#endif
 };
 
 /* Convenience structures */
-struct ipt_error_target
-{
-	STRUCT_ENTRY_TARGET t;
-	char error[TABLE_MAXNAMELEN];
-};
-
 struct chain_head;
 struct rule_head;
 
@@ -130,8 +124,7 @@
 	unsigned int foot_offset;	/* offset in rule blob */
 };
 
-STRUCT_TC_HANDLE
-{
+struct xtc_handle {
 	int sockfd;
 	int changed;			 /* Have changes been made? */
 
@@ -167,7 +160,7 @@
 		return NULL;
 	memset(c, 0, sizeof(*c));
 
-	strncpy(c->name, name, TABLE_MAXNAMELEN);
+	strncpy(c->name, name, TABLE_MAXNAMELEN - 1);
 	c->hooknum = hooknum;
 	INIT_LIST_HEAD(&c->rules);
 
@@ -195,14 +188,6 @@
 	h->changed = 1;
 }
 
-#ifdef IPTC_DEBUG
-static void do_check(struct xtc_handle *h, unsigned int line);
-#define CHECK(h) do { if (!getenv("IPTC_NO_CHECK")) do_check((h), __LINE__); } while(0)
-#else
-#define CHECK(h)
-#endif
-
-
 /**********************************************************************
  * iptc blob utility functions (iptcb_*)
  **********************************************************************/
@@ -403,7 +388,7 @@
 		}
 		debug("jump back to pos:%d (end:%d)\n", pos, end);
 		goto loop;
-	} else if (res > 0 ){ /* Not far enough, jump forward */
+	} else { /* res > 0; Not far enough, jump forward */
 
 		/* Exit case: Last element of array */
 		if (pos == handle->chain_index_sz-1) {
@@ -430,8 +415,6 @@
 		debug("jump forward to pos:%d (end:%d)\n", pos, end);
 		goto loop;
 	}
-
-	return list_pos;
 }
 
 /* Wrapper for string chain name based bsearch */
@@ -601,7 +584,7 @@
  * There are different strategies, the simple and safe is to rebuild
  * the chain index every time.  The more advanced is to update the
  * array index to point to the next element, but that requires some
- * house keeping and boundry checks.  The advanced is implemented, as
+ * house keeping and boundary checks.  The advanced is implemented, as
  * the simple approach behaves badly when all chains are deleted
  * because list_for_each processing will always hit the first chain
  * index, thus causing a rebuild for every chain.
@@ -1014,6 +997,7 @@
 			if (t->target.u.target_size
 			    != ALIGN(sizeof(STRUCT_STANDARD_TARGET))) {
 				errno = EINVAL;
+				free(r);
 				return -1;
 			}
 
@@ -1094,10 +1078,10 @@
 /* Convenience structures */
 struct iptcb_chain_start{
 	STRUCT_ENTRY e;
-	struct ipt_error_target name;
+	struct xt_error_target name;
 };
 #define IPTCB_CHAIN_START_SIZE	(sizeof(STRUCT_ENTRY) +			\
-				 ALIGN(sizeof(struct ipt_error_target)))
+				 ALIGN(sizeof(struct xt_error_target)))
 
 struct iptcb_chain_foot {
 	STRUCT_ENTRY e;
@@ -1108,10 +1092,10 @@
 
 struct iptcb_chain_error {
 	STRUCT_ENTRY entry;
-	struct ipt_error_target target;
+	struct xt_error_target target;
 };
 #define IPTCB_CHAIN_ERROR_SIZE	(sizeof(STRUCT_ENTRY) +			\
-				 ALIGN(sizeof(struct ipt_error_target)))
+				 ALIGN(sizeof(struct xt_error_target)))
 
 
 
@@ -1123,8 +1107,9 @@
 		STRUCT_STANDARD_TARGET *t;
 		t = (STRUCT_STANDARD_TARGET *)GET_TARGET(r->entry);
 		/* memset for memcmp convenience on delete/replace */
-		memset(t->target.u.user.name, 0, FUNCTION_MAXNAMELEN);
+		memset(t->target.u.user.name, 0, XT_EXTENSION_MAXNAMELEN);
 		strcpy(t->target.u.user.name, STANDARD_TARGET);
+		t->target.u.user.revision = 0;
 		/* Jumps can only happen to builtin chains, so we
 		 * can safely assume that they always have a header */
 		t->verdict = r->jump->head_offset + IPTCB_CHAIN_START_SIZE;
@@ -1154,10 +1139,11 @@
 		head = (void *)repl->entries + c->head_offset;
 		head->e.target_offset = sizeof(STRUCT_ENTRY);
 		head->e.next_offset = IPTCB_CHAIN_START_SIZE;
-		strcpy(head->name.t.u.user.name, ERROR_TARGET);
-		head->name.t.u.target_size =
-				ALIGN(sizeof(struct ipt_error_target));
-		strcpy(head->name.error, c->name);
+		strcpy(head->name.target.u.user.name, ERROR_TARGET);
+		head->name.target.u.target_size =
+				ALIGN(sizeof(struct xt_error_target));
+		strncpy(head->name.errorname, c->name, XT_FUNCTION_MAXNAMELEN);
+		head->name.errorname[XT_FUNCTION_MAXNAMELEN - 1] = '\0';
 	} else {
 		repl->hook_entry[c->hooknum-1] = c->head_offset;
 		repl->underflow[c->hooknum-1] = c->foot_offset;
@@ -1183,7 +1169,7 @@
 	else
 		foot->target.verdict = RETURN;
 	/* set policy-counters */
-	memcpy(&foot->e.counters, &c->counters, sizeof(STRUCT_COUNTERS));
+	foot->e.counters = c->counters;
 
 	return 0;
 }
@@ -1200,7 +1186,7 @@
 	if (!iptcc_is_builtin(c))  {
 		/* Chain has header */
 		*offset += sizeof(STRUCT_ENTRY)
-			     + ALIGN(sizeof(struct ipt_error_target));
+			     + ALIGN(sizeof(struct xt_error_target));
 		(*num)++;
 	}
 
@@ -1240,7 +1226,7 @@
 	/* Append one error rule at end of chain */
 	num++;
 	offset += sizeof(STRUCT_ENTRY)
-		  + ALIGN(sizeof(struct ipt_error_target));
+		  + ALIGN(sizeof(struct xt_error_target));
 
 	/* ruleset size is now in offset */
 	*size = offset;
@@ -1263,10 +1249,10 @@
 	error = (void *)repl->entries + repl->size - IPTCB_CHAIN_ERROR_SIZE;
 	error->entry.target_offset = sizeof(STRUCT_ENTRY);
 	error->entry.next_offset = IPTCB_CHAIN_ERROR_SIZE;
-	error->target.t.u.user.target_size =
-		ALIGN(sizeof(struct ipt_error_target));
-	strcpy((char *)&error->target.t.u.user.name, ERROR_TARGET);
-	strcpy((char *)&error->target.error, "ERROR");
+	error->target.target.u.user.target_size =
+		ALIGN(sizeof(struct xt_error_target));
+	strcpy((char *)&error->target.target.u.user.name, ERROR_TARGET);
+	strcpy((char *)&error->target.errorname, "ERROR");
 
 	return 1;
 }
@@ -1277,25 +1263,25 @@
 
 /* Allocate handle of given size */
 static struct xtc_handle *
-alloc_handle(const char *tablename, unsigned int size, unsigned int num_rules)
+alloc_handle(STRUCT_GETINFO *infop)
 {
 	struct xtc_handle *h;
 
-	h = malloc(sizeof(STRUCT_TC_HANDLE));
+	h = malloc(sizeof(*h));
 	if (!h) {
 		errno = ENOMEM;
 		return NULL;
 	}
 	memset(h, 0, sizeof(*h));
 	INIT_LIST_HEAD(&h->chains);
-	strcpy(h->info.name, tablename);
+	strcpy(h->info.name, infop->name);
 
-	h->entries = malloc(sizeof(STRUCT_GET_ENTRIES) + size);
+	h->entries = malloc(sizeof(STRUCT_GET_ENTRIES) + infop->size);
 	if (!h->entries)
 		goto out_free_handle;
 
-	strcpy(h->entries->name, tablename);
-	h->entries->size = size;
+	strcpy(h->entries->name, infop->name);
+	h->entries->size = infop->size;
 
 	return h;
 
@@ -1315,6 +1301,7 @@
 	socklen_t s;
 	int sockfd;
 
+retry:
 	iptc_fn = TC_INIT;
 
 	if (strlen(tablename) >= TABLE_MAXNAMELEN) {
@@ -1326,7 +1313,12 @@
 	if (sockfd < 0)
 		return NULL;
 
-retry:
+	if (fcntl(sockfd, F_SETFD, FD_CLOEXEC) == -1) {
+		fprintf(stderr, "Could not set close on exec: %s\n",
+			strerror(errno));
+		abort();
+	}
+
 	s = sizeof(info);
 
 	strcpy(info.name, tablename);
@@ -1338,8 +1330,8 @@
 	DEBUGP("valid_hooks=0x%08x, num_entries=%u, size=%u\n",
 		info.valid_hooks, info.num_entries, info.size);
 
-	if ((h = alloc_handle(info.name, info.size, info.num_entries))
-	    == NULL) {
+	h = alloc_handle(&info);
+	if (h == NULL) {
 		close(sockfd);
 		return NULL;
 	}
@@ -1359,7 +1351,7 @@
 #ifdef IPTC_DEBUG2
 	{
 		int fd = open("/tmp/libiptc-so_get_entries.blob",
-				O_CREAT|O_WRONLY);
+				O_CREAT|O_WRONLY, 0644);
 		if (fd >= 0) {
 			write(fd, h->entries, tmp);
 			close(fd);
@@ -1370,7 +1362,6 @@
 	if (parse_table(h) < 0)
 		goto error;
 
-	CHECK(h);
 	return h;
 error:
 	TC_FREE(h);
@@ -1417,7 +1408,6 @@
 TC_DUMP_ENTRIES(struct xtc_handle *const handle)
 {
 	iptc_fn = TC_DUMP_ENTRIES;
-	CHECK(handle);
 
 	printf("libiptc v%s. %u bytes.\n",
 	       XTABLES_VERSION, handle->entries->size);
@@ -1677,8 +1667,9 @@
 		return 0;
 	}
 	/* memset for memcmp convenience on delete/replace */
-	memset(t->target.u.user.name, 0, FUNCTION_MAXNAMELEN);
+	memset(t->target.u.user.name, 0, XT_EXTENSION_MAXNAMELEN);
 	strcpy(t->target.u.user.name, STANDARD_TARGET);
+	t->target.u.user.revision = 0;
 	t->verdict = verdict;
 
 	r->type = IPTCC_R_STANDARD;
@@ -1688,7 +1679,8 @@
 
 static int
 iptcc_map_target(struct xtc_handle *const handle,
-	   struct rule_head *r)
+	   struct rule_head *r,
+	   bool dry_run)
 {
 	STRUCT_ENTRY *e = r->entry;
 	STRUCT_ENTRY_TARGET *t = GET_TARGET(e);
@@ -1733,7 +1725,8 @@
 	       0,
 	       FUNCTION_MAXNAMELEN - 1 - strlen(t->u.user.name));
 	r->type = IPTCC_R_MODULE;
-	set_changed(handle);
+	if (!dry_run)
+		set_changed(handle);
 	return 1;
 }
 
@@ -1783,7 +1776,7 @@
 	memcpy(r->entry, e, e->next_offset);
 	r->counter_map.maptype = COUNTER_MAP_SET;
 
-	if (!iptcc_map_target(handle, r)) {
+	if (!iptcc_map_target(handle, r, false)) {
 		free(r);
 		return 0;
 	}
@@ -1833,7 +1826,7 @@
 	memcpy(r->entry, e, e->next_offset);
 	r->counter_map.maptype = COUNTER_MAP_SET;
 
-	if (!iptcc_map_target(handle, r)) {
+	if (!iptcc_map_target(handle, r, false)) {
 		free(r);
 		return 0;
 	}
@@ -1872,7 +1865,7 @@
 	memcpy(r->entry, e, e->next_offset);
 	r->counter_map.maptype = COUNTER_MAP_SET;
 
-	if (!iptcc_map_target(handle, r)) {
+	if (!iptcc_map_target(handle, r, false)) {
 		DEBUGP("unable to map target of rule for chain `%s'\n", chain);
 		free(r);
 		return 0;
@@ -1978,7 +1971,7 @@
 
 	memcpy(r->entry, origfw, origfw->next_offset);
 	r->counter_map.maptype = COUNTER_MAP_NOMAP;
-	if (!iptcc_map_target(handle, r)) {
+	if (!iptcc_map_target(handle, r, dry_run)) {
 		DEBUGP("unable to map target of rule for chain `%s'\n", chain);
 		free(r);
 		return 0;
@@ -2003,8 +1996,10 @@
 			continue;
 
 		/* if we are just doing a dry run, we simply skip the rest */
-		if (dry_run)
+		if (dry_run){
+			free(r);
 			return 1;
+		}
 
 		/* If we are about to delete the rule that is the
 		 * current iterator, move rule iterator back.  next
@@ -2147,7 +2142,6 @@
 	struct rule_head *r;
 
 	iptc_fn = TC_READ_COUNTER;
-	CHECK(*handle);
 
 	if (!(c = iptcc_find_label(chain, handle))) {
 		errno = ENOENT;
@@ -2171,7 +2165,6 @@
 	struct rule_head *r;
 
 	iptc_fn = TC_ZERO_COUNTER;
-	CHECK(handle);
 
 	if (!(c = iptcc_find_label(chain, handle))) {
 		errno = ENOENT;
@@ -2202,7 +2195,6 @@
 	STRUCT_ENTRY *e;
 
 	iptc_fn = TC_SET_COUNTER;
-	CHECK(handle);
 
 	if (!(c = iptcc_find_label(chain, handle))) {
 		errno = ENOENT;
@@ -2393,7 +2385,7 @@
 	iptcc_chain_index_delete_chain(c, handle);
 
 	/* Change the name of the chain */
-	strncpy(c->name, newname, sizeof(IPT_CHAINLABEL));
+	strncpy(c->name, newname, sizeof(IPT_CHAINLABEL) - 1);
 
 	/* Insert sorted into to list again */
 	iptc_insert_chain(handle, c);
@@ -2527,7 +2519,6 @@
 	unsigned int new_size;
 
 	iptc_fn = TC_COMMIT;
-	CHECK(*handle);
 
 	/* Don't commit if nothing changed. */
 	if (!handle->changed)
@@ -2588,7 +2579,7 @@
 #ifdef IPTC_DEBUG2
 	{
 		int fd = open("/tmp/libiptc-so_set_replace.blob",
-				O_CREAT|O_WRONLY);
+				O_CREAT|O_WRONLY, 0644);
 		if (fd >= 0) {
 			write(fd, repl, sizeof(*repl) + repl->size);
 			close(fd);
@@ -2664,7 +2655,7 @@
 #ifdef IPTC_DEBUG2
 	{
 		int fd = open("/tmp/libiptc-so_set_add_counters.blob",
-				O_CREAT|O_WRONLY);
+				O_CREAT|O_WRONLY, 0644);
 		if (fd >= 0) {
 			write(fd, newcounters, counterlen);
 			close(fd);
@@ -2743,3 +2734,18 @@
 
 	return strerror(err);
 }
+
+const struct xtc_ops TC_OPS = {
+	.commit        = TC_COMMIT,
+	.init          = TC_INIT,
+	.free          = TC_FREE,
+	.builtin       = TC_BUILTIN,
+	.is_chain      = TC_IS_CHAIN,
+	.flush_entries = TC_FLUSH_ENTRIES,
+	.create_chain  = TC_CREATE_CHAIN,
+	.first_chain   = TC_FIRST_CHAIN,
+	.next_chain    = TC_NEXT_CHAIN,
+	.get_policy    = TC_GET_POLICY,
+	.set_policy    = TC_SET_POLICY,
+	.strerror      = TC_STRERROR,
+};
diff --git a/libiptc/libiptc.pc.in b/libiptc/libiptc.pc.in
index 99a3544..0264bf0 100644
--- a/libiptc/libiptc.pc.in
+++ b/libiptc/libiptc.pc.in
@@ -5,8 +5,6 @@
 includedir=@includedir@
 
 Name:		libiptc
-Description:	iptables ruleset ADT and kernel interface
+Description:	iptables v4/v6 ruleset ADT and kernel interface
 Version:	@PACKAGE_VERSION@
-Libs:		-L${libdir} -liptc
-Libs.private:	-lip4tc -lip6tc
-Cflags:		-I${includedir}
+Requires:	libip4tc libip6tc
diff --git a/libiptc/linux_list.h b/libiptc/linux_list.h
index abdcf88..559e33c 100644
--- a/libiptc/linux_list.h
+++ b/libiptc/linux_list.h
@@ -27,7 +27,7 @@
 	1; \
 })
 
-#define prefetch(x)		1
+#define prefetch(x)		((void)0)
 
 /* empty define to make this work in userspace -HW */
 #define smp_wmb()
diff --git a/libxtables/Android.bp b/libxtables/Android.bp
new file mode 100644
index 0000000..ed0cc44
--- /dev/null
+++ b/libxtables/Android.bp
@@ -0,0 +1,36 @@
+//----------------------------------------------------------------
+// libxtables
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "external_iptables_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-GPL-2.0
+    //   SPDX-license-identifier-LGPL
+    default_applicable_licenses: ["external_iptables_license"],
+}
+
+cc_library_static {
+    name: "libxtables",
+    defaults: ["iptables_defaults"],
+
+    header_libs: [
+        "iptables_iptables_headers",
+        "iptables_config_header",
+    ],
+    export_header_lib_headers: ["iptables_headers"],
+
+    cflags: [
+        "-DNO_SHARED_LIBS=1",
+        "-DXTABLES_INTERNAL",
+        "-DXTABLES_LIBDIR=\"xtables_libdir_not_used\"",
+
+        "-Wno-missing-field-initializers",
+    ],
+
+    srcs: [
+        "xtables.c",
+        "xtoptions.c",
+    ],
+}
diff --git a/libxtables/Android.mk b/libxtables/Android.mk
new file mode 100644
index 0000000..1fb44e8
--- /dev/null
+++ b/libxtables/Android.mk
@@ -0,0 +1,46 @@
+LOCAL_PATH:= $(call my-dir)
+#----------------------------------------------------------------
+# libxtables
+
+include $(CLEAR_VARS)
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../include/ \
+	$(LOCAL_PATH)/../iptables/ \
+	$(LOCAL_PATH)/.. \
+
+LOCAL_CFLAGS:=-DNO_SHARED_LIBS=1
+LOCAL_CFLAGS+=-DXTABLES_INTERNAL
+LOCAL_CFLAGS+=-DXTABLES_LIBDIR=\"xtables_libdir_not_used\"
+LOCAL_CFLAGS+=-D_LARGEFILE_SOURCE=1 -D_LARGE_FILES -D_FILE_OFFSET_BITS=64 -D_REENTRANT -DENABLE_IPV4 -DENABLE_IPV6
+LOCAL_CFLAGS += -Wno-sign-compare -Wno-pointer-arith -Wno-type-limits -Wno-missing-field-initializers -Wno-unused-parameter -Wno-clobbered -Wno-tautological-compare -Werror
+
+LOCAL_LDFLAGS:=-version-info 10:0:0
+LOCAL_SRC_FILES:= \
+	xtables.c xtoptions.c
+
+LOCAL_MODULE:=libxtables
+
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../include/ \
+	$(LOCAL_PATH)/../iptables/ \
+	$(LOCAL_PATH)/..
+
+LOCAL_CFLAGS:=-DSHARED_LIBS=1
+LOCAL_CFLAGS+=-DXTABLES_INTERNAL
+LOCAL_CFLAGS+=-DXTABLES_LIBDIR=\"xtables_libdir_not_used\"
+LOCAL_CFLAGS+=-D_LARGEFILE_SOURCE=1 -D_LARGE_FILES -D_FILE_OFFSET_BITS=64 -D_REENTRANT -DENABLE_IPV4 -DENABLE_IPV6
+
+LOCAL_CFLAGS += -Wno-sign-compare -Wno-pointer-arith -Wno-type-limits -Wno-missing-field-initializers -Wno-unused-parameter -Wno-clobbered -Wno-tautological-compare -Werror
+
+LOCAL_SRC_FILES:= \
+	xtables.c xtoptions.c
+
+LOCAL_MODULE:=libxtables
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/../include/
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/libxtables/Makefile.am b/libxtables/Makefile.am
new file mode 100644
index 0000000..8ff6b0c
--- /dev/null
+++ b/libxtables/Makefile.am
@@ -0,0 +1,20 @@
+# -*- Makefile -*-
+
+AM_CFLAGS   = ${regular_CFLAGS}
+AM_CPPFLAGS = ${regular_CPPFLAGS} -I${top_builddir}/include -I${top_srcdir}/include -I${top_srcdir}/iptables ${kinclude_CPPFLAGS}
+
+lib_LTLIBRARIES       = libxtables.la
+libxtables_la_SOURCES = xtables.c xtoptions.c getethertype.c
+libxtables_la_LDFLAGS = -version-info ${libxtables_vcurrent}:0:${libxtables_vage}
+libxtables_la_LIBADD  =
+if ENABLE_STATIC
+# With --enable-static, shipped extensions are linked into the main executable,
+# so we need all the LIBADDs here too
+libxtables_la_LIBADD += -lm ${libnetfilter_conntrack_LIBS}
+endif
+if ENABLE_SHARED
+libxtables_la_CFLAGS  = ${AM_CFLAGS}
+libxtables_la_LIBADD += -ldl
+else
+libxtables_la_CFLAGS  = ${AM_CFLAGS} -DNO_SHARED_LIBS=1
+endif
diff --git a/libxtables/getethertype.c b/libxtables/getethertype.c
new file mode 100644
index 0000000..59949b7
--- /dev/null
+++ b/libxtables/getethertype.c
@@ -0,0 +1,160 @@
+/*
+* getethertype.c
+*
+* This file was part of the NYS Library.
+*
+** The NYS Library is free software; you can redistribute it and/or
+** modify it under the terms of the GNU Library 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 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+/********************************************************************
+* Description: Ethertype name service switch and the ethertypes 
+* database access functions
+* Author: Nick Fedchik <fnm@ukrsat.com>
+* Checker: Bart De Schuymer <bdschuym@pandora.be>
+* Origin: uClibc-0.9.16/libc/inet/getproto.c
+* Created at: Mon Nov 11 12:20:11 EET 2002
+********************************************************************/
+
+#include <ctype.h>
+#include <features.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netinet/ether.h>
+#include <net/ethernet.h>
+#include <xtables.h>
+
+#define	MAXALIASES	35
+
+static FILE *etherf = NULL;
+static char line[BUFSIZ + 1];
+static struct xt_ethertypeent et_ent;
+static char *ethertype_aliases[MAXALIASES];
+static int ethertype_stayopen;
+
+static void setethertypeent(int f)
+{
+	if (etherf == NULL)
+		etherf = fopen(XT_PATH_ETHERTYPES, "r");
+	else
+		rewind(etherf);
+	ethertype_stayopen |= f;
+}
+
+static void endethertypeent(void)
+{
+	if (etherf) {
+		fclose(etherf);
+		etherf = NULL;
+	}
+	ethertype_stayopen = 0;
+}
+
+
+static struct xt_ethertypeent *getethertypeent(void)
+{
+	char *e;
+	char *endptr;
+	register char *cp, **q;
+
+	if (etherf == NULL
+	    && (etherf = fopen(XT_PATH_ETHERTYPES, "r")) == NULL) {
+		return (NULL);
+	}
+
+again:
+	if ((e = fgets(line, BUFSIZ, etherf)) == NULL) {
+		return (NULL);
+	}
+	if (*e == '#')
+		goto again;
+	cp = strpbrk(e, "#\n");
+	if (cp == NULL)
+		goto again;
+	*cp = '\0';
+	et_ent.e_name = e;
+	cp = strpbrk(e, " \t");
+	if (cp == NULL)
+		goto again;
+	*cp++ = '\0';
+	while (*cp == ' ' || *cp == '\t')
+		cp++;
+	e = strpbrk(cp, " \t");
+	if (e != NULL)
+		*e++ = '\0';
+// Check point
+	et_ent.e_ethertype = strtol(cp, &endptr, 16);
+	if (*endptr != '\0'
+	    || (et_ent.e_ethertype < ETH_ZLEN
+		|| et_ent.e_ethertype > 0xFFFF))
+		goto again;	// Skip invalid etherproto type entry
+	q = et_ent.e_aliases = ethertype_aliases;
+	if (e != NULL) {
+		cp = e;
+		while (cp && *cp) {
+			if (*cp == ' ' || *cp == '\t') {
+				cp++;
+				continue;
+			}
+			if (q < &ethertype_aliases[MAXALIASES - 1])
+				*q++ = cp;
+			cp = strpbrk(cp, " \t");
+			if (cp != NULL)
+				*cp++ = '\0';
+		}
+	}
+	*q = NULL;
+	return (&et_ent);
+}
+
+struct xt_ethertypeent *xtables_getethertypebyname(const char *name)
+{
+	register struct xt_ethertypeent *e;
+	register char **cp;
+
+	setethertypeent(ethertype_stayopen);
+	while ((e = getethertypeent()) != NULL) {
+		if (strcasecmp(e->e_name, name) == 0)
+			break;
+		for (cp = e->e_aliases; *cp != 0; cp++)
+			if (strcasecmp(*cp, name) == 0)
+				goto found;
+	}
+found:
+	if (!ethertype_stayopen)
+		endethertypeent();
+	return (e);
+}
+
+struct xt_ethertypeent *xtables_getethertypebynumber(int type)
+{
+	register struct xt_ethertypeent *e;
+
+	setethertypeent(ethertype_stayopen);
+	while ((e = getethertypeent()) != NULL)
+		if (e->e_ethertype == type)
+			break;
+	if (!ethertype_stayopen)
+		endethertypeent();
+	return (e);
+}
diff --git a/libxtables/xtables.c b/libxtables/xtables.c
new file mode 100644
index 0000000..35fa625
--- /dev/null
+++ b/libxtables/xtables.c
@@ -0,0 +1,2397 @@
+/*
+ * (C) 2000-2006 by the netfilter coreteam <coreteam@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 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#include "config.h"
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <netdb.h>
+#include <spawn.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <sys/types.h>
+#include <sys/utsname.h>
+#include <sys/wait.h>
+#include <arpa/inet.h>
+#if defined(HAVE_LINUX_MAGIC_H)
+#	include <linux/magic.h> /* for PROC_SUPER_MAGIC */
+#elif defined(HAVE_LINUX_PROC_FS_H)
+#	include <linux/proc_fs.h>	/* Linux 2.4 */
+#else
+#	define PROC_SUPER_MAGIC	0x9fa0
+#endif
+
+#include <xtables.h>
+#include <limits.h> /* INT_MAX in ip_tables.h/ip6_tables.h */
+#ifdef __BIONIC__
+#include <linux/if_ether.h> /* ETH_ALEN */
+#endif
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <libiptc/libxtc.h>
+
+#ifndef NO_SHARED_LIBS
+#include <dlfcn.h>
+#endif
+#ifndef IPT_SO_GET_REVISION_MATCH /* Old kernel source. */
+#	define IPT_SO_GET_REVISION_MATCH	(IPT_BASE_CTL + 2)
+#	define IPT_SO_GET_REVISION_TARGET	(IPT_BASE_CTL + 3)
+#endif
+#ifndef IP6T_SO_GET_REVISION_MATCH /* Old kernel source. */
+#	define IP6T_SO_GET_REVISION_MATCH	68
+#	define IP6T_SO_GET_REVISION_TARGET	69
+#endif
+#include <getopt.h>
+#include "iptables/internal.h"
+#include "xshared.h"
+
+#define NPROTO	255
+
+#ifndef PROC_SYS_MODPROBE
+#define PROC_SYS_MODPROBE "/proc/sys/kernel/modprobe"
+#endif
+
+/* we need this for ip6?tables-restore.  ip6?tables-restore.c sets line to the
+ * current line of the input file, in order  to give a more precise error
+ * message.  ip6?tables itself doesn't need this, so it is initialized to the
+ * magic number of -1 */
+int line = -1;
+
+void basic_exit_err(enum xtables_exittype status, const char *msg, ...) __attribute__((noreturn, format(printf,2,3)));
+
+struct xtables_globals *xt_params = NULL;
+
+void basic_exit_err(enum xtables_exittype status, const char *msg, ...)
+{
+	va_list args;
+
+	va_start(args, msg);
+	fprintf(stderr, "%s v%s: ", xt_params->program_name, xt_params->program_version);
+	vfprintf(stderr, msg, args);
+	va_end(args);
+	fprintf(stderr, "\n");
+	exit(status);
+}
+
+void xtables_free_opts(int unused)
+{
+	if (xt_params->opts != xt_params->orig_opts) {
+		free(xt_params->opts);
+		xt_params->opts = NULL;
+	}
+}
+
+struct option *xtables_merge_options(struct option *orig_opts,
+				     struct option *oldopts,
+				     const struct option *newopts,
+				     unsigned int *option_offset)
+{
+	unsigned int num_oold = 0, num_old = 0, num_new = 0, i;
+	struct option *merge, *mp;
+
+	if (newopts == NULL)
+		return oldopts;
+
+	for (num_oold = 0; orig_opts[num_oold].name; num_oold++) ;
+	if (oldopts != NULL)
+		for (num_old = 0; oldopts[num_old].name; num_old++) ;
+	for (num_new = 0; newopts[num_new].name; num_new++) ;
+
+	/*
+	 * Since @oldopts also has @orig_opts already (and does so at the
+	 * start), skip these entries.
+	 */
+	if (oldopts != NULL) {
+		oldopts += num_oold;
+		num_old -= num_oold;
+	}
+
+	merge = malloc(sizeof(*mp) * (num_oold + num_old + num_new + 1));
+	if (merge == NULL)
+		return NULL;
+
+	/* Let the base options -[ADI...] have precedence over everything */
+	memcpy(merge, orig_opts, sizeof(*mp) * num_oold);
+	mp = merge + num_oold;
+
+	/* Second, the new options */
+	xt_params->option_offset += XT_OPTION_OFFSET_SCALE;
+	*option_offset = xt_params->option_offset;
+	memcpy(mp, newopts, sizeof(*mp) * num_new);
+
+	for (i = 0; i < num_new; ++i, ++mp)
+		mp->val += *option_offset;
+
+	/* Third, the old options */
+	if (oldopts != NULL) {
+		memcpy(mp, oldopts, sizeof(*mp) * num_old);
+		mp += num_old;
+	}
+	xtables_free_opts(0);
+
+	/* Clear trailing entry */
+	memset(mp, 0, sizeof(*mp));
+	return merge;
+}
+
+static const struct xtables_afinfo afinfo_ipv4 = {
+	.kmod          = "ip_tables",
+	.proc_exists   = "/proc/net/ip_tables_names",
+	.libprefix     = "libipt_",
+	.family	       = NFPROTO_IPV4,
+	.ipproto       = IPPROTO_IP,
+	.so_rev_match  = IPT_SO_GET_REVISION_MATCH,
+	.so_rev_target = IPT_SO_GET_REVISION_TARGET,
+};
+
+static const struct xtables_afinfo afinfo_ipv6 = {
+	.kmod          = "ip6_tables",
+	.proc_exists   = "/proc/net/ip6_tables_names",
+	.libprefix     = "libip6t_",
+	.family        = NFPROTO_IPV6,
+	.ipproto       = IPPROTO_IPV6,
+	.so_rev_match  = IP6T_SO_GET_REVISION_MATCH,
+	.so_rev_target = IP6T_SO_GET_REVISION_TARGET,
+};
+
+/* Dummy families for arptables-compat and ebtables-compat. Leave structure
+ * fields that we don't use unset.
+ */
+static const struct xtables_afinfo afinfo_bridge = {
+	.libprefix     = "libebt_",
+	.family        = NFPROTO_BRIDGE,
+};
+
+static const struct xtables_afinfo afinfo_arp = {
+	.libprefix     = "libarpt_",
+	.family        = NFPROTO_ARP,
+};
+
+const struct xtables_afinfo *afinfo;
+
+/* Search path for Xtables .so files */
+static const char *xtables_libdir;
+
+/* the path to command to load kernel module */
+const char *xtables_modprobe_program;
+
+/* Keep track of matches/targets pending full registration: linked lists. */
+struct xtables_match *xtables_pending_matches;
+struct xtables_target *xtables_pending_targets;
+
+/* Keep track of fully registered external matches/targets: linked lists. */
+struct xtables_match *xtables_matches;
+struct xtables_target *xtables_targets;
+
+/* Fully register a match/target which was previously partially registered. */
+static bool xtables_fully_register_pending_match(struct xtables_match *me,
+						 struct xtables_match *prev);
+static bool xtables_fully_register_pending_target(struct xtables_target *me,
+						  struct xtables_target *prev);
+
+#ifndef NO_SHARED_LIBS
+/* registry for loaded shared objects to close later */
+struct dlreg {
+	struct dlreg *next;
+	void *handle;
+};
+static struct dlreg *dlreg = NULL;
+
+static int dlreg_add(void *handle)
+{
+	struct dlreg *new = malloc(sizeof(*new));
+
+	if (!new)
+		return -1;
+
+	new->handle = handle;
+	new->next = dlreg;
+	dlreg = new;
+	return 0;
+}
+
+static void dlreg_free(void)
+{
+	struct dlreg *next;
+
+	while (dlreg) {
+		next = dlreg->next;
+		dlclose(dlreg->handle);
+		free(dlreg);
+		dlreg = next;
+	}
+}
+#endif
+
+void xtables_init(void)
+{
+	xtables_libdir = getenv("XTABLES_LIBDIR");
+	if (xtables_libdir != NULL)
+		return;
+	xtables_libdir = getenv("IPTABLES_LIB_DIR");
+	if (xtables_libdir != NULL) {
+		fprintf(stderr, "IPTABLES_LIB_DIR is deprecated, "
+		        "use XTABLES_LIBDIR.\n");
+		return;
+	}
+	/*
+	 * Well yes, IP6TABLES_LIB_DIR is of lower priority over
+	 * IPTABLES_LIB_DIR since this moved to libxtables; I think that is ok
+	 * for these env vars are deprecated anyhow, and in light of the
+	 * (shared) libxt_*.so files, makes less sense to have
+	 * IPTABLES_LIB_DIR != IP6TABLES_LIB_DIR.
+	 */
+	xtables_libdir = getenv("IP6TABLES_LIB_DIR");
+	if (xtables_libdir != NULL) {
+		fprintf(stderr, "IP6TABLES_LIB_DIR is deprecated, "
+		        "use XTABLES_LIBDIR.\n");
+		return;
+	}
+	xtables_libdir = XTABLES_LIBDIR;
+}
+
+void xtables_fini(void)
+{
+#ifndef NO_SHARED_LIBS
+	dlreg_free();
+#endif
+}
+
+void xtables_set_nfproto(uint8_t nfproto)
+{
+	switch (nfproto) {
+	case NFPROTO_IPV4:
+		afinfo = &afinfo_ipv4;
+		break;
+	case NFPROTO_IPV6:
+		afinfo = &afinfo_ipv6;
+		break;
+	case NFPROTO_BRIDGE:
+		afinfo = &afinfo_bridge;
+		break;
+	case NFPROTO_ARP:
+		afinfo = &afinfo_arp;
+		break;
+	default:
+		fprintf(stderr, "libxtables: unhandled NFPROTO in %s\n",
+		        __func__);
+	}
+}
+
+/**
+ * xtables_set_params - set the global parameters used by xtables
+ * @xtp:	input xtables_globals structure
+ *
+ * The app is expected to pass a valid xtables_globals data-filled
+ * with proper values
+ * @xtp cannot be NULL
+ *
+ * Returns -1 on failure to set and 0 on success
+ */
+int xtables_set_params(struct xtables_globals *xtp)
+{
+	if (!xtp) {
+		fprintf(stderr, "%s: Illegal global params\n",__func__);
+		return -1;
+	}
+
+	xt_params = xtp;
+
+	if (!xt_params->exit_err)
+		xt_params->exit_err = basic_exit_err;
+
+	return 0;
+}
+
+int xtables_init_all(struct xtables_globals *xtp, uint8_t nfproto)
+{
+	xtables_init();
+	xtables_set_nfproto(nfproto);
+	return xtables_set_params(xtp);
+}
+
+/**
+ * xtables_*alloc - wrappers that exit on failure
+ */
+void *xtables_calloc(size_t count, size_t size)
+{
+	void *p;
+
+	if ((p = calloc(count, size)) == NULL) {
+		perror("ip[6]tables: calloc failed");
+		exit(1);
+	}
+
+	return p;
+}
+
+void *xtables_malloc(size_t size)
+{
+	void *p;
+
+	if ((p = malloc(size)) == NULL) {
+		perror("ip[6]tables: malloc failed");
+		exit(1);
+	}
+
+	return p;
+}
+
+void *xtables_realloc(void *ptr, size_t size)
+{
+	void *p;
+
+	if ((p = realloc(ptr, size)) == NULL) {
+		perror("ip[6]tables: realloc failed");
+		exit(1);
+	}
+
+	return p;
+}
+
+static char *get_modprobe(void)
+{
+	int procfile;
+	char *ret;
+	int count;
+
+	procfile = open(PROC_SYS_MODPROBE, O_RDONLY);
+	if (procfile < 0)
+		return NULL;
+	if (fcntl(procfile, F_SETFD, FD_CLOEXEC) == -1) {
+		fprintf(stderr, "Could not set close on exec: %s\n",
+			strerror(errno));
+		exit(1);
+	}
+
+	ret = malloc(PATH_MAX);
+	if (ret) {
+		count = read(procfile, ret, PATH_MAX);
+		if (count > 0 && count < PATH_MAX)
+		{
+			if (ret[count - 1] == '\n')
+				ret[count - 1] = '\0';
+			else
+				ret[count] = '\0';
+			close(procfile);
+			return ret;
+		}
+	}
+	free(ret);
+	close(procfile);
+	return NULL;
+}
+
+int xtables_insmod(const char *modname, const char *modprobe, bool quiet)
+{
+	char *buf = NULL;
+	char *argv[4];
+	int status;
+	pid_t pid;
+
+	/* If they don't explicitly set it, read out of kernel */
+	if (!modprobe) {
+		buf = get_modprobe();
+		if (!buf)
+			return -1;
+		modprobe = buf;
+	}
+
+	argv[0] = (char *)modprobe;
+	argv[1] = (char *)modname;
+	argv[2] = quiet ? "-q" : NULL;
+	argv[3] = NULL;
+
+	/*
+	 * Need to flush the buffer, or the child may output it again
+	 * when switching the program thru execv.
+	 */
+	fflush(stdout);
+
+	if (posix_spawn(&pid, argv[0], NULL, NULL, argv, NULL)) {
+		free(buf);
+		return -1;
+	} else {
+		waitpid(pid, &status, 0);
+	}
+
+	free(buf);
+	if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
+		return 0;
+	return -1;
+}
+
+/* return true if a given file exists within procfs */
+static bool proc_file_exists(const char *filename)
+{
+	struct stat s;
+	struct statfs f;
+
+	if (lstat(filename, &s))
+		return false;
+	if (!S_ISREG(s.st_mode))
+		return false;
+	if (statfs(filename, &f))
+		return false;
+	if (f.f_type != PROC_SUPER_MAGIC)
+		return false;
+	return true;
+}
+
+int xtables_load_ko(const char *modprobe, bool quiet)
+{
+	static bool loaded = false;
+	int ret;
+
+	if (loaded)
+		return 0;
+
+	if (proc_file_exists(afinfo->proc_exists)) {
+		loaded = true;
+		return 0;
+	};
+
+	ret = xtables_insmod(afinfo->kmod, modprobe, quiet);
+	if (ret == 0)
+		loaded = true;
+
+	return ret;
+}
+
+/**
+ * xtables_strtou{i,l} - string to number conversion
+ * @s:	input string
+ * @end:	like strtoul's "end" pointer
+ * @value:	pointer for result
+ * @min:	minimum accepted value
+ * @max:	maximum accepted value
+ *
+ * If @end is NULL, we assume the caller wants a "strict strtoul", and hence
+ * "15a" is rejected.
+ * In either case, the value obtained is compared for min-max compliance.
+ * Base is always 0, i.e. autodetect depending on @s.
+ *
+ * Returns true/false whether number was accepted. On failure, *value has
+ * undefined contents.
+ */
+bool xtables_strtoul(const char *s, char **end, uintmax_t *value,
+                     uintmax_t min, uintmax_t max)
+{
+	uintmax_t v;
+	const char *p;
+	char *my_end;
+
+	errno = 0;
+	/* Since strtoul allows leading minus, we have to check for ourself. */
+	for (p = s; isspace(*p); ++p)
+		;
+	if (*p == '-')
+		return false;
+	v = strtoumax(s, &my_end, 0);
+	if (my_end == s)
+		return false;
+	if (end != NULL)
+		*end = my_end;
+
+	if (errno != ERANGE && min <= v && (max == 0 || v <= max)) {
+		if (value != NULL)
+			*value = v;
+		if (end == NULL)
+			return *my_end == '\0';
+		return true;
+	}
+
+	return false;
+}
+
+bool xtables_strtoui(const char *s, char **end, unsigned int *value,
+                     unsigned int min, unsigned int max)
+{
+	uintmax_t v;
+	bool ret;
+
+	ret = xtables_strtoul(s, end, &v, min, max);
+	if (ret && value != NULL)
+		*value = v;
+	return ret;
+}
+
+int xtables_service_to_port(const char *name, const char *proto)
+{
+	struct servent *service;
+
+	if ((service = getservbyname(name, proto)) != NULL)
+		return ntohs((unsigned short) service->s_port);
+
+	return -1;
+}
+
+uint16_t xtables_parse_port(const char *port, const char *proto)
+{
+	unsigned int portnum;
+
+	if (xtables_strtoui(port, NULL, &portnum, 0, UINT16_MAX) ||
+	    (portnum = xtables_service_to_port(port, proto)) != (unsigned)-1)
+		return portnum;
+
+	xt_params->exit_err(PARAMETER_PROBLEM,
+		   "invalid port/service `%s' specified", port);
+}
+
+void xtables_parse_interface(const char *arg, char *vianame,
+			     unsigned char *mask)
+{
+	unsigned int vialen = strlen(arg);
+	unsigned int i;
+
+	memset(mask, 0, IFNAMSIZ);
+	memset(vianame, 0, IFNAMSIZ);
+
+	if (vialen + 1 > IFNAMSIZ)
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			   "interface name `%s' must be shorter than IFNAMSIZ"
+			   " (%i)", arg, IFNAMSIZ-1);
+
+	strcpy(vianame, arg);
+	if (vialen == 0)
+		return;
+	else if (vianame[vialen - 1] == '+') {
+		memset(mask, 0xFF, vialen - 1);
+		/* Don't remove `+' here! -HW */
+	} else {
+		/* Include nul-terminator in match */
+		memset(mask, 0xFF, vialen + 1);
+	}
+
+	/* Display warning on invalid characters */
+	for (i = 0; vianame[i]; i++) {
+		if (vianame[i] == '/' || vianame[i] == ' ') {
+			fprintf(stderr,	"Warning: weird character in interface"
+				" `%s' ('/' and ' ' are not allowed by the kernel).\n",
+				vianame);
+			break;
+		}
+	}
+}
+
+#ifndef NO_SHARED_LIBS
+static void *load_extension(const char *search_path, const char *af_prefix,
+    const char *name, bool is_target)
+{
+	const char *all_prefixes[] = {af_prefix, "libxt_", NULL};
+	const char **prefix;
+	const char *dir = search_path, *next;
+	void *ptr = NULL;
+	struct stat sb;
+	char path[256];
+
+	do {
+		next = strchr(dir, ':');
+		if (next == NULL)
+			next = dir + strlen(dir);
+
+		for (prefix = all_prefixes; *prefix != NULL; ++prefix) {
+			void *handle;
+
+			snprintf(path, sizeof(path), "%.*s/%s%s.so",
+			         (unsigned int)(next - dir), dir,
+			         *prefix, name);
+
+			if (stat(path, &sb) != 0) {
+				if (errno == ENOENT)
+					continue;
+				fprintf(stderr, "%s: %s\n", path,
+					strerror(errno));
+				return NULL;
+			}
+			handle = dlopen(path, RTLD_NOW);
+			if (handle == NULL) {
+				fprintf(stderr, "%s: %s\n", path, dlerror());
+				break;
+			}
+
+			dlreg_add(handle);
+
+			if (is_target)
+				ptr = xtables_find_target(name, XTF_DONT_LOAD);
+			else
+				ptr = xtables_find_match(name,
+				      XTF_DONT_LOAD, NULL);
+
+			if (ptr != NULL)
+				return ptr;
+
+			errno = ENOENT;
+			return NULL;
+		}
+		dir = next + 1;
+	} while (*next != '\0');
+
+	return NULL;
+}
+#endif
+
+static bool extension_cmp(const char *name1, const char *name2, uint32_t family)
+{
+	if (strcmp(name1, name2) == 0 &&
+	    (family == afinfo->family ||
+	     family == NFPROTO_UNSPEC))
+		return true;
+
+	return false;
+}
+
+struct xtables_match *
+xtables_find_match(const char *name, enum xtables_tryload tryload,
+		   struct xtables_rule_match **matches)
+{
+	struct xtables_match *prev = NULL;
+	struct xtables_match **dptr;
+	struct xtables_match *ptr;
+	const char *icmp6 = "icmp6";
+
+	if (strlen(name) >= XT_EXTENSION_MAXNAMELEN)
+		xtables_error(PARAMETER_PROBLEM,
+			   "Invalid match name \"%s\" (%u chars max)",
+			   name, XT_EXTENSION_MAXNAMELEN - 1);
+
+	/* This is ugly as hell. Nonetheless, there is no way of changing
+	 * this without hurting backwards compatibility */
+	if ( (strcmp(name,"icmpv6") == 0) ||
+	     (strcmp(name,"ipv6-icmp") == 0) ||
+	     (strcmp(name,"icmp6") == 0) )
+		name = icmp6;
+
+	/* Trigger delayed initialization */
+	for (dptr = &xtables_pending_matches; *dptr; ) {
+		if (extension_cmp(name, (*dptr)->name, (*dptr)->family)) {
+			ptr = *dptr;
+			*dptr = (*dptr)->next;
+			if (xtables_fully_register_pending_match(ptr, prev)) {
+				prev = ptr;
+				continue;
+			} else if (prev) {
+				continue;
+			}
+			*dptr = ptr;
+		}
+		dptr = &((*dptr)->next);
+	}
+
+	for (ptr = xtables_matches; ptr; ptr = ptr->next) {
+		if (extension_cmp(name, ptr->name, ptr->family)) {
+			struct xtables_match *clone;
+
+			/* First match of this type: */
+			if (ptr->m == NULL)
+				break;
+
+			/* Second and subsequent clones */
+			clone = xtables_malloc(sizeof(struct xtables_match));
+			memcpy(clone, ptr, sizeof(struct xtables_match));
+			clone->udata = NULL;
+			clone->mflags = 0;
+			/* This is a clone: */
+			clone->next = clone;
+
+			ptr = clone;
+			break;
+		}
+	}
+
+#ifndef NO_SHARED_LIBS
+	if (!ptr && tryload != XTF_DONT_LOAD && tryload != XTF_DURING_LOAD) {
+		ptr = load_extension(xtables_libdir, afinfo->libprefix,
+		      name, false);
+
+		if (ptr == NULL && tryload == XTF_LOAD_MUST_SUCCEED)
+			xt_params->exit_err(PARAMETER_PROBLEM,
+				   "Couldn't load match `%s':%s\n",
+				   name, strerror(errno));
+	}
+#else
+	if (ptr && !ptr->loaded) {
+		if (tryload != XTF_DONT_LOAD)
+			ptr->loaded = 1;
+		else
+			ptr = NULL;
+	}
+	if(!ptr && (tryload == XTF_LOAD_MUST_SUCCEED)) {
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			   "Couldn't find match `%s'\n", name);
+	}
+#endif
+
+	if (ptr && matches) {
+		struct xtables_rule_match **i;
+		struct xtables_rule_match *newentry;
+
+		newentry = xtables_malloc(sizeof(struct xtables_rule_match));
+
+		for (i = matches; *i; i = &(*i)->next) {
+			if (extension_cmp(name, (*i)->match->name,
+					  (*i)->match->family))
+				(*i)->completed = true;
+		}
+		newentry->match = ptr;
+		newentry->completed = false;
+		newentry->next = NULL;
+		*i = newentry;
+	}
+
+	return ptr;
+}
+
+struct xtables_match *
+xtables_find_match_revision(const char *name, enum xtables_tryload tryload,
+			    struct xtables_match *match, int revision)
+{
+	if (!match) {
+		match = xtables_find_match(name, tryload, NULL);
+		if (!match)
+			return NULL;
+	}
+
+	while (1) {
+		if (match->revision == revision)
+			return match;
+		match = match->next;
+		if (!match)
+			return NULL;
+		if (!extension_cmp(name, match->name, match->family))
+			return NULL;
+	}
+}
+
+struct xtables_target *
+xtables_find_target(const char *name, enum xtables_tryload tryload)
+{
+	struct xtables_target *prev = NULL;
+	struct xtables_target **dptr;
+	struct xtables_target *ptr;
+
+	/* Standard target? */
+	if (strcmp(name, "") == 0
+	    || strcmp(name, XTC_LABEL_ACCEPT) == 0
+	    || strcmp(name, XTC_LABEL_DROP) == 0
+	    || strcmp(name, XTC_LABEL_QUEUE) == 0
+	    || strcmp(name, XTC_LABEL_RETURN) == 0)
+		name = "standard";
+
+	/* Trigger delayed initialization */
+	for (dptr = &xtables_pending_targets; *dptr; ) {
+		if (extension_cmp(name, (*dptr)->name, (*dptr)->family)) {
+			ptr = *dptr;
+			*dptr = (*dptr)->next;
+			if (xtables_fully_register_pending_target(ptr, prev)) {
+				prev = ptr;
+				continue;
+			} else if (prev) {
+				continue;
+			}
+			*dptr = ptr;
+		}
+		dptr = &((*dptr)->next);
+	}
+
+	for (ptr = xtables_targets; ptr; ptr = ptr->next) {
+		if (extension_cmp(name, ptr->name, ptr->family)) {
+#if 0			/* Code block below causes memory leak.  (Bugs 162925719 and 168688680) */
+			struct xtables_target *clone;
+
+			/* First target of this type: */
+			if (ptr->t == NULL)
+				break;
+
+			/* Second and subsequent clones */
+			clone = xtables_malloc(sizeof(struct xtables_target));
+			memcpy(clone, ptr, sizeof(struct xtables_target));
+			clone->udata = NULL;
+			clone->tflags = 0;
+			/* This is a clone: */
+			clone->next = clone;
+
+			ptr = clone;
+#endif
+			break;
+		}
+	}
+
+#ifndef NO_SHARED_LIBS
+	if (!ptr && tryload != XTF_DONT_LOAD && tryload != XTF_DURING_LOAD) {
+		ptr = load_extension(xtables_libdir, afinfo->libprefix,
+		      name, true);
+
+		if (ptr == NULL && tryload == XTF_LOAD_MUST_SUCCEED)
+			xt_params->exit_err(PARAMETER_PROBLEM,
+				   "Couldn't load target `%s':%s\n",
+				   name, strerror(errno));
+	}
+#else
+	if (ptr && !ptr->loaded) {
+		if (tryload != XTF_DONT_LOAD)
+			ptr->loaded = 1;
+		else
+			ptr = NULL;
+	}
+	if (ptr == NULL && tryload == XTF_LOAD_MUST_SUCCEED) {
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			   "Couldn't find target `%s'\n", name);
+	}
+#endif
+
+	if (ptr)
+		ptr->used = 1;
+
+	return ptr;
+}
+
+struct xtables_target *
+xtables_find_target_revision(const char *name, enum xtables_tryload tryload,
+			     struct xtables_target *target, int revision)
+{
+	if (!target) {
+		target = xtables_find_target(name, tryload);
+		if (!target)
+			return NULL;
+	}
+
+	while (1) {
+		if (target->revision == revision)
+			return target;
+		target = target->next;
+		if (!target)
+			return NULL;
+		if (!extension_cmp(name, target->name, target->family))
+			return NULL;
+	}
+}
+
+int xtables_compatible_revision(const char *name, uint8_t revision, int opt)
+{
+	struct xt_get_revision rev;
+	socklen_t s = sizeof(rev);
+	int max_rev, sockfd;
+
+	sockfd = socket(afinfo->family, SOCK_RAW, IPPROTO_RAW);
+	if (sockfd < 0) {
+		if (errno == EPERM) {
+			/* revision 0 is always supported. */
+			if (revision != 0)
+				fprintf(stderr, "%s: Could not determine whether "
+						"revision %u is supported, "
+						"assuming it is.\n",
+					name, revision);
+			return 1;
+		}
+		fprintf(stderr, "Could not open socket to kernel: %s\n",
+			strerror(errno));
+		exit(1);
+	}
+
+	if (fcntl(sockfd, F_SETFD, FD_CLOEXEC) == -1) {
+		fprintf(stderr, "Could not set close on exec: %s\n",
+			strerror(errno));
+		exit(1);
+	}
+
+	xtables_load_ko(xtables_modprobe_program, true);
+
+	strncpy(rev.name, name, XT_EXTENSION_MAXNAMELEN - 1);
+	rev.name[XT_EXTENSION_MAXNAMELEN - 1] = '\0';
+	rev.revision = revision;
+
+	max_rev = getsockopt(sockfd, afinfo->ipproto, opt, &rev, &s);
+	if (max_rev < 0) {
+		/* Definitely don't support this? */
+		if (errno == ENOENT || errno == EPROTONOSUPPORT) {
+			close(sockfd);
+			return 0;
+		} else if (errno == ENOPROTOOPT) {
+			close(sockfd);
+			/* Assume only revision 0 support (old kernel) */
+			return (revision == 0);
+		} else {
+			fprintf(stderr, "getsockopt failed strangely: %s\n",
+				strerror(errno));
+			exit(1);
+		}
+	}
+	close(sockfd);
+	return 1;
+}
+
+
+static int compatible_match_revision(const char *name, uint8_t revision)
+{
+	return xt_params->compat_rev(name, revision, afinfo->so_rev_match);
+}
+
+static int compatible_target_revision(const char *name, uint8_t revision)
+{
+	return xt_params->compat_rev(name, revision, afinfo->so_rev_target);
+}
+
+static void xtables_check_options(const char *name, const struct option *opt)
+{
+	for (; opt->name != NULL; ++opt)
+		if (opt->val < 0 || opt->val >= XT_OPTION_OFFSET_SCALE) {
+			fprintf(stderr, "%s: Extension %s uses invalid "
+			        "option value %d\n",xt_params->program_name,
+			        name, opt->val);
+			exit(1);
+		}
+}
+
+static int xtables_match_prefer(const struct xtables_match *a,
+				const struct xtables_match *b);
+
+void xtables_register_match(struct xtables_match *me)
+{
+	struct xtables_match **pos;
+	bool seen_myself = false;
+
+	if (me->next) {
+		fprintf(stderr, "%s: match \"%s\" already registered\n",
+			xt_params->program_name, me->name);
+		exit(1);
+	}
+
+	if (me->version == NULL) {
+		fprintf(stderr, "%s: match %s<%u> is missing a version\n",
+		        xt_params->program_name, me->name, me->revision);
+		exit(1);
+	}
+
+	if (me->size != XT_ALIGN(me->size)) {
+		fprintf(stderr, "%s: match \"%s\" has invalid size %u.\n",
+		        xt_params->program_name, me->name,
+		        (unsigned int)me->size);
+		exit(1);
+	}
+
+	if (strcmp(me->version, XTABLES_VERSION) != 0) {
+		fprintf(stderr, "%s: match \"%s\" has version \"%s\", "
+		        "but \"%s\" is required.\n",
+			xt_params->program_name, me->name,
+			me->version, XTABLES_VERSION);
+		exit(1);
+	}
+
+	if (strlen(me->name) >= XT_EXTENSION_MAXNAMELEN) {
+		fprintf(stderr, "%s: match `%s' has invalid name\n",
+			xt_params->program_name, me->name);
+		exit(1);
+	}
+
+	if (me->real_name && strlen(me->real_name) >= XT_EXTENSION_MAXNAMELEN) {
+		fprintf(stderr, "%s: match `%s' has invalid real name\n",
+			xt_params->program_name, me->real_name);
+		exit(1);
+	}
+
+	if (me->family >= NPROTO) {
+		fprintf(stderr,
+			"%s: BUG: match %s has invalid protocol family\n",
+			xt_params->program_name, me->name);
+		exit(1);
+	}
+
+	if (me->x6_options != NULL)
+		xtables_option_metavalidate(me->name, me->x6_options);
+	if (me->extra_opts != NULL)
+		xtables_check_options(me->name, me->extra_opts);
+
+	/* order into linked list of matches pending full registration */
+	for (pos = &xtables_pending_matches; *pos; pos = &(*pos)->next) {
+		/* group by name and family */
+		if (strcmp(me->name, (*pos)->name) ||
+		    me->family != (*pos)->family) {
+			if (seen_myself)
+				break; /* end of own group, append to it */
+			continue;
+		}
+		/* found own group */
+		seen_myself = true;
+		if (xtables_match_prefer(me, *pos) >= 0)
+			break; /* put preferred items first in group */
+	}
+	/* if own group was not found, prepend item */
+	if (!*pos && !seen_myself)
+		pos = &xtables_pending_matches;
+
+	me->next = *pos;
+	*pos = me;
+#ifdef DEBUG
+	printf("%s: inserted match %s (family %d, revision %d):\n",
+			__func__, me->name, me->family, me->revision);
+	for (pos = &xtables_pending_matches; *pos; pos = &(*pos)->next) {
+		printf("%s:\tmatch %s (family %d, revision %d)\n", __func__,
+		       (*pos)->name, (*pos)->family, (*pos)->revision);
+	}
+#endif
+}
+
+/**
+ * Compare two actions for their preference
+ * @a:	one action
+ * @b: 	another
+ *
+ * Like strcmp, returns a negative number if @a is less preferred than @b,
+ * positive number if @a is more preferred than @b, or zero if equally
+ * preferred.
+ */
+static int
+xtables_mt_prefer(bool a_alias, unsigned int a_rev, unsigned int a_fam,
+		  bool b_alias, unsigned int b_rev, unsigned int b_fam)
+{
+	/*
+	 * Alias ranks higher than no alias.
+	 * (We want the new action to be used whenever possible.)
+	 */
+	if (!a_alias && b_alias)
+		return -1;
+	if (a_alias && !b_alias)
+		return 1;
+
+	/* Higher revision ranks higher. */
+	if (a_rev < b_rev)
+		return -1;
+	if (a_rev > b_rev)
+		return 1;
+
+	/* NFPROTO_<specific> ranks higher than NFPROTO_UNSPEC. */
+	if (a_fam == NFPROTO_UNSPEC && b_fam != NFPROTO_UNSPEC)
+		return -1;
+	if (a_fam != NFPROTO_UNSPEC && b_fam == NFPROTO_UNSPEC)
+		return 1;
+
+	/* Must be the same thing. */
+	return 0;
+}
+
+static int xtables_match_prefer(const struct xtables_match *a,
+				const struct xtables_match *b)
+{
+	return xtables_mt_prefer(a->real_name != NULL,
+				 a->revision, a->family,
+				 b->real_name != NULL,
+				 b->revision, b->family);
+}
+
+static int xtables_target_prefer(const struct xtables_target *a,
+				 const struct xtables_target *b)
+{
+	/*
+	 * Note that if x->real_name==NULL, it will be set to x->name in
+	 * xtables_register_*; the direct pointer comparison here is therefore
+	 * legitimate to detect an alias.
+	 */
+	return xtables_mt_prefer(a->real_name != NULL,
+				 a->revision, a->family,
+				 b->real_name != NULL,
+				 b->revision, b->family);
+}
+
+static bool xtables_fully_register_pending_match(struct xtables_match *me,
+						 struct xtables_match *prev)
+{
+	struct xtables_match **i;
+	const char *rn;
+
+	/* See if new match can be used. */
+	rn = (me->real_name != NULL) ? me->real_name : me->name;
+	if (!compatible_match_revision(rn, me->revision))
+		return false;
+
+	if (!prev) {
+		/* Append to list. */
+		for (i = &xtables_matches; *i; i = &(*i)->next);
+	} else {
+		/* Append it */
+		i = &prev->next;
+		prev = prev->next;
+	}
+
+	me->next = prev;
+	*i = me;
+
+	me->m = NULL;
+	me->mflags = 0;
+
+	return true;
+}
+
+void xtables_register_matches(struct xtables_match *match, unsigned int n)
+{
+	int i;
+
+	for (i = 0; i < n; i++)
+		xtables_register_match(&match[i]);
+}
+
+void xtables_register_target(struct xtables_target *me)
+{
+	struct xtables_target **pos;
+	bool seen_myself = false;
+
+	if (me->next) {
+		fprintf(stderr, "%s: target \"%s\" already registered\n",
+			xt_params->program_name, me->name);
+		exit(1);
+	}
+
+	if (me->version == NULL) {
+		fprintf(stderr, "%s: target %s<%u> is missing a version\n",
+		        xt_params->program_name, me->name, me->revision);
+		exit(1);
+	}
+
+	if (me->size != XT_ALIGN(me->size)) {
+		fprintf(stderr, "%s: target \"%s\" has invalid size %u.\n",
+		        xt_params->program_name, me->name,
+		        (unsigned int)me->size);
+		exit(1);
+	}
+
+	if (strcmp(me->version, XTABLES_VERSION) != 0) {
+		fprintf(stderr, "%s: target \"%s\" has version \"%s\", "
+		        "but \"%s\" is required.\n",
+			xt_params->program_name, me->name,
+			me->version, XTABLES_VERSION);
+		exit(1);
+	}
+
+	if (strlen(me->name) >= XT_EXTENSION_MAXNAMELEN) {
+		fprintf(stderr, "%s: target `%s' has invalid name\n",
+			xt_params->program_name, me->name);
+		exit(1);
+	}
+
+	if (me->real_name && strlen(me->real_name) >= XT_EXTENSION_MAXNAMELEN) {
+		fprintf(stderr, "%s: target `%s' has invalid real name\n",
+			xt_params->program_name, me->real_name);
+		exit(1);
+	}
+
+	if (me->family >= NPROTO) {
+		fprintf(stderr,
+			"%s: BUG: target %s has invalid protocol family\n",
+			xt_params->program_name, me->name);
+		exit(1);
+	}
+
+	if (me->x6_options != NULL)
+		xtables_option_metavalidate(me->name, me->x6_options);
+	if (me->extra_opts != NULL)
+		xtables_check_options(me->name, me->extra_opts);
+
+	/* ignore not interested target */
+	if (me->family != afinfo->family && me->family != AF_UNSPEC)
+		return;
+
+	/* order into linked list of targets pending full registration */
+	for (pos = &xtables_pending_targets; *pos; pos = &(*pos)->next) {
+		/* group by name */
+		if (!extension_cmp(me->name, (*pos)->name, (*pos)->family)) {
+			if (seen_myself)
+				break; /* end of own group, append to it */
+			continue;
+		}
+		/* found own group */
+		seen_myself = true;
+		if (xtables_target_prefer(me, *pos) >= 0)
+			break; /* put preferred items first in group */
+	}
+	/* if own group was not found, prepend item */
+	if (!*pos && !seen_myself)
+		pos = &xtables_pending_targets;
+
+	me->next = *pos;
+	*pos = me;
+#ifdef DEBUG
+	printf("%s: inserted target %s (family %d, revision %d):\n",
+			__func__, me->name, me->family, me->revision);
+	for (pos = &xtables_pending_targets; *pos; pos = &(*pos)->next) {
+		printf("%s:\ttarget %s (family %d, revision %d)\n", __func__,
+		       (*pos)->name, (*pos)->family, (*pos)->revision);
+	}
+#endif
+}
+
+static bool xtables_fully_register_pending_target(struct xtables_target *me,
+						  struct xtables_target *prev)
+{
+	struct xtables_target **i;
+	const char *rn;
+
+	if (strcmp(me->name, "standard") != 0) {
+		/* See if new target can be used. */
+		rn = (me->real_name != NULL) ? me->real_name : me->name;
+		if (!compatible_target_revision(rn, me->revision))
+			return false;
+	}
+
+	if (!prev) {
+		/* Prepend to list. */
+		i = &xtables_targets;
+		prev = xtables_targets;
+	} else {
+		/* Append it */
+		i = &prev->next;
+		prev = prev->next;
+	}
+
+	me->next = prev;
+	*i = me;
+
+	me->t = NULL;
+	me->tflags = 0;
+
+	return true;
+}
+
+void xtables_register_targets(struct xtables_target *target, unsigned int n)
+{
+	int i;
+
+	for (i = 0; i < n; i++)
+		xtables_register_target(&target[i]);
+}
+
+/* receives a list of xtables_rule_match, release them */
+void xtables_rule_matches_free(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;
+}
+
+/**
+ * xtables_param_act - act on condition
+ * @status:	a constant from enum xtables_exittype
+ *
+ * %XTF_ONLY_ONCE: print error message that option may only be used once.
+ * @p1:		module name (e.g. "mark")
+ * @p2(...):	option in conflict (e.g. "--mark")
+ * @p3(...):	condition to match on (see extensions/ for examples)
+ *
+ * %XTF_NO_INVERT: option does not support inversion
+ * @p1:		module name
+ * @p2:		option in conflict
+ * @p3:		condition to match on
+ *
+ * %XTF_BAD_VALUE: bad value for option
+ * @p1:		module name
+ * @p2:		option with which the problem occurred (e.g. "--mark")
+ * @p3:		string the user passed in (e.g. "99999999999999")
+ *
+ * %XTF_ONE_ACTION: two mutually exclusive actions have been specified
+ * @p1:		module name
+ *
+ * Displays an error message and exits the program.
+ */
+void xtables_param_act(unsigned int status, const char *p1, ...)
+{
+	const char *p2, *p3;
+	va_list args;
+	bool b;
+
+	va_start(args, p1);
+
+	switch (status) {
+	case XTF_ONLY_ONCE:
+		p2 = va_arg(args, const char *);
+		b  = va_arg(args, unsigned int);
+		if (!b) {
+			va_end(args);
+			return;
+		}
+		xt_params->exit_err(PARAMETER_PROBLEM,
+		           "%s: \"%s\" option may only be specified once",
+		           p1, p2);
+		break;
+	case XTF_NO_INVERT:
+		p2 = va_arg(args, const char *);
+		b  = va_arg(args, unsigned int);
+		if (!b) {
+			va_end(args);
+			return;
+		}
+		xt_params->exit_err(PARAMETER_PROBLEM,
+		           "%s: \"%s\" option cannot be inverted", p1, p2);
+		break;
+	case XTF_BAD_VALUE:
+		p2 = va_arg(args, const char *);
+		p3 = va_arg(args, const char *);
+		xt_params->exit_err(PARAMETER_PROBLEM,
+		           "%s: Bad value for \"%s\" option: \"%s\"",
+		           p1, p2, p3);
+		break;
+	case XTF_ONE_ACTION:
+		b = va_arg(args, unsigned int);
+		if (!b) {
+			va_end(args);
+			return;
+		}
+		xt_params->exit_err(PARAMETER_PROBLEM,
+		           "%s: At most one action is possible", p1);
+		break;
+	default:
+		xt_params->exit_err(status, p1, args);
+		break;
+	}
+
+	va_end(args);
+}
+
+const char *xtables_ipaddr_to_numeric(const struct in_addr *addrp)
+{
+	static char buf[20];
+	const unsigned char *bytep = (const void *)&addrp->s_addr;
+
+	sprintf(buf, "%u.%u.%u.%u", bytep[0], bytep[1], bytep[2], bytep[3]);
+	return buf;
+}
+
+static const char *ipaddr_to_host(const struct in_addr *addr)
+{
+	static char hostname[NI_MAXHOST];
+	struct sockaddr_in saddr = {
+		.sin_family = AF_INET,
+		.sin_addr = *addr,
+	};
+	int err;
+
+
+	err = getnameinfo((const void *)&saddr, sizeof(struct sockaddr_in),
+		       hostname, sizeof(hostname) - 1, NULL, 0, 0);
+	if (err != 0)
+		return NULL;
+
+	return hostname;
+}
+
+static const char *ipaddr_to_network(const struct in_addr *addr)
+{
+	struct netent *net;
+
+	if ((net = getnetbyaddr(ntohl(addr->s_addr), AF_INET)) != NULL)
+		return net->n_name;
+
+	return NULL;
+}
+
+const char *xtables_ipaddr_to_anyname(const struct in_addr *addr)
+{
+	const char *name;
+
+	if ((name = ipaddr_to_host(addr)) != NULL ||
+	    (name = ipaddr_to_network(addr)) != NULL)
+		return name;
+
+	return xtables_ipaddr_to_numeric(addr);
+}
+
+int xtables_ipmask_to_cidr(const struct in_addr *mask)
+{
+	uint32_t maskaddr, bits;
+	int i;
+
+	maskaddr = ntohl(mask->s_addr);
+	/* shortcut for /32 networks */
+	if (maskaddr == 0xFFFFFFFFL)
+		return 32;
+
+	i = 32;
+	bits = 0xFFFFFFFEL;
+	while (--i >= 0 && maskaddr != bits)
+		bits <<= 1;
+	if (i >= 0)
+		return i;
+
+	/* this mask cannot be converted to CIDR notation */
+	return -1;
+}
+
+const char *xtables_ipmask_to_numeric(const struct in_addr *mask)
+{
+	static char buf[20];
+	uint32_t cidr;
+
+	cidr = xtables_ipmask_to_cidr(mask);
+	if (cidr == (unsigned int)-1) {
+		/* mask was not a decent combination of 1's and 0's */
+		sprintf(buf, "/%s", xtables_ipaddr_to_numeric(mask));
+		return buf;
+	} else if (cidr == 32) {
+		/* we don't want to see "/32" */
+		return "";
+	}
+
+	sprintf(buf, "/%d", cidr);
+	return buf;
+}
+
+static struct in_addr *__numeric_to_ipaddr(const char *dotted, bool is_mask)
+{
+	static struct in_addr addr;
+	unsigned char *addrp;
+	unsigned int onebyte;
+	char buf[20], *p, *q;
+	int i;
+
+	/* copy dotted string, because we need to modify it */
+	strncpy(buf, dotted, sizeof(buf) - 1);
+	buf[sizeof(buf) - 1] = '\0';
+	addrp = (void *)&addr.s_addr;
+
+	p = buf;
+	for (i = 0; i < 3; ++i) {
+		if ((q = strchr(p, '.')) == NULL) {
+			if (is_mask)
+				return NULL;
+
+			/* autocomplete, this is a network address */
+			if (!xtables_strtoui(p, NULL, &onebyte, 0, UINT8_MAX))
+				return NULL;
+
+			addrp[i] = onebyte;
+			while (i < 3)
+				addrp[++i] = 0;
+
+			return &addr;
+		}
+
+		*q = '\0';
+		if (!xtables_strtoui(p, NULL, &onebyte, 0, UINT8_MAX))
+			return NULL;
+
+		addrp[i] = onebyte;
+		p = q + 1;
+	}
+
+	/* we have checked 3 bytes, now we check the last one */
+	if (!xtables_strtoui(p, NULL, &onebyte, 0, UINT8_MAX))
+		return NULL;
+
+	addrp[3] = onebyte;
+	return &addr;
+}
+
+struct in_addr *xtables_numeric_to_ipaddr(const char *dotted)
+{
+	return __numeric_to_ipaddr(dotted, false);
+}
+
+struct in_addr *xtables_numeric_to_ipmask(const char *dotted)
+{
+	return __numeric_to_ipaddr(dotted, true);
+}
+
+static struct in_addr *network_to_ipaddr(const char *name)
+{
+	static struct in_addr addr;
+	struct netent *net;
+
+	if ((net = getnetbyname(name)) != NULL) {
+		if (net->n_addrtype != AF_INET)
+			return NULL;
+		addr.s_addr = htonl(net->n_net);
+		return &addr;
+	}
+
+	return NULL;
+}
+
+static struct in_addr *host_to_ipaddr(const char *name, unsigned int *naddr)
+{
+	struct in_addr *addr;
+	struct addrinfo hints;
+	struct addrinfo *res, *p;
+	int err;
+	unsigned int i;
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_family   = AF_INET;
+	hints.ai_socktype = SOCK_RAW;
+
+	*naddr = 0;
+	err = getaddrinfo(name, NULL, &hints, &res);
+	if (err != 0)
+		return NULL;
+	for (p = res; p != NULL; p = p->ai_next)
+		++*naddr;
+	addr = xtables_calloc(*naddr, sizeof(struct in_addr));
+	for (i = 0, p = res; p != NULL; p = p->ai_next)
+		memcpy(&addr[i++],
+		       &((const struct sockaddr_in *)p->ai_addr)->sin_addr,
+		       sizeof(struct in_addr));
+	freeaddrinfo(res);
+	return addr;
+}
+
+static struct in_addr *
+ipparse_hostnetwork(const char *name, unsigned int *naddrs)
+{
+	struct in_addr *addrptmp, *addrp;
+
+	if ((addrptmp = xtables_numeric_to_ipaddr(name)) != NULL ||
+	    (addrptmp = network_to_ipaddr(name)) != NULL) {
+		addrp = xtables_malloc(sizeof(struct in_addr));
+		memcpy(addrp, addrptmp, sizeof(*addrp));
+		*naddrs = 1;
+		return addrp;
+	}
+	if ((addrptmp = host_to_ipaddr(name, naddrs)) != NULL)
+		return addrptmp;
+
+	xt_params->exit_err(PARAMETER_PROBLEM, "host/network `%s' not found", name);
+}
+
+static struct in_addr *parse_ipmask(const char *mask)
+{
+	static struct in_addr maskaddr;
+	struct in_addr *addrp;
+	unsigned int bits;
+
+	if (mask == NULL) {
+		/* no mask at all defaults to 32 bits */
+		maskaddr.s_addr = 0xFFFFFFFF;
+		return &maskaddr;
+	}
+	if ((addrp = xtables_numeric_to_ipmask(mask)) != NULL)
+		/* dotted_to_addr already returns a network byte order addr */
+		return addrp;
+	if (!xtables_strtoui(mask, NULL, &bits, 0, 32))
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			   "invalid mask `%s' specified", mask);
+	if (bits != 0) {
+		maskaddr.s_addr = htonl(0xFFFFFFFF << (32 - bits));
+		return &maskaddr;
+	}
+
+	maskaddr.s_addr = 0U;
+	return &maskaddr;
+}
+
+void xtables_ipparse_multiple(const char *name, struct in_addr **addrpp,
+                              struct in_addr **maskpp, unsigned int *naddrs)
+{
+	struct in_addr *addrp;
+	char buf[256], *p, *next;
+	unsigned int len, i, j, n, count = 1;
+	const char *loop = name;
+
+	while ((loop = strchr(loop, ',')) != NULL) {
+		++count;
+		++loop; /* skip ',' */
+	}
+
+	*addrpp = xtables_malloc(sizeof(struct in_addr) * count);
+	*maskpp = xtables_malloc(sizeof(struct in_addr) * count);
+
+	loop = name;
+
+	for (i = 0; i < count; ++i) {
+		while (isspace(*loop))
+			++loop;
+		next = strchr(loop, ',');
+		if (next != NULL)
+			len = next - loop;
+		else
+			len = strlen(loop);
+		if (len > sizeof(buf) - 1)
+			xt_params->exit_err(PARAMETER_PROBLEM,
+				"Hostname too long");
+
+		strncpy(buf, loop, len);
+		buf[len] = '\0';
+		if ((p = strrchr(buf, '/')) != NULL) {
+			*p = '\0';
+			addrp = parse_ipmask(p + 1);
+		} else {
+			addrp = parse_ipmask(NULL);
+		}
+		memcpy(*maskpp + i, addrp, sizeof(*addrp));
+
+		/* if a null mask is given, the name is ignored, like in "any/0" */
+		if ((*maskpp + i)->s_addr == 0)
+			/*
+			 * A bit pointless to process multiple addresses
+			 * in this case...
+			 */
+			strcpy(buf, "0.0.0.0");
+
+		addrp = ipparse_hostnetwork(buf, &n);
+		if (n > 1) {
+			count += n - 1;
+			*addrpp = xtables_realloc(*addrpp,
+			          sizeof(struct in_addr) * count);
+			*maskpp = xtables_realloc(*maskpp,
+			          sizeof(struct in_addr) * count);
+			for (j = 0; j < n; ++j)
+				/* for each new addr */
+				memcpy(*addrpp + i + j, addrp + j,
+				       sizeof(*addrp));
+			for (j = 1; j < n; ++j)
+				/* for each new mask */
+				memcpy(*maskpp + i + j, *maskpp + i,
+				       sizeof(*addrp));
+			i += n - 1;
+		} else {
+			memcpy(*addrpp + i, addrp, sizeof(*addrp));
+		}
+		/* free what ipparse_hostnetwork had allocated: */
+		free(addrp);
+		if (next == NULL)
+			break;
+		loop = next + 1;
+	}
+	*naddrs = count;
+	for (i = 0; i < count; ++i)
+		(*addrpp+i)->s_addr &= (*maskpp+i)->s_addr;
+}
+
+
+/**
+ * xtables_ipparse_any - transform arbitrary name to in_addr
+ *
+ * Possible inputs (pseudo regex):
+ * 	m{^($hostname|$networkname|$ipaddr)(/$mask)?}
+ * "1.2.3.4/5", "1.2.3.4", "hostname", "networkname"
+ */
+void xtables_ipparse_any(const char *name, struct in_addr **addrpp,
+                         struct in_addr *maskp, unsigned int *naddrs)
+{
+	unsigned int i, j, k, n;
+	struct in_addr *addrp;
+	char buf[256], *p;
+
+	strncpy(buf, name, sizeof(buf) - 1);
+	buf[sizeof(buf) - 1] = '\0';
+	if ((p = strrchr(buf, '/')) != NULL) {
+		*p = '\0';
+		addrp = parse_ipmask(p + 1);
+	} else {
+		addrp = parse_ipmask(NULL);
+	}
+	memcpy(maskp, addrp, sizeof(*maskp));
+
+	/* if a null mask is given, the name is ignored, like in "any/0" */
+	if (maskp->s_addr == 0U)
+		strcpy(buf, "0.0.0.0");
+
+	addrp = *addrpp = ipparse_hostnetwork(buf, naddrs);
+	n = *naddrs;
+	for (i = 0, j = 0; i < n; ++i) {
+		addrp[j++].s_addr &= maskp->s_addr;
+		for (k = 0; k < j - 1; ++k)
+			if (addrp[k].s_addr == addrp[j-1].s_addr) {
+				/*
+				 * Nuke the dup by copying an address from the
+				 * tail here, and check the current position
+				 * again (--j).
+				 */
+				memcpy(&addrp[--j], &addrp[--*naddrs],
+				       sizeof(struct in_addr));
+				break;
+			}
+	}
+}
+
+const char *xtables_ip6addr_to_numeric(const struct in6_addr *addrp)
+{
+	/* 0000:0000:0000:0000:0000:0000:000.000.000.000
+	 * 0000:0000:0000:0000:0000:0000:0000:0000 */
+	static char buf[50+1];
+	return inet_ntop(AF_INET6, addrp, buf, sizeof(buf));
+}
+
+static const char *ip6addr_to_host(const struct in6_addr *addr)
+{
+	static char hostname[NI_MAXHOST];
+	struct sockaddr_in6 saddr;
+	int err;
+
+	memset(&saddr, 0, sizeof(struct sockaddr_in6));
+	memcpy(&saddr.sin6_addr, addr, sizeof(*addr));
+	saddr.sin6_family = AF_INET6;
+
+	err = getnameinfo((const void *)&saddr, sizeof(struct sockaddr_in6),
+			hostname, sizeof(hostname) - 1, NULL, 0, 0);
+	if (err != 0)
+		return NULL;
+
+	return hostname;
+}
+
+const char *xtables_ip6addr_to_anyname(const struct in6_addr *addr)
+{
+	const char *name;
+
+	if ((name = ip6addr_to_host(addr)) != NULL)
+		return name;
+
+	return xtables_ip6addr_to_numeric(addr);
+}
+
+int xtables_ip6mask_to_cidr(const struct in6_addr *k)
+{
+	unsigned int bits = 0;
+	uint32_t a, b, c, d;
+
+	a = ntohl(k->s6_addr32[0]);
+	b = ntohl(k->s6_addr32[1]);
+	c = ntohl(k->s6_addr32[2]);
+	d = ntohl(k->s6_addr32[3]);
+	while (a & 0x80000000U) {
+		++bits;
+		a <<= 1;
+		a  |= (b >> 31) & 1;
+		b <<= 1;
+		b  |= (c >> 31) & 1;
+		c <<= 1;
+		c  |= (d >> 31) & 1;
+		d <<= 1;
+	}
+	if (a != 0 || b != 0 || c != 0 || d != 0)
+		return -1;
+	return bits;
+}
+
+const char *xtables_ip6mask_to_numeric(const struct in6_addr *addrp)
+{
+	static char buf[50+2];
+	int l = xtables_ip6mask_to_cidr(addrp);
+
+	if (l == -1) {
+		strcpy(buf, "/");
+		strcat(buf, xtables_ip6addr_to_numeric(addrp));
+		return buf;
+	}
+	/* we don't want to see "/128" */
+	if (l == 128)
+		return "";
+	else
+		sprintf(buf, "/%d", l);
+	return buf;
+}
+
+struct in6_addr *xtables_numeric_to_ip6addr(const char *num)
+{
+	static struct in6_addr ap;
+	int err;
+
+	if ((err = inet_pton(AF_INET6, num, &ap)) == 1)
+		return &ap;
+
+	return NULL;
+}
+
+static struct in6_addr *
+host_to_ip6addr(const char *name, unsigned int *naddr)
+{
+	struct in6_addr *addr;
+	struct addrinfo hints;
+	struct addrinfo *res, *p;
+	int err;
+	unsigned int i;
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_family   = AF_INET6;
+	hints.ai_socktype = SOCK_RAW;
+
+	*naddr = 0;
+	err = getaddrinfo(name, NULL, &hints, &res);
+	if (err != 0)
+		return NULL;
+	/* Find length of address chain */
+	for (p = res; p != NULL; p = p->ai_next)
+		++*naddr;
+	/* Copy each element of the address chain */
+	addr = xtables_calloc(*naddr, sizeof(struct in6_addr));
+	for (i = 0, p = res; p != NULL; p = p->ai_next)
+		memcpy(&addr[i++],
+		       &((const struct sockaddr_in6 *)p->ai_addr)->sin6_addr,
+		       sizeof(struct in6_addr));
+	freeaddrinfo(res);
+	return addr;
+}
+
+static struct in6_addr *network_to_ip6addr(const char *name)
+{
+	/*	abort();*/
+	/* TODO: not implemented yet, but the exception breaks the
+	 *       name resolvation */
+	return NULL;
+}
+
+static struct in6_addr *
+ip6parse_hostnetwork(const char *name, unsigned int *naddrs)
+{
+	struct in6_addr *addrp, *addrptmp;
+
+	if ((addrptmp = xtables_numeric_to_ip6addr(name)) != NULL ||
+	    (addrptmp = network_to_ip6addr(name)) != NULL) {
+		addrp = xtables_malloc(sizeof(struct in6_addr));
+		memcpy(addrp, addrptmp, sizeof(*addrp));
+		*naddrs = 1;
+		return addrp;
+	}
+	if ((addrp = host_to_ip6addr(name, naddrs)) != NULL)
+		return addrp;
+
+	xt_params->exit_err(PARAMETER_PROBLEM, "host/network `%s' not found", name);
+}
+
+static struct in6_addr *parse_ip6mask(char *mask)
+{
+	static struct in6_addr maskaddr;
+	struct in6_addr *addrp;
+	unsigned int bits;
+
+	if (mask == NULL) {
+		/* no mask at all defaults to 128 bits */
+		memset(&maskaddr, 0xff, sizeof maskaddr);
+		return &maskaddr;
+	}
+	if ((addrp = xtables_numeric_to_ip6addr(mask)) != NULL)
+		return addrp;
+	if (!xtables_strtoui(mask, NULL, &bits, 0, 128))
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			   "invalid mask `%s' specified", mask);
+	if (bits != 0) {
+		char *p = (void *)&maskaddr;
+		memset(p, 0xff, bits / 8);
+		memset(p + ((bits + 7) / 8), 0, (128 - bits) / 8);
+		if (bits < 128)
+			p[bits/8] = 0xff << (8 - (bits & 7));
+		return &maskaddr;
+	}
+
+	memset(&maskaddr, 0, sizeof(maskaddr));
+	return &maskaddr;
+}
+
+void
+xtables_ip6parse_multiple(const char *name, struct in6_addr **addrpp,
+		      struct in6_addr **maskpp, unsigned int *naddrs)
+{
+	static const struct in6_addr zero_addr;
+	struct in6_addr *addrp;
+	char buf[256], *p, *next;
+	unsigned int len, i, j, n, count = 1;
+	const char *loop = name;
+
+	while ((loop = strchr(loop, ',')) != NULL) {
+		++count;
+		++loop; /* skip ',' */
+	}
+
+	*addrpp = xtables_malloc(sizeof(struct in6_addr) * count);
+	*maskpp = xtables_malloc(sizeof(struct in6_addr) * count);
+
+	loop = name;
+
+	for (i = 0; i < count /*NB: count can grow*/; ++i) {
+		while (isspace(*loop))
+			++loop;
+		next = strchr(loop, ',');
+		if (next != NULL)
+			len = next - loop;
+		else
+			len = strlen(loop);
+		if (len > sizeof(buf) - 1)
+			xt_params->exit_err(PARAMETER_PROBLEM,
+				"Hostname too long");
+
+		strncpy(buf, loop, len);
+		buf[len] = '\0';
+		if ((p = strrchr(buf, '/')) != NULL) {
+			*p = '\0';
+			addrp = parse_ip6mask(p + 1);
+		} else {
+			addrp = parse_ip6mask(NULL);
+		}
+		memcpy(*maskpp + i, addrp, sizeof(*addrp));
+
+		/* if a null mask is given, the name is ignored, like in "any/0" */
+		if (memcmp(*maskpp + i, &zero_addr, sizeof(zero_addr)) == 0)
+			strcpy(buf, "::");
+
+		addrp = ip6parse_hostnetwork(buf, &n);
+		if (n > 1) {
+			count += n - 1;
+			*addrpp = xtables_realloc(*addrpp,
+			          sizeof(struct in6_addr) * count);
+			*maskpp = xtables_realloc(*maskpp,
+			          sizeof(struct in6_addr) * count);
+			for (j = 0; j < n; ++j)
+				/* for each new addr */
+				memcpy(*addrpp + i + j, addrp + j,
+				       sizeof(*addrp));
+			for (j = 1; j < n; ++j)
+				/* for each new mask */
+				memcpy(*maskpp + i + j, *maskpp + i,
+				       sizeof(*addrp));
+			i += n - 1;
+		} else {
+			memcpy(*addrpp + i, addrp, sizeof(*addrp));
+		}
+		/* free what ip6parse_hostnetwork had allocated: */
+		free(addrp);
+		if (next == NULL)
+			break;
+		loop = next + 1;
+	}
+	*naddrs = count;
+	for (i = 0; i < count; ++i)
+		for (j = 0; j < 4; ++j)
+			(*addrpp+i)->s6_addr32[j] &= (*maskpp+i)->s6_addr32[j];
+}
+
+void xtables_ip6parse_any(const char *name, struct in6_addr **addrpp,
+                          struct in6_addr *maskp, unsigned int *naddrs)
+{
+	static const struct in6_addr zero_addr;
+	struct in6_addr *addrp;
+	unsigned int i, j, k, n;
+	char buf[256], *p;
+
+	strncpy(buf, name, sizeof(buf) - 1);
+	buf[sizeof(buf)-1] = '\0';
+	if ((p = strrchr(buf, '/')) != NULL) {
+		*p = '\0';
+		addrp = parse_ip6mask(p + 1);
+	} else {
+		addrp = parse_ip6mask(NULL);
+	}
+	memcpy(maskp, addrp, sizeof(*maskp));
+
+	/* if a null mask is given, the name is ignored, like in "any/0" */
+	if (memcmp(maskp, &zero_addr, sizeof(zero_addr)) == 0)
+		strcpy(buf, "::");
+
+	addrp = *addrpp = ip6parse_hostnetwork(buf, naddrs);
+	n = *naddrs;
+	for (i = 0, j = 0; i < n; ++i) {
+		for (k = 0; k < 4; ++k)
+			addrp[j].s6_addr32[k] &= maskp->s6_addr32[k];
+		++j;
+		for (k = 0; k < j - 1; ++k)
+			if (IN6_ARE_ADDR_EQUAL(&addrp[k], &addrp[j - 1])) {
+				/*
+				 * Nuke the dup by copying an address from the
+				 * tail here, and check the current position
+				 * again (--j).
+				 */
+				memcpy(&addrp[--j], &addrp[--*naddrs],
+				       sizeof(struct in_addr));
+				break;
+			}
+	}
+}
+
+void xtables_save_string(const char *value)
+{
+	static const char no_quote_chars[] = "_-0123456789"
+		"abcdefghijklmnopqrstuvwxyz"
+		"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+	static const char escape_chars[] = "\"\\'";
+	size_t length;
+	const char *p;
+
+	length = strspn(value, no_quote_chars);
+	if (length > 0 && value[length] == 0) {
+		/* no quoting required */
+		putchar(' ');
+		fputs(value, stdout);
+	} else {
+		/* there is at least one dangerous character in the
+		   value, which we have to quote.  Write double quotes
+		   around the value and escape special characters with
+		   a backslash */
+		printf(" \"");
+
+		for (p = strpbrk(value, escape_chars); p != NULL;
+		     p = strpbrk(value, escape_chars)) {
+			if (p > value)
+				fwrite(value, 1, p - value, stdout);
+			putchar('\\');
+			putchar(*p);
+			value = p + 1;
+		}
+
+		/* print the rest and finish the double quoted
+		   string */
+		fputs(value, stdout);
+		putchar('\"');
+	}
+}
+
+const struct xtables_pprot xtables_chain_protos[] = {
+	{"tcp",       IPPROTO_TCP},
+	{"sctp",      IPPROTO_SCTP},
+	{"udp",       IPPROTO_UDP},
+	{"udplite",   IPPROTO_UDPLITE},
+	{"icmp",      IPPROTO_ICMP},
+	{"icmpv6",    IPPROTO_ICMPV6},
+	{"ipv6-icmp", IPPROTO_ICMPV6},
+	{"esp",       IPPROTO_ESP},
+	{"ah",        IPPROTO_AH},
+	{"ipv6-mh",   IPPROTO_MH},
+	{"mh",        IPPROTO_MH},
+	{"all",       0},
+	{NULL},
+};
+
+uint16_t
+xtables_parse_protocol(const char *s)
+{
+	const struct protoent *pent;
+	unsigned int proto, i;
+
+	if (xtables_strtoui(s, NULL, &proto, 0, UINT8_MAX))
+		return proto;
+
+	/* first deal with the special case of 'all' to prevent
+	 * people from being able to redefine 'all' in nsswitch
+	 * and/or provoke expensive [not working] ldap/nis/...
+	 * lookups */
+	if (strcmp(s, "all") == 0)
+		return 0;
+
+	pent = getprotobyname(s);
+	if (pent != NULL)
+		return pent->p_proto;
+
+	for (i = 0; i < ARRAY_SIZE(xtables_chain_protos); ++i) {
+		if (xtables_chain_protos[i].name == NULL)
+			continue;
+		if (strcmp(s, xtables_chain_protos[i].name) == 0)
+			return xtables_chain_protos[i].num;
+	}
+	xt_params->exit_err(PARAMETER_PROBLEM,
+		"unknown protocol \"%s\" specified", s);
+	return -1;
+}
+
+void xtables_print_num(uint64_t number, unsigned int format)
+{
+	if (!(format & FMT_KILOMEGAGIGA)) {
+		printf(FMT("%8llu ","%llu "), (unsigned long long)number);
+		return;
+	}
+	if (number <= 99999) {
+		printf(FMT("%5llu ","%llu "), (unsigned long long)number);
+		return;
+	}
+	number = (number + 500) / 1000;
+	if (number <= 9999) {
+		printf(FMT("%4lluK ","%lluK "), (unsigned long long)number);
+		return;
+	}
+	number = (number + 500) / 1000;
+	if (number <= 9999) {
+		printf(FMT("%4lluM ","%lluM "), (unsigned long long)number);
+		return;
+	}
+	number = (number + 500) / 1000;
+	if (number <= 9999) {
+		printf(FMT("%4lluG ","%lluG "), (unsigned long long)number);
+		return;
+	}
+	number = (number + 500) / 1000;
+	printf(FMT("%4lluT ","%lluT "), (unsigned long long)number);
+}
+
+#include <netinet/ether.h>
+
+static const unsigned char mac_type_unicast[ETH_ALEN] =   {};
+static const unsigned char msk_type_unicast[ETH_ALEN] =   {1};
+static const unsigned char mac_type_multicast[ETH_ALEN] = {1};
+static const unsigned char msk_type_multicast[ETH_ALEN] = {1};
+#define ALL_ONE_MAC {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
+static const unsigned char mac_type_broadcast[ETH_ALEN] = ALL_ONE_MAC;
+static const unsigned char msk_type_broadcast[ETH_ALEN] = ALL_ONE_MAC;
+static const unsigned char mac_type_bridge_group[ETH_ALEN] = {0x01, 0x80, 0xc2};
+static const unsigned char msk_type_bridge_group[ETH_ALEN] = ALL_ONE_MAC;
+#undef ALL_ONE_MAC
+
+int xtables_parse_mac_and_mask(const char *from, void *to, void *mask)
+{
+	char *p;
+	int i;
+	struct ether_addr *addr = NULL;
+
+	if (strcasecmp(from, "Unicast") == 0) {
+		memcpy(to, mac_type_unicast, ETH_ALEN);
+		memcpy(mask, msk_type_unicast, ETH_ALEN);
+		return 0;
+	}
+	if (strcasecmp(from, "Multicast") == 0) {
+		memcpy(to, mac_type_multicast, ETH_ALEN);
+		memcpy(mask, msk_type_multicast, ETH_ALEN);
+		return 0;
+	}
+	if (strcasecmp(from, "Broadcast") == 0) {
+		memcpy(to, mac_type_broadcast, ETH_ALEN);
+		memcpy(mask, msk_type_broadcast, ETH_ALEN);
+		return 0;
+	}
+	if (strcasecmp(from, "BGA") == 0) {
+		memcpy(to, mac_type_bridge_group, ETH_ALEN);
+		memcpy(mask, msk_type_bridge_group, ETH_ALEN);
+		return 0;
+	}
+	if ( (p = strrchr(from, '/')) != NULL) {
+		*p = '\0';
+		if (!(addr = ether_aton(p + 1)))
+			return -1;
+		memcpy(mask, addr, ETH_ALEN);
+	} else
+		memset(mask, 0xff, ETH_ALEN);
+	if (!(addr = ether_aton(from)))
+		return -1;
+	memcpy(to, addr, ETH_ALEN);
+	for (i = 0; i < ETH_ALEN; i++)
+		((char *)to)[i] &= ((char *)mask)[i];
+	return 0;
+}
+
+int xtables_print_well_known_mac_and_mask(const void *mac, const void *mask)
+{
+	if (!memcmp(mac, mac_type_unicast, ETH_ALEN) &&
+	    !memcmp(mask, msk_type_unicast, ETH_ALEN))
+		printf("Unicast");
+	else if (!memcmp(mac, mac_type_multicast, ETH_ALEN) &&
+	         !memcmp(mask, msk_type_multicast, ETH_ALEN))
+		printf("Multicast");
+	else if (!memcmp(mac, mac_type_broadcast, ETH_ALEN) &&
+	         !memcmp(mask, msk_type_broadcast, ETH_ALEN))
+		printf("Broadcast");
+	else if (!memcmp(mac, mac_type_bridge_group, ETH_ALEN) &&
+	         !memcmp(mask, msk_type_bridge_group, ETH_ALEN))
+		printf("BGA");
+	else
+		return -1;
+	return 0;
+}
+
+void xtables_print_mac(const unsigned char *macaddress)
+{
+	unsigned int i;
+
+	printf("%02x", macaddress[0]);
+	for (i = 1; i < 6; ++i)
+		printf(":%02x", macaddress[i]);
+}
+
+void xtables_print_mac_and_mask(const unsigned char *mac, const unsigned char *mask)
+{
+	static const char hlpmsk[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+	xtables_print_mac(mac);
+
+	if (memcmp(mask, hlpmsk, 6) == 0)
+		return;
+
+	printf("/");
+	xtables_print_mac(mask);
+}
+
+void xtables_parse_val_mask(struct xt_option_call *cb,
+			    unsigned int *val, unsigned int *mask,
+			    const struct xtables_lmap *lmap)
+{
+	char *end;
+
+	*mask = ~0U;
+
+	if (!xtables_strtoui(cb->arg, &end, val, 0, UINT32_MAX)) {
+		if (lmap)
+			goto name2val;
+		else
+			goto bad_val;
+	}
+
+	if (*end == '\0')
+		return;
+
+	if (*end != '/') {
+		if (lmap)
+			goto name2val;
+		else
+			goto garbage;
+	}
+
+	if (!xtables_strtoui(end + 1, &end, mask, 0, UINT32_MAX))
+		goto bad_val;
+
+	if (*end == '\0')
+		return;
+
+garbage:
+	xt_params->exit_err(PARAMETER_PROBLEM,
+			"%s: trailing garbage after value "
+			"for option \"--%s\".\n",
+			cb->ext_name, cb->entry->name);
+
+bad_val:
+	xt_params->exit_err(PARAMETER_PROBLEM,
+			"%s: bad integer value for option \"--%s\", "
+			"or out of range.\n",
+			cb->ext_name, cb->entry->name);
+
+name2val:
+	*val = xtables_lmap_name2id(lmap, cb->arg);
+	if ((int)*val == -1)
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			"%s: could not map name %s to an integer value "
+			"for option \"--%s\".\n",
+			cb->ext_name, cb->arg, cb->entry->name);
+}
+
+void xtables_print_val_mask(unsigned int val, unsigned int mask,
+			    const struct xtables_lmap *lmap)
+{
+	if (mask != ~0U) {
+		printf(" 0x%x/0x%x", val, mask);
+		return;
+	}
+
+	if (lmap) {
+		const char *name = xtables_lmap_id2name(lmap, val);
+
+		if (name) {
+			printf(" %s", name);
+			return;
+		}
+	}
+
+	printf(" 0x%x", val);
+}
+
+int kernel_version;
+
+void get_kernel_version(void)
+{
+	static struct utsname uts;
+	int x = 0, y = 0, z = 0;
+
+	if (uname(&uts) == -1) {
+		fprintf(stderr, "Unable to retrieve kernel version.\n");
+		xtables_free_opts(1);
+		exit(1);
+	}
+
+	sscanf(uts.release, "%d.%d.%d", &x, &y, &z);
+	kernel_version = LINUX_VERSION(x, y, z);
+}
+
+#include <linux/netfilter/nf_tables.h>
+
+struct xt_xlate {
+	struct {
+		char	*data;
+		int	size;
+		int	rem;
+		int	off;
+	} buf;
+	char comment[NFT_USERDATA_MAXLEN];
+};
+
+struct xt_xlate *xt_xlate_alloc(int size)
+{
+	struct xt_xlate *xl;
+
+	xl = malloc(sizeof(struct xt_xlate));
+	if (xl == NULL)
+		xtables_error(RESOURCE_PROBLEM, "OOM");
+
+	xl->buf.data = malloc(size);
+	if (xl->buf.data == NULL)
+		xtables_error(RESOURCE_PROBLEM, "OOM");
+
+	xl->buf.data[0] = '\0';
+	xl->buf.size = size;
+	xl->buf.rem = size;
+	xl->buf.off = 0;
+	xl->comment[0] = '\0';
+
+	return xl;
+}
+
+void xt_xlate_free(struct xt_xlate *xl)
+{
+	free(xl->buf.data);
+	free(xl);
+}
+
+void xt_xlate_add(struct xt_xlate *xl, const char *fmt, ...)
+{
+	va_list ap;
+	int len;
+
+	va_start(ap, fmt);
+	len = vsnprintf(xl->buf.data + xl->buf.off, xl->buf.rem, fmt, ap);
+	if (len < 0 || len >= xl->buf.rem)
+		xtables_error(RESOURCE_PROBLEM, "OOM");
+
+	va_end(ap);
+	xl->buf.rem -= len;
+	xl->buf.off += len;
+}
+
+void xt_xlate_add_comment(struct xt_xlate *xl, const char *comment)
+{
+	strncpy(xl->comment, comment, NFT_USERDATA_MAXLEN - 1);
+	xl->comment[NFT_USERDATA_MAXLEN - 1] = '\0';
+}
+
+const char *xt_xlate_get_comment(struct xt_xlate *xl)
+{
+	return xl->comment[0] ? xl->comment : NULL;
+}
+
+const char *xt_xlate_get(struct xt_xlate *xl)
+{
+	return xl->buf.data;
+}
diff --git a/libxtables/xtoptions.c b/libxtables/xtoptions.c
new file mode 100644
index 0000000..d329f2f
--- /dev/null
+++ b/libxtables/xtoptions.c
@@ -0,0 +1,1188 @@
+/*
+ *	Argument parser
+ *	Copyright © Jan Engelhardt, 2011
+ *
+ *	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.
+ */
+#include <ctype.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <netdb.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <arpa/inet.h>
+#include <netinet/ip.h>
+#include "xtables.h"
+#include "xshared.h"
+#ifndef IPTOS_NORMALSVC
+#	define IPTOS_NORMALSVC 0
+#endif
+
+#define XTOPT_MKPTR(cb) \
+	((void *)((char *)(cb)->data + (cb)->entry->ptroff))
+
+/**
+ * Simple key-value pairs for syslog levels
+ */
+struct syslog_level {
+	char name[8];
+	uint8_t level;
+};
+
+struct tos_value_mask {
+	uint8_t value, mask;
+};
+
+static const size_t xtopt_psize[] = {
+	/*
+	 * All types not listed here, and thus essentially being initialized to
+	 * zero have zero on purpose.
+	 */
+	[XTTYPE_UINT8]       = sizeof(uint8_t),
+	[XTTYPE_UINT16]      = sizeof(uint16_t),
+	[XTTYPE_UINT32]      = sizeof(uint32_t),
+	[XTTYPE_UINT64]      = sizeof(uint64_t),
+	[XTTYPE_UINT8RC]     = sizeof(uint8_t[2]),
+	[XTTYPE_UINT16RC]    = sizeof(uint16_t[2]),
+	[XTTYPE_UINT32RC]    = sizeof(uint32_t[2]),
+	[XTTYPE_UINT64RC]    = sizeof(uint64_t[2]),
+	[XTTYPE_DOUBLE]      = sizeof(double),
+	[XTTYPE_STRING]      = -1,
+	[XTTYPE_SYSLOGLEVEL] = sizeof(uint8_t),
+	[XTTYPE_HOST]        = sizeof(union nf_inet_addr),
+	[XTTYPE_HOSTMASK]    = sizeof(union nf_inet_addr),
+	[XTTYPE_PROTOCOL]    = sizeof(uint8_t),
+	[XTTYPE_PORT]        = sizeof(uint16_t),
+	[XTTYPE_PORTRC]      = sizeof(uint16_t[2]),
+	[XTTYPE_PLENMASK]    = sizeof(union nf_inet_addr),
+	[XTTYPE_ETHERMAC]    = sizeof(uint8_t[6]),
+};
+
+/**
+ * Creates getopt options from the x6-style option map, and assigns each a
+ * getopt id.
+ */
+struct option *
+xtables_options_xfrm(struct option *orig_opts, struct option *oldopts,
+		     const struct xt_option_entry *entry, unsigned int *offset)
+{
+	unsigned int num_orig, num_old = 0, num_new, i;
+	struct option *merge, *mp;
+
+	if (entry == NULL)
+		return oldopts;
+	for (num_orig = 0; orig_opts[num_orig].name != NULL; ++num_orig)
+		;
+	if (oldopts != NULL)
+		for (num_old = 0; oldopts[num_old].name != NULL; ++num_old)
+			;
+	for (num_new = 0; entry[num_new].name != NULL; ++num_new)
+		;
+
+	/*
+	 * Since @oldopts also has @orig_opts already (and does so at the
+	 * start), skip these entries.
+	 */
+	if (oldopts != NULL) {
+		oldopts += num_orig;
+		num_old -= num_orig;
+	}
+
+	merge = malloc(sizeof(*mp) * (num_orig + num_old + num_new + 1));
+	if (merge == NULL)
+		return NULL;
+
+	/* Let the base options -[ADI...] have precedence over everything */
+	memcpy(merge, orig_opts, sizeof(*mp) * num_orig);
+	mp = merge + num_orig;
+
+	/* Second, the new options */
+	xt_params->option_offset += XT_OPTION_OFFSET_SCALE;
+	*offset = xt_params->option_offset;
+
+	for (i = 0; i < num_new; ++i, ++mp, ++entry) {
+		mp->name         = entry->name;
+		mp->has_arg      = entry->type != XTTYPE_NONE;
+		mp->flag         = NULL;
+		mp->val          = entry->id + *offset;
+	}
+
+	/* Third, the old options */
+	if (oldopts != NULL) {
+		memcpy(mp, oldopts, sizeof(*mp) * num_old);
+		mp += num_old;
+	}
+	xtables_free_opts(0);
+
+	/* Clear trailing entry */
+	memset(mp, 0, sizeof(*mp));
+	return merge;
+}
+
+/**
+ * Give the upper limit for a certain type.
+ */
+static uintmax_t xtopt_max_by_type(enum xt_option_type type)
+{
+	switch (type) {
+	case XTTYPE_UINT8:
+	case XTTYPE_UINT8RC:
+		return UINT8_MAX;
+	case XTTYPE_UINT16:
+	case XTTYPE_UINT16RC:
+		return UINT16_MAX;
+	case XTTYPE_UINT32:
+	case XTTYPE_UINT32RC:
+		return UINT32_MAX;
+	case XTTYPE_UINT64:
+	case XTTYPE_UINT64RC:
+		return UINT64_MAX;
+	default:
+		return 0;
+	}
+}
+
+/**
+ * Return the size of a single entity based upon a type - predominantly an
+ * XTTYPE_UINT*RC type.
+ */
+static size_t xtopt_esize_by_type(enum xt_option_type type)
+{
+	switch (type) {
+	case XTTYPE_UINT8RC:
+		return xtopt_psize[XTTYPE_UINT8];
+	case XTTYPE_UINT16RC:
+		return xtopt_psize[XTTYPE_UINT16];
+	case XTTYPE_UINT32RC:
+		return xtopt_psize[XTTYPE_UINT32];
+	case XTTYPE_UINT64RC:
+		return xtopt_psize[XTTYPE_UINT64];
+	default:
+		return xtopt_psize[type];
+	}
+}
+
+/**
+ * Require a simple integer.
+ */
+static void xtopt_parse_int(struct xt_option_call *cb)
+{
+	const struct xt_option_entry *entry = cb->entry;
+	uintmax_t lmin = 0, lmax = xtopt_max_by_type(entry->type);
+	uintmax_t value;
+
+	if (cb->entry->min != 0)
+		lmin = cb->entry->min;
+	if (cb->entry->max != 0)
+		lmax = cb->entry->max;
+
+	if (!xtables_strtoul(cb->arg, NULL, &value, lmin, lmax))
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			"%s: bad value for option \"--%s\", "
+			"or out of range (%ju-%ju).\n",
+			cb->ext_name, entry->name, lmin, lmax);
+
+	if (entry->type == XTTYPE_UINT8) {
+		cb->val.u8 = value;
+		if (entry->flags & XTOPT_PUT)
+			*(uint8_t *)XTOPT_MKPTR(cb) = cb->val.u8;
+	} else if (entry->type == XTTYPE_UINT16) {
+		cb->val.u16 = value;
+		if (entry->flags & XTOPT_PUT)
+			*(uint16_t *)XTOPT_MKPTR(cb) = cb->val.u16;
+	} else if (entry->type == XTTYPE_UINT32) {
+		cb->val.u32 = value;
+		if (entry->flags & XTOPT_PUT)
+			*(uint32_t *)XTOPT_MKPTR(cb) = cb->val.u32;
+	} else if (entry->type == XTTYPE_UINT64) {
+		cb->val.u64 = value;
+		if (entry->flags & XTOPT_PUT)
+			*(uint64_t *)XTOPT_MKPTR(cb) = cb->val.u64;
+	}
+}
+
+/**
+ * Require a simple floating point number.
+ */
+static void xtopt_parse_float(struct xt_option_call *cb)
+{
+	const struct xt_option_entry *entry = cb->entry;
+	double value;
+	char *end;
+
+	value = strtod(cb->arg, &end);
+	if (end == cb->arg || *end != '\0' ||
+	    (entry->min != entry->max &&
+	    (value < entry->min || value > entry->max)))
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			"%s: bad value for option \"--%s\", "
+			"or out of range (%u-%u).\n",
+			cb->ext_name, entry->name, entry->min, entry->max);
+
+	cb->val.dbl = value;
+	if (entry->flags & XTOPT_PUT)
+		*(double *)XTOPT_MKPTR(cb) = cb->val.dbl;
+}
+
+/**
+ * Copy the parsed value to the appropriate entry in cb->val.
+ */
+static void xtopt_mint_value_to_cb(struct xt_option_call *cb, uintmax_t value)
+{
+	const struct xt_option_entry *entry = cb->entry;
+
+	if (cb->nvals >= ARRAY_SIZE(cb->val.u32_range))
+		return;
+	if (entry->type == XTTYPE_UINT8RC)
+		cb->val.u8_range[cb->nvals] = value;
+	else if (entry->type == XTTYPE_UINT16RC)
+		cb->val.u16_range[cb->nvals] = value;
+	else if (entry->type == XTTYPE_UINT32RC)
+		cb->val.u32_range[cb->nvals] = value;
+	else if (entry->type == XTTYPE_UINT64RC)
+		cb->val.u64_range[cb->nvals] = value;
+}
+
+/**
+ * Copy the parsed value to the data area, using appropriate type access.
+ */
+static void xtopt_mint_value_to_ptr(struct xt_option_call *cb, void **datap,
+				    uintmax_t value)
+{
+	const struct xt_option_entry *entry = cb->entry;
+	void *data = *datap;
+
+	if (!(entry->flags & XTOPT_PUT))
+		return;
+	if (entry->type == XTTYPE_UINT8RC)
+		*(uint8_t *)data = value;
+	else if (entry->type == XTTYPE_UINT16RC)
+		*(uint16_t *)data = value;
+	else if (entry->type == XTTYPE_UINT32RC)
+		*(uint32_t *)data = value;
+	else if (entry->type == XTTYPE_UINT64RC)
+		*(uint64_t *)data = value;
+	data += xtopt_esize_by_type(entry->type);
+	*datap = data;
+}
+
+/**
+ * Multiple integer parse routine.
+ *
+ * This function is capable of parsing any number of fields. Only the first
+ * two values from the string will be put into @cb however (and as such,
+ * @cb->val.uXX_range is just that large) to cater for the few extensions that
+ * do not have a range[2] field, but {min, max}, and which cannot use
+ * XTOPT_POINTER.
+ */
+static void xtopt_parse_mint(struct xt_option_call *cb)
+{
+	const struct xt_option_entry *entry = cb->entry;
+	const char *arg;
+	size_t esize = xtopt_esize_by_type(entry->type);
+	const uintmax_t lmax = xtopt_max_by_type(entry->type);
+	void *put = XTOPT_MKPTR(cb);
+	unsigned int maxiter;
+	uintmax_t value;
+	char *end = "";
+	char sep = ':';
+
+	maxiter = entry->size / esize;
+	if (maxiter == 0)
+		maxiter = ARRAY_SIZE(cb->val.u32_range);
+	if (entry->size % esize != 0)
+		xt_params->exit_err(OTHER_PROBLEM, "%s: memory block does "
+			"not have proper size\n", __func__);
+
+	cb->nvals = 0;
+	for (arg = cb->arg, end = (char *)arg; ; arg = end + 1) {
+		if (cb->nvals == maxiter)
+			xt_params->exit_err(PARAMETER_PROBLEM, "%s: Too many "
+				"components for option \"--%s\" (max: %u)\n",
+				cb->ext_name, entry->name, maxiter);
+		if (*arg == '\0' || *arg == sep) {
+			/* Default range components when field not spec'd. */
+			end = (char *)arg;
+			value = (cb->nvals == 1) ? lmax : 0;
+		} else {
+			if (!xtables_strtoul(arg, &end, &value, 0, lmax))
+				xt_params->exit_err(PARAMETER_PROBLEM,
+					"%s: bad value for option \"--%s\" near "
+					"\"%s\", or out of range (0-%ju).\n",
+					cb->ext_name, entry->name, arg, lmax);
+			if (*end != '\0' && *end != sep)
+				xt_params->exit_err(PARAMETER_PROBLEM,
+					"%s: Argument to \"--%s\" has "
+					"unexpected characters near \"%s\".\n",
+					cb->ext_name, entry->name, end);
+		}
+		xtopt_mint_value_to_cb(cb, value);
+		++cb->nvals;
+		xtopt_mint_value_to_ptr(cb, &put, value);
+		if (*end == '\0')
+			break;
+	}
+}
+
+static void xtopt_parse_string(struct xt_option_call *cb)
+{
+	const struct xt_option_entry *entry = cb->entry;
+	size_t z = strlen(cb->arg);
+	char *p;
+
+	if (entry->min != 0 && z < entry->min)
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			"Argument must have a minimum length of "
+			"%u characters\n", entry->min);
+	if (entry->max != 0 && z > entry->max)
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			"Argument must have a maximum length of "
+			"%u characters\n", entry->max);
+	if (!(entry->flags & XTOPT_PUT))
+		return;
+	if (z >= entry->size)
+		z = entry->size - 1;
+	p = XTOPT_MKPTR(cb);
+	strncpy(p, cb->arg, z);
+	p[z] = '\0';
+}
+
+static const struct tos_symbol_info {
+	unsigned char value;
+	const char *name;
+} tos_symbol_names[] = {
+	{IPTOS_LOWDELAY,    "Minimize-Delay"},
+	{IPTOS_THROUGHPUT,  "Maximize-Throughput"},
+	{IPTOS_RELIABILITY, "Maximize-Reliability"},
+	{IPTOS_MINCOST,     "Minimize-Cost"},
+	{IPTOS_NORMALSVC,   "Normal-Service"},
+	{},
+};
+
+/*
+ * tos_parse_numeric - parse a string like "15/255"
+ *
+ * @str:	input string
+ * @tvm:	(value/mask) tuple
+ * @max:	maximum allowed value (must be pow(2,some_int)-1)
+ */
+static bool tos_parse_numeric(const char *str, struct xt_option_call *cb,
+                              unsigned int max)
+{
+	unsigned int value;
+	char *end;
+
+	xtables_strtoui(str, &end, &value, 0, max);
+	cb->val.tos_value = value;
+	cb->val.tos_mask  = max;
+
+	if (*end == '/') {
+		const char *p = end + 1;
+
+		if (!xtables_strtoui(p, &end, &value, 0, max))
+			xtables_error(PARAMETER_PROBLEM, "Illegal value: \"%s\"",
+			           str);
+		cb->val.tos_mask = value;
+	}
+
+	if (*end != '\0')
+		xtables_error(PARAMETER_PROBLEM, "Illegal value: \"%s\"", str);
+	return true;
+}
+
+/**
+ * @str:	input string
+ * @tvm:	(value/mask) tuple
+ * @def_mask:	mask to force when a symbolic name is used
+ */
+static void xtopt_parse_tosmask(struct xt_option_call *cb)
+{
+	const struct tos_symbol_info *symbol;
+	char *tmp;
+
+	if (xtables_strtoui(cb->arg, &tmp, NULL, 0, UINT8_MAX)) {
+		tos_parse_numeric(cb->arg, cb, UINT8_MAX);
+		return;
+	}
+	/*
+	 * This is our way we deal with different defaults
+	 * for different revisions.
+	 */
+	cb->val.tos_mask = cb->entry->max;
+	for (symbol = tos_symbol_names; symbol->name != NULL; ++symbol)
+		if (strcasecmp(cb->arg, symbol->name) == 0) {
+			cb->val.tos_value = symbol->value;
+			return;
+		}
+
+	xtables_error(PARAMETER_PROBLEM, "Symbolic name \"%s\" is unknown",
+		      cb->arg);
+}
+
+/**
+ * Validate the input for being conformant to "mark[/mask]".
+ */
+static void xtopt_parse_markmask(struct xt_option_call *cb)
+{
+	xtables_parse_mark_mask(cb, &cb->val.mark, &cb->val.mask);
+}
+
+static int xtopt_sysloglvl_compare(const void *a, const void *b)
+{
+	const char *name = a;
+	const struct syslog_level *entry = b;
+
+	return strcmp(name, entry->name);
+}
+
+static void xtopt_parse_sysloglevel(struct xt_option_call *cb)
+{
+	static const struct syslog_level log_names[] = { /* must be sorted */
+		{"alert",   LOG_ALERT},
+		{"crit",    LOG_CRIT},
+		{"debug",   LOG_DEBUG},
+		{"emerg",   LOG_EMERG},
+		{"error",   LOG_ERR}, /* deprecated */
+		{"info",    LOG_INFO},
+		{"notice",  LOG_NOTICE},
+		{"panic",   LOG_EMERG}, /* deprecated */
+		{"warning", LOG_WARNING},
+	};
+	const struct syslog_level *e;
+	unsigned int num = 0;
+
+	if (!xtables_strtoui(cb->arg, NULL, &num, 0, 7)) {
+		e = bsearch(cb->arg, log_names, ARRAY_SIZE(log_names),
+			    sizeof(*log_names), xtopt_sysloglvl_compare);
+		if (e == NULL)
+			xt_params->exit_err(PARAMETER_PROBLEM,
+				"log level \"%s\" unknown\n", cb->arg);
+		num = e->level;
+	}
+	cb->val.syslog_level = num;
+	if (cb->entry->flags & XTOPT_PUT)
+		*(uint8_t *)XTOPT_MKPTR(cb) = num;
+}
+
+static void *xtables_sa_host(const void *sa, unsigned int afproto)
+{
+	if (afproto == AF_INET6)
+		return &((struct sockaddr_in6 *)sa)->sin6_addr;
+	else if (afproto == AF_INET)
+		return &((struct sockaddr_in *)sa)->sin_addr;
+	return (void *)sa;
+}
+
+static socklen_t xtables_sa_hostlen(unsigned int afproto)
+{
+	if (afproto == AF_INET6)
+		return sizeof(struct in6_addr);
+	else if (afproto == AF_INET)
+		return sizeof(struct in_addr);
+	return 0;
+}
+
+/**
+ * Accepts: a hostname (DNS), or a single inetaddr - without any mask. The
+ * result is stored in @cb->val.haddr. Additionally, @cb->val.hmask and
+ * @cb->val.hlen are set for completeness to the appropriate values.
+ */
+static void xtopt_parse_host(struct xt_option_call *cb)
+{
+	struct addrinfo hints = {.ai_family = afinfo->family};
+	unsigned int adcount = 0;
+	struct addrinfo *res, *p;
+	int ret;
+
+	ret = getaddrinfo(cb->arg, NULL, &hints, &res);
+	if (ret != 0)
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			"getaddrinfo: %s\n", gai_strerror(ret));
+
+	memset(&cb->val.hmask, 0xFF, sizeof(cb->val.hmask));
+	cb->val.hlen = (afinfo->family == NFPROTO_IPV4) ? 32 : 128;
+
+	for (p = res; p != NULL; p = p->ai_next) {
+		if (adcount == 0) {
+			memset(&cb->val.haddr, 0, sizeof(cb->val.haddr));
+			memcpy(&cb->val.haddr,
+			       xtables_sa_host(p->ai_addr, p->ai_family),
+			       xtables_sa_hostlen(p->ai_family));
+			++adcount;
+			continue;
+		}
+		if (memcmp(&cb->val.haddr,
+		    xtables_sa_host(p->ai_addr, p->ai_family),
+		    xtables_sa_hostlen(p->ai_family)) != 0)
+			xt_params->exit_err(PARAMETER_PROBLEM,
+				"%s resolves to more than one address\n",
+				cb->arg);
+	}
+
+	freeaddrinfo(res);
+	if (cb->entry->flags & XTOPT_PUT)
+		/* Validation in xtables_option_metavalidate */
+		memcpy(XTOPT_MKPTR(cb), &cb->val.haddr,
+		       sizeof(cb->val.haddr));
+}
+
+/**
+ * @name:	port name, or number as a string (e.g. "http" or "80")
+ *
+ * Resolve a port name to a number. Returns the port number in integral
+ * form on success, or <0 on error. (errno will not be set.)
+ */
+static int xtables_getportbyname(const char *name)
+{
+	struct addrinfo *res = NULL, *p;
+	int ret;
+
+	ret = getaddrinfo(NULL, name, NULL, &res);
+	if (ret != 0)
+		return -1;
+	ret = -1;
+	for (p = res; p != NULL; p = p->ai_next) {
+		if (p->ai_family == AF_INET6) {
+			ret = ((struct sockaddr_in6 *)p->ai_addr)->sin6_port;
+			break;
+		} else if (p->ai_family == AF_INET) {
+			ret = ((struct sockaddr_in *)p->ai_addr)->sin_port;
+			break;
+		}
+	}
+	freeaddrinfo(res);
+	if (ret < 0)
+		return ret;
+	return ntohs(ret);
+}
+
+/**
+ * Validate and parse a protocol specification (number or name) by use of
+ * /etc/protocols and put the result into @cb->val.protocol.
+ */
+static void xtopt_parse_protocol(struct xt_option_call *cb)
+{
+	cb->val.protocol = xtables_parse_protocol(cb->arg);
+	if (cb->entry->flags & XTOPT_PUT)
+		*(uint8_t *)XTOPT_MKPTR(cb) = cb->val.protocol;
+}
+
+/**
+ * Validate and parse a port specification and put the result into
+ * @cb->val.port.
+ */
+static void xtopt_parse_port(struct xt_option_call *cb)
+{
+	const struct xt_option_entry *entry = cb->entry;
+	int ret;
+
+	ret = xtables_getportbyname(cb->arg);
+	if (ret < 0)
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			"Port \"%s\" does not resolve to anything.\n",
+			cb->arg);
+	if (entry->flags & XTOPT_NBO)
+		ret = htons(ret);
+	cb->val.port = ret;
+	if (entry->flags & XTOPT_PUT)
+		*(uint16_t *)XTOPT_MKPTR(cb) = cb->val.port;
+}
+
+static void xtopt_parse_mport(struct xt_option_call *cb)
+{
+	static const size_t esize = sizeof(uint16_t);
+	const struct xt_option_entry *entry = cb->entry;
+	char *lo_arg, *wp_arg, *arg;
+	unsigned int maxiter;
+	int value;
+
+	wp_arg = lo_arg = strdup(cb->arg);
+	if (lo_arg == NULL)
+		xt_params->exit_err(RESOURCE_PROBLEM, "strdup");
+
+	maxiter = entry->size / esize;
+	if (maxiter == 0)
+		maxiter = 2; /* ARRAY_SIZE(cb->val.port_range) */
+	if (entry->size % esize != 0)
+		xt_params->exit_err(OTHER_PROBLEM, "%s: memory block does "
+			"not have proper size\n", __func__);
+
+	cb->val.port_range[0] = 0;
+	cb->val.port_range[1] = UINT16_MAX;
+	cb->nvals = 0;
+
+	while ((arg = strsep(&wp_arg, ":")) != NULL) {
+		if (cb->nvals == maxiter)
+			xt_params->exit_err(PARAMETER_PROBLEM, "%s: Too many "
+				"components for option \"--%s\" (max: %u)\n",
+				cb->ext_name, entry->name, maxiter);
+		if (*arg == '\0') {
+			++cb->nvals;
+			continue;
+		}
+
+		value = xtables_getportbyname(arg);
+		if (value < 0)
+			xt_params->exit_err(PARAMETER_PROBLEM,
+				"Port \"%s\" does not resolve to "
+				"anything.\n", arg);
+		if (entry->flags & XTOPT_NBO)
+			value = htons(value);
+		if (cb->nvals < ARRAY_SIZE(cb->val.port_range))
+			cb->val.port_range[cb->nvals] = value;
+		++cb->nvals;
+	}
+
+	if (cb->nvals == 1) {
+		cb->val.port_range[1] = cb->val.port_range[0];
+		++cb->nvals;
+	}
+	if (entry->flags & XTOPT_PUT)
+		memcpy(XTOPT_MKPTR(cb), cb->val.port_range, sizeof(uint16_t) *
+		       (cb->nvals <= maxiter ? cb->nvals : maxiter));
+	free(lo_arg);
+}
+
+static int xtopt_parse_mask(struct xt_option_call *cb)
+{
+	struct addrinfo hints = {.ai_family = afinfo->family,
+				 .ai_flags = AI_NUMERICHOST };
+	struct addrinfo *res;
+	int ret;
+
+	ret = getaddrinfo(cb->arg, NULL, &hints, &res);
+	if (ret != 0)
+		return 0;
+
+	memcpy(&cb->val.hmask, xtables_sa_host(res->ai_addr, res->ai_family),
+	       xtables_sa_hostlen(res->ai_family));
+
+	switch(afinfo->family) {
+	case AF_INET:
+		cb->val.hlen = xtables_ipmask_to_cidr(&cb->val.hmask.in);
+		break;
+	case AF_INET6:
+		cb->val.hlen = xtables_ip6mask_to_cidr(&cb->val.hmask.in6);
+		break;
+	}
+
+	freeaddrinfo(res);
+	return 1;
+}
+
+/**
+ * Parse an integer and ensure it is within the address family's prefix length
+ * limits. The result is stored in @cb->val.hlen.
+ */
+static void xtopt_parse_plen(struct xt_option_call *cb)
+{
+	const struct xt_option_entry *entry = cb->entry;
+	unsigned int prefix_len = 128; /* happiness is a warm gcc */
+
+	cb->val.hlen = (afinfo->family == NFPROTO_IPV4) ? 32 : 128;
+	if (!xtables_strtoui(cb->arg, NULL, &prefix_len, 0, cb->val.hlen)) {
+		/* Is this mask expressed in full format? e.g. 255.255.255.0 */
+		if (xtopt_parse_mask(cb))
+			return;
+
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			"%s: bad value for option \"--%s\", "
+			"neither a valid network mask "
+			"nor valid CIDR (%u-%u).\n",
+			cb->ext_name, entry->name, 0, cb->val.hlen);
+	}
+	cb->val.hlen = prefix_len;
+}
+
+/**
+ * Reuse xtopt_parse_plen for testing the integer. Afterwards convert this to
+ * a bitmask, and make it available through @cb->val.hmask (hlen remains
+ * valid). If %XTOPT_PUT is used, hmask will be copied to the target area.
+ */
+static void xtopt_parse_plenmask(struct xt_option_call *cb)
+{
+	const struct xt_option_entry *entry = cb->entry;
+	uint32_t *mask = cb->val.hmask.all;
+
+	xtopt_parse_plen(cb);
+
+	memset(mask, 0xFF, sizeof(union nf_inet_addr));
+	/* This shifting is AF-independent. */
+	if (cb->val.hlen == 0) {
+		mask[0] = mask[1] = mask[2] = mask[3] = 0;
+	} else if (cb->val.hlen <= 32) {
+		mask[0] <<= 32 - cb->val.hlen;
+		mask[1] = mask[2] = mask[3] = 0;
+	} else if (cb->val.hlen <= 64) {
+		mask[1] <<= 32 - (cb->val.hlen - 32);
+		mask[2] = mask[3] = 0;
+	} else if (cb->val.hlen <= 96) {
+		mask[2] <<= 32 - (cb->val.hlen - 64);
+		mask[3] = 0;
+	} else if (cb->val.hlen <= 128) {
+		mask[3] <<= 32 - (cb->val.hlen - 96);
+	}
+	mask[0] = htonl(mask[0]);
+	mask[1] = htonl(mask[1]);
+	mask[2] = htonl(mask[2]);
+	mask[3] = htonl(mask[3]);
+	if (entry->flags & XTOPT_PUT)
+		memcpy(XTOPT_MKPTR(cb), mask, sizeof(union nf_inet_addr));
+}
+
+static void xtopt_parse_hostmask(struct xt_option_call *cb)
+{
+	const char *orig_arg = cb->arg;
+	char *work, *p;
+
+	if (strchr(cb->arg, '/') == NULL) {
+		xtopt_parse_host(cb);
+		return;
+	}
+	work = strdup(orig_arg);
+	if (work == NULL)
+		xt_params->exit_err(PARAMETER_PROBLEM, "strdup");
+	p = strchr(work, '/'); /* by def this can't be NULL now */
+	*p++ = '\0';
+	/*
+	 * Because xtopt_parse_host and xtopt_parse_plenmask would store
+	 * different things in the same target area, XTTYPE_HOSTMASK must
+	 * disallow XTOPT_PUT, which it does by forcing its absence,
+	 * cf. not being listed in xtopt_psize.
+	 */
+	cb->arg = work;
+	xtopt_parse_host(cb);
+	cb->arg = p;
+	xtopt_parse_plenmask(cb);
+	cb->arg = orig_arg;
+}
+
+static void xtopt_parse_ethermac(struct xt_option_call *cb)
+{
+	const char *arg = cb->arg;
+	unsigned int i;
+	char *end;
+
+	for (i = 0; i < ARRAY_SIZE(cb->val.ethermac) - 1; ++i) {
+		cb->val.ethermac[i] = strtoul(arg, &end, 16);
+		if (*end != ':' || end - arg > 2)
+			goto out;
+		arg = end + 1;
+	}
+	i = ARRAY_SIZE(cb->val.ethermac) - 1;
+	cb->val.ethermac[i] = strtoul(arg, &end, 16);
+	if (*end != '\0' || end - arg > 2)
+		goto out;
+	if (cb->entry->flags & XTOPT_PUT)
+		memcpy(XTOPT_MKPTR(cb), cb->val.ethermac,
+		       sizeof(cb->val.ethermac));
+	return;
+ out:
+	xt_params->exit_err(PARAMETER_PROBLEM, "Invalid MAC address specified.");
+}
+
+static void (*const xtopt_subparse[])(struct xt_option_call *) = {
+	[XTTYPE_UINT8]       = xtopt_parse_int,
+	[XTTYPE_UINT16]      = xtopt_parse_int,
+	[XTTYPE_UINT32]      = xtopt_parse_int,
+	[XTTYPE_UINT64]      = xtopt_parse_int,
+	[XTTYPE_UINT8RC]     = xtopt_parse_mint,
+	[XTTYPE_UINT16RC]    = xtopt_parse_mint,
+	[XTTYPE_UINT32RC]    = xtopt_parse_mint,
+	[XTTYPE_UINT64RC]    = xtopt_parse_mint,
+	[XTTYPE_DOUBLE]      = xtopt_parse_float,
+	[XTTYPE_STRING]      = xtopt_parse_string,
+	[XTTYPE_TOSMASK]     = xtopt_parse_tosmask,
+	[XTTYPE_MARKMASK32]  = xtopt_parse_markmask,
+	[XTTYPE_SYSLOGLEVEL] = xtopt_parse_sysloglevel,
+	[XTTYPE_HOST]        = xtopt_parse_host,
+	[XTTYPE_HOSTMASK]    = xtopt_parse_hostmask,
+	[XTTYPE_PROTOCOL]    = xtopt_parse_protocol,
+	[XTTYPE_PORT]        = xtopt_parse_port,
+	[XTTYPE_PORTRC]      = xtopt_parse_mport,
+	[XTTYPE_PLEN]        = xtopt_parse_plen,
+	[XTTYPE_PLENMASK]    = xtopt_parse_plenmask,
+	[XTTYPE_ETHERMAC]    = xtopt_parse_ethermac,
+};
+
+/**
+ * The master option parsing routine. May be used for the ".x6_parse"
+ * function pointer in extensions if fully automatic parsing is desired.
+ * It may be also called manually from a custom x6_parse function.
+ */
+void xtables_option_parse(struct xt_option_call *cb)
+{
+	const struct xt_option_entry *entry = cb->entry;
+	unsigned int eflag = 1 << cb->entry->id;
+
+	/*
+	 * With {.id = P_FOO, .excl = P_FOO} we can have simple double-use
+	 * prevention. Though it turned out that this is too much typing (most
+	 * of the options are one-time use only), so now we also have
+	 * %XTOPT_MULTI.
+	 */
+	if ((!(entry->flags & XTOPT_MULTI) || (entry->excl & eflag)) &&
+	    cb->xflags & eflag)
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			"%s: option \"--%s\" can only be used once.\n",
+			cb->ext_name, cb->entry->name);
+	if (cb->invert && !(entry->flags & XTOPT_INVERT))
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			"%s: option \"--%s\" cannot be inverted.\n",
+			cb->ext_name, entry->name);
+	if (entry->type != XTTYPE_NONE && optarg == NULL)
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			"%s: option \"--%s\" requires an argument.\n",
+			cb->ext_name, entry->name);
+	/*
+	 * Fill in fallback value for "nvals", in case an extension (as it
+	 * happened with libxt_conntrack.2) tries to read it, despite not using
+	 * a *RC option type.
+	 */
+	cb->nvals = 1;
+	if (entry->type < ARRAY_SIZE(xtopt_subparse) &&
+	    xtopt_subparse[entry->type] != NULL)
+		xtopt_subparse[entry->type](cb);
+	/* Exclusion with other flags tested later in finalize. */
+	cb->xflags |= 1 << entry->id;
+}
+
+/**
+ * Verifies that an extension's option map descriptor is valid, and ought to
+ * be called right after the extension has been loaded, and before option
+ * merging/xfrm.
+ */
+void xtables_option_metavalidate(const char *name,
+				 const struct xt_option_entry *entry)
+{
+	for (; entry->name != NULL; ++entry) {
+		if (entry->id >= CHAR_BIT * sizeof(unsigned int) ||
+		    entry->id >= XT_OPTION_OFFSET_SCALE)
+			xt_params->exit_err(OTHER_PROBLEM,
+				"Extension %s uses invalid ID %u\n",
+				name, entry->id);
+		if (!(entry->flags & XTOPT_PUT)) {
+			if (entry->ptroff != 0)
+				xt_params->exit_err(OTHER_PROBLEM,
+					"%s: ptroff for \"--%s\" is non-"
+					"zero but no XTOPT_PUT is specified. "
+					"Oversight?", name, entry->name);
+			continue;
+		}
+		if (entry->type >= ARRAY_SIZE(xtopt_psize) ||
+		    xtopt_psize[entry->type] == 0)
+			xt_params->exit_err(OTHER_PROBLEM,
+				"%s: entry type of option \"--%s\" cannot be "
+				"combined with XTOPT_PUT\n",
+				name, entry->name);
+		if (xtopt_psize[entry->type] != -1 &&
+		    xtopt_psize[entry->type] != entry->size)
+			xt_params->exit_err(OTHER_PROBLEM,
+				"%s: option \"--%s\" points to a memory block "
+				"of wrong size (expected %zu, got %zu)\n",
+				name, entry->name,
+				xtopt_psize[entry->type], entry->size);
+	}
+}
+
+/**
+ * Find an option entry by its id.
+ */
+static const struct xt_option_entry *
+xtables_option_lookup(const struct xt_option_entry *entry, unsigned int id)
+{
+	for (; entry->name != NULL; ++entry)
+		if (entry->id == id)
+			return entry;
+	return NULL;
+}
+
+/**
+ * @c:		getopt id (i.e. with offset)
+ * @fw:		struct ipt_entry or ip6t_entry
+ *
+ * Dispatch arguments to the appropriate parse function, based upon the
+ * extension's choice of API.
+ */
+void xtables_option_tpcall(unsigned int c, char **argv, bool invert,
+			   struct xtables_target *t, void *fw)
+{
+	struct xt_option_call cb;
+
+	if (t->x6_parse == NULL) {
+		if (t->parse != NULL)
+			t->parse(c - t->option_offset, argv, invert,
+				 &t->tflags, fw, &t->t);
+		return;
+	}
+
+	c -= t->option_offset;
+	cb.entry = xtables_option_lookup(t->x6_options, c);
+	if (cb.entry == NULL)
+		xtables_error(OTHER_PROBLEM,
+			"Extension does not know id %u\n", c);
+	cb.arg      = optarg;
+	cb.invert   = invert;
+	cb.ext_name = t->name;
+	cb.data     = t->t->data;
+	cb.xflags   = t->tflags;
+	cb.target   = &t->t;
+	cb.xt_entry = fw;
+	cb.udata    = t->udata;
+	t->x6_parse(&cb);
+	t->tflags = cb.xflags;
+}
+
+/**
+ * @c:		getopt id (i.e. with offset)
+ * @fw:		struct ipt_entry or ip6t_entry
+ *
+ * Dispatch arguments to the appropriate parse function, based upon the
+ * extension's choice of API.
+ */
+void xtables_option_mpcall(unsigned int c, char **argv, bool invert,
+			   struct xtables_match *m, void *fw)
+{
+	struct xt_option_call cb;
+
+	if (m->x6_parse == NULL) {
+		if (m->parse != NULL)
+			m->parse(c - m->option_offset, argv, invert,
+				 &m->mflags, fw, &m->m);
+		return;
+	}
+
+	c -= m->option_offset;
+	cb.entry = xtables_option_lookup(m->x6_options, c);
+	if (cb.entry == NULL)
+		xtables_error(OTHER_PROBLEM,
+			"Extension does not know id %u\n", c);
+	cb.arg      = optarg;
+	cb.invert   = invert;
+	cb.ext_name = m->name;
+	cb.data     = m->m->data;
+	cb.xflags   = m->mflags;
+	cb.match    = &m->m;
+	cb.xt_entry = fw;
+	cb.udata    = m->udata;
+	m->x6_parse(&cb);
+	m->mflags = cb.xflags;
+}
+
+/**
+ * @name:	name of extension
+ * @entry:	current option (from all ext's entries) being validated
+ * @xflags:	flags the extension has collected
+ * @i:		conflicting option (id) to test for
+ */
+static void
+xtables_option_fcheck2(const char *name, const struct xt_option_entry *entry,
+		       const struct xt_option_entry *other,
+		       unsigned int xflags)
+{
+	unsigned int ef = 1 << entry->id, of = 1 << other->id;
+
+	if (entry->also & of && !(xflags & of))
+		xt_params->exit_err(PARAMETER_PROBLEM,
+			"%s: option \"--%s\" also requires \"--%s\".\n",
+			name, entry->name, other->name);
+
+	if (!(entry->excl & of))
+		/* Use of entry does not collide with other option, good. */
+		return;
+	if ((xflags & (ef | of)) != (ef | of))
+		/* Conflicting options were not used. */
+		return;
+
+	xt_params->exit_err(PARAMETER_PROBLEM,
+		"%s: option \"--%s\" cannot be used together with \"--%s\".\n",
+		name, entry->name, other->name);
+}
+
+/**
+ * @name:	name of extension
+ * @xflags:	accumulated flags
+ * @entry:	extension's option table
+ *
+ * Check that all option constraints have been met. This effectively replaces
+ * ->final_check of the older API.
+ */
+void xtables_options_fcheck(const char *name, unsigned int xflags,
+			    const struct xt_option_entry *table)
+{
+	const struct xt_option_entry *entry, *other;
+	unsigned int i;
+
+	for (entry = table; entry->name != NULL; ++entry) {
+		if (entry->flags & XTOPT_MAND &&
+		    !(xflags & (1 << entry->id)))
+			xt_params->exit_err(PARAMETER_PROBLEM,
+				"%s: option \"--%s\" must be specified\n",
+				name, entry->name);
+		if (!(xflags & (1 << entry->id)))
+			/* Not required, not specified, thus skip. */
+			continue;
+
+		for (i = 0; i < CHAR_BIT * sizeof(entry->id); ++i) {
+			if (entry->id == i)
+				/*
+				 * Avoid conflict with self. Multi-use check
+				 * was done earlier in xtables_option_parse.
+				 */
+				continue;
+			other = xtables_option_lookup(table, i);
+			if (other == NULL)
+				continue;
+			xtables_option_fcheck2(name, entry, other, xflags);
+		}
+	}
+}
+
+/**
+ * Dispatch arguments to the appropriate final_check function, based upon the
+ * extension's choice of API.
+ */
+void xtables_option_tfcall(struct xtables_target *t)
+{
+	if (t->x6_fcheck != NULL) {
+		struct xt_fcheck_call cb;
+
+		cb.ext_name = t->name;
+		cb.data     = t->t->data;
+		cb.xflags   = t->tflags;
+		cb.udata    = t->udata;
+		t->x6_fcheck(&cb);
+	} else if (t->final_check != NULL) {
+		t->final_check(t->tflags);
+	}
+	if (t->x6_options != NULL)
+		xtables_options_fcheck(t->name, t->tflags, t->x6_options);
+}
+
+/**
+ * Dispatch arguments to the appropriate final_check function, based upon the
+ * extension's choice of API.
+ */
+void xtables_option_mfcall(struct xtables_match *m)
+{
+	if (m->x6_fcheck != NULL) {
+		struct xt_fcheck_call cb;
+
+		cb.ext_name = m->name;
+		cb.data     = m->m->data;
+		cb.xflags   = m->mflags;
+		cb.udata    = m->udata;
+		m->x6_fcheck(&cb);
+	} else if (m->final_check != NULL) {
+		m->final_check(m->mflags);
+	}
+	if (m->x6_options != NULL)
+		xtables_options_fcheck(m->name, m->mflags, m->x6_options);
+}
+
+struct xtables_lmap *xtables_lmap_init(const char *file)
+{
+	struct xtables_lmap *lmap_head = NULL, *lmap_prev = NULL, *lmap_this;
+	char buf[512];
+	FILE *fp;
+	char *cur, *nxt;
+	int id;
+
+	fp = fopen(file, "re");
+	if (fp == NULL)
+		return NULL;
+
+	while (fgets(buf, sizeof(buf), fp) != NULL) {
+		cur = buf;
+		while (isspace(*cur))
+			++cur;
+		if (*cur == '#' || *cur == '\n' || *cur == '\0')
+			continue;
+
+		/* iproute2 allows hex and dec format */
+		errno = 0;
+		id = strtoul(cur, &nxt, strncmp(cur, "0x", 2) == 0 ? 16 : 10);
+		if (nxt == cur || errno != 0)
+			continue;
+
+		/* same boundaries as in iproute2 */
+		if (id < 0 || id > 255)
+			continue;
+		cur = nxt;
+
+		if (!isspace(*cur))
+			continue;
+		while (isspace(*cur))
+			++cur;
+		if (*cur == '#' || *cur == '\n' || *cur == '\0')
+			continue;
+		nxt = cur;
+		while (*nxt != '\0' && !isspace(*nxt))
+			++nxt;
+		if (nxt == cur)
+			continue;
+		*nxt = '\0';
+
+		/* found valid data */
+		lmap_this = malloc(sizeof(*lmap_this));
+		if (lmap_this == NULL) {
+			perror("malloc");
+			goto out;
+		}
+		lmap_this->id   = id;
+		lmap_this->name = strdup(cur);
+		if (lmap_this->name == NULL) {
+			free(lmap_this);
+			goto out;
+		}
+		lmap_this->next = NULL;
+
+		if (lmap_prev != NULL)
+			lmap_prev->next = lmap_this;
+		else
+			lmap_head = lmap_this;
+		lmap_prev = lmap_this;
+	}
+
+	fclose(fp);
+	return lmap_head;
+ out:
+	fclose(fp);
+	xtables_lmap_free(lmap_head);
+	return NULL;
+}
+
+void xtables_lmap_free(struct xtables_lmap *head)
+{
+	struct xtables_lmap *next;
+
+	for (; head != NULL; head = next) {
+		next = head->next;
+		free(head->name);
+		free(head);
+	}
+}
+
+int xtables_lmap_name2id(const struct xtables_lmap *head, const char *name)
+{
+	for (; head != NULL; head = head->next)
+		if (strcmp(head->name, name) == 0)
+			return head->id;
+	return -1;
+}
+
+const char *xtables_lmap_id2name(const struct xtables_lmap *head, int id)
+{
+	for (; head != NULL; head = head->next)
+		if (head->id == id)
+			return head->name;
+	return NULL;
+}
diff --git a/m4/ax_check_linker_flags.m4 b/m4/ax_check_linker_flags.m4
deleted file mode 100644
index ba7bf3c..0000000
--- a/m4/ax_check_linker_flags.m4
+++ /dev/null
@@ -1,78 +0,0 @@
-#http://git.savannah.gnu.org/gitweb/?p=autoconf-archive.git;a=blob_plain;f=m4/ax_check_linker_flags.m4
-# ===========================================================================
-#   http://www.gnu.org/software/autoconf-archive/ax_check_linker_flags.html
-# ===========================================================================
-#
-# SYNOPSIS
-#
-#   AX_CHECK_LINKER_FLAGS(FLAGS, [ACTION-SUCCESS], [ACTION-FAILURE])
-#
-# DESCRIPTION
-#
-#   Check whether the given linker FLAGS work with the current language's
-#   linker, or whether they give an error.
-#
-#   ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on
-#   success/failure.
-#
-#   NOTE: Based on AX_CHECK_COMPILER_FLAGS.
-#
-# LICENSE
-#
-#   Copyright (c) 2009 Mike Frysinger <vapier@gentoo.org>
-#   Copyright (c) 2009 Steven G. Johnson <stevenj@alum.mit.edu>
-#   Copyright (c) 2009 Matteo Frigo
-#
-#   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 3 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, see <http://www.gnu.org/licenses/>.
-#
-#   As a special exception, the respective Autoconf Macro's copyright owner
-#   gives unlimited permission to copy, distribute and modify the configure
-#   scripts that are the output of Autoconf when processing the Macro. You
-#   need not follow the terms of the GNU General Public License when using
-#   or distributing such scripts, even though portions of the text of the
-#   Macro appear in them. The GNU General Public License (GPL) does govern
-#   all other use of the material that constitutes the Autoconf Macro.
-#
-#   This special exception to the GPL applies to versions of the Autoconf
-#   Macro released by the Autoconf Archive. When you make and distribute a
-#   modified version of the Autoconf Macro, you may extend this special
-#   exception to the GPL to apply to your modified version as well.
-
-#serial 6
-
-AC_DEFUN([AX_CHECK_LINKER_FLAGS],
-[AC_MSG_CHECKING([whether the linker accepts $1])
-dnl Some hackery here since AC_CACHE_VAL can't handle a non-literal varname:
-AS_LITERAL_IF([$1],
-  [AC_CACHE_VAL(AS_TR_SH(ax_cv_linker_flags_[$1]), [
-      ax_save_FLAGS=$LDFLAGS
-      LDFLAGS="$1"
-      AC_LINK_IFELSE([AC_LANG_PROGRAM()],
-        AS_TR_SH(ax_cv_linker_flags_[$1])=yes,
-        AS_TR_SH(ax_cv_linker_flags_[$1])=no)
-      LDFLAGS=$ax_save_FLAGS])],
-  [ax_save_FLAGS=$LDFLAGS
-   LDFLAGS="$1"
-   AC_LINK_IFELSE([AC_LANG_PROGRAM()],
-     eval AS_TR_SH(ax_cv_linker_flags_[$1])=yes,
-     eval AS_TR_SH(ax_cv_linker_flags_[$1])=no)
-   LDFLAGS=$ax_save_FLAGS])
-eval ax_check_linker_flags=$AS_TR_SH(ax_cv_linker_flags_[$1])
-AC_MSG_RESULT($ax_check_linker_flags)
-if test "x$ax_check_linker_flags" = xyes; then
-	m4_default([$2], :)
-else
-	m4_default([$3], :)
-fi
-])dnl AX_CHECK_LINKER_FLAGS
diff --git a/release.sh b/release.sh
deleted file mode 100644
index 7c76423..0000000
--- a/release.sh
+++ /dev/null
@@ -1,31 +0,0 @@
-#! /bin/sh
-#
-set -e
-
-VERSION=1.4.7
-PREV_VERSION=1.4.6
-TMPDIR=/tmp/ipt-release
-IPTDIR="$TMPDIR/iptables-$VERSION"
-
-PATCH="patch-iptables-$PREV_VERSION-$VERSION.bz2";
-TARBALL="iptables-$VERSION.tar.bz2";
-CHANGELOG="changes-iptables-$PREV_VERSION-$VERSION.txt";
-
-mkdir -p "$TMPDIR"
-git shortlog "v$PREV_VERSION..v$VERSION" > "$TMPDIR/$CHANGELOG"
-git diff "v$PREV_VERSION..v$VERSION" | bzip2 > "$TMPDIR/$PATCH"
-git archive --prefix="iptables-$VERSION/" "v$VERSION" | tar -xC "$TMPDIR/"
-
-cd "$IPTDIR" && {
-	sh autogen.sh
-	cd ..
-}
-
-tar -cjf "$TARBALL" "iptables-$VERSION";
-gpg -u "Netfilter Core Team" -sb "$TARBALL";
-md5sum "$TARBALL" >"$TARBALL.md5sum";
-sha1sum "$TARBALL" >"$TARBALL.sha1sum";
-
-gpg -u "Netfilter Core Team" -sb "$PATCH";
-md5sum "$PATCH" >"$PATCH.md5sum";
-sha1sum "$PATCH" >"$PATCH.sha1sum";
diff --git a/tests/options-ipv4.rules b/tests/options-ipv4.rules
deleted file mode 100644
index b4adc92..0000000
--- a/tests/options-ipv4.rules
+++ /dev/null
@@ -1,52 +0,0 @@
-# Generated by iptables-save v1.4.10 on Mon Jan 31 03:03:38 2011
-*mangle
-:PREROUTING ACCEPT [2461:977932]
-:INPUT ACCEPT [2461:977932]
-:FORWARD ACCEPT [0:0]
-:OUTPUT ACCEPT [1740:367048]
-:POSTROUTING ACCEPT [1740:367048]
-
-# libipt_
--A INPUT -p ah -m ah --ahspi 1
--A INPUT -p ah -m ah --ahspi :2
--A INPUT -p ah -m ah --ahspi 0:3
--A INPUT -p ah -m ah --ahspi 4:
--A INPUT -p ah -m ah --ahspi 5:4294967295
-
--A FORWARD -p tcp -j ECN --ecn-tcp-remove
--A FORWARD -j LOG --log-prefix "hi" --log-tcp-sequence --log-tcp-options --log-ip-options --log-uid --log-macdecode
--A FORWARD -j TTL --ttl-inc 1
--A FORWARD -j TTL --ttl-dec 1
--A FORWARD -j TTL --ttl-set 1
--A FORWARD -j ULOG --ulog-prefix "abc" --ulog-cprange 2 --ulog-qthreshold 2
-COMMIT
-# Completed on Mon Jan 31 03:03:38 2011
-# Generated by iptables-save v1.4.10 on Mon Jan 31 03:03:38 2011
-*nat
-:PREROUTING ACCEPT [0:0]
-:INPUT ACCEPT [0:0]
-:OUTPUT ACCEPT [0:0]
-:POSTROUTING ACCEPT [0:0]
--A PREROUTING -d 1.2.3.4/32 -i lo -j CLUSTERIP --new --hashmode sourceip --clustermac 01:02:03:04:05:06 --total-nodes 9 --local-node 2 --hash-init 123456789
--A PREROUTING -i dummy0 -j DNAT --to-destination 1.2.3.4 --random --persistent
--A PREROUTING -i dummy0 -p tcp -j REDIRECT --to-ports 1-2 --random
--A POSTROUTING -o dummy0 -p tcp -j MASQUERADE --to-ports 1-2 --random
--A POSTROUTING -o dummy0 -p tcp -j NETMAP --to 1.0.0.0/8
--A POSTROUTING -o dummy0 -p tcp -j SNAT --to-source 1.2.3.4-1.2.3.5 --random --persistent
-COMMIT
-# Completed on Mon Jan 31 03:03:38 2011
-# Generated by iptables-save v1.4.10 on Mon Jan 31 03:03:38 2011
-*filter
-:INPUT ACCEPT [76:13548]
-:FORWARD ACCEPT [0:0]
-:OUTPUT ACCEPT [59:11240]
-#-A INPUT -m addrtype --src-type UNICAST --dst-type UNICAST --limit-iface-in
--A INPUT -p tcp -m ecn --ecn-tcp-ece --ecn-tcp-cwr --ecn-ip-ect 0
--A INPUT -p tcp -m ecn --ecn-tcp-ece --ecn-tcp-cwr --ecn-ip-ect 1
--A INPUT -p icmp -m icmp --icmp-type 5/0
--A INPUT -p icmp -m icmp --icmp-type 5/1
--A INPUT -p icmp -m icmp --icmp-type 5
--A INPUT -m realm --realm 0x1 -m ttl --ttl-eq  64 -m ttl --ttl-lt  64 -m ttl --ttl-gt  64
--A FORWARD -p tcp -j REJECT --reject-with tcp-reset
-COMMIT
-# Completed on Mon Jan 31 03:03:39 2011
diff --git a/tests/options-most.rules b/tests/options-most.rules
deleted file mode 100644
index 6c4a831..0000000
--- a/tests/options-most.rules
+++ /dev/null
@@ -1,172 +0,0 @@
-# Generated by ip6tables-save v1.4.10 on Mon Jan 31 02:19:53 2011
-*filter
-:INPUT ACCEPT [0:0]
-:FORWARD ACCEPT [0:0]
-:OUTPUT ACCEPT [0:0]
-:matches - -
-:ntarg - -
-:zmatches - -
--A INPUT -j matches
--A INPUT -m u32 --u32 "0x0=0x0&&0x0=0x1" -j ntarg
--A INPUT -j zmatches
--A INPUT -m conntrack --ctstate INVALID --ctproto 6 --ctorigsrc fe80::/64 --ctorigdst fe80::/64 --ctreplsrc fe80::/64 --ctrepldst fe80::/64 --ctorigsrcport 12 --ctorigdstport 13 --ctreplsrcport 14 --ctrepldstport 15 --ctstatus EXPECTED --ctexpire 1:2 --ctdir REPLY 
--A INPUT -p tcp -m cluster --cluster-local-nodemask 0x00000001 --cluster-total-nodes 2 --cluster-hash-seed 0x00000001 -m cluster --cluster-local-nodemask 0x00000001 --cluster-total-nodes 2 --cluster-hash-seed 0x00000001 -m comment --comment foo -m connbytes --connbytes 1:2 --connbytes-mode packets --connbytes-dir both -m connlimit --connlimit-upto 1 --connlimit-mask 8 --connlimit-saddr -m connlimit --connlimit-above 1 --connlimit-mask 9 --connlimit-daddr -m connmark --mark 0x99 -m conntrack --ctstate INVALID --ctproto 6 --ctorigsrc fe80::/64 --ctorigdst fe80::/64 --ctreplsrc fe80::/64 --ctrepldst fe80::/64 --ctorigsrcport 12 --ctorigdstport 13 --ctreplsrcport 14 --ctrepldstport 15 --ctstatus EXPECTED --ctexpire 1:2 --ctdir REPLY -m cpu --cpu 2 -m dscp --dscp 0x04 -m dscp --dscp 0x00 -m hashlimit --hashlimit-upto 1/sec --hashlimit-burst 5 --hashlimit-mode srcip,dstip --hashlimit-name f1 --hashlimit-htable-size 64 --hashlimit-htable-max 128 --hashlimit-htable-gcinterval 60 --hashlimit-htable-expire 120 --hashlimit-srcmask 24 --hashlimit-dstmask 24 -m hashlimit --hashlimit-above 5/sec --hashlimit-burst 5 --hashlimit-name f1 -m helper --helper ftp -m iprange --src-range ::1-::2 --dst-range ::1-::2 -m ipvs --vaddr fe80::/64 --vport 1 --vdir REPLY --vmethod GATE --vportctl 21 -m length --length 1:2 -m limit --limit 1/sec -m mac --mac-source 01:02:03:04:05:06 -m mark --mark 0x1 -m physdev --physdev-in eth0 -m pkttype --pkt-type unicast -m policy --dir in --pol ipsec --strict --reqid 1 --spi 0x1 --proto esp --mode tunnel --tunnel-dst fe80::/64 --tunnel-src fe80::/64 --next --reqid 2 -m quota --quota 0 -m recent --rcheck --name DEFAULT --rsource -m socket --transparent -m string --string "foobar" --algo kmp --from 1 --to 2 --icase -m time --timestart 01:02:03 --timestop 03:04:05 --monthdays 1,2,3,4,5 --weekdays Mon,Fri,Sun --datestart 2001-02-03T04:05:06 --datestop 2012-09-08T09:06:05 --utc -m tos --tos 0xff/0x01 -m u32 --u32 "0x0=0x0" -m u32 --u32 "0x0=0x0" -m hbh -m hbh -m hl --hl-eq 1
--A INPUT -m ipv6header --header hop-by-hop --soft
--A INPUT -p tcp -m cluster --cluster-local-nodemask 0x00000001 --cluster-total-nodes 2 --cluster-hash-seed 0x00000001
--A INPUT -p tcp -m cluster --cluster-local-nodemask 0x00000001 --cluster-total-nodes 2 --cluster-hash-seed 0x00000001
--A INPUT -p tcp -m comment --comment foo
--A INPUT -p tcp -m connbytes --connbytes 1:2 --connbytes-mode packets --connbytes-dir both
--A INPUT -p tcp -m connlimit --connlimit-upto 1 --connlimit-mask 8 --connlimit-saddr
--A INPUT -p tcp -m connlimit --connlimit-above 1 --connlimit-mask 9 --connlimit-daddr
--A INPUT -p tcp -m connmark --mark 0x99
--A INPUT -p tcp -m conntrack --ctstate INVALID --ctproto 6 --ctorigsrc fe80::/64 --ctorigdst fe80::/64 --ctreplsrc fe80::/64 --ctrepldst fe80::/64 --ctorigsrcport 12 --ctorigdstport 13 --ctreplsrcport 14 --ctrepldstport 15 --ctstatus EXPECTED --ctexpire 1:2 --ctdir REPLY
--A INPUT -p tcp -m cpu --cpu 2
--A INPUT -p tcp -m dscp --dscp 0x04
--A INPUT -p tcp -m dscp --dscp 0x00
--A INPUT -p tcp -m hashlimit --hashlimit-upto 1/sec --hashlimit-burst 5 --hashlimit-mode srcip,dstip --hashlimit-name f1 --hashlimit-htable-size 64 --hashlimit-htable-max 128 --hashlimit-htable-gcinterval 60 --hashlimit-htable-expire 120 --hashlimit-srcmask 24 --hashlimit-dstmask 24
--A INPUT -p tcp -m hashlimit --hashlimit-above 5/sec --hashlimit-burst 5 --hashlimit-name f1
--A INPUT -p tcp -m helper --helper ftp
--A INPUT -p tcp -m iprange --src-range ::1-::2 --dst-range ::1-::2
--A INPUT -p tcp -m length --length 1:2
--A INPUT -p tcp -m limit --limit 1/sec
--A INPUT -p tcp -m mac --mac-source 01:02:03:04:05:06
--A INPUT -p tcp -m mark --mark 0x1
--A INPUT -p tcp -m physdev --physdev-in eth0
--A INPUT -p tcp -m pkttype --pkt-type unicast
--A INPUT -p tcp -m policy --dir in --pol ipsec --strict --reqid 1 --spi 0x1 --proto esp --mode tunnel --tunnel-dst fe80::/64 --tunnel-src fe80::/64 --next --reqid 2
--A INPUT -p tcp -m quota --quota 0
--A INPUT -p tcp -m recent --rcheck --name DEFAULT --rsource
--A INPUT -p tcp -m socket --transparent
--A INPUT -p tcp -m string --string "foobar" --algo kmp --from 1 --to 2 --icase
--A INPUT -p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN
--A INPUT -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN
--A INPUT -p tcp -m tos --tos 0xff/0x01
--A INPUT -p tcp -m u32 --u32 "0x0=0x0" -m u32 --u32 "0x0=0x0"
--A INPUT -p tcp -m hbh -m hbh -m hl --hl-eq 1 -m ipv6header --header hop-by-hop --soft
--A INPUT -m ipv6header --header hop-by-hop --soft -m rt --rt-type 2 --rt-segsleft 2 --rt-len 5 -m rt --rt-type 0 --rt-segsleft 2 --rt-len 5 --rt-0-res --rt-0-addrs ::1 --rt-0-not-strict -m rt --rt-type 0 --rt-segsleft 2 --rt-len 5 --rt-0-res --rt-0-addrs ::1,::2 --rt-0-not-strict
--A INPUT -p tcp -m cpu --cpu 1 -m tcp --sport 1:2 --dport 1:2 --tcp-option 1 --tcp-flags FIN,SYN,RST,ACK SYN -m cpu --cpu 1
--A INPUT -p dccp -m cpu --cpu 1 -m dccp --sport 1:2 --dport 3:4 -m cpu --cpu 1
--A INPUT -p udp -m cpu --cpu 1 -m udp --sport 1:2 --dport 3:4 -m cpu --cpu 1
--A INPUT -p sctp -m cpu --cpu 1 -m sctp --sport 1:2 --dport 3:4 --chunk-types all INIT,SACK -m cpu --cpu 1
--A INPUT -p esp -m esp --espspi 1:2
--A INPUT -p tcp -m multiport --dports 1,2 -m multiport --dports 1,2
--A INPUT -p tcp -m tcpmss --mss 1:2 -m tcp --tcp-flags FIN,SYN,RST,ACK SYN
--A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 4/0
--A INPUT
--A INPUT -p mobility
--A INPUT -p mobility -m mh --mh-type 3
--A OUTPUT -m owner --socket-exists --uid-owner 1-2 --gid-owner 2-3
--A matches -m connbytes --connbytes 1 --connbytes-mode bytes --connbytes-dir both
--A matches
--A matches -m connbytes --connbytes :2 --connbytes-mode bytes --connbytes-dir both
--A matches
--A matches -m connbytes --connbytes 0:3 --connbytes-mode bytes --connbytes-dir both
--A matches
--A matches -m connbytes --connbytes 4: --connbytes-mode bytes --connbytes-dir both
--A matches
--A matches -m connbytes --connbytes 5:18446744073709551615 --connbytes-mode bytes --connbytes-dir both
--A matches
--A matches -m conntrack --ctexpire 1
--A matches
--A matches -m conntrack --ctexpire :2
--A matches
--A matches -m conntrack --ctexpire 0:3
--A matches
--A matches -m conntrack --ctexpire 4:
--A matches
--A matches -m conntrack --ctexpire 5:4294967295
--A matches
--A matches -p esp -m esp --espspi 1
--A matches
--A matches -p esp -m esp --espspi :2
--A matches
--A matches -p esp -m esp --espspi 0:3
--A matches
--A matches -p esp -m esp --espspi 4:
--A matches
--A matches -p esp -m esp --espspi 5:4294967295
--A matches
--A matches -m ipvs --vaddr fe80::/64 --vport 1 --vdir REPLY --vmethod GATE --vportctl 21
--A matches
--A matches -m length --length 1
--A matches
--A matches -m length --length :2
--A matches
--A matches -m length --length 0:3
--A matches
--A matches -m length --length 4:
--A matches
--A matches -m length --length 5:65535
--A matches
--A matches -p tcp -m tcpmss --mss 1
--A matches
--A matches -p tcp -m tcpmss --mss :2
--A matches
--A matches -p tcp -m tcpmss --mss 0:3
--A matches
--A matches -p tcp -m tcpmss --mss 4:
--A matches
--A matches -p tcp -m tcpmss --mss 5:65535
--A matches
--A matches -m time --timestart 01:02:03 --timestop 04:05:06 --monthdays 1,2,3,4,5 --weekdays Mon,Fri,Sun --datestart 2001-02-03T04:05:06 --datestop 2012-09-08T09:06:05 --localtz
--A matches
--A matches -m time --timestart 01:02:03 --timestop 04:05:06 --monthdays 1,2,3,4,5 --weekdays Mon,Fri,Sun --datestart 2001-02-03T04:05:06 --datestop 2012-09-08T09:06:05 --kerneltz
--A matches
--A matches -m time --timestart 01:02:03 --timestop 04:05:06 --monthdays 1,2,3,4,5 --weekdays Mon,Fri,Sun --datestart 2001-02-03T04:05:06 --datestop 2012-09-08T09:06:05
--A matches
--A matches -m time --timestart 02:00:00 --timestop 03:00:00 --datestart 1970-01-01T02:00:00 --datestop 1970-01-01T03:00:00
--A matches
--A matches -m ah --ahspi 1
--A matches
--A matches -m ah --ahspi :2
--A matches
--A matches -m ah --ahspi 0:3
--A matches
--A matches -m ah --ahspi 4:
--A matches
--A matches -m ah --ahspi 5:4294967295
--A matches
--A matches -m frag --fragid 1
--A matches
--A matches -m frag --fragid :2
--A matches
--A matches -m frag --fragid 0:3
--A matches
--A matches -m frag --fragid 4:
--A matches
--A matches -m frag --fragid 5:4294967295
--A matches
--A matches -m rt --rt-segsleft 1
--A matches
--A matches -m rt --rt-segsleft :2
--A matches
--A matches -m rt --rt-segsleft 0:3
--A matches
--A matches -m rt --rt-segsleft 4:
--A matches
--A matches -m rt --rt-segsleft 5:4294967295
--A matches
--A ntarg -j NFQUEUE --queue-num 1
--A ntarg
--A ntarg -j NFQUEUE --queue-balance 8:99
--A ntarg
--A ntarg -j RATEEST --rateest-name RE1 --rateest-interval 250.0ms --rateest-ewmalog 500.0ms
--A ntarg
--A ntarg -j RATEEST --rateest-name RE2 --rateest-interval 250.0ms --rateest-ewmalog 500.0ms
--A ntarg
-#-A zmatches -m rateest --rateest RE1 --rateest-lt --rateest-bps 8bit
-#-A zmatches -m rateest --rateest RE1 --rateest-eq --rateest-bps 8bit
-#-A zmatches -m rateest --rateest RE1 --rateest-gt --rateest-bps 8bit
-#-A zmatches -m rateest --rateest RE1 --rateest-lt --rateest-pps 5
-#-A zmatches -m rateest --rateest RE1 --rateest-eq --rateest-pps 5
-#-A zmatches -m rateest --rateest RE1 --rateest-gt --rateest-pps 5
-#-A zmatches -m rateest --rateest-delta --rateest RE1 --rateest-bps1 8bit --rateest-lt --rateest-bps2 16bit
-#-A zmatches -m rateest --rateest1 RE1 --rateest-lt --rateest2 RE2 --bytes
-#-A zmatches -m rateest --rateest1 RE1 --rateest-lt --rateest2 RE2 --packets
-#-A zmatches -m rateest --rateest-delta --rateest RE1 --rateest-bps1 8bit --rateest-eq --rateest-bps2 16bit
-#-A zmatches -m rateest --rateest-delta --rateest RE1 --rateest-bps1 8bit --rateest-gt --rateest-bps2 16bit
-#-A zmatches -m rateest --rateest-delta --rateest RE1 --rateest-pps1 8 --rateest-lt --rateest-pps2 9
-#-A zmatches -m rateest --rateest-delta --rateest RE1 --rateest-pps1 8 --rateest-eq --rateest-pps2 9
-#-A zmatches -m rateest --rateest-delta --rateest RE1 --rateest-pps1 8 --rateest-gt --rateest-pps2 9
-COMMIT
-# Completed on Mon Jan 31 02:19:54 2011
diff --git a/utils/.gitignore b/utils/.gitignore
index ccfd2ec..6300812 100644
--- a/utils/.gitignore
+++ b/utils/.gitignore
@@ -1 +1,4 @@
 /nfnl_osf
+/nfnl_osf.8
+/nfbpf_compile
+/nfbpf_compile.8
diff --git a/utils/Makefile.am b/utils/Makefile.am
index 306d993..42bd973 100644
--- a/utils/Makefile.am
+++ b/utils/Makefile.am
@@ -1,9 +1,35 @@
 # -*- Makefile -*-
 
 AM_CFLAGS = ${regular_CFLAGS}
-AM_CPPFLAGS = ${regular_CPPFLAGS} -I${top_builddir}/include -I${top_srcdir}/include
+AM_CPPFLAGS = ${regular_CPPFLAGS} -I${top_builddir}/include \
+              -I${top_srcdir}/include ${libnfnetlink_CFLAGS}
 
-sbin_PROGRAMS = nfnl_osf
-pkgdata_DATA = pf.os
+sbin_PROGRAMS =
+pkgdata_DATA =
+man_MANS =
 
-nfnl_osf_LDADD = -lnfnetlink
+if HAVE_LIBNFNETLINK
+man_MANS += nfnl_osf.8
+sbin_PROGRAMS += nfnl_osf
+pkgdata_DATA += pf.os
+
+nfnl_osf_LDADD = ${libnfnetlink_LIBS}
+
+uninstall-hook:
+	dir=${DESTDIR}${pkgdatadir}; { \
+		test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; \
+	} || rmdir -p --ignore-fail-on-non-empty "$$dir"
+endif
+
+if ENABLE_BPFC
+man_MANS += nfbpf_compile.8
+sbin_PROGRAMS += nfbpf_compile
+nfbpf_compile_LDADD = -lpcap
+endif
+
+if ENABLE_SYNCONF
+sbin_PROGRAMS += nfsynproxy
+nfsynproxy_LDADD = -lpcap
+endif
+
+CLEANFILES = nfnl_osf.8 nfbpf_compile.8
diff --git a/utils/nfbpf_compile.8.in b/utils/nfbpf_compile.8.in
new file mode 100644
index 0000000..d02979a
--- /dev/null
+++ b/utils/nfbpf_compile.8.in
@@ -0,0 +1,70 @@
+.TH NFBPF_COMPILE 8 "" "@PACKAGE_STRING@" "@PACKAGE_STRING@"
+
+.SH NAME
+nfbpf_compile \- generate bytecode for use with xt_bpf
+.SH SYNOPSIS
+
+.ad l
+.in +8
+.ti -8
+.B nfbpf_compile
+[
+.I LLTYPE
+]
+.I PROGRAM
+
+.ti -8
+.I LLTYPE
+:= {
+.BR EN10MB " | " RAW " | " SLIP " | "
+.I ...
+}
+
+.SH DESCRIPTION
+The
+.B nfbpf_compile
+utility aids in generating BPF byte code suitable for passing to
+the iptables
+.B bpf
+match.
+
+.SH OPTIONS
+
+.TP
+.I LLTYPE
+Link-layer header type to operate on. This is a name as defined in
+.RB < pcap/dlt.h >
+but with the leading
+.B DLT_
+prefix stripped. For use with iptables,
+.B RAW
+should be the right choice (it's also the default if not specified).
+
+.TP
+.I PROGRAM
+The BPF expression to compile, see
+.BR pcap-filter (7)
+for a description of the language.
+
+.SH EXIT STATUS
+The program returns 0 on success, 1 otherwise.
+
+.SH EXAMPLE
+Match incoming TCP packets with size bigger than 100 bytes:
+.P
+.in +8
+.EE
+bpf=$(nfbpf_compile 'tcp and greater 100')
+.br
+iptables -A INPUT -m bpf --bytecode "$bpf" -j ACCEPT
+.RE
+.P
+The description of
+.B bpf
+match in
+.BR iptables-extensions (8)
+lists a few more examples.
+
+.SH SEE ALSO
+.BR iptables-extensions (8),
+.BR pcap-filter (7)
diff --git a/utils/nfbpf_compile.c b/utils/nfbpf_compile.c
new file mode 100644
index 0000000..2c46c7b
--- /dev/null
+++ b/utils/nfbpf_compile.c
@@ -0,0 +1,55 @@
+/*
+ * BPF program compilation tool
+ *
+ * Generates decimal output, similar to `tcpdump -ddd ...`.
+ * Unlike tcpdump, will generate for any given link layer type.
+ *
+ * Written by Willem de Bruijn (willemb@google.com)
+ * Copyright Google, Inc. 2013
+ * Licensed under the GNU General Public License version 2 (GPLv2)
+*/
+
+#include <pcap.h>
+#include <stdio.h>
+
+int main(int argc, char **argv)
+{
+	struct bpf_program program;
+	struct bpf_insn *ins;
+	int i, dlt = DLT_RAW;
+
+	if (argc < 2 || argc > 3) {
+		fprintf(stderr, "Usage:    %s [link] '<program>'\n\n"
+				"          link is a pcap linklayer type:\n"
+				"          one of EN10MB, RAW, SLIP, ...\n\n"
+				"Examples: %s RAW 'tcp and greater 100'\n"
+				"          %s EN10MB 'ip proto 47'\n'",
+				argv[0], argv[0], argv[0]);
+		return 1;
+	}
+
+	if (argc == 3) {
+		dlt = pcap_datalink_name_to_val(argv[1]);
+		if (dlt == -1) {
+			fprintf(stderr, "Unknown datalinktype: %s\n", argv[1]);
+			return 1;
+		}
+	}
+
+	if (pcap_compile_nopcap(65535, dlt, &program, argv[argc - 1], 1,
+				PCAP_NETMASK_UNKNOWN)) {
+		fprintf(stderr, "Compilation error\n");
+		return 1;
+	}
+
+	printf("%d,", program.bf_len);
+	ins = program.bf_insns;
+	for (i = 0; i < program.bf_len-1; ++ins, ++i)
+		printf("%u %u %u %u,", ins->code, ins->jt, ins->jf, ins->k);
+
+	printf("%u %u %u %u\n", ins->code, ins->jt, ins->jf, ins->k);
+
+	pcap_freecode(&program);
+	return 0;
+}
+
diff --git a/utils/nfnl_osf.8.in b/utils/nfnl_osf.8.in
new file mode 100644
index 0000000..140b5c3
--- /dev/null
+++ b/utils/nfnl_osf.8.in
@@ -0,0 +1,67 @@
+.TH NFNL_OSF 8 "" "@PACKAGE_STRING@" "@PACKAGE_STRING@"
+
+.SH NAME
+nfnl_osf \- OS fingerprint loader utility
+.SH SYNOPSIS
+
+.ad l
+.in +8
+.ti -8
+.B nfnl_osf
+.BI -f " fingerprints"
+[
+.B -d
+]
+
+.SH DESCRIPTION
+The
+.B nfnl_osf
+utility allows to load a set of operating system signatures into the kernel for
+later matching against using iptables'
+.B osf
+match.
+
+.SH OPTIONS
+
+.TP
+.BI -f " fingerprints"
+Read signatures from file
+.IR fingerprints .
+
+.TP
+.B -d
+Instead of adding the signatures from
+.I fingerprints
+into the kernel, remove them.
+
+.SH EXIT STATUS
+Exit status is 0 if command succeeded, otherwise a negative return code
+indicates the type of error which happened:
+
+.TP
+.B -1
+Illegal arguments passed, fingerprints file not readable or failure in netlink
+communication.
+
+.TP
+.B -ENOENT
+Fingerprints file not specified.
+
+.TP
+.B -EINVAL
+Netlink handle initialization failed or fingerprints file format invalid.
+
+.SH FILES
+
+An up to date set of operating system signatures can be downloaded from
+http://www.openbsd.org/cgi-bin/cvsweb/src/etc/pf.os .
+
+.SH SEE ALSO
+
+The description of
+.B osf
+match in
+.BR iptables-extensions (8)
+contains further information about the topic as well as example
+.B nfnl_osf
+invocations.
diff --git a/utils/nfnl_osf.c b/utils/nfnl_osf.c
index bb5f92d..8008e83 100644
--- a/utils/nfnl_osf.c
+++ b/utils/nfnl_osf.c
@@ -14,7 +14,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
 #include <sys/types.h>
@@ -141,7 +141,7 @@
 	if (tmp)
 		*tmp = '\0';
 
-	while (tmp && tmp + 1 && isspace(*(tmp + 1)))
+	while (tmp && isspace(*(tmp + 1)))
 		tmp++;
 
 	return tmp;
@@ -157,7 +157,6 @@
 	i = 0;
 	while (ptr != NULL && i < olen && *ptr != 0) {
 		val = 0;
-		op = 0;
 		wc = OSF_WSS_PLAIN;
 		switch (obuf[i]) {
 		case 'N':
@@ -344,33 +343,34 @@
 	pend = xt_osf_strchr(pbeg, OSFPDEL);
 	if (pend) {
 		*pend = '\0';
-		cnt = snprintf(obuf, sizeof(obuf), "%s,", pbeg);
+		i = sizeof(obuf);
+		snprintf(obuf, i, "%.*s,", i - 2, pbeg);
 		pbeg = pend + 1;
 	}
 
 	pend = xt_osf_strchr(pbeg, OSFPDEL);
 	if (pend) {
 		*pend = '\0';
+		i = sizeof(f.genre);
 		if (pbeg[0] == '@' || pbeg[0] == '*')
-			cnt = snprintf(f.genre, sizeof(f.genre), "%s", pbeg + 1);
-		else
-			cnt = snprintf(f.genre, sizeof(f.genre), "%s", pbeg);
+			pbeg++;
+		snprintf(f.genre, i, "%.*s", i - 1, pbeg);
 		pbeg = pend + 1;
 	}
 
 	pend = xt_osf_strchr(pbeg, OSFPDEL);
 	if (pend) {
 		*pend = '\0';
-		cnt = snprintf(f.version, sizeof(f.version), "%s", pbeg);
+		i = sizeof(f.version);
+		snprintf(f.version, i, "%.*s", i - 1, pbeg);
 		pbeg = pend + 1;
 	}
 
 	pend = xt_osf_strchr(pbeg, OSFPDEL);
 	if (pend) {
 		*pend = '\0';
-		cnt =
-		    snprintf(f.subtype, sizeof(f.subtype), "%s", pbeg);
-		pbeg = pend + 1;
+		i = sizeof(f.subtype);
+		snprintf(f.subtype, i, "%.*s", i - 1, pbeg);
 	}
 
 	xt_osf_parse_opt(f.opt, &f.opt_num, obuf, sizeof(obuf));
@@ -378,19 +378,21 @@
 	memset(buf, 0, sizeof(buf));
 
 	if (del)
-		nfnl_fill_hdr(nfnlssh, nmh, 0, AF_UNSPEC, 0, OSF_MSG_REMOVE, NLM_F_REQUEST);
+		nfnl_fill_hdr(nfnlssh, nmh, 0, AF_UNSPEC, 0, OSF_MSG_REMOVE,
+			      NLM_F_ACK | NLM_F_REQUEST);
 	else
-		nfnl_fill_hdr(nfnlssh, nmh, 0, AF_UNSPEC, 0, OSF_MSG_ADD, NLM_F_REQUEST | NLM_F_CREATE);
+		nfnl_fill_hdr(nfnlssh, nmh, 0, AF_UNSPEC, 0, OSF_MSG_ADD,
+			      NLM_F_ACK | NLM_F_REQUEST | NLM_F_CREATE);
 
 	nfnl_addattr_l(nmh, sizeof(buf), OSF_ATTR_FINGER, &f, sizeof(struct xt_osf_user_finger));
 
-	return nfnl_talk(nfnlh, nmh, 0, 0, NULL, NULL, NULL);
+	return nfnl_query(nfnlh, nmh);
 }
 
 static int osf_load_entries(char *path, int del)
 {
 	FILE *inf;
-	int err = 0;
+	int err = 0, lineno = 0;
 	char buf[1024];
 
 	inf = fopen(path, "r");
@@ -400,7 +402,9 @@
 	}
 
 	while(fgets(buf, sizeof(buf), inf)) {
-		int len;
+		int len, rc;
+
+		lineno++;
 
 		if (buf[0] == '#' || buf[0] == '\n' || buf[0] == '\r')
 			continue;
@@ -412,9 +416,11 @@
 
 		buf[len] = '\0';
 
-		err = osf_load_line(buf, len, del);
-		if (err)
-			break;
+		rc = osf_load_line(buf, len, del);
+		if (rc && (!del || errno != ENOENT)) {
+			ulog_err("Failed to load line %d", lineno);
+			err = rc;
+		}
 
 		memset(buf, 0, sizeof(buf));
 	}
@@ -438,7 +444,7 @@
 				break;
 			default:
 				fprintf(stderr,
-					"Usage: %s -f fingerprints -d <del rules> -h\n",
+					"Usage: %s -f fingerprints [-d]\n",
 					argv[0]);
 				return -1;
 		}
@@ -446,6 +452,7 @@
 
 	if (!fingerprints) {
 		err = -ENOENT;
+		ulog("Missing fingerprints file argument.\n");
 		goto err_out_exit;
 	}
 
diff --git a/utils/nfsynproxy.c b/utils/nfsynproxy.c
new file mode 100644
index 0000000..bf5c416
--- /dev/null
+++ b/utils/nfsynproxy.c
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2013 Patrick McHardy <kaber@trash.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <pcap/pcap.h>
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+
+static const char *iface = "lo";
+static uint16_t port;
+static const char *chain = "SYNPROXY";
+
+static int parse_packet(const char *host, const uint8_t *data)
+{
+	const struct iphdr *iph = (void *)data + 14;
+	const struct tcphdr *th = (void *)iph + iph->ihl * 4;
+	int length;
+	uint8_t *ptr;
+
+	if (!th->syn || !th->ack)
+		return 0;
+
+	printf("-A %s -d %s -p tcp --dport %u "
+	       "-m state --state UNTRACKED,INVALID "
+	       "-j SYNPROXY ", chain, host, port);
+
+	/* ECE && !CWR */
+	if (th->res2 == 0x1)
+		printf("--ecn ");
+
+	length = th->doff * 4 - sizeof(*th);
+	ptr = (uint8_t *)(th + 1);
+	while (length > 0) {
+		int opcode = *ptr++;
+		int opsize;
+
+		switch (opcode) {
+		case TCPOPT_EOL:
+			return 1;
+		case TCPOPT_NOP:
+			length--;
+			continue;
+		default:
+			opsize = *ptr++;
+			if (opsize < 2)
+				return 1;
+			if (opsize > length)
+				return 1;
+
+			switch (opcode) {
+			case TCPOPT_MAXSEG:
+				if (opsize == TCPOLEN_MAXSEG)
+					printf("--mss %u ", ntohs(*(uint16_t *)ptr));
+				break;
+			case TCPOPT_WINDOW:
+				if (opsize == TCPOLEN_WINDOW)
+					printf("--wscale %u ", *ptr);
+				break;
+			case TCPOPT_TIMESTAMP:
+				if (opsize == TCPOLEN_TIMESTAMP)
+					printf("--timestamp ");
+				break;
+			case TCPOPT_SACK_PERMITTED:
+				if (opsize == TCPOLEN_SACK_PERMITTED)
+					printf("--sack-perm ");
+				break;
+			}
+
+			ptr += opsize - 2;
+			length -= opsize;
+		}
+	}
+	printf("\n");
+	return 1;
+}
+
+static void probe_host(const char *host)
+{
+	struct sockaddr_in sin;
+	char pcap_errbuf[PCAP_ERRBUF_SIZE];
+	struct pcap_pkthdr pkthdr;
+	const uint8_t *data;
+	struct bpf_program fp;
+	pcap_t *ph;
+	int fd;
+
+	ph = pcap_create(iface, pcap_errbuf);
+	if (ph == NULL) {
+		perror("pcap_create");
+		goto err1;
+	}
+
+	if (pcap_setnonblock(ph, 1, pcap_errbuf) == -1) {
+		perror("pcap_setnonblock");
+		goto err2;
+	}
+
+	if (pcap_setfilter(ph, &fp) == -1) {
+		pcap_perror(ph, "pcap_setfilter");
+		goto err2;
+	}
+
+	if (pcap_activate(ph) != 0) {
+		pcap_perror(ph, "pcap_activate");
+		goto err2;
+	}
+
+	if (pcap_compile(ph, &fp, "src host 127.0.0.1 and tcp and src port 80",
+			 1, PCAP_NETMASK_UNKNOWN) == -1) {
+		pcap_perror(ph, "pcap_compile");
+		goto err2;
+	}
+
+	fd = socket(AF_INET, SOCK_STREAM, 0);
+	if (fd < 0) {
+		perror("socket");
+		goto err3;
+	}
+
+	memset(&sin, 0, sizeof(sin));
+	sin.sin_family		= AF_INET;
+	sin.sin_port		= htons(port);
+	sin.sin_addr.s_addr	= inet_addr(host);
+
+	if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
+		perror("connect");
+		goto err4;
+	}
+
+	for (;;) {
+		data = pcap_next(ph, &pkthdr);
+		if (data == NULL)
+			break;
+		if (parse_packet(host, data))
+			break;
+	}
+
+	close(fd);
+
+err4:
+	close(fd);
+err3:
+	pcap_freecode(&fp);
+err2:
+	pcap_close(ph);
+err1:
+	return;
+}
+
+enum {
+	OPT_HELP	= 'h',
+	OPT_IFACE	= 'i',
+	OPT_PORT	= 'p',
+	OPT_CHAIN	= 'c',
+};
+
+static const struct option options[] = {
+	{ .name = "help",  .has_arg = false, .val = OPT_HELP },
+	{ .name = "iface", .has_arg = true,  .val = OPT_IFACE },
+	{ .name = "port" , .has_arg = true,  .val = OPT_PORT },
+	{ .name = "chain", .has_arg = true,  .val = OPT_CHAIN },
+	{ }
+};
+
+static void print_help(const char *name)
+{
+	printf("%s [ options ] address...\n"
+	       "\n"
+	       "Options:\n"
+	       " -i/--iface        Outbound interface\n"
+	       " -p/--port         Port number to probe\n"
+	       " -c/--chain        Chain name to use for rules\n"
+	       " -h/--help         Show this help\n",
+	       name);
+}
+
+int main(int argc, char **argv)
+{
+	int optidx = 0, c;
+
+	for (;;) {
+		c = getopt_long(argc, argv, "hi:p:c:", options, &optidx);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case OPT_IFACE:
+			iface = optarg;
+			break;
+		case OPT_PORT:
+			port = atoi(optarg);
+			break;
+		case OPT_CHAIN:
+			chain = optarg;
+			break;
+		case OPT_HELP:
+			print_help(argv[0]);
+			exit(0);
+		case '?':
+			print_help(argv[0]);
+			exit(1);
+		}
+	}
+
+	argc -= optind;
+	argv += optind;
+
+	while (argc > 0) {
+		probe_host(*argv);
+		argc--;
+		argv++;
+	}
+	return 0;
+}
diff --git a/utils/pf.os b/utils/pf.os
index 44e0014..e285851 100644
--- a/utils/pf.os
+++ b/utils/pf.os
@@ -1,4 +1,5 @@
-# $OpenBSD: pf.os,v 1.20 2006/06/02 16:54:34 david Exp $
+# $FreeBSD: head/etc/pf.os 258865 2013-12-03 04:32:02Z eadler $
+# $OpenBSD: pf.os,v 1.27 2016/09/03 17:08:57 sthen Exp $
 # passive OS fingerprinting
 # -------------------------
 #
@@ -225,7 +226,13 @@
 S3:64:1:60:M*,S,T,N,W0:		Linux:2.4:.18-21:Linux 2.4.18 and newer
 S4:64:1:60:M*,S,T,N,W0:		Linux:2.4::Linux 2.4/2.6 <= 2.6.7
 S4:64:1:60:M*,S,T,N,W0:		Linux:2.6:.1-7:Linux 2.4/2.6 <= 2.6.7
-S4:64:1:60:M*,S,T,N,W7:		Linux:2.6:8:Linux 2.6.8 and newer (?)
+
+S4:64:1:60:M*,S,T,N,W5:		Linux:2.6::Linux 2.6 (newer, 1)
+S4:64:1:60:M*,S,T,N,W6:		Linux:2.6::Linux 2.6 (newer, 2)
+S4:64:1:60:M*,S,T,N,W7:		Linux:2.6::Linux 2.6 (newer, 3)
+T4:64:1:60:M*,S,T,N,W7:		Linux:2.6::Linux 2.6 (newer, 4)
+
+S10:64:1:60:M*,S,T,N,W4:	Linux:3.0::Linux 3.0
 
 S3:64:1:60:M*,S,T,N,W1:		Linux:2.5::Linux 2.5 (sometimes 2.4)
 S4:64:1:60:M*,S,T,N,W1:		Linux:2.5-2.6::Linux 2.5/2.6
@@ -298,12 +305,26 @@
 # ----------------- OpenBSD -----------------
 
 16384:64:0:60:M*,N,W0,N,N,T:		OpenBSD:2.6::NetBSD 1.3 (or OpenBSD 2.6)
-16384:64:1:64:M*,N,N,S,N,W0,N,N,T:	OpenBSD:3.0-3.9::OpenBSD 3.0-3.9
-16384:64:0:64:M*,N,N,S,N,W0,N,N,T:	OpenBSD:3.0-3.9:no-df:OpenBSD 3.0-3.9 (scrub no-df)
-57344:64:1:64:M*,N,N,S,N,W0,N,N,T:	OpenBSD:3.3-3.9::OpenBSD 3.3-3.9
-57344:64:0:64:M*,N,N,S,N,W0,N,N,T:	OpenBSD:3.3-3.9:no-df:OpenBSD 3.3-3.9 (scrub no-df)
+16384:64:1:64:M*,N,N,S,N,W0,N,N,T:	OpenBSD:3.0-4.8::OpenBSD 3.0-4.8
+16384:64:0:64:M*,N,N,S,N,W0,N,N,T:	OpenBSD:3.0-4.8:no-df:OpenBSD 3.0-4.8 (scrub no-df)
+57344:64:1:64:M*,N,N,S,N,W0,N,N,T:	OpenBSD:3.3-4.0::OpenBSD 3.3-4.0
+57344:64:0:64:M*,N,N,S,N,W0,N,N,T:	OpenBSD:3.3-4.0:no-df:OpenBSD 3.3-4.0 (scrub no-df)
 
-65535:64:1:64:M*,N,N,S,N,W0,N,N,T:	OpenBSD:3.0-3.9:opera:OpenBSD 3.0-3.9 (Opera)
+65535:64:1:64:M*,N,N,S,N,W0,N,N,T:	OpenBSD:3.0-4.0:opera:OpenBSD 3.0-4.0 (Opera)
+
+16384:64:1:64:M*,N,N,S,N,W3,N,N,T:	OpenBSD:4.9::OpenBSD 4.9
+16384:64:0:64:M*,N,N,S,N,W3,N,N,T:	OpenBSD:4.9:no-df:OpenBSD 4.9 (scrub no-df)
+
+16384:64:1:64:M*,N,N,S,N,W6,N,N,T:      OpenBSD:6.1::OpenBSD 6.1
+16384:64:0:64:M*,N,N,S,N,W6,N,N,T:      OpenBSD:6.1:no-df:OpenBSD 6.1 (scrub no-df)
+
+# ----------------- DragonFly BSD -----------------
+
+57344:64:1:60:M*,N,W0,N,N,T:		DragonFly:1.0:A:DragonFly 1.0A
+57344:64:0:64:M*,N,W0,N,N,S,N,N,T:	DragonFly:1.2-1.12::DragonFly 1.2-1.12
+5840:64:1:60:M*,S,T,N,W4:			DragonFly:2.0-2.1::DragonFly 2.0-2.1
+57344:64:0:64:M*,N,W0,N,N,S,N,N,T:	DragonFly:2.2-2.3::DragonFly 2.2-2.3
+57344:64:0:64:M*,N,W5,N,N,S,N,N,T:	DragonFly:2.4-2.7::DragonFly 2.4-2.7
 
 # ----------------- Solaris -----------------
 
@@ -356,13 +377,12 @@
 16616:255:1:48:M*,N,N,N:		MacOS:8.1-8.6:OTTCP:MacOS 8.1-8.6 (OTTCP)
 32768:255:1:48:M*,W0,N:			MacOS:9.0-9.2::MacOS 9.0-9.2
 65535:255:1:48:M*,N,N,N,N:		MacOS:9.1::MacOS 9.1 (OT 2.7.4)
-65535:64:1:64:M*,N,W0,N,N,T,S,E,E:	MacOS:10::MacOS X
 
 
 # ----------------- Windows -----------------
 
 # Windows TCP/IP stack is a mess. For most recent XP, 2000 and
-# even 98, the pathlevel, not the actual OS version, is more
+# even 98, the patchlevel, not the actual OS version, is more
 # relevant to the signature. They share the same code, so it would
 # seem. Luckily for us, almost all Windows 9x boxes have an
 # awkward MSS of 536, which I use to tell one from another
@@ -426,6 +446,8 @@
 32767:128:1:48:M*,N,N,S:		Windows:2000:SP4:Windows SP1, 2000 SP4
 32767:128:1:48:M*,N,N,S:		Windows:XP:SP1:Windows SP1, 2000 SP4
 
+8192:128:1:52:M*,N,W2,N,N,S:		Windows:Vista::Windows Vista/7
+
 # Odds, ends, mods:
 
 S52:128:1:48:M1260,N,N,S:		Windows:2000:cisco:Windows XP/2000 via Cisco
diff --git a/xlate-test.py b/xlate-test.py
new file mode 100755
index 0000000..4c014f9
--- /dev/null
+++ b/xlate-test.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python3
+# encoding: utf-8
+
+import os
+import sys
+import shlex
+import argparse
+from subprocess import Popen, PIPE
+
+keywords = ("iptables-translate", "ip6tables-translate", "ebtables-translate")
+xtables_nft_multi = 'xtables-nft-multi'
+
+if sys.stdout.isatty():
+    colors = {"magenta": "\033[95m", "green": "\033[92m", "yellow": "\033[93m",
+              "red": "\033[91m", "end": "\033[0m"}
+else:
+    colors = {"magenta": "", "green": "", "yellow": "", "red": "", "end": ""}
+
+
+def magenta(string):
+    return colors["magenta"] + string + colors["end"]
+
+
+def red(string):
+    return colors["red"] + string + colors["end"]
+
+
+def yellow(string):
+    return colors["yellow"] + string + colors["end"]
+
+
+def green(string):
+    return colors["green"] + string + colors["end"]
+
+
+def run_test(name, payload):
+    global xtables_nft_multi
+    test_passed = True
+    tests = passed = failed = errors = 0
+    result = []
+
+    for line in payload:
+        if line.startswith(keywords):
+            tests += 1
+            process = Popen([ xtables_nft_multi ] + shlex.split(line), stdout=PIPE, stderr=PIPE)
+            (output, error) = process.communicate()
+            if process.returncode == 0:
+                translation = output.decode("utf-8").rstrip(" \n")
+                expected = next(payload).rstrip(" \n")
+                if translation != expected:
+                    test_passed = False
+                    failed += 1
+                    result.append(name + ": " + red("Fail"))
+                    result.append(magenta("src: ") + line.rstrip(" \n"))
+                    result.append(magenta("exp: ") + expected)
+                    result.append(magenta("res: ") + translation + "\n")
+                    test_passed = False
+                else:
+                    passed += 1
+            else:
+                test_passed = False
+                errors += 1
+                result.append(name + ": " + red("Error: ") + "iptables-translate failure")
+                result.append(error.decode("utf-8"))
+    if (passed == tests) and not args.test:
+        print(name + ": " + green("OK"))
+    if not test_passed:
+        print("\n".join(result))
+    if args.test:
+        print("1 test file, %d tests, %d tests passed, %d tests failed, %d errors" % (tests, passed, failed, errors))
+    else:
+        return tests, passed, failed, errors
+
+
+def load_test_files():
+    test_files = total_tests = total_passed = total_error = total_failed = 0
+    for test in sorted(os.listdir("extensions")):
+        if test.endswith(".txlate"):
+            with open("extensions/" + test, "r") as payload:
+                tests, passed, failed, errors = run_test(test, payload)
+                test_files += 1
+                total_tests += tests
+                total_passed += passed
+                total_failed += failed
+                total_error += errors
+
+
+    print("%d test files, %d tests, %d tests passed, %d tests failed, %d errors" % (test_files, total_tests, total_passed, total_failed, total_error))
+
+def main():
+    global xtables_nft_multi
+    if not args.host:
+        os.putenv("XTABLES_LIBDIR", os.path.abspath("extensions"))
+        xtables_nft_multi = os.path.abspath(os.path.curdir) \
+                            + '/iptables/' + xtables_nft_multi
+
+    if args.test:
+        if not args.test.endswith(".txlate"):
+            args.test += ".txlate"
+        try:
+            with open(args.test, "r") as payload:
+                run_test(args.test, payload)
+        except IOError:
+            print(red("Error: ") + "test file does not exist")
+    else:
+        load_test_files()
+
+
+parser = argparse.ArgumentParser()
+parser.add_argument('-H', '--host', action='store_true',
+                    help='Run tests against installed binaries')
+parser.add_argument("test", nargs="?", help="run only the specified test file")
+args = parser.parse_args()
+main()