Merge tag 'v1.8.1' of git://git.netfilter.org/iptables into work

iptables 1.8.1 release

Generated via:
  git fetch git://git.netfilter.org/iptables v1.8.1
  git merge FETCH_HEAD

Signed-off-by: Maciej Żenczykowski <maze@google.com>
Change-Id: Idc800408d701158383d4c40dd479cfe14e024594
diff --git a/configure.ac b/configure.ac
index 07e3206..1da8555 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,9 +1,9 @@
 
-AC_INIT([iptables], [1.8.0])
+AC_INIT([iptables], [1.8.1])
 
 # See libtool.info "Libtool's versioning system"
-libxtables_vcurrent=12
-libxtables_vage=0
+libxtables_vcurrent=13
+libxtables_vage=1
 
 AC_CONFIG_AUX_DIR([build-aux])
 AC_CONFIG_HEADERS([config.h])
diff --git a/extensions/GNUmakefile.in b/extensions/GNUmakefile.in
index bee666e..c0d73cd 100644
--- a/extensions/GNUmakefile.in
+++ b/extensions/GNUmakefile.in
@@ -40,8 +40,8 @@
 #	Wildcard module list
 #
 pfx_build_mod := $(patsubst ${srcdir}/libxt_%.c,%,$(sort $(wildcard ${srcdir}/libxt_*.c)))
-pfb_build_mod := $(patsubst ${srcdir}/libebt_%.c,%,$(sort $(wildcard ${srcdir}/libebt_*.c)))
-pfa_build_mod := $(patsubst ${srcdir}/libarpt_%.c,%,$(sort $(wildcard ${srcdir}/libarpt_*.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)))
diff --git a/extensions/libarpt_mangle.c b/extensions/libarpt_mangle.c
index 0d1f140..547f5b6 100644
--- a/extensions/libarpt_mangle.c
+++ b/extensions/libarpt_mangle.c
@@ -139,47 +139,39 @@
 			(j==l-1) ? "" : ":");
 }
 
+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);
-	char buf[100];
 
 	if (m->flags & ARPT_MANGLE_SIP) {
-		if (numeric)
-			sprintf(buf, "%s",
-				xtables_ipaddr_to_numeric(&(m->u_s.src_ip)));
-		else
-			sprintf(buf, "%s",
-				xtables_ipaddr_to_anyname(&(m->u_s.src_ip)));
-		printf("--mangle-ip-s %s ", buf);
+		printf(" --mangle-ip-s %s",
+		       ipaddr_to(&(m->u_s.src_ip), numeric));
 	}
 	if (m->flags & ARPT_MANGLE_SDEV) {
-		printf("--mangle-mac-s ");
+		printf(" --mangle-mac-s ");
 		print_mac((unsigned char *)m->src_devaddr, 6);
-		printf(" ");
 	}
 	if (m->flags & ARPT_MANGLE_TIP) {
-		if (numeric)
-			sprintf(buf, "%s",
-				xtables_ipaddr_to_numeric(&(m->u_t.tgt_ip)));
-		else
-			sprintf(buf, "%s",
-				xtables_ipaddr_to_anyname(&(m->u_t.tgt_ip)));
-		printf("--mangle-ip-d %s ", buf);
+		printf(" --mangle-ip-d %s",
+		       ipaddr_to(&(m->u_t.tgt_ip), numeric));
 	}
 	if (m->flags & ARPT_MANGLE_TDEV) {
-		printf("--mangle-mac-d ");
+		printf(" --mangle-mac-d ");
 		print_mac((unsigned char *)m->tgt_devaddr, 6);
-		printf(" ");
 	}
 	if (m->target != NF_ACCEPT) {
-		printf("--mangle-target ");
-		if (m->target == NF_DROP)
-			printf("DROP ");
-		else
-			printf("CONTINUE ");
+		printf(" --mangle-target %s",
+		       m->target == NF_DROP ? "DROP" : "CONTINUE");
 	}
 }
 
diff --git a/extensions/libebt_arp.c b/extensions/libebt_arp.c
index 45fc8d7..522c57c 100644
--- a/extensions/libebt_arp.c
+++ b/extensions/libebt_arp.c
@@ -14,7 +14,7 @@
 #include <xtables.h>
 #include <netinet/ether.h>
 
-#include <ebtables/ethernetdb.h>
+#include <xtables.h>
 #include <net/if_arp.h>
 #include <linux/netfilter_bridge/ebt_arp.h>
 #include "iptables/nft.h"
@@ -75,7 +75,7 @@
 		printf(" %d = %s\n", i + 1, opcodes[i]);
 	printf(
 " hardware type string: 1 = Ethernet\n"
-" protocol type string: see "_PATH_ETHERTYPES"\n");
+" protocol type string: see "XT_PATH_ETHERTYPES"\n");
 }
 
 #define OPT_OPCODE 0x01
@@ -209,76 +209,6 @@
 	return 0;
 }
 
-static struct ethertypeent *brarp_getethertypeent(FILE *etherf, const char *name)
-{
-	static struct ethertypeent et_ent;
-	char *e, *found_name;
-	char line[1024];
-
-	while ((e = fgets(line, sizeof(line), etherf))) {
-		char *endptr, *cp;
-
-		if (*e == '#')
-			continue;
-
-		cp = strpbrk(e, "#\n");
-		if (cp == NULL)
-			continue;
-		*cp = '\0';
-		found_name = e;
-
-		cp = strpbrk(e, " \t");
-		if (cp == NULL)
-			continue;
-
-		*cp++ = '\0';
-		while (*cp == ' ' || *cp == '\t')
-			cp++;
-		e = strpbrk(cp, " \t");
-		if (e != NULL)
-			*e++ = '\0';
-
-		et_ent.e_ethertype = strtol(cp, &endptr, 16);
-		if (*endptr != '\0' ||
-		    (et_ent.e_ethertype < ETH_ZLEN || et_ent.e_ethertype > 0xFFFF))
-			continue;
-
-		if (strcasecmp(found_name, name) == 0)
-			return (&et_ent);
-
-		if (e != NULL) {
-			cp = e;
-			while (cp && *cp) {
-				if (*cp == ' ' || *cp == '\t') {
-					cp++;
-					continue;
-				}
-				e = cp;
-				cp = strpbrk(cp, " \t");
-				if (cp != NULL)
-					*cp++ = '\0';
-				if (strcasecmp(e, name) == 0)
-					return (&et_ent);
-				e = cp;
-			}
-		}
-	}
-
-	return NULL;
-}
-
-static struct ethertypeent *brarp_getethertypebyname(const char *name)
-{
-	struct ethertypeent *e;
-	FILE *etherf;
-
-	etherf = fopen(_PATH_ETHERTYPES, "r");
-
-	e = brarp_getethertypeent(etherf, name);
-	fclose(etherf);
-	return (e);
-}
-
 static int
 brarp_parse(int c, char **argv, int invert, unsigned int *flags,
 	    const void *entry, struct xt_entry_match **match)
@@ -332,9 +262,9 @@
 
 		i = strtol(optarg, &end, 16);
 		if (i < 0 || i >= (0x1 << 16) || *end !='\0') {
-			struct ethertypeent *ent;
+			struct xt_ethertypeent *ent;
 
-			ent = brarp_getethertypebyname(argv[optind - 1]);
+			ent = xtables_getethertypebyname(argv[optind - 1]);
 			if (!ent)
 				xtables_error(PARAMETER_PROBLEM, "Problem with specified ARP "
 								 "protocol type");
diff --git a/extensions/libebt_ip.c b/extensions/libebt_ip.c
index 4b5c166..2b28c61 100644
--- a/extensions/libebt_ip.c
+++ b/extensions/libebt_ip.c
@@ -437,10 +437,6 @@
 		if (invert)
 			info->invflags |= EBT_IP_PROTO;
 		info->protocol = xtables_parse_protocol(optarg);
-		if (info->protocol == -1)
-			xtables_error(PARAMETER_PROBLEM,
-				      "Unknown specified IP protocol - %s",
-				      optarg);
 		info->bitmask |= EBT_IP_PROTO;
 		break;
 	default:
diff --git a/extensions/libebt_ip6.c b/extensions/libebt_ip6.c
index 4c60570..ab1413b 100644
--- a/extensions/libebt_ip6.c
+++ b/extensions/libebt_ip6.c
@@ -376,10 +376,6 @@
 		if (invert)
 			info->invflags |= EBT_IP6_PROTO;
 		info->protocol = xtables_parse_protocol(optarg);
-		if (info->protocol == -1)
-			xtables_error(PARAMETER_PROBLEM,
-				      "Unknown specified IP protocol - %s",
-				      optarg);
 		info->bitmask |= EBT_IP6_PROTO;
 		break;
 	default:
diff --git a/extensions/libebt_limit.c b/extensions/libebt_limit.c
deleted file mode 100644
index e8da2a1..0000000
--- a/extensions/libebt_limit.c
+++ /dev/null
@@ -1,205 +0,0 @@
-/* ebt_limit
- *
- * Authors:
- * Tom Marshall <tommy@home.tig-grr.com>
- *
- * Mostly copied from iptables' limit match.
- *
- * September, 2003
- *
- * Translated to use 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 <errno.h>
-#include <xtables.h>
-#include <linux/netfilter_bridge/ebt_limit.h>
-#include "iptables/nft.h"
-#include "iptables/nft-bridge.h"
-
-#define EBT_LIMIT_AVG	"3/hour"
-#define EBT_LIMIT_BURST	5
-
-#define FLAG_LIMIT		0x01
-#define FLAG_LIMIT_BURST	0x02
-#define ARG_LIMIT		'1'
-#define ARG_LIMIT_BURST		'2'
-
-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 void brlimit_print_help(void)
-{
-	printf(
-"limit options:\n"
-"--limit avg                   : max average match rate: default "EBT_LIMIT_AVG"\n"
-"                                [Packets per second unless followed by \n"
-"                                /sec /minute /hour /day postfixes]\n"
-"--limit-burst number          : number to match in a burst, -1 < number < 10001,\n"
-"                                default %u\n", EBT_LIMIT_BURST);
-}
-
-static int parse_rate(const char *rate, uint32_t *val)
-{
-	const char *delim;
-	uint32_t r;
-	uint32_t 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;
-		else if (strncasecmp(delim+1, "minute", strlen(delim+1)) == 0)
-			mult = 60;
-		else if (strncasecmp(delim+1, "hour", strlen(delim+1)) == 0)
-			mult = 60*60;
-		else if (strncasecmp(delim+1, "day", strlen(delim+1)) == 0)
-			mult = 24*60*60;
-		else
-			return 0;
-	}
-	r = atoi(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 > EBT_LIMIT_SCALE)
-		return 0;
-
-	*val = EBT_LIMIT_SCALE * mult / r;
-	return 1;
-}
-
-static void brlimit_init(struct xt_entry_match *match)
-{
-	struct ebt_limit_info *r = (struct ebt_limit_info *)match->data;
-
-	parse_rate(EBT_LIMIT_AVG, &r->avg);
-	r->burst = EBT_LIMIT_BURST;
-}
-
-static int brlimit_parse(int c, char **argv, int invert, unsigned int *flags,
-			 const void *entry, struct xt_entry_match **match)
-{
-	struct ebt_limit_info *r = (struct ebt_limit_info *)(*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;
-}
-
-struct rates
-{
-	const char	*name;
-	uint32_t	mult;
-};
-
-static struct rates g_rates[] =
-{
-	{ "day",	EBT_LIMIT_SCALE*24*60*60 },
-	{ "hour",	EBT_LIMIT_SCALE*60*60 },
-	{ "minute",	EBT_LIMIT_SCALE*60 },
-	{ "second",	EBT_LIMIT_SCALE }
-};
-
-static void print_rate(uint32_t period)
-{
-	unsigned int i;
-
-	for (i = 1; i < sizeof(g_rates)/sizeof(struct rates); i++)
-		if (period > g_rates[i].mult ||
-		    g_rates[i].mult/period < g_rates[i].mult%period)
-			break;
-
-	printf("%u/%s ", g_rates[i-1].mult / period, g_rates[i-1].name);
-}
-
-static void brlimit_print(const void *ip, const struct xt_entry_match *match,
-			  int numeric)
-{
-	struct ebt_limit_info *r = (struct ebt_limit_info *)match->data;
-
-	printf("--limit ");
-	print_rate(r->avg);
-	printf("--limit-burst %u ", r->burst);
-}
-
-static void print_rate_xlate(struct xt_xlate *xl, uint32_t period)
-{
-	unsigned int i;
-
-	for (i = 1; i < sizeof(g_rates)/sizeof(struct rates); i++)
-		if (period > g_rates[i].mult ||
-		    g_rates[i].mult/period < g_rates[i].mult%period)
-			break;
-
-	xt_xlate_add(xl, "%u/%s ", g_rates[i-1].mult / period, g_rates[i-1].name);
-}
-
-static int brlimit_xlate(struct xt_xlate *xl,
-			 const struct xt_xlate_mt_params *params)
-{
-	const struct ebt_limit_info *r = (const void *)params->match->data;
-
-	xt_xlate_add(xl, "limit rate ");
-	print_rate_xlate(xl, r->avg);
-	if (r->burst != 0)
-		xt_xlate_add(xl, "burst %u packets ", r->burst);
-
-	return 1;
-}
-
-static struct xtables_match brlimit_match = {
-	.name		= "limit",
-	.revision	= 0,
-	.version	= XTABLES_VERSION,
-	.family		= NFPROTO_BRIDGE,
-	.size		= XT_ALIGN(sizeof(struct ebt_limit_info)),
-	.userspacesize	= offsetof(struct ebt_limit_info, prev),
-	.init		= brlimit_init,
-	.help		= brlimit_print_help,
-	.parse		= brlimit_parse,
-	.print		= brlimit_print,
-	.xlate		= brlimit_xlate,
-	.extra_opts	= brlimit_opts,
-};
-
-void _init(void)
-{
-	xtables_register_match(&brlimit_match);
-}
diff --git a/extensions/libebt_log.c b/extensions/libebt_log.c
index a86bdeb..8858cf0 100644
--- a/extensions/libebt_log.c
+++ b/extensions/libebt_log.c
@@ -92,6 +92,14 @@
 	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)
 {
@@ -125,26 +133,16 @@
 				      "Problem with the log-level");
 		break;
 	case LOG_IP:
-		if (invert)
-			xtables_error(PARAMETER_PROBLEM,
-				      "Unexpected `!' after --log-ip");
-		loginfo->bitmask |= EBT_LOG_IP;
+		loginfo->bitmask |= log_chk_inv(invert, EBT_LOG_IP, "-ip");
 		break;
 	case LOG_ARP:
-		if (invert)
-			xtables_error(PARAMETER_PROBLEM,
-				      "Unexpected `!' after --log-arp");
-		loginfo->bitmask |= EBT_LOG_ARP;
+		loginfo->bitmask |= log_chk_inv(invert, EBT_LOG_ARP, "-arp");
+		break;
 	case LOG_LOG:
-		if (invert)
-			xtables_error(PARAMETER_PROBLEM,
-				      "Unexpected `!' after --log");
+		loginfo->bitmask |= log_chk_inv(invert, 0, "");
 		break;
 	case LOG_IP6:
-		if (invert)
-			xtables_error(PARAMETER_PROBLEM,
-				      "Unexpected `!' after --log-ip6");
-		loginfo->bitmask |= EBT_LOG_IP6;
+		loginfo->bitmask |= log_chk_inv(invert, EBT_LOG_IP6, "-ip6");
 		break;
 	default:
 		return 0;
diff --git a/extensions/libebt_mark.c b/extensions/libebt_mark.c
index 94f489e..423c5c9 100644
--- a/extensions/libebt_mark.c
+++ b/extensions/libebt_mark.c
@@ -18,8 +18,6 @@
 #include "iptables/nft.h"
 #include "iptables/nft-bridge.h"
 
-static int mark_supplied;
-
 #define MARK_TARGET  '1'
 #define MARK_SETMARK '2'
 #define MARK_ORMARK  '3'
@@ -54,7 +52,6 @@
 
 	info->target = EBT_ACCEPT;
 	info->mark = 0;
-	mark_supplied = 0;
 }
 
 #define OPT_MARK_TARGET   0x01
@@ -133,7 +130,6 @@
 		xtables_error(PARAMETER_PROBLEM, "Bad MARK value '%s'",
 			      optarg);
 
-	mark_supplied = 1;
 	return 1;
 }
 
@@ -162,9 +158,6 @@
 
 static void brmark_final_check(unsigned int flags)
 {
-	if (mark_supplied == 0)
-		xtables_error(PARAMETER_PROBLEM, "No mark value supplied");
-
 	if (!flags)
 		xtables_error(PARAMETER_PROBLEM,
 			      "You must specify some option");
diff --git a/extensions/libebt_vlan.c b/extensions/libebt_vlan.c
index 4e2ea0f..a2a9dcc 100644
--- a/extensions/libebt_vlan.c
+++ b/extensions/libebt_vlan.c
@@ -12,7 +12,6 @@
 #include <getopt.h>
 #include <ctype.h>
 #include <xtables.h>
-#include <ebtables/ethernetdb.h>
 #include <linux/netfilter_bridge/ebt_vlan.h>
 #include <linux/if_ether.h>
 #include "iptables/nft.h"
@@ -50,82 +49,12 @@
 "--vlan-encap [!] encap : Encapsulated frame protocol (hexadecimal or name)\n");
 }
 
-static struct ethertypeent *vlan_getethertypeent(FILE *etherf, const char *name)
-{
-	static struct ethertypeent et_ent;
-	char *e, *found_name;
-	char line[1024];
-
-	while ((e = fgets(line, sizeof(line), etherf))) {
-		char *endptr, *cp;
-
-		if (*e == '#')
-			continue;
-
-		cp = strpbrk(e, "#\n");
-		if (cp == NULL)
-			continue;
-		*cp = '\0';
-		found_name = e;
-
-		cp = strpbrk(e, " \t");
-		if (cp == NULL)
-			continue;
-
-		*cp++ = '\0';
-		while (*cp == ' ' || *cp == '\t')
-			cp++;
-		e = strpbrk(cp, " \t");
-		if (e != NULL)
-			*e++ = '\0';
-
-		et_ent.e_ethertype = strtol(cp, &endptr, 16);
-		if (*endptr != '\0' ||
-		    (et_ent.e_ethertype < ETH_ZLEN || et_ent.e_ethertype > 0xFFFF))
-			continue; // skip invalid etherproto type entry
-
-		if (strcasecmp(found_name, name) == 0)
-			return (&et_ent);
-
-		if (e != NULL) {
-			cp = e;
-			while (cp && *cp) {
-				if (*cp == ' ' || *cp == '\t') {
-					cp++;
-					continue;
-				}
-				e = cp;
-				cp = strpbrk(cp, " \t");
-				if (cp != NULL)
-					*cp++ = '\0';
-				if (strcasecmp(e, name) == 0)
-					return (&et_ent);
-				e = cp;
-			}
-		}
-	}
-
-	return NULL;
-}
-
-static struct ethertypeent *brvlan_getethertypebyname(const char *name)
-{
-	struct ethertypeent *e;
-	FILE *etherf;
-
-	etherf = fopen(_PATH_ETHERTYPES, "r");
-
-	e = vlan_getethertypeent(etherf, name);
-	fclose(etherf);
-	return (e);
-}
-
 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 ethertypeent *ethent;
+	struct xt_ethertypeent *ethent;
 	char *end;
 	struct ebt_vlan_info local;
 
@@ -156,7 +85,7 @@
 			vlaninfo->invflags |= EBT_VLAN_ENCAP;
 		local.encap = strtoul(optarg, &end, 16);
 		if (*end != '\0') {
-			ethent = brvlan_getethertypebyname(optarg);
+			ethent = xtables_getethertypebyname(optarg);
 			if (ethent == NULL)
 				xtables_error(PARAMETER_PROBLEM, "Unknown --vlan-encap value ('%s')", optarg);
 			local.encap = ethent->e_ethertype;
diff --git a/extensions/libip6t_REJECT.c b/extensions/libip6t_REJECT.c
index c5b980d..e3929d1 100644
--- a/extensions/libip6t_REJECT.c
+++ b/extensions/libip6t_REJECT.c
@@ -13,13 +13,8 @@
 struct reject_names {
 	const char *name;
 	const char *alias;
-	enum ip6t_reject_with with;
 	const char *desc;
-};
-
-struct reject_names_xlate {
-	const char *name;
-	enum ip6t_reject_with with;
+	const char *xlate;
 };
 
 enum {
@@ -27,24 +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"},
-	{"icmp6-policy-fail", "policy-fail",
-		IP6T_ICMP6_POLICY_FAIL, "ICMPv6 policy fail"},
-	{"icmp6-reject-route", "reject-route",
-		IP6T_ICMP6_REJECT_ROUTE, "ICMPv6 reject route"}
+	[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
@@ -55,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);
 	}
@@ -91,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);
 }
@@ -108,55 +134,32 @@
 {
 	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[i].name);
+	printf(" --reject-with %s", reject_table[reject->with].name);
 }
 
-static const struct reject_names_xlate reject_table_xlate[] = {
-	{"no-route",		IP6T_ICMP6_NO_ROUTE},
-	{"admin-prohibited",	IP6T_ICMP6_ADM_PROHIBITED},
-	{"addr-unreachable",	IP6T_ICMP6_ADDR_UNREACH},
-	{"port-unreachable",	IP6T_ICMP6_PORT_UNREACH},
-	{"tcp reset",		IP6T_TCP_RESET},
-	{"policy-fail",		IP6T_ICMP6_POLICY_FAIL},
-	{"reject-route",	IP6T_ICMP6_REJECT_ROUTE}
-};
-
 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;
-	unsigned int i;
-
-	for (i = 0; i < ARRAY_SIZE(reject_table_xlate); ++i)
-		if (reject_table_xlate[i].with == reject->with)
-			break;
 
 	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_xlate[i].name);
+		xt_xlate_add(xl, "reject with %s",
+			     reject_table[reject->with].xlate);
 	else
 		xt_xlate_add(xl, "reject with icmpv6 type %s",
-			   reject_table_xlate[i].name);
+			     reject_table[reject->with].xlate);
 
 	return 1;
 }
diff --git a/extensions/libip6t_hbh.c b/extensions/libip6t_hbh.c
index 76b4ff0..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
 
diff --git a/extensions/libip6t_mh.txlate b/extensions/libip6t_mh.txlate
index f5d638c..ccc07c3 100644
--- a/extensions/libip6t_mh.txlate
+++ b/extensions/libip6t_mh.txlate
@@ -1,5 +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
+nft add rule ip6 filter INPUT meta l4proto 135 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
+nft add rule ip6 filter INPUT meta l4proto 135 mh type 1-3 counter accept
diff --git a/extensions/libipt_REJECT.c b/extensions/libipt_REJECT.c
index ba815ba..743dfff 100644
--- a/extensions/libipt_REJECT.c
+++ b/extensions/libipt_REJECT.c
@@ -20,13 +20,8 @@
 struct reject_names {
 	const char *name;
 	const char *alias;
-	enum ipt_reject_with with;
 	const char *desc;
-};
-
-struct reject_names_xlate {
-	const char *name;
-	enum ipt_reject_with with;
+	const char *xlate;
 };
 
 enum {
@@ -34,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
@@ -64,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);
 	}
@@ -102,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)
@@ -124,61 +151,32 @@
 {
 	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;
 
-	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 const struct reject_names_xlate reject_table_xlate[] = {
-	{"net-unreachable",	IPT_ICMP_NET_UNREACHABLE},
-	{"host-unreachable",	IPT_ICMP_HOST_UNREACHABLE},
-	{"prot-unreachable",	IPT_ICMP_PROT_UNREACHABLE},
-	{"port-unreachable",	IPT_ICMP_PORT_UNREACHABLE},
-#if 0
-	{"echo-reply",		IPT_ICMP_ECHOREPLY},
-#endif
-	{"net-prohibited",	IPT_ICMP_NET_PROHIBITED},
-	{"host-prohibited",	IPT_ICMP_HOST_PROHIBITED},
-	{"tcp reset",		IPT_TCP_RESET},
-	{"admin-prohibited",	IPT_ICMP_ADMIN_PROHIBITED}
-};
-
 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;
-	unsigned int i;
-
-	for (i = 0; i < ARRAY_SIZE(reject_table_xlate); ++i) {
-		if (reject_table_xlate[i].with == reject->with)
-			break;
-	}
 
 	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_xlate[i].name);
+			     reject_table[reject->with].xlate);
 	else
 		xt_xlate_add(xl, "reject with icmp type %s",
-			   reject_table_xlate[i].name);
+			     reject_table[reject->with].xlate);
 
 	return 1;
 }
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.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_LED.c b/extensions/libxt_LED.c
index 8622c37..6ada795 100644
--- a/extensions/libxt_LED.c
+++ b/extensions/libxt_LED.c
@@ -53,8 +53,7 @@
 	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)
diff --git a/extensions/libxt_cgroup.c b/extensions/libxt_cgroup.c
index 480d64c..327032e 100644
--- a/extensions/libxt_cgroup.c
+++ b/extensions/libxt_cgroup.c
@@ -51,6 +51,24 @@
 	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;
@@ -80,6 +98,26 @@
 	}
 }
 
+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)
 {
@@ -121,6 +159,32 @@
 		       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)
 {
@@ -147,6 +211,22 @@
 	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,
@@ -176,6 +256,20 @@
 		.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)
diff --git a/extensions/libxt_conntrack.c b/extensions/libxt_conntrack.c
index 72c5220..daa8c15 100644
--- a/extensions/libxt_conntrack.c
+++ b/extensions/libxt_conntrack.c
@@ -673,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));
 	}
 }
 
@@ -774,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
diff --git a/extensions/libxt_ipvs.c b/extensions/libxt_ipvs.c
index 4672766..a6c57a0 100644
--- a/extensions/libxt_ipvs.c
+++ b/extensions/libxt_ipvs.c
@@ -126,19 +126,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 +146,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));
 	}
 }
 
diff --git a/extensions/libxt_limit.c b/extensions/libxt_limit.c
index c8ddca8..c7b6629 100644
--- a/extensions/libxt_limit.c
+++ b/extensions/libxt_limit.c
@@ -6,6 +6,8 @@
 #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>
@@ -13,6 +15,8 @@
 #include <xtables.h>
 #include <linux/netfilter/x_tables.h>
 #include <linux/netfilter/xt_limit.h>
+#include "iptables/nft.h"
+#include "iptables/nft-bridge.h"
 
 #define XT_LIMIT_AVG	"3/hour"
 #define XT_LIMIT_BURST	5
@@ -191,22 +195,100 @@
 	return 1;
 }
 
-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,
+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_mangle.c b/extensions/libxt_mangle.c
deleted file mode 100644
index 360742b..0000000
--- a/extensions/libxt_mangle.c
+++ /dev/null
@@ -1,396 +0,0 @@
-/*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * Authors:
- * 	Libarptc code from: Bart De Schuymer <bdschuym@pandora.be>
- * 	Port to libxtables: Tomasz Bursztyka <tomasz.bursztyka@linux.intel.com>
- */
-
-#include <stdio.h>
-#include <netdb.h>
-#include <string.h>
-#include <stdlib.h>
-#include <limits.h>
-#include <getopt.h>
-#include <errno.h>
-#include <netinet/ether.h>
-
-#include <xtables.h>
-#include <linux/netfilter_arp/arpt_mangle.h>
-
-static void mangle_help(void)
-{
-	printf(
-"mangle target options:\n"
-"--mangle-ip-s IP address\n"
-"--mangle-ip-d IP address\n"
-"--mangle-mac-s MAC address\n"
-"--mangle-mac-d MAC address\n"
-"--mangle-target target (DROP, CONTINUE or ACCEPT -- default is ACCEPT)\n"
-	);
-}
-
-enum {
-	MANGLE_IPS    = 0,
-	MANGLE_IPT    = 1,
-	MANGLE_DEVS   = 2,
-	MANGLE_DEVT   = 3,
-	MANGLE_TARGET = 4,
-};
-
-static const struct xt_option_entry mangle_opts[] = {
-	{ .name = "mangle-ip-s", .id = MANGLE_IPS, .type = XTTYPE_STRING },
-	{ .name = "mangle-ip-d", .id = MANGLE_IPT, .type = XTTYPE_STRING },
-	{ .name = "mangle-mac-s", .id = MANGLE_DEVS, .type = XTTYPE_STRING },
-	{ .name = "mangle-mac-d", .id = MANGLE_DEVT, .type = XTTYPE_STRING },
-	{ .name = "mangle-target", .id = MANGLE_TARGET,
-	  .type = XTTYPE_STRING },
-	XTOPT_TABLEEND,
-};
-
-
-static struct in_addr *network_to_addr(const char *name)
-{
-	struct netent *net;
-	static struct in_addr addr;
-
-	if ((net = getnetbyname(name)) != NULL) {
-		if (net->n_addrtype != AF_INET)
-			return (struct in_addr *) NULL;
-		addr.s_addr = htonl((unsigned long) net->n_net);
-		return &addr;
-	}
-
-	return (struct in_addr *) NULL;
-}
-
-static void inaddrcpy(struct in_addr *dst, struct in_addr *src)
-{
-	dst->s_addr = src->s_addr;
-}
-
-static struct in_addr *host_to_addr(const char *name, unsigned int *naddr)
-{
-	struct in_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_INET;
-	hints.ai_socktype = SOCK_RAW;
-
-	*naddr = 0;
-	err = getaddrinfo(name, NULL, &hints, &res);
-	if (err != 0)
-		return NULL;
-	else {
-		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;
-	}
-
-	return (struct in_addr *) NULL;
-}
-
-static int string_to_number(const char *s, unsigned int min,
-			    unsigned int max, unsigned int *ret)
-{
-	long number;
-	char *end;
-
-	/* Handle hex, octal, etc. */
-	errno = 0;
-	number = strtol(s, &end, 0);
-	if (*end == '\0' && end != s) {
-		/* we parsed a number, let's see if we want this */
-		if (errno != ERANGE && min <= number && number <= max) {
-			*ret = number;
-			return 0;
-		}
-	}
-	return -1;
-}
-
-static struct in_addr *dotted_to_addr(const char *dotted)
-{
-	static struct in_addr addr;
-	unsigned char *addrp;
-	char *p, *q;
-	unsigned int onebyte;
-	int i;
-	char buf[20];
-
-	/* copy dotted string, because we need to modify it */
-	strncpy(buf, dotted, sizeof(buf) - 1);
-	addrp = (unsigned char *) &(addr.s_addr);
-
-	p = buf;
-	for (i = 0; i < 3; i++) {
-		if ((q = strchr(p, '.')) == NULL)
-			return (struct in_addr *) NULL;
-
-		*q = '\0';
-		if (string_to_number(p, 0, 255, &onebyte) == -1)
-			return (struct in_addr *) NULL;
-
-		addrp[i] = (unsigned char) onebyte;
-		p = q + 1;
-	}
-
-	/* we've checked 3 bytes, now we check the last one */
-	if (string_to_number(p, 0, 255, &onebyte) == -1)
-		return (struct in_addr *) NULL;
-
-	addrp[3] = (unsigned char) onebyte;
-
-	return &addr;
-}
-
-static struct in_addr *parse_hostnetwork(const char *name,
-					 unsigned int *naddrs)
-{
-	struct in_addr *addrp, *addrptmp;
-
-	if ((addrptmp = dotted_to_addr(name)) != NULL ||
-		(addrptmp = network_to_addr(name)) != NULL) {
-		addrp = xtables_malloc(sizeof(struct in_addr));
-		inaddrcpy(addrp, addrptmp);
-		*naddrs = 1;
-		return addrp;
-	}
-	if ((addrp = host_to_addr(name, naddrs)) != NULL)
-		return addrp;
-
-	xtables_error(PARAMETER_PROBLEM, "host/network `%s' not found", name);
-}
-
-static void mangle_parse(struct xt_option_call *cb)
-{
-	const struct arpt_entry *e = cb->xt_entry;
-	struct arpt_mangle *mangle =  cb->data;
-	struct in_addr *ipaddr;
-	struct ether_addr *macaddr;
-
-	/* mangle target is by default "ACCEPT". Setting it here,
-	 * since original arpt_mangle.c init() no longer exists*/
-	mangle->target = NF_ACCEPT;
-
-	xtables_option_parse(cb);
-	switch (cb->entry->id) {
-	case MANGLE_IPS:
-/*
-		if (e->arp.arpln_mask == 0)
-			xtables_error(PARAMETER_PROBLEM, "no pln defined");
-
-		if (e->arp.invflags & ARPT_INV_ARPPLN)
-			xtables_error(PARAMETER_PROBLEM,
-				   "! pln not allowed for --mangle-ip-s");
-*/
-/*
-		if (e->arp.arpln != 4)
-			xtables_error(PARAMETER_PROBLEM, "only pln=4 supported");
-*/
-		{
-			unsigned int nr;
-			ipaddr = parse_hostnetwork(cb->arg, &nr);
-		}
-		mangle->u_s.src_ip.s_addr = ipaddr->s_addr;
-		free(ipaddr);
-		mangle->flags |= ARPT_MANGLE_SIP;
-		break;
-	case MANGLE_IPT:
-/*
-		if (e->arp.arpln_mask == 0)
-			xtables_error(PARAMETER_PROBLEM, "no pln defined");
-
-		if (e->arp.invflags & ARPT_INV_ARPPLN)
-			xtables_error(PARAMETER_PROBLEM,
-				   "! pln not allowed for --mangle-ip-d");
-*/
-/*
-		if (e->arp.arpln != 4)
-			xtables_error(PARAMETER_PROBLEM, "only pln=4 supported");
-*/
-		{
-			unsigned int nr;
-			ipaddr = parse_hostnetwork(cb->arg, &nr);
-		}
-		mangle->u_t.tgt_ip.s_addr = ipaddr->s_addr;
-		free(ipaddr);
-		mangle->flags |= ARPT_MANGLE_TIP;
-		break;
-	case MANGLE_DEVS:
-		if (e->arp.arhln_mask == 0)
-			xtables_error(PARAMETER_PROBLEM,
-				      "no --h-length defined");
-		if (e->arp.invflags & ARPT_INV_ARPHLN)
-			xtables_error(PARAMETER_PROBLEM,
-				      "! --h-length not allowed for "
-				      "--mangle-mac-s");
-		if (e->arp.arhln != 6)
-			xtables_error(PARAMETER_PROBLEM,
-				      "only --h-length 6 supported");
-		macaddr = ether_aton(cb->arg);
-		if (macaddr == NULL)
-			xtables_error(PARAMETER_PROBLEM, "invalid source MAC");
-		memcpy(mangle->src_devaddr, macaddr, e->arp.arhln);
-		mangle->flags |= ARPT_MANGLE_SDEV;
-		break;
-	case MANGLE_DEVT:
-		if (e->arp.arhln_mask == 0)
-			xtables_error(PARAMETER_PROBLEM,
-				      "no --h-length defined");
-		if (e->arp.invflags & ARPT_INV_ARPHLN)
-			xtables_error(PARAMETER_PROBLEM,
-				      "! hln not allowed for --mangle-mac-d");
-		if (e->arp.arhln != 6)
-			xtables_error(PARAMETER_PROBLEM,
-				      "only --h-length 6 supported");
-		macaddr = ether_aton(cb->arg);
-		if (macaddr == NULL)
-			xtables_error(PARAMETER_PROBLEM, "invalid target MAC");
-		memcpy(mangle->tgt_devaddr, macaddr, e->arp.arhln);
-		mangle->flags |= ARPT_MANGLE_TDEV;
-		break;
-	case MANGLE_TARGET:
-		if (!strcmp(cb->arg, "DROP"))
-			mangle->target = NF_DROP;
-		else if (!strcmp(cb->arg, "ACCEPT"))
-			mangle->target = NF_ACCEPT;
-		else if (!strcmp(cb->arg, "CONTINUE"))
-			mangle->target = ARPT_CONTINUE;
-		else
-			xtables_error(PARAMETER_PROBLEM,
-				      "bad target for --mangle-target");
-		break;
-	}
-}
-
-static void mangle_fcheck(struct xt_fcheck_call *cb)
-{
-}
-
-static char *addr_to_dotted(const struct in_addr *addrp)
-{
-	static char buf[20];
-	const unsigned char *bytep;
-
-	bytep = (const unsigned char *) &(addrp->s_addr);
-	sprintf(buf, "%d.%d.%d.%d", bytep[0], bytep[1], bytep[2], bytep[3]);
-	return buf;
-}
-
-static char *addr_to_host(const struct in_addr *addr)
-{
-	struct hostent *host;
-
-	if ((host = gethostbyaddr((char *) addr,
-				  sizeof(struct in_addr), AF_INET)) != NULL)
-		return (char *) host->h_name;
-
-	return (char *) NULL;
-}
-
-static char *addr_to_network(const struct in_addr *addr)
-{
-	struct netent *net;
-
-	if ((net = getnetbyaddr((long) ntohl(addr->s_addr), AF_INET)) != NULL)
-		return (char *) net->n_name;
-
-	return (char *) NULL;
-}
-
-static char *addr_to_anyname(const struct in_addr *addr)
-{
-	char *name;
-
-	if ((name = addr_to_host(addr)) != NULL ||
-		(name = addr_to_network(addr)) != NULL)
-		return name;
-
-	return addr_to_dotted(addr);
-}
-
-static void print_mac(const unsigned char *mac, int l)
-{
-	int j;
-
-	for (j = 0; j < l; j++)
-		printf("%02x%s", mac[j],
-			(j==l-1) ? "" : ":");
-}
-
-static void mangle_print(const void *ip, const struct xt_entry_target *target,
-			 int numeric)
-{
-	const struct arpt_mangle *m = (const void *)target;
-	char buf[100];
-
-	if (m->flags & ARPT_MANGLE_SIP) {
-		if (numeric)
-			sprintf(buf, "%s", addr_to_dotted(&(m->u_s.src_ip)));
-		else
-			sprintf(buf, "%s", addr_to_anyname(&(m->u_s.src_ip)));
-		printf("--mangle-ip-s %s ", buf);
-	}
-	if (m->flags & ARPT_MANGLE_SDEV) {
-		printf("--mangle-mac-s ");
-		print_mac((unsigned char *)m->src_devaddr, 6);
-		printf(" ");
-	}
-	if (m->flags & ARPT_MANGLE_TIP) {
-		if (numeric)
-			sprintf(buf, "%s", addr_to_dotted(&(m->u_t.tgt_ip)));
-		else
-			sprintf(buf, "%s", addr_to_anyname(&(m->u_t.tgt_ip)));
-		printf("--mangle-ip-d %s ", buf);
-	}
-	if (m->flags & ARPT_MANGLE_TDEV) {
-		printf("--mangle-mac-d ");
-		print_mac((unsigned char *)m->tgt_devaddr, 6);
-		printf(" ");
-	}
-	if (m->target != NF_ACCEPT) {
-		printf("--mangle-target ");
-		if (m->target == NF_DROP)
-			printf("DROP ");
-		else
-			printf("CONTINUE ");
-	}
-}
-
-static void mangle_save(const void *ip, const struct xt_entry_target *target)
-{
-}
-
-static struct xtables_target mangle_tg_reg = {
-	.family		= NFPROTO_ARP,
-	.name		= "mangle",
-	.version	= XTABLES_VERSION,
-	.size		= XT_ALIGN(sizeof(struct arpt_mangle)),
-	.userspacesize	= XT_ALIGN(sizeof(struct arpt_mangle)),
-	.help		= mangle_help,
-	.x6_parse	= mangle_parse,
-	.x6_fcheck	= mangle_fcheck,
-	.print		= mangle_print,
-	.save		= mangle_save,
-	.x6_options	= mangle_opts,
-};
-
-void _init(void)
-{
-	xtables_register_target(&mangle_tg_reg);
-}
diff --git a/extensions/libxt_set.c b/extensions/libxt_set.c
index 679c04c..1692102 100644
--- a/extensions/libxt_set.c
+++ b/extensions/libxt_set.c
@@ -60,6 +60,7 @@
 	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,
@@ -140,6 +141,7 @@
 	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,
@@ -238,6 +240,7 @@
 	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,
@@ -415,6 +418,7 @@
 	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,
@@ -583,6 +587,7 @@
 	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,
diff --git a/extensions/libxt_set.h b/extensions/libxt_set.h
index 5a1bdcf..41dfbd3 100644
--- a/extensions/libxt_set.h
+++ b/extensions/libxt_set.h
@@ -8,12 +8,6 @@
 #include <errno.h>
 #include "../iptables/xshared.h"
 
-#ifdef DEBUG
-#define DEBUGP(x, args...) fprintf(stderr, x , ## args)
-#else
-#define DEBUGP(x, args...) 
-#endif
-
 static int
 get_version(unsigned *version)
 {
diff --git a/extensions/libxt_string.c b/extensions/libxt_string.c
index fb15980..7c6366c 100644
--- a/extensions/libxt_string.c
+++ b/extensions/libxt_string.c
@@ -103,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] == '\\') {
@@ -159,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;
diff --git a/extensions/libxt_time.c b/extensions/libxt_time.c
index 9c5bda8..5a8cc5d 100644
--- a/extensions/libxt_time.c
+++ b/extensions/libxt_time.c
@@ -88,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;
@@ -265,10 +265,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);
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/include/ebtables/ethernetdb.h b/include/ebtables/ethernetdb.h
deleted file mode 100644
index 1683abe..0000000
--- a/include/ebtables/ethernetdb.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
-* 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
-*/
-
-/* All data returned by the network data base library are supplied in
-   host order and returned in network order (suitable for use in
-   system calls).  */
-
-#ifndef	_ETHERNETDB_H
-#define	_ETHERNETDB_H	1
-
-#include <features.h>
-#include <netinet/in.h>
-#include <stdint.h>
-
-/* Absolute file name for network data base files.  */
-#ifndef	_PATH_ETHERTYPES
-#define	_PATH_ETHERTYPES	"/etc/ethertypes"
-#endif				/* _PATH_ETHERTYPES */
-
-struct ethertypeent {
-	char *e_name;		/* Official ethernet type name.  */
-	char **e_aliases;	/* Alias list.  */
-	int e_ethertype;	/* Ethernet type number.  */
-};
-
-/* Open ethertype data base files and mark them as staying open even
-   after a later search if STAY_OPEN is non-zero.  */
-extern void setethertypeent(int __stay_open);
-
-/* Close ethertype data base files and clear `stay open' flag.  */
-extern void endethertypeent(void);
-
-/* Get next entry from ethertype data base file.  Open data base if
-   necessary.  */
-extern struct ethertypeent *getethertypeent(void);
-
-/* Return entry from ethertype data base for network with NAME.  */
-extern struct ethertypeent *getethertypebyname(__const char *__name);
-
-/* Return entry from ethertype data base which number is PROTO.  */
-extern struct ethertypeent *getethertypebynumber(int __ethertype);
-
-
-#endif				/* ethernetdb.h */
diff --git a/include/linux/netfilter/xt_cgroup.h b/include/linux/netfilter/xt_cgroup.h
index 7fe61ed..b74e370 100644
--- a/include/linux/netfilter/xt_cgroup.h
+++ b/include/linux/netfilter/xt_cgroup.h
@@ -1,5 +1,6 @@
-#ifndef _XT_CGROUP_H
-#define _XT_CGROUP_H
+/* 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>
@@ -21,4 +22,20 @@
 	void		*priv __attribute__((aligned(8)));
 };
 
-#endif /* _XT_CGROUP_H */
+#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/xtables.h b/include/xtables.h
index 743906b..8fb8843 100644
--- a/include/xtables.h
+++ b/include/xtables.h
@@ -521,6 +521,18 @@
 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.
@@ -536,6 +548,8 @@
 #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)
diff --git a/iptables-test.py b/iptables-test.py
index 9bfb808..5e6bfb7 100755
--- a/iptables-test.py
+++ b/iptables-test.py
@@ -61,7 +61,7 @@
     return 0
 
 
-def run_test(iptables, rule, rule_save, res, filename, lineno):
+def run_test(iptables, rule, rule_save, res, filename, lineno, netns):
     '''
     Executes an unit test. Returns the output of delete_rule().
 
@@ -76,6 +76,9 @@
     ret = 0
 
     cmd = iptables + " -A " + rule
+    if netns:
+            cmd = "ip netns exec ____iptables-container-test " + EXECUTEABLE + " " + cmd
+
     ret = execute_cmd(cmd, filename, lineno)
 
     #
@@ -108,8 +111,15 @@
             command = IPTABLES_SAVE
         elif splitted[0] == IP6TABLES:
             command = IP6TABLES_SAVE
+
+    path = os.path.abspath(os.path.curdir) + "/iptables/" + EXECUTEABLE
+    command = path + " " + command
+
+    if netns:
+            command = "ip netns exec ____iptables-container-test " + command
+
     args = splitted[1:]
-    proc = subprocess.Popen((os.path.abspath(os.path.curdir) + "/iptables/" + EXECUTEABLE, command),
+    proc = subprocess.Popen(command, shell=True,
                             stdin=subprocess.PIPE,
                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
     out, err = proc.communicate()
@@ -131,8 +141,17 @@
         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 run_test_netns(iptables, rule, rule_save, res, filename, lineno):
+    execute_cmd("ip netns add ____iptables-container-test", filename, lineno)
+    ret = run_test(iptables, rule, rule_save, res, filename, lineno, True)
+    execute_cmd("ip netns del ____iptables-container-test", filename, lineno)
+    return ret
 
 def execute_cmd(cmd, filename, lineno):
     '''
@@ -159,7 +178,7 @@
     return ret
 
 
-def run_test_file(filename):
+def run_test_file(filename, netns):
     '''
     Runs a test file
 
@@ -227,8 +246,13 @@
 
             res = item[2].rstrip()
 
-            ret = run_test(iptables, rule, rule_save,
-                           res, filename, lineno + 1)
+            if netns:
+                ret = run_test_netns(iptables, rule, rule_save,
+                                     res, filename, lineno + 1)
+            else:
+                ret = run_test(iptables, rule, rule_save,
+                               res, filename, lineno + 1, False)
+
             if ret < 0:
                 test_passed = False
                 total_test_passed = False
@@ -275,6 +299,8 @@
                         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()
 
     #
@@ -313,7 +339,7 @@
     if args.filename:
         file_list = [args.filename]
     for filename in file_list:
-        file_tests, file_passed = run_test_file(filename)
+        file_tests, file_passed = run_test_file(filename, args.netns)
         if file_tests:
             tests += file_tests
             passed += file_passed
diff --git a/iptables/.gitignore b/iptables/.gitignore
index 5ca54ad..d0301c6 100644
--- a/iptables/.gitignore
+++ b/iptables/.gitignore
@@ -14,6 +14,8 @@
 /iptables-xml
 /iptables-xml.1
 /xtables-multi
+/xtables-legacy-multi
+/xtables-nft-multi
 /xtables-config-parser.c
 /xtables-config-parser.h
 /xtables-config-syntax.c
diff --git a/iptables/Makefile.am b/iptables/Makefile.am
index 9e6f3f4..581dc32 100644
--- a/iptables/Makefile.am
+++ b/iptables/Makefile.am
@@ -43,7 +43,7 @@
 				nft-shared.c nft-ipv4.c nft-ipv6.c nft-arp.c \
 				xtables-monitor.c \
 				xtables-arp-standalone.c xtables-arp.c \
-				getethertype.c nft-bridge.c \
+				nft-bridge.c \
 				xtables-eb-standalone.c xtables-eb.c \
 				xtables-eb-translate.c \
 				xtables-translate.c
@@ -80,7 +80,13 @@
 		ip6tables-nft ip6tables-nft-restore ip6tables-nft-save \
 		iptables-translate ip6tables-translate \
 		iptables-restore-translate ip6tables-restore-translate \
-		arptables ebtables xtables-monitor
+		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-extensions.8: iptables-extensions.8.tmpl ../extensions/matches.man ../extensions/targets.man
diff --git a/iptables/ip6tables-restore.c b/iptables/ip6tables-restore.c
index 11725d3..28484aa 100644
--- a/iptables/ip6tables-restore.c
+++ b/iptables/ip6tables-restore.c
@@ -20,12 +20,6 @@
 #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 counters, verbose, noflush, wait;
 
 static struct timeval wait_interval = {
@@ -85,116 +79,12 @@
 	return handle;
 }
 
-static int parse_counters(char *string, struct xt_counters *ctr)
-{
-	unsigned long long pcnt, bcnt;
-	int ret;
-
-	ret = sscanf(string, "[%llu:%llu]", &pcnt, &bcnt);
-	ctr->pcnt = pcnt;
-	ctr->bcnt = bcnt;
-	return ret == 2;
-}
-
-/* global new argv and argc */
-static char *newargv[255];
-static int newargc;
-
-/* function adding one argument to newargv, updating newargc
- * returns true if argument added, false otherwise */
-static int add_argv(char *what) {
-	DEBUGP("add_argv: %s\n", what);
-	if (what && newargc + 1 < ARRAY_SIZE(newargv)) {
-		newargv[newargc] = strdup(what);
-		newargv[++newargc] = NULL;
-		return 1;
-	} else {
-		xtables_error(PARAMETER_PROBLEM,
-			"Parser cannot handle more arguments\n");
-		return 0;
-	}
-}
-
-static void free_argv(void) {
-	int i;
-
-	for (i = 0; i < newargc; i++)
-		free(newargv[i]);
-}
-
-static void add_param_to_argv(char *parsestart)
-{
-	int quote_open = 0, escaped = 0, param_len = 0;
-	char param_buffer[1024], *curchar;
-
-	/* After fighting with strtok enough, here's now
-	 * a 'real' parser. According to Rusty I'm now no
-	 * longer a real hacker, but I can live with that */
-
-	for (curchar = parsestart; *curchar; curchar++) {
-		if (quote_open) {
-			if (escaped) {
-				param_buffer[param_len++] = *curchar;
-				escaped = 0;
-				continue;
-			} else if (*curchar == '\\') {
-				escaped = 1;
-				continue;
-			} else if (*curchar == '"') {
-				quote_open = 0;
-				*curchar = ' ';
-			} else {
-				param_buffer[param_len++] = *curchar;
-				continue;
-			}
-		} else {
-			if (*curchar == '"') {
-				quote_open = 1;
-				continue;
-			}
-		}
-
-		if (*curchar == ' '
-		    || *curchar == '\t'
-		    || * curchar == '\n') {
-			if (!param_len) {
-				/* two spaces? */
-				continue;
-			}
-
-			param_buffer[param_len] = '\0';
-
-			/* check if table name specified */
-			if ((param_buffer[0] == '-' &&
-			     param_buffer[1] != '-' &&
-			     strchr(param_buffer, 't')) ||
-			    (!strncmp(param_buffer, "--t", 3) &&
-			     !strncmp(param_buffer, "--table", strlen(param_buffer)))) {
-				xtables_error(PARAMETER_PROBLEM,
-				"The -t option (seen in line %u) cannot be "
-				"used in ip6tables-restore.\n", line);
-				exit(1);
-			}
-
-			add_argv(param_buffer);
-			param_len = 0;
-		} else {
-			/* regular character, copy to buffer */
-			param_buffer[param_len++] = *curchar;
-
-			if (param_len >= sizeof(param_buffer))
-				xtables_error(PARAMETER_PROBLEM,
-				   "Parameter too long!");
-		}
-	}
-}
-
 int ip6tables_restore_main(int argc, char *argv[])
 {
 	struct xtc_handle *handle = NULL;
 	char buffer[10240];
 	int c, lock;
-	char curtable[XT_TABLE_MAXNAMELEN + 1];
+	char curtable[XT_TABLE_MAXNAMELEN + 1] = {};
 	FILE *in;
 	int in_table = 0, testing = 0;
 	const char *tablename = NULL;
@@ -327,8 +217,13 @@
 			strncpy(curtable, table, XT_TABLE_MAXNAMELEN);
 			curtable[XT_TABLE_MAXNAMELEN] = '\0';
 
-			if (tablename != NULL && strcmp(tablename, table) != 0)
+			if (tablename != NULL && strcmp(tablename, table) != 0) {
+				if (lock >= 0) {
+					xtables_unlock(lock);
+					lock = XT_LOCK_NOT_ACQUIRED;
+				}
 				continue;
+			}
 			if (handle)
 				ops->free(handle);
 
@@ -395,7 +290,7 @@
 			}
 
 			if (strcmp(policy, "-") != 0) {
-				struct xt_counters count;
+				struct xt_counters count = {};
 
 				if (counters) {
 					char *ctrs;
@@ -405,9 +300,6 @@
 						xtables_error(PARAMETER_PROBLEM,
 							  "invalid policy counters "
 							  "for chain '%s'\n", chain);
-
-				} else {
-					memset(&count, 0, sizeof(count));
 				}
 
 				DEBUGP("Setting policy of chain %s to %s\n",
@@ -426,17 +318,14 @@
 
 		} else if (in_table) {
 			int a;
-			char *ptr = buffer;
 			char *pcnt = NULL;
 			char *bcnt = NULL;
 			char *parsestart;
 
-			/* reset the newargv */
-			newargc = 0;
-
 			if (buffer[0] == '[') {
 				/* we have counters in our input */
-				ptr = strchr(buffer, ']');
+				char *ptr = strchr(buffer, ']');
+
 				if (!ptr)
 					xtables_error(PARAMETER_PROBLEM,
 						   "Bad line %u: need ]\n",
@@ -461,17 +350,17 @@
 				parsestart = buffer;
 			}
 
-			add_argv(argv[0]);
-			add_argv("-t");
-			add_argv(curtable);
+			add_argv(argv[0], 0);
+			add_argv("-t", 0);
+			add_argv(curtable, 0);
 
 			if (counters && pcnt && bcnt) {
-				add_argv("--set-counters");
-				add_argv((char *) pcnt);
-				add_argv((char *) bcnt);
+				add_argv("--set-counters", 0);
+				add_argv((char *) pcnt, 0);
+				add_argv((char *) bcnt, 0);
 			}
 
-			add_param_to_argv(parsestart);
+			add_param_to_argv(parsestart, line);
 
 			DEBUGP("calling do_command6(%u, argv, &%s, handle):\n",
 				newargc, curtable);
diff --git a/iptables/ip6tables.c b/iptables/ip6tables.c
index 2cb115f..fe089de 100644
--- a/iptables/ip6tables.c
+++ b/iptables/ip6tables.c
@@ -420,27 +420,6 @@
 				   "Invalid chain name `%s'", chainname);
 }
 
-static const char *
-parse_target(const char *targetname)
-{
-	const char *ptr;
-
-	if (strlen(targetname) < 1)
-		xtables_error(PARAMETER_PROBLEM,
-			   "Invalid target name (too short)");
-
-	if (strlen(targetname) >= XT_EXTENSION_MAXNAMELEN)
-		xtables_error(PARAMETER_PROBLEM,
-			   "Invalid target name `%s' (%u chars max)",
-			   targetname, XT_EXTENSION_MAXNAMELEN - 1);
-
-	for (ptr = targetname; *ptr; ptr++)
-		if (isspace(*ptr))
-			xtables_error(PARAMETER_PROBLEM,
-				   "Invalid target name `%s'", targetname);
-	return targetname;
-}
-
 static void
 set_option(unsigned int *options, unsigned int option, uint8_t *invflg,
 	   int invert)
@@ -550,7 +529,6 @@
 {
 	struct xtables_target *target, *tg;
 	const struct xt_entry_target *t;
-	char buf[BUFSIZ];
 
 	if (!ip6tc_is_chain(targname, handle))
 		target = xtables_find_target(targname, XTF_TRY_LOAD);
@@ -588,61 +566,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);
@@ -1273,85 +1200,13 @@
 	return e;
 }
 
-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 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 {
-		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(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 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(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 xtc_handle **handle, bool restore)
 {
-	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;
@@ -1374,10 +1229,6 @@
 	struct xtables_target *t;
 	unsigned long long cnt;
 
-	memset(&cs, 0, sizeof(cs));
-	cs.jumpto = "";
-	cs.argv = argv;
-
 	/* re-set optind to 0 in case do_command6 gets called
 	 * a second time */
 	optind = 0;
@@ -1583,11 +1434,13 @@
 			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':
+			set_option(&cs.options, OPT_JUMP, &cs.fw6.ipv6.invflags,
+					cs.invert);
 			command_jump(&cs);
 			break;
 
diff --git a/iptables/iptables-apply b/iptables/iptables-apply
index 86b8d5a..819ca4a 100755
--- a/iptables/iptables-apply
+++ b/iptables/iptables-apply
@@ -123,7 +123,8 @@
 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
+trap "rm -f $TMPFILE" EXIT HUP INT QUIT ILL TRAP ABRT BUS \
+		      FPE USR1 SEGV USR2 PIPE ALRM TERM
 
 if ! "$SAVE" >"$TMPFILE"; then
 	if ! grep -q ipt /proc/modules 2>/dev/null; then
@@ -143,7 +144,7 @@
 	echo "E: unknown error applying new iptables ruleset." >&2
 	exit 5
 else
-	echo done.
+	echo "done."
 fi
 
 echo -n "Can you establish NEW connections to the machine? (y/N) "
@@ -152,7 +153,7 @@
 case "${ret:-}" in
 	(y*|Y*)
 		echo
-		echo ... then my job is done. See you next time.
+		echo "... then my job is done. See you next time."
 		;;
 	(*)
 		if [[ -z "${ret:-}" ]]; then
@@ -163,7 +164,7 @@
 		echo "Timeout. Something happened (or did not). Better play it safe..."
 		echo -n "Reverting to old ruleset... "
 		"$RESTORE" <"$TMPFILE";
-		echo done.
+		echo "done."
 		exit 255
 		;;
 esac
diff --git a/iptables/iptables-restore.c b/iptables/iptables-restore.c
index d097712..3a08d0b 100644
--- a/iptables/iptables-restore.c
+++ b/iptables/iptables-restore.c
@@ -17,12 +17,6 @@
 #include "libiptc/libiptc.h"
 #include "iptables-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 struct timeval wait_interval = {
@@ -82,117 +76,13 @@
 	return handle;
 }
 
-static int parse_counters(char *string, struct xt_counters *ctr)
-{
-	unsigned long long pcnt, bcnt;
-	int ret;
-
-	ret = sscanf(string, "[%llu:%llu]", &pcnt, &bcnt);
-	ctr->pcnt = pcnt;
-	ctr->bcnt = bcnt;
-	return ret == 2;
-}
-
-/* global new argv and argc */
-static char *newargv[255];
-static int newargc;
-
-/* function adding one argument to newargv, updating newargc 
- * returns true if argument added, false otherwise */
-static int add_argv(char *what) {
-	DEBUGP("add_argv: %s\n", what);
-	if (what && newargc + 1 < ARRAY_SIZE(newargv)) {
-		newargv[newargc] = strdup(what);
-		newargv[++newargc] = NULL;
-		return 1;
-	} else {
-		xtables_error(PARAMETER_PROBLEM,
-			"Parser cannot handle more arguments\n");
-		return 0;
-	}
-}
-
-static void free_argv(void) {
-	int i;
-
-	for (i = 0; i < newargc; i++)
-		free(newargv[i]);
-}
-
-static void add_param_to_argv(char *parsestart)
-{
-	int quote_open = 0, escaped = 0, param_len = 0;
-	char param_buffer[1024], *curchar;
-
-	/* After fighting with strtok enough, here's now
-	 * a 'real' parser. According to Rusty I'm now no
-	 * longer a real hacker, but I can live with that */
-
-	for (curchar = parsestart; *curchar; curchar++) {
-		if (quote_open) {
-			if (escaped) {
-				param_buffer[param_len++] = *curchar;
-				escaped = 0;
-				continue;
-			} else if (*curchar == '\\') {
-				escaped = 1;
-				continue;
-			} else if (*curchar == '"') {
-				quote_open = 0;
-				*curchar = ' ';
-			} else {
-				param_buffer[param_len++] = *curchar;
-				continue;
-			}
-		} else {
-			if (*curchar == '"') {
-				quote_open = 1;
-				continue;
-			}
-		}
-
-		if (*curchar == ' '
-		    || *curchar == '\t'
-		    || * curchar == '\n') {
-			if (!param_len) {
-				/* two spaces? */
-				continue;
-			}
-
-			param_buffer[param_len] = '\0';
-
-			/* check if table name specified */
-			if ((param_buffer[0] == '-' &&
-			     param_buffer[1] != '-' &&
-			     strchr(param_buffer, 't')) ||
-			    (!strncmp(param_buffer, "--t", 3) &&
-			     !strncmp(param_buffer, "--table", strlen(param_buffer)))) {
-				xtables_error(PARAMETER_PROBLEM,
-				"The -t option (seen in line %u) cannot be "
-				"used in iptables-restore.\n", line);
-				exit(1);
-			}
-
-			add_argv(param_buffer);
-			param_len = 0;
-		} else {
-			/* regular character, copy to buffer */
-			param_buffer[param_len++] = *curchar;
-
-			if (param_len >= sizeof(param_buffer))
-				xtables_error(PARAMETER_PROBLEM,
-				   "Parameter too long!");
-		}
-	}
-}
-
 int
 iptables_restore_main(int argc, char *argv[])
 {
 	struct xtc_handle *handle = NULL;
 	char buffer[10240];
 	int c, lock;
-	char curtable[XT_TABLE_MAXNAMELEN + 1];
+	char curtable[XT_TABLE_MAXNAMELEN + 1] = {};
 	FILE *in;
 	int in_table = 0, testing = 0;
 	const char *tablename = NULL;
@@ -325,8 +215,13 @@
 			strncpy(curtable, table, XT_TABLE_MAXNAMELEN);
 			curtable[XT_TABLE_MAXNAMELEN] = '\0';
 
-			if (tablename && (strcmp(tablename, table) != 0))
+			if (tablename && (strcmp(tablename, table) != 0)) {
+				if (lock >= 0) {
+					xtables_unlock(lock);
+					lock = XT_LOCK_NOT_ACQUIRED;
+				}
 				continue;
+			}
 			if (handle)
 				ops->free(handle);
 
@@ -393,7 +288,7 @@
 			}
 
 			if (strcmp(policy, "-") != 0) {
-				struct xt_counters count;
+				struct xt_counters count = {};
 
 				if (counters) {
 					char *ctrs;
@@ -403,9 +298,6 @@
 						xtables_error(PARAMETER_PROBLEM,
 							   "invalid policy counters "
 							   "for chain '%s'\n", chain);
-
-				} else {
-					memset(&count, 0, sizeof(count));
 				}
 
 				DEBUGP("Setting policy of chain %s to %s\n",
@@ -424,17 +316,14 @@
 
 		} else if (in_table) {
 			int a;
-			char *ptr = buffer;
 			char *pcnt = NULL;
 			char *bcnt = NULL;
 			char *parsestart;
 
-			/* reset the newargv */
-			newargc = 0;
-
 			if (buffer[0] == '[') {
 				/* we have counters in our input */
-				ptr = strchr(buffer, ']');
+				char *ptr = strchr(buffer, ']');
+
 				if (!ptr)
 					xtables_error(PARAMETER_PROBLEM,
 						   "Bad line %u: need ]\n",
@@ -459,17 +348,17 @@
 				parsestart = buffer;
 			}
 
-			add_argv(argv[0]);
-			add_argv("-t");
-			add_argv(curtable);
+			add_argv(argv[0], 0);
+			add_argv("-t", 0);
+			add_argv(curtable, 0);
 
 			if (counters && pcnt && bcnt) {
-				add_argv("--set-counters");
-				add_argv((char *) pcnt);
-				add_argv((char *) bcnt);
+				add_argv("--set-counters", 0);
+				add_argv((char *) pcnt, 0);
+				add_argv((char *) bcnt, 0);
 			}
 
-			add_param_to_argv(parsestart);
+			add_param_to_argv(parsestart, line);
 
 			DEBUGP("calling do_command4(%u, argv, &%s, handle):\n",
 				newargc, curtable);
diff --git a/iptables/iptables-xml.c b/iptables/iptables-xml.c
index 69c19a6..07300ef 100644
--- a/iptables/iptables-xml.c
+++ b/iptables/iptables-xml.c
@@ -16,12 +16,7 @@
 #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
+#include "xshared.h"
 
 struct xtables_globals iptables_xml_globals = {
 	.option_offset = 0,
@@ -55,32 +50,6 @@
 	exit(1);
 }
 
-static int
-parse_counters(char *string, struct xt_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;
-
-static char *oldargv[255];
-static unsigned int oldargc;
-
-/* arg meta data, were they quoted, frinstance */
-static int newargvattr[255];
-
 #define XT_CHAIN_MAXNAMELEN XT_TABLE_MAXNAMELEN
 static char closeActionTag[XT_TABLE_MAXNAMELEN + 1];
 static char closeRuleTag[XT_TABLE_MAXNAMELEN + 1];
@@ -98,57 +67,6 @@
 static struct chain chains[maxChains];
 static int nextChain;
 
-/* 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 aggregation
- * 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;
-	}
-}
-
 /* like puts but with xml encoding */
 static void
 xmlEncode(char *text)
@@ -730,7 +648,6 @@
 			ret = 1;
 		} else if (curTable[0]) {
 			unsigned int a;
-			char *ptr = buffer;
 			char *pcnt = NULL;
 			char *bcnt = NULL;
 			char *parsestart;
@@ -741,12 +658,10 @@
 			int quote_open, quoted;
 			char param_buffer[1024];
 
-			/* reset the newargv */
-			newargc = 0;
-
 			if (buffer[0] == '[') {
 				/* we have counters in our input */
-				ptr = strchr(buffer, ']');
+				char *ptr = strchr(buffer, ']');
+
 				if (!ptr)
 					xtables_error(PARAMETER_PROBLEM,
 						   "Bad line %u: need ]\n",
diff --git a/iptables/iptables.c b/iptables/iptables.c
index 08ea7af..f8041f5 100644
--- a/iptables/iptables.c
+++ b/iptables/iptables.c
@@ -405,27 +405,6 @@
 				   "Invalid chain name `%s'", chainname);
 }
 
-static const char *
-parse_target(const char *targetname)
-{
-	const char *ptr;
-
-	if (strlen(targetname) < 1)
-		xtables_error(PARAMETER_PROBLEM,
-			   "Invalid target name (too short)");
-
-	if (strlen(targetname) >= XT_EXTENSION_MAXNAMELEN)
-		xtables_error(PARAMETER_PROBLEM,
-			   "Invalid target name `%s' (%u chars max)",
-			   targetname, XT_EXTENSION_MAXNAMELEN - 1);
-
-	for (ptr = targetname; *ptr; ptr++)
-		if (isspace(*ptr))
-			xtables_error(PARAMETER_PROBLEM,
-				   "Invalid target name `%s'", targetname);
-	return targetname;
-}
-
 static void
 set_option(unsigned int *options, unsigned int option, uint8_t *invflg,
 	   int invert)
@@ -535,7 +514,6 @@
 	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);
@@ -574,59 +552,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);
@@ -1262,90 +1190,13 @@
 	return e;
 }
 
-static void command_jump(struct iptables_command_state *cs)
-{
-	size_t size;
-
-	set_option(&cs->options, OPT_JUMP, &cs->fw.ip.invflags, cs->invert);
-	cs->jumpto = parse_target(optarg);
-	/* TRY_LOAD (may be chain name) */
-	cs->target = xtables_find_target(cs->jumpto, XTF_TRY_LOAD);
-
-	if (cs->target == NULL)
-		return;
-
-	size = XT_ALIGN(sizeof(struct xt_entry_target))
-		+ cs->target->size;
-
-	cs->target->t = xtables_calloc(1, size);
-	cs->target->t->u.target_size = size;
-	if (cs->target->real_name == NULL) {
-		strcpy(cs->target->t->u.user.name, cs->jumpto);
-	} else {
-		/* Alias support for userspace side */
-		strcpy(cs->target->t->u.user.name, cs->target->real_name);
-		if (!(cs->target->ext_flags & XTABLES_EXT_ALIAS))
-			fprintf(stderr, "Notice: The %s target is converted into %s target "
-			        "in rule listing and saving.\n",
-			        cs->jumpto, cs->target->real_name);
-	}
-	cs->target->t->u.user.revision = cs->target->revision;
-
-	xs_init_target(cs->target);
-
-	if (cs->target->x6_options != NULL)
-		opts = xtables_options_xfrm(iptables_globals.orig_opts, opts,
-					    cs->target->x6_options,
-					    &cs->target->option_offset);
-	else
-		opts = xtables_merge_options(iptables_globals.orig_opts, opts,
-					     cs->target->extra_opts,
-					     &cs->target->option_offset);
-	if (opts == NULL)
-		xtables_error(OTHER_PROBLEM, "can't alloc memory!");
-}
-
-static void command_match(struct iptables_command_state *cs)
-{
-	struct xtables_match *m;
-	size_t size;
-
-	if (cs->invert)
-		xtables_error(PARAMETER_PROBLEM,
-			   "unexpected ! flag before --match");
-
-	m = xtables_find_match(optarg, XTF_LOAD_MUST_SUCCEED, &cs->matches);
-	size = XT_ALIGN(sizeof(struct xt_entry_match)) + m->size;
-	m->m = xtables_calloc(1, size);
-	m->m->u.match_size = size;
-	if (m->real_name == NULL) {
-		strcpy(m->m->u.user.name, m->name);
-	} else {
-		strcpy(m->m->u.user.name, m->real_name);
-		if (!(m->ext_flags & XTABLES_EXT_ALIAS))
-			fprintf(stderr, "Notice: the %s match is converted into %s match "
-			        "in rule listing and saving.\n", m->name, m->real_name);
-	}
-	m->m->u.user.revision = m->revision;
-
-	xs_init_match(m);
-	if (m == m->next)
-		return;
-	/* Merge options for non-cloned matches */
-	if (m->x6_options != NULL)
-		opts = xtables_options_xfrm(iptables_globals.orig_opts, opts,
-					    m->x6_options, &m->option_offset);
-	else if (m->extra_opts != NULL)
-		opts = xtables_merge_options(iptables_globals.orig_opts, opts,
-					     m->extra_opts, &m->option_offset);
-	if (opts == NULL)
-		xtables_error(OTHER_PROBLEM, "can't alloc memory!");
-}
-
 int do_command4(int argc, char *argv[], char **table,
 		struct xtc_handle **handle, bool restore)
 {
-	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;
@@ -1367,10 +1218,6 @@
 	struct xtables_target *t;
 	unsigned long long cnt;
 
-	memset(&cs, 0, sizeof(cs));
-	cs.jumpto = "";
-	cs.argv = argv;
-
 	/* re-set optind to 0 in case do_command4 gets called
 	 * a second time */
 	optind = 0;
@@ -1567,11 +1414,13 @@
 			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':
+			set_option(&cs.options, OPT_JUMP, &cs.fw.ip.invflags,
+				   cs.invert);
 			command_jump(&cs);
 			break;
 
diff --git a/iptables/nft-arp.c b/iptables/nft-arp.c
index 4eacc61..bd78a86 100644
--- a/iptables/nft-arp.c
+++ b/iptables/nft-arp.c
@@ -139,8 +139,8 @@
 
 static int nft_arp_add(struct nftnl_rule *r, void *data)
 {
-	struct arptables_command_state *cs = data;
-	struct arpt_entry *fw = &cs->fw;
+	struct iptables_command_state *cs = data;
+	struct arpt_entry *fw = &cs->arp;
 	uint32_t op;
 	int ret = 0;
 
@@ -260,8 +260,8 @@
 static void nft_arp_parse_meta(struct nft_xt_ctx *ctx, struct nftnl_expr *e,
 			       void *data)
 {
-	struct arptables_command_state *cs = data;
-	struct arpt_entry *fw = &cs->fw;
+	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,
@@ -271,17 +271,10 @@
 	fw->arp.invflags |= ipt_to_arpt_flags(flags);
 }
 
-static void nft_arp_parse_target(struct xtables_target *target, void *data)
-{
-	struct arptables_command_state *cs = data;
-
-	cs->target = target;
-}
-
 static void nft_arp_parse_immediate(const char *jumpto, bool nft_goto,
 				    void *data)
 {
-	struct arptables_command_state *cs = data;
+	struct iptables_command_state *cs = data;
 
 	cs->jumpto = jumpto;
 }
@@ -294,8 +287,8 @@
 static void nft_arp_parse_payload(struct nft_xt_ctx *ctx,
 				  struct nftnl_expr *e, void *data)
 {
-	struct arptables_command_state *cs = data;
-	struct arpt_entry *fw = &cs->fw;
+	struct iptables_command_state *cs = data;
+	struct arpt_entry *fw = &cs->arp;
 	struct in_addr addr;
 	unsigned short int ar_hrd, ar_pro, ar_op, ar_hln;
 	bool inv;
@@ -330,9 +323,6 @@
 			fw->arp.invflags |= ARPT_INV_ARPOP;
 		break;
 	default:
-		if (fw->arp.arhln < 0)
-			break;
-
 		if (ctx->payload.offset == sizeof(struct arphdr) +
 					   fw->arp.arhln) {
 			get_cmp_data(e, &addr, sizeof(addr), &inv);
@@ -365,14 +355,14 @@
 	}
 }
 
-void nft_rule_to_arptables_command_state(struct nftnl_rule *r,
-					 struct arptables_command_state *cs)
+static void nft_arp_rule_to_cs(const struct nftnl_rule *r,
+			       struct iptables_command_state *cs)
 {
 	struct nftnl_expr_iter *iter;
 	struct nftnl_expr *expr;
 	int family = nftnl_rule_get_u32(r, NFTNL_RULE_FAMILY);
 	struct nft_xt_ctx ctx = {
-		.state.cs_arp = cs,
+		.cs = cs,
 		.family = family,
 	};
 
@@ -387,7 +377,7 @@
 			nftnl_expr_get_str(expr, NFTNL_EXPR_NAME);
 
 		if (strcmp(name, "counter") == 0)
-			nft_parse_counter(expr, &ctx.state.cs_arp->fw.counters);
+			nft_parse_counter(expr, &ctx.cs->arp.counters);
 		else if (strcmp(name, "payload") == 0)
 			nft_parse_payload(&ctx, expr);
 		else if (strcmp(name, "meta") == 0)
@@ -418,10 +408,11 @@
 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)
+				 bool basechain, uint32_t refs,
+				 uint32_t entries)
 {
 	printf("Chain %s", chain);
-	if (pol) {
+	if (basechain && pol) {
 		printf(" (policy %s", pol);
 		if (!(format & FMT_NOCOUNTS)) {
 			fputc(' ', stdout);
@@ -436,7 +427,8 @@
 	}
 }
 
-static void print_fw_details(struct arpt_entry *fw, unsigned int format)
+static void nft_arp_print_rule_details(const struct arpt_entry *fw,
+				       unsigned int format)
 {
 	char buf[BUFSIZ];
 	char iface[IFNAMSIZ+2];
@@ -542,6 +534,7 @@
 		if (tmp <= NUMOPCODES && !(format & FMT_NUMERIC))
 			printf("--opcode %s", opcodes[tmp-1]);
 		else
+			printf("--opcode %d", tmp);
 
 		if (fw->arp.arpop_mask != 65535)
 			printf("/%d", ntohs(fw->arp.arpop_mask));
@@ -578,36 +571,48 @@
 }
 
 static void
-nft_arp_print_firewall(struct nftnl_rule *r, unsigned int num,
-		       unsigned int format)
+__nft_arp_save_rule(const void *data, unsigned int format)
 {
-	struct arptables_command_state cs = {};
+	const struct iptables_command_state *cs = data;
 
-	nft_rule_to_arptables_command_state(r, &cs);
+	nft_arp_print_rule_details(&cs->arp, format);
 
-	if (format & FMT_LINENUMBERS)
-		printf("%u ", num);
-
-	print_fw_details(&cs.fw, format);
-
-	if (cs.jumpto != NULL && strcmp(cs.jumpto, "") != 0) {
-		printf("-j %s", cs.jumpto);
-	} else if (cs.target) {
-		printf("-j %s", cs.target->name);
-		cs.target->print(&cs.fw, cs.target->t, format & FMT_NUMERIC);
+	if (cs->jumpto != NULL && strcmp(cs->jumpto, "") != 0) {
+		printf("-j %s", cs->jumpto);
+	} else if (cs->target) {
+		printf("-j %s", cs->target->name);
+		cs->target->print(&cs->arp, cs->target->t, format & FMT_NUMERIC);
 	}
 
 	if (!(format & FMT_NOCOUNTS)) {
 		printf(", pcnt=");
-		xtables_print_num(cs.fw.counters.pcnt, format);
+		xtables_print_num(cs->arp.counters.pcnt, format);
 		printf("-- bcnt=");
-		xtables_print_num(cs.fw.counters.bcnt, format);
+		xtables_print_num(cs->arp.counters.bcnt, format);
 	}
 
 	if (!(format & FMT_NONEWLINE))
 		fputc('\n', stdout);
 }
 
+static void
+nft_arp_save_rule(const void *data, unsigned int format)
+{
+	__nft_arp_save_rule(data, format | FMT_NUMERIC);
+}
+
+static void
+nft_arp_print_rule(struct nftnl_rule *r, unsigned int num, unsigned int format)
+{
+	struct iptables_command_state cs = {};
+
+	if (format & FMT_LINENUMBERS)
+		printf("%u ", num);
+
+	nft_arp_rule_to_cs(r, &cs);
+	__nft_arp_save_rule(&cs, format);
+}
+
 static bool nft_arp_is_same(const void *data_a,
 			    const void *data_b)
 {
@@ -637,24 +642,31 @@
 static bool nft_arp_rule_find(struct nft_family_ops *ops, struct nftnl_rule *r,
 			      void *data)
 {
-	const struct arptables_command_state *cs = data;
-	struct arptables_command_state this = {};
+	const struct iptables_command_state *cs = data;
+	struct iptables_command_state this = {};
 
 	/* Delete by matching rule case */
-	nft_rule_to_arptables_command_state(r, &this);
+	nft_arp_rule_to_cs(r, &this);
 
-	if (!nft_arp_is_same(cs, &this))
+	if (!nft_arp_is_same(&cs->arp, &this.arp))
 		return false;
 
 	if (!compare_targets(cs->target, this.target))
 		return false;
 
-	if (strcmp(cs->jumpto, this.jumpto) != 0)
+	if (this.jumpto && strcmp(cs->jumpto, this.jumpto) != 0)
 		return false;
 
 	return true;
 }
 
+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,
@@ -663,10 +675,13 @@
 	.parse_payload		= nft_arp_parse_payload,
 	.parse_immediate	= nft_arp_parse_immediate,
 	.print_header		= nft_arp_print_header,
-	.print_firewall		= nft_arp_print_firewall,
-	.save_firewall		= NULL,
+	.print_rule		= nft_arp_print_rule,
+	.save_rule		= nft_arp_save_rule,
 	.save_counters		= NULL,
+	.save_chain		= nft_arp_save_chain,
 	.post_parse		= NULL,
+	.rule_to_cs		= nft_arp_rule_to_cs,
+	.clear_cs		= nft_clear_iptables_command_state,
 	.rule_find		= nft_arp_rule_find,
-	.parse_target		= nft_arp_parse_target,
+	.parse_target		= nft_ipv46_parse_target,
 };
diff --git a/iptables/nft-arp.h b/iptables/nft-arp.h
index 05889b4..da6bd38 100644
--- a/iptables/nft-arp.h
+++ b/iptables/nft-arp.h
@@ -4,13 +4,4 @@
 extern char *opcodes[];
 #define NUMOPCODES 9
 
-struct arptables_command_state {
-	struct arpt_entry fw;
-	struct xtables_target *target;
-	const char *jumpto;
-};
-
-void nft_rule_to_arptables_command_state(struct nftnl_rule *r,
-					 struct arptables_command_state *cs);
-
 #endif
diff --git a/iptables/nft-bridge.c b/iptables/nft-bridge.c
index 917d802..35c862c 100644
--- a/iptables/nft-bridge.c
+++ b/iptables/nft-bridge.c
@@ -16,7 +16,6 @@
 #include <xtables.h>
 #include <libiptc/libxtc.h>
 #include <linux/netfilter/nf_tables.h>
-#include <ebtables/ethernetdb.h>
 
 #include "nft-shared.h"
 #include "nft-bridge.h"
@@ -29,9 +28,18 @@
 	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;
-		if (!m->ismatch)
-			free(m->u.watcher->t);
 		free(m);
 		m = nm;
 	}
@@ -193,7 +201,7 @@
 		add_cmp_u16(r, fw->ethproto, op);
 	}
 
-	add_compat(r, fw->ethproto, fw->invflags);
+	add_compat(r, fw->ethproto, fw->invflags & EBT_IPROTO);
 
 	for (iter = cs->match_list; iter; iter = iter->next) {
 		if (iter->ismatch) {
@@ -217,10 +225,7 @@
 	struct iptables_command_state *cs = data;
 	struct ebt_entry *fw = &cs->eb;
 	uint8_t invflags = 0;
-	char iifname[IFNAMSIZ], oifname[IFNAMSIZ];
-
-	memset(iifname, 0, sizeof(iifname));
-	memset(oifname, 0, sizeof(oifname));
+	char iifname[IFNAMSIZ] = {}, oifname[IFNAMSIZ] = {};
 
 	parse_meta(e, ctx->meta.key, iifname, NULL, oifname, NULL, &invflags);
 
@@ -350,7 +355,7 @@
 	cs->target = t;
 }
 
-static void nft_rule_to_ebtables_command_state(struct nftnl_rule *r,
+static void nft_rule_to_ebtables_command_state(const struct nftnl_rule *r,
 					       struct iptables_command_state *cs)
 {
 	cs->eb.bitmask = EBT_NOPROTO;
@@ -371,10 +376,10 @@
 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)
+				    bool basechain, uint32_t refs, uint32_t entries)
 {
 	printf("Bridge chain: %s, entries: %u, policy: %s\n",
-	       chain, refs, basechain ? pol : "RETURN");
+	       chain, entries, basechain ? pol : "RETURN");
 }
 
 static void print_matches_and_watchers(const struct iptables_command_state *cs,
@@ -415,7 +420,7 @@
 
 static void print_protocol(uint16_t ethproto, bool invert, unsigned int bitmask)
 {
-	struct ethertypeent *ent;
+	struct xt_ethertypeent *ent;
 
 	/* Dont print anything about the protocol if no protocol was
 	 * specified, obviously this means any protocol will do. */
@@ -431,59 +436,87 @@
 		return;
 	}
 
-	ent = getethertypebynumber(ntohs(ethproto));
+	ent = xtables_getethertypebynumber(ntohs(ethproto));
 	if (!ent)
 		printf("0x%x ", ntohs(ethproto));
 	else
 		printf("%s ", ent->e_name);
 }
 
-static void nft_bridge_print_firewall(struct nftnl_rule *r, unsigned int num,
-				      unsigned int format)
+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)) {
+		const char *counter_fmt;
+
+		if (format & FMT_EBT_SAVE)
+			counter_fmt = " -c %"PRIu64" %"PRIu64"";
+		else
+			counter_fmt = " , pcnt = %"PRIu64" -- bcnt = %"PRIu64"";
+
+		printf(counter_fmt,
+		       (uint64_t)cs->counters.pcnt,
+		       (uint64_t)cs->counters.bcnt);
+	}
+
+	if (!(format & FMT_NONEWLINE))
+		fputc('\n', stdout);
+}
+
+static void nft_bridge_print_rule(struct nftnl_rule *r, unsigned int num,
+				  unsigned int format)
 {
 	struct iptables_command_state cs = {};
 
-	nft_rule_to_ebtables_command_state(r, &cs);
-
 	if (format & FMT_LINENUMBERS)
 		printf("%d ", num);
 
-	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");
-	}
-	else if (cs.target != NULL && cs.target->print != NULL)
-		cs.target->print(&cs.fw, cs.target->t, format & FMT_NUMERIC);
-
-	if (!(format & FMT_NOCOUNTS))
-		printf(" , pcnt = %"PRIu64" -- bcnt = %"PRIu64"",
-		       (uint64_t)cs.counters.pcnt, (uint64_t)cs.counters.bcnt);
-
-	if (!(format & FMT_NONEWLINE))
-		fputc('\n', stdout);
-
+	nft_rule_to_ebtables_command_state(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;
@@ -732,10 +765,13 @@
 	.parse_target		= nft_bridge_parse_target,
 	.print_table_header	= nft_bridge_print_table_header,
 	.print_header		= nft_bridge_print_header,
-	.print_firewall		= nft_bridge_print_firewall,
-	.save_firewall		= NULL,
+	.print_rule		= nft_bridge_print_rule,
+	.save_rule		= nft_bridge_save_rule,
 	.save_counters		= NULL,
+	.save_chain		= nft_bridge_save_chain,
 	.post_parse		= NULL,
+	.rule_to_cs		= nft_rule_to_ebtables_command_state,
+	.clear_cs		= ebt_cs_clean,
 	.rule_find		= nft_bridge_rule_find,
 	.xlate			= nft_bridge_xlate,
 };
diff --git a/iptables/nft-bridge.h b/iptables/nft-bridge.h
index 8dcb151..9d49ccb 100644
--- a/iptables/nft-bridge.h
+++ b/iptables/nft-bridge.h
@@ -78,7 +78,7 @@
 
 static inline const char *nft_ebt_standard_target(unsigned int num)
 {
-	if (num > NUM_STANDARD_TARGETS)
+	if (num >= NUM_STANDARD_TARGETS)
 		return NULL;
 
 	return ebt_standard_targets[num];
@@ -120,5 +120,7 @@
 			  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 xtables_target *ebt_command_jump(const char *jumpto);
 
 #endif
diff --git a/iptables/nft-ipv4.c b/iptables/nft-ipv4.c
index bddd784..39e6184 100644
--- a/iptables/nft-ipv4.c
+++ b/iptables/nft-ipv4.c
@@ -45,8 +45,7 @@
 
 	if (cs->fw.ip.proto != 0) {
 		op = nft_invflags2cmp(cs->fw.ip.invflags, XT_INV_PROTO);
-		add_proto(r, offsetof(struct iphdr, protocol), 1,
-			  cs->fw.ip.proto, op);
+		add_l4proto(r, cs->fw.ip.proto, op);
 	}
 
 	if (cs->fw.ip.src.s_addr != 0) {
@@ -75,7 +74,7 @@
 		add_cmp_u16(r, 0, op);
 	}
 
-	add_compat(r, cs->fw.ip.proto, cs->fw.ip.invflags);
+	add_compat(r, cs->fw.ip.proto, cs->fw.ip.invflags & XT_INV_PROTO);
 
 	for (matchp = cs->matches; matchp; matchp = matchp->next) {
 		/* Use nft built-in comments support instead of comment match */
@@ -172,6 +171,16 @@
 {
 	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);
@@ -225,6 +234,7 @@
 		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;
@@ -246,44 +256,6 @@
 		cs->fw.ip.flags |= IPT_F_GOTO;
 }
 
-static void nft_ipv4_print_header(unsigned int format, const char *chain,
-				  const char *pol,
-				  const struct xt_counters *counters,
-				  bool basechain, uint32_t refs)
-{
-	print_header(format, chain, pol, counters, basechain, refs);
-}
-
-static void print_ipv4_addr(const struct iptables_command_state *cs,
-			    unsigned int format)
-{
-	char buf[BUFSIZ];
-
-	fputc(cs->fw.ip.invflags & IPT_INV_SRCIP ? '!' : ' ', stdout);
-	if (cs->fw.ip.smsk.s_addr == 0L && !(format & FMT_NUMERIC))
-		printf(FMT("%-19s ","%s "), "anywhere");
-	else {
-		if (format & FMT_NUMERIC)
-			strcpy(buf, xtables_ipaddr_to_numeric(&cs->fw.ip.src));
-		else
-			strcpy(buf, xtables_ipaddr_to_anyname(&cs->fw.ip.src));
-		strcat(buf, xtables_ipmask_to_numeric(&cs->fw.ip.smsk));
-		printf(FMT("%-19s ","%s "), buf);
-	}
-
-	fputc(cs->fw.ip.invflags & IPT_INV_DSTIP ? '!' : ' ', stdout);
-	if (cs->fw.ip.dmsk.s_addr == 0L && !(format & FMT_NUMERIC))
-		printf(FMT("%-19s ","-> %s"), "anywhere");
-	else {
-		if (format & FMT_NUMERIC)
-			strcpy(buf, xtables_ipaddr_to_numeric(&cs->fw.ip.dst));
-		else
-			strcpy(buf, xtables_ipaddr_to_anyname(&cs->fw.ip.dst));
-		strcat(buf, xtables_ipmask_to_numeric(&cs->fw.ip.dmsk));
-		printf(FMT("%-19s ","-> %s"), buf);
-	}
-}
-
 static void print_fragment(unsigned int flags, unsigned int invflags,
 			   unsigned int format)
 {
@@ -297,20 +269,19 @@
 	fputc(' ', stdout);
 }
 
-static void nft_ipv4_print_firewall(struct nftnl_rule *r, unsigned int num,
-				    unsigned int format)
+static void nft_ipv4_print_rule(struct nftnl_rule *r, unsigned int num,
+				unsigned int format)
 {
 	struct iptables_command_state cs = {};
 
 	nft_rule_to_iptables_command_state(r, &cs);
 
-	print_firewall_details(&cs, cs.jumpto, cs.fw.ip.flags,
-			       cs.fw.ip.invflags, cs.fw.ip.proto,
-			       num, format);
+	print_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_addr(&cs, format);
+	print_ipv4_addresses(&cs.fw, format);
 
 	if (format & FMT_NOTABLE)
 		fputs("  ", stdout);
@@ -338,7 +309,7 @@
 	       mask_to_str(mask));
 }
 
-static void nft_ipv4_save_firewall(const void *data, unsigned int format)
+static void nft_ipv4_save_rule(const void *data, unsigned int format)
 {
 	const struct iptables_command_state *cs = data;
 
@@ -347,9 +318,9 @@
 	save_ipv4_addr('d', &cs->fw.ip.dst, cs->fw.ip.dmsk.s_addr,
 		       cs->fw.ip.invflags & IPT_INV_DSTIP);
 
-	save_firewall_details(cs, cs->fw.ip.invflags, cs->fw.ip.proto,
-			      cs->fw.ip.iniface, cs->fw.ip.iniface_mask,
-			      cs->fw.ip.outiface, cs->fw.ip.outiface_mask);
+	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)
@@ -357,14 +328,8 @@
 		printf("-f ");
 	}
 
-	save_matches_and_target(cs->matches, cs->target,
-				cs->jumpto, cs->fw.ip.flags, &cs->fw);
-
-	if (cs->target == NULL && strlen(cs->jumpto) > 0) {
-		printf("-%c %s", cs->fw.ip.flags & IPT_F_GOTO ? 'g' : 'j',
-		       cs->jumpto);
-	}
-	printf("\n");
+	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,
@@ -422,28 +387,6 @@
 			      " source or destination IP addresses");
 }
 
-static void nft_ipv4_parse_target(struct xtables_target *t, void *data)
-{
-	struct iptables_command_state *cs = data;
-
-	cs->target = t;
-}
-
-static bool nft_ipv4_rule_find(struct nft_family_ops *ops,
-			       struct nftnl_rule *r, void *data)
-{
-	struct iptables_command_state *cs = data;
-
-	return nft_ipv46_rule_find(ops, r, cs);
-}
-
-static void nft_ipv4_save_counters(const void *data)
-{
-	const struct iptables_command_state *cs = data;
-
-	save_counters(cs->counters.pcnt, cs->counters.bcnt);
-}
-
 static int nft_ipv4_xlate(const void *data, struct xt_xlate *xl)
 {
 	const struct iptables_command_state *cs = data;
@@ -512,13 +455,16 @@
 	.parse_meta		= nft_ipv4_parse_meta,
 	.parse_payload		= nft_ipv4_parse_payload,
 	.parse_immediate	= nft_ipv4_parse_immediate,
-	.print_header		= nft_ipv4_print_header,
-	.print_firewall		= nft_ipv4_print_firewall,
-	.save_firewall		= nft_ipv4_save_firewall,
-	.save_counters		= nft_ipv4_save_counters,
+	.print_header		= print_header,
+	.print_rule		= nft_ipv4_print_rule,
+	.save_rule		= nft_ipv4_save_rule,
+	.save_counters		= save_counters,
+	.save_chain		= nft_ipv46_save_chain,
 	.proto_parse		= nft_ipv4_proto_parse,
 	.post_parse		= nft_ipv4_post_parse,
-	.parse_target		= nft_ipv4_parse_target,
-	.rule_find		= nft_ipv4_rule_find,
+	.parse_target		= nft_ipv46_parse_target,
+	.rule_to_cs		= nft_rule_to_iptables_command_state,
+	.clear_cs		= nft_clear_iptables_command_state,
+	.rule_find		= nft_ipv46_rule_find,
 	.xlate			= nft_ipv4_xlate,
 };
diff --git a/iptables/nft-ipv6.c b/iptables/nft-ipv6.c
index 79c02e4..1952164 100644
--- a/iptables/nft-ipv6.c
+++ b/iptables/nft-ipv6.c
@@ -44,8 +44,7 @@
 
 	if (cs->fw6.ipv6.proto != 0) {
 		op = nft_invflags2cmp(cs->fw6.ipv6.invflags, XT_INV_PROTO);
-		add_proto(r, offsetof(struct ip6_hdr, ip6_nxt), 1,
-			  cs->fw6.ipv6.proto, op);
+		add_l4proto(r, cs->fw6.ipv6.proto, op);
 	}
 
 	if (!IN6_IS_ADDR_UNSPECIFIED(&cs->fw6.ipv6.src)) {
@@ -60,7 +59,7 @@
 			 &cs->fw6.ipv6.dst, &cs->fw6.ipv6.dmsk,
 			 sizeof(struct in6_addr), op);
 	}
-	add_compat(r, cs->fw6.ipv6.proto, cs->fw6.ipv6.invflags);
+	add_compat(r, cs->fw6.ipv6.proto, cs->fw6.ipv6.invflags & XT_INV_PROTO);
 
 	for (matchp = cs->matches; matchp; matchp = matchp->next) {
 		/* Use nft built-in comments support instead of comment match */
@@ -115,6 +114,16 @@
 {
 	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);
@@ -162,7 +171,6 @@
 		break;
 	case offsetof(struct ip6_hdr, ip6_nxt):
 		get_cmp_data(e, &proto, sizeof(proto), &inv);
-		cs->fw6.ipv6.flags |= IP6T_F_PROTO;
 		cs->fw6.ipv6.proto = proto;
 		if (inv)
 			cs->fw6.ipv6.invflags |= IP6T_INV_PROTO;
@@ -183,64 +191,24 @@
 		cs->fw6.ipv6.flags |= IP6T_F_GOTO;
 }
 
-static void nft_ipv6_print_header(unsigned int format, const char *chain,
-				  const char *pol,
-				  const struct xt_counters *counters,
-				  bool basechain, uint32_t refs)
-{
-	print_header(format, chain, pol, counters, basechain, refs);
-}
-
-static void print_ipv6_addr(const struct iptables_command_state *cs,
-			    unsigned int format)
-{
-	char buf[BUFSIZ];
-
-	fputc(cs->fw6.ipv6.invflags & IP6T_INV_SRCIP ? '!' : ' ', stdout);
-	if (IN6_IS_ADDR_UNSPECIFIED(&cs->fw6.ipv6.src)
-	    && !(format & FMT_NUMERIC))
-		printf(FMT("%-19s ","%s "), "anywhere");
-	else {
-		if (format & FMT_NUMERIC)
-			strcpy(buf,
-			       xtables_ip6addr_to_numeric(&cs->fw6.ipv6.src));
-		else
-			strcpy(buf,
-			       xtables_ip6addr_to_anyname(&cs->fw6.ipv6.src));
-		strcat(buf, xtables_ip6mask_to_numeric(&cs->fw6.ipv6.smsk));
-		printf(FMT("%-19s ","%s "), buf);
-	}
-
-
-	fputc(cs->fw6.ipv6.invflags & IP6T_INV_DSTIP ? '!' : ' ', stdout);
-	if (IN6_IS_ADDR_UNSPECIFIED(&cs->fw6.ipv6.dst)
-	    && !(format & FMT_NUMERIC))
-		printf(FMT("%-19s ","-> %s"), "anywhere");
-	else {
-		if (format & FMT_NUMERIC)
-			strcpy(buf,
-			       xtables_ip6addr_to_numeric(&cs->fw6.ipv6.dst));
-		else
-			strcpy(buf,
-			       xtables_ip6addr_to_anyname(&cs->fw6.ipv6.dst));
-		strcat(buf, xtables_ip6mask_to_numeric(&cs->fw6.ipv6.dmsk));
-		printf(FMT("%-19s ","-> %s"), buf);
-	}
-}
-
-static void nft_ipv6_print_firewall(struct nftnl_rule *r, unsigned int num,
-				    unsigned int format)
+static void nft_ipv6_print_rule(struct nftnl_rule *r, unsigned int num,
+				unsigned int format)
 {
 	struct iptables_command_state cs = {};
 
 	nft_rule_to_iptables_command_state(r, &cs);
 
-	print_firewall_details(&cs, cs.jumpto, cs.fw6.ipv6.flags,
-			       cs.fw6.ipv6.invflags, cs.fw6.ipv6.proto,
-			       num, format);
+	print_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_addr(&cs, format);
+	print_ipv6_addresses(&cs.fw6, format);
 
 	if (format & FMT_NOTABLE)
 		fputs("  ", stdout);
@@ -276,7 +244,7 @@
 		printf("/%d ", l);
 }
 
-static void nft_ipv6_save_firewall(const void *data, unsigned int format)
+static void nft_ipv6_save_rule(const void *data, unsigned int format)
 {
 	const struct iptables_command_state *cs = data;
 
@@ -285,19 +253,12 @@
 	save_ipv6_addr('d', &cs->fw6.ipv6.dst, &cs->fw6.ipv6.dmsk,
 		       cs->fw6.ipv6.invflags & IP6T_INV_DSTIP);
 
-	save_firewall_details(cs, cs->fw6.ipv6.invflags, cs->fw6.ipv6.proto,
-			      cs->fw6.ipv6.iniface, cs->fw6.ipv6.iniface_mask,
-			      cs->fw6.ipv6.outiface,
-			      cs->fw6.ipv6.outiface_mask);
+	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->matches, cs->target,
-				cs->jumpto, cs->fw6.ipv6.flags, &cs->fw6);
-
-	if (cs->target == NULL && strlen(cs->jumpto) > 0) {
-		printf("-%c %s", cs->fw6.ipv6.flags & IP6T_F_GOTO ? 'g' : 'j',
-		       cs->jumpto);
-	}
-	printf("\n");
+	save_matches_and_target(cs, cs->fw6.ipv6.flags & IP6T_F_GOTO,
+				&cs->fw6, format);
 }
 
 /* These are invalid numbers as upper layer protocol */
@@ -326,9 +287,6 @@
 static void nft_ipv6_post_parse(int command, struct iptables_command_state *cs,
 				struct xtables_args *args)
 {
-	if (args->proto != 0)
-		args->flags |= IP6T_F_PROTO;
-
 	cs->fw6.ipv6.flags = args->flags;
 	/* We already set invflags in proto_parse, but we need to refresh it
 	 * to include new parsed options.
@@ -375,28 +333,6 @@
 			      " source or destination IP addresses");
 }
 
-static void nft_ipv6_parse_target(struct xtables_target *t, void *data)
-{
-	struct iptables_command_state *cs = data;
-
-	cs->target = t;
-}
-
-static bool nft_ipv6_rule_find(struct nft_family_ops *ops,
-			       struct nftnl_rule *r, void *data)
-{
-	struct iptables_command_state *cs = data;
-
-	return nft_ipv46_rule_find(ops, r, cs);
-}
-
-static void nft_ipv6_save_counters(const void *data)
-{
-	const struct iptables_command_state *cs = data;
-
-	save_counters(cs->counters.pcnt, cs->counters.bcnt);
-}
-
 static void xlate_ipv6_addr(const char *selector, const struct in6_addr *addr,
 			    const struct in6_addr *mask,
 			    int invert, struct xt_xlate *xl)
@@ -467,13 +403,16 @@
 	.parse_meta		= nft_ipv6_parse_meta,
 	.parse_payload		= nft_ipv6_parse_payload,
 	.parse_immediate	= nft_ipv6_parse_immediate,
-	.print_header		= nft_ipv6_print_header,
-	.print_firewall		= nft_ipv6_print_firewall,
-	.save_firewall		= nft_ipv6_save_firewall,
-	.save_counters		= nft_ipv6_save_counters,
+	.print_header		= print_header,
+	.print_rule		= nft_ipv6_print_rule,
+	.save_rule		= nft_ipv6_save_rule,
+	.save_counters		= save_counters,
+	.save_chain		= nft_ipv46_save_chain,
 	.proto_parse		= nft_ipv6_proto_parse,
 	.post_parse		= nft_ipv6_post_parse,
-	.parse_target		= nft_ipv6_parse_target,
-	.rule_find		= nft_ipv6_rule_find,
+	.parse_target		= nft_ipv46_parse_target,
+	.rule_to_cs		= nft_rule_to_iptables_command_state,
+	.clear_cs		= nft_clear_iptables_command_state,
+	.rule_find		= nft_ipv46_rule_find,
 	.xlate			= nft_ipv6_xlate,
 };
diff --git a/iptables/nft-shared.c b/iptables/nft-shared.c
index eb2af85..492e4ec 100644
--- a/iptables/nft-shared.c
+++ b/iptables/nft-shared.c
@@ -16,11 +16,13 @@
 #include <stdbool.h>
 #include <netdb.h>
 #include <errno.h>
+#include <inttypes.h>
 
 #include <xtables.h>
 
 #include <linux/netfilter/nf_tables.h>
 #include <linux/netfilter/xt_comment.h>
+#include <linux/netfilter/xt_limit.h>
 
 #include <libmnl/libmnl.h>
 #include <libnftnl/rule.h>
@@ -186,6 +188,12 @@
 	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,
@@ -294,21 +302,6 @@
 	return 0;
 }
 
-static void *nft_get_data(struct nft_xt_ctx *ctx)
-{
-	switch(ctx->family) {
-	case NFPROTO_IPV4:
-	case NFPROTO_IPV6:
-	case NFPROTO_BRIDGE:
-		return ctx->state.cs;
-	case NFPROTO_ARP:
-		return ctx->state.cs_arp;
-	default:
-		/* Should not happen */
-		return NULL;
-	}
-}
-
 void nft_parse_target(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
 {
 	uint32_t tg_len;
@@ -318,7 +311,7 @@
 	struct xt_entry_target *t;
 	size_t size;
 	struct nft_family_ops *ops;
-	void *data = nft_get_data(ctx);
+	void *data = ctx->cs;
 
 	target = xtables_find_target(targname, XTF_TRY_LOAD);
 	if (target == NULL)
@@ -326,11 +319,7 @@
 
 	size = XT_ALIGN(sizeof(struct xt_entry_target)) + tg_len;
 
-	t = calloc(1, size);
-	if (t == NULL) {
-		fprintf(stderr, "OOM");
-		exit(EXIT_FAILURE);
-	}
+	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);
@@ -356,7 +345,7 @@
 	case NFPROTO_IPV4:
 	case NFPROTO_IPV6:
 	case NFPROTO_BRIDGE:
-		matches = &ctx->state.cs->matches;
+		matches = &ctx->cs->matches;
 		break;
 	default:
 		fprintf(stderr, "BUG: nft_parse_match() unknown family %d\n",
@@ -368,12 +357,7 @@
 	if (match == NULL)
 		return;
 
-	m = calloc(1, sizeof(struct xt_entry_match) + mt_len);
-	if (m == NULL) {
-		fprintf(stderr, "OOM");
-		exit(EXIT_FAILURE);
-	}
-
+	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);
@@ -383,7 +367,7 @@
 
 	ops = nft_family_ops_lookup(ctx->family);
 	if (ops->parse_match != NULL)
-		ops->parse_match(match, nft_get_data(ctx));
+		ops->parse_match(match, ctx->cs);
 }
 
 void print_proto(uint16_t proto, int invert)
@@ -446,7 +430,7 @@
 	target->t = t;
 
 	ops = nft_family_ops_lookup(ctx->family);
-	ops->parse_target(target, nft_get_data(ctx));
+	ops->parse_target(target, ctx->cs);
 }
 
 void nft_parse_meta(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
@@ -491,7 +475,7 @@
 void nft_parse_cmp(struct nft_xt_ctx *ctx, struct nftnl_expr *e)
 {
 	struct nft_family_ops *ops = nft_family_ops_lookup(ctx->family);
-	void *data = nft_get_data(ctx);
+	void *data = ctx->cs;
 	uint32_t reg;
 
 	reg = nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_SREG);
@@ -521,7 +505,7 @@
 	struct nft_family_ops *ops;
 	const char *jumpto = NULL;
 	bool nft_goto = false;
-	void *data = nft_get_data(ctx);
+	void *data = ctx->cs;
 	int verdict;
 
 	if (nftnl_expr_is_set(e, NFTNL_EXPR_IMM_DATA)) {
@@ -554,6 +538,7 @@
 		break;;
 	case NFT_GOTO:
 		nft_goto = true;
+		/* fall through */
 	case NFT_JUMP:
 		jumpto = chain;
 		break;
@@ -563,14 +548,57 @@
 	ops->parse_immediate(jumpto, nft_goto, data);
 }
 
-void nft_rule_to_iptables_command_state(struct nftnl_rule *r,
+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 nft_family_ops *ops;
+	struct xt_rateinfo *rinfo;
+	size_t size;
+
+	switch (ctx->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->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;
+
+	ops = nft_family_ops_lookup(ctx->family);
+	if (ops->parse_match != NULL)
+		ops->parse_match(match, ctx->cs);
+}
+
+void nft_rule_to_iptables_command_state(const struct nftnl_rule *r,
 					struct iptables_command_state *cs)
 {
 	struct nftnl_expr_iter *iter;
 	struct nftnl_expr *expr;
 	int family = nftnl_rule_get_u32(r, NFTNL_RULE_FAMILY);
 	struct nft_xt_ctx ctx = {
-		.state.cs = cs,
+		.cs = cs,
 		.family = family,
 	};
 
@@ -585,7 +613,7 @@
 			nftnl_expr_get_str(expr, NFTNL_EXPR_NAME);
 
 		if (strcmp(name, "counter") == 0)
-			nft_parse_counter(expr, &ctx.state.cs->counters);
+			nft_parse_counter(expr, &ctx.cs->counters);
 		else if (strcmp(name, "payload") == 0)
 			nft_parse_payload(&ctx, expr);
 		else if (strcmp(name, "meta") == 0)
@@ -600,6 +628,8 @@
 			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);
 
 		expr = nftnl_expr_iter_next(iter);
 	}
@@ -608,7 +638,7 @@
 
 	if (nftnl_rule_is_set(r, NFTNL_RULE_USERDATA)) {
 		const void *data;
-		uint32_t len;
+		uint32_t len, size;
 		struct xtables_match *match;
 		struct xt_entry_match *m;
 
@@ -618,15 +648,12 @@
 		if (match == NULL)
 			return;
 
-		m = calloc(1, sizeof(struct xt_entry_match) +
-			      sizeof(struct xt_comment_info));
-		if (m == NULL) {
-			fprintf(stderr, "OOM");
-			exit(EXIT_FAILURE);
-		}
+		size = XT_ALIGN(sizeof(struct xt_entry_match)) + match->size;
+		m = xtables_calloc(1, size);
 
-		memcpy(&m->data, get_comment(data, len), len);
-		m->u.match_size = len + XT_ALIGN(sizeof(struct xt_entry_match));
+		strncpy((char *)m->data, get_comment(data, len),
+			match->size - 1);
+		m->u.match_size = size;
 		m->u.user.revision = 0;
 		strcpy(m->u.user.name, match->name);
 
@@ -641,9 +668,16 @@
 		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);
+}
+
 void print_header(unsigned int format, const char *chain, const char *pol,
 		  const struct xt_counters *counters, bool basechain,
-		  uint32_t refs)
+		  uint32_t refs, uint32_t entries)
 {
 	printf("Chain %s", chain);
 	if (basechain) {
@@ -685,10 +719,10 @@
 	printf("\n");
 }
 
-void print_firewall_details(const struct iptables_command_state *cs,
-			    const char *targname, uint8_t flags,
-			    uint8_t invflags, uint8_t proto,
-			    unsigned int num, unsigned int format)
+void print_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);
@@ -712,45 +746,6 @@
 	}
 }
 
-void print_ifaces(const char *iniface, const char *outiface, uint8_t invflags,
-		  unsigned int format)
-{
-	char iface[IFNAMSIZ+2];
-
-	if (!(format & FMT_VIA))
-		return;
-
-	if (invflags & IPT_INV_VIA_IN) {
-		iface[0] = '!';
-		iface[1] = '\0';
-	} else
-		iface[0] = '\0';
-
-	if (iniface[0] != '\0')
-		strcat(iface, iniface);
-	else if (format & FMT_NUMERIC)
-		strcat(iface, "*");
-	else
-		strcat(iface, "any");
-
-	printf(FMT(" %-6s ","in %s "), iface);
-
-	if (invflags & IPT_INV_VIA_OUT) {
-		iface[0] = '!';
-		iface[1] = '\0';
-	} else
-		iface[0] = '\0';
-
-	if (outiface[0] != '\0')
-		strcat(iface, outiface);
-	else if (format & FMT_NUMERIC)
-		strcat(iface, "*");
-	else
-		strcat(iface, "any");
-
-	printf(FMT("%-6s ","out %s "), iface);
-}
-
 static void
 print_iface(char letter, const char *iface, const unsigned char *mask, int inv)
 {
@@ -775,12 +770,12 @@
 	printf(" ");
 }
 
-void save_firewall_details(const struct iptables_command_state *cs,
-			   uint8_t invflags, uint16_t proto,
-			   const char *iniface,
-			   unsigned const char *iniface_mask,
-			   const char *outiface,
-			   unsigned const char *outiface_mask)
+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,
@@ -804,19 +799,31 @@
 	}
 }
 
-void save_counters(uint64_t pcnt, uint64_t bcnt)
+void save_counters(const void *data)
 {
-	printf("[%llu:%llu] ", (unsigned long long)pcnt,
-			       (unsigned long long)bcnt);
+	const struct iptables_command_state *cs = data;
+
+	printf("[%llu:%llu] ", (unsigned long long)cs->counters.pcnt,
+			       (unsigned long long)cs->counters.bcnt);
 }
 
-void save_matches_and_target(struct xtables_rule_match *m,
-			     struct xtables_target *target,
-			     const char *jumpto, uint8_t flags, const void *fw)
+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 = m; matchp; matchp = matchp->next) {
+	for (matchp = cs->matches; matchp; matchp = matchp->next) {
 		if (matchp->match->alias) {
 			printf("-m %s",
 			       matchp->match->alias(matchp->match->m));
@@ -830,15 +837,24 @@
 		printf(" ");
 	}
 
-	if (target != NULL) {
-		if (target->alias) {
-			printf("-j %s", target->alias(target->t));
-		} else
-			printf("-j %s", jumpto);
+	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 (target->save != NULL)
-			target->save(fw, target->t);
+	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,
@@ -934,16 +950,23 @@
 	return true;
 }
 
-bool nft_ipv46_rule_find(struct nft_family_ops *ops,
-			 struct nftnl_rule *r, struct iptables_command_state *cs)
+void nft_ipv46_parse_target(struct xtables_target *t, void *data)
 {
-	struct iptables_command_state this = {};
+	struct iptables_command_state *cs = data;
+
+	cs->target = t;
+}
+
+bool nft_ipv46_rule_find(struct nft_family_ops *ops,
+			 struct nftnl_rule *r, void *data)
+{
+	struct iptables_command_state *cs = data, this = {};
 
 	nft_rule_to_iptables_command_state(r, &this);
 
 	DEBUGP("comparing with... ");
 #ifdef DEBUG_DEL
-	nft_rule_print_save(&this, r, NFT_RULE_APPEND, 0);
+	nft_rule_print_save(r, NFT_RULE_APPEND, 0);
 #endif
 	if (!ops->is_same(cs, &this))
 		return false;
diff --git a/iptables/nft-shared.h b/iptables/nft-shared.h
index d74eeb0..1281f08 100644
--- a/iptables/nft-shared.h
+++ b/iptables/nft-shared.h
@@ -5,17 +5,15 @@
 
 #include <libnftnl/rule.h>
 #include <libnftnl/expr.h>
+#include <libnftnl/chain.h>
 
 #include <linux/netfilter_arp/arp_tables.h>
 
 #include "xshared.h"
 
-#if 0
-#define DEBUGP(x, args...) fprintf(stdout, x, ## args)
+#ifdef DEBUG
 #define NLDEBUG
 #define DEBUG_DEL
-#else
-#define DEBUGP(x, args...)
 #endif
 
 /*
@@ -47,10 +45,7 @@
 };
 
 struct nft_xt_ctx {
-	union {
-		struct iptables_command_state *cs;
-		struct arptables_command_state *cs_arp;
-	} state;
+	struct iptables_command_state *cs;
 	struct nftnl_expr_iter *iter;
 	int family;
 	uint32_t flags;
@@ -93,17 +88,21 @@
 	void (*print_header)(unsigned int format, const char *chain,
 			     const char *pol,
 			     const struct xt_counters *counters, bool basechain,
-			     uint32_t refs);
-	void (*print_firewall)(struct nftnl_rule *r, unsigned int num,
-			       unsigned int format);
-	void (*save_firewall)(const void *data, unsigned int format);
+			     uint32_t refs, uint32_t entries);
+	void (*print_rule)(struct nftnl_rule *r, unsigned int num,
+			   unsigned int format);
+	void (*save_rule)(const void *data, unsigned int format);
 	void (*save_counters)(const void *data);
+	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)(const struct nftnl_rule *r,
+			   struct iptables_command_state *cs);
+	void (*clear_cs)(struct iptables_command_state *cs);
 	bool (*rule_find)(struct nft_family_ops *ops, struct nftnl_rule *r,
 			  void *data);
 	int (*xlate)(const void *data, struct xt_xlate *xl);
@@ -123,6 +122,7 @@
 	      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,
@@ -145,36 +145,36 @@
 void nft_parse_payload(struct nft_xt_ctx *ctx, struct nftnl_expr *e);
 void nft_parse_counter(struct nftnl_expr *e, struct xt_counters *counters);
 void nft_parse_immediate(struct nft_xt_ctx *ctx, struct nftnl_expr *e);
-void nft_rule_to_iptables_command_state(struct nftnl_rule *r,
+void nft_rule_to_iptables_command_state(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);
-void print_firewall_details(const struct iptables_command_state *cs,
-			    const char *targname, uint8_t flags,
-			    uint8_t invflags, uint8_t proto,
-			    unsigned int num, unsigned int format);
-void print_ifaces(const char *iniface, const char *outiface, uint8_t invflags,
-		  unsigned int format);
+		  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_firewall_details(const struct iptables_command_state *cs,
-			   uint8_t invflags, uint16_t proto,
-			   const char *iniface,
-			   unsigned const char *iniface_mask,
-			   const char *outiface,
-			   unsigned const char *outiface_mask);
-void save_counters(uint64_t pcnt, uint64_t bcnt);
-void save_matches_and_target(struct xtables_rule_match *m,
-			     struct xtables_target *target,
-			     const char *jumpto,
-			     uint8_t flags, const void *fw);
+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 save_counters(const void *data);
+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);
 
 struct nft_handle;
+void nft_ipv46_parse_target(struct xtables_target *t, void *data);
 bool nft_ipv46_rule_find(struct nft_family_ops *ops, struct nftnl_rule *r,
-			 struct iptables_command_state *cs);
+			 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);
@@ -243,6 +243,7 @@
 	FILE		*in;
 	int		testing;
 	const char	*tablename;
+	bool		commit;
 };
 
 struct nftnl_chain_list;
diff --git a/iptables/nft.c b/iptables/nft.c
index 3cacf5f..b81f41a 100644
--- a/iptables/nft.c
+++ b/iptables/nft.c
@@ -38,6 +38,8 @@
 #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/table.h>
 #include <libnftnl/chain.h>
@@ -246,6 +248,7 @@
 	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,
@@ -310,6 +313,7 @@
 			 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:
@@ -327,9 +331,7 @@
 			 nftnl_rule_get_str(o->rule, NFTNL_RULE_CHAIN));
 #if 0
 		{
-			struct iptables_command_state cs = {};
-			nft_rule_to_iptables_command_state(o->rule, &cs);
-			nft_rule_print_save(&cs, o->rule, NFT_RULE_APPEND, FMT_NOCOUNTS);
+			nft_rule_print_save(o->rule, NFT_RULE_APPEND, FMT_NOCOUNTS);
 		}
 #endif
 		break;
@@ -630,7 +632,7 @@
 }
 
 /* find if built-in table already exists */
-static struct builtin_table *
+struct builtin_table *
 nft_table_builtin_find(struct nft_handle *h, const char *table)
 {
 	int i;
@@ -651,7 +653,7 @@
 }
 
 /* find if built-in chain already exists */
-static struct builtin_chain *
+struct builtin_chain *
 nft_chain_builtin_find(struct builtin_table *t, const char *chain)
 {
 	int i;
@@ -675,7 +677,7 @@
 	struct nftnl_chain *c;
 
 	/* Initialize built-in chains if they don't exist yet */
-	for (i=0; i<NF_IP_NUMHOOKS && table->chains[i].name != NULL; i++) {
+	for (i=0; i < NF_INET_NUMHOOKS && table->chains[i].name != NULL; i++) {
 
 		c = nft_chain_list_find(list, table->name,
 					table->chains[i].name);
@@ -815,7 +817,7 @@
 #ifdef NLDEBUG
 	char tmp[1024];
 
-	nft_chain_snprintf(tmp, sizeof(tmp), c, 0, 0);
+	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
@@ -831,9 +833,13 @@
 	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 */
-	if (_t != NULL)
-		nft_table_builtin_add(h, _t);
+	nft_table_builtin_add(h, _t);
 
 	_c = nft_chain_builtin_find(_t, chain);
 	if (_c != NULL) {
@@ -869,6 +875,8 @@
 		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
+		errno = EINVAL;
 
 	if (c == NULL)
 		return 0;
@@ -896,11 +904,50 @@
 	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;
+}
+
 int add_match(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);
+
 	expr = nftnl_expr_alloc("match");
 	if (expr == NULL)
 		return -ENOMEM;
@@ -1031,7 +1078,7 @@
 #ifdef NLDEBUG
 	char tmp[1024];
 
-	nft_rule_snprintf(tmp, sizeof(tmp), r, 0, 0);
+	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
@@ -1173,10 +1220,16 @@
 	} else
 		type = NFT_COMPAT_RULE_APPEND;
 
-	if (batch_rule_add(h, type, r) < 0)
+	if (batch_rule_add(h, type, r) < 0) {
 		nftnl_rule_free(r);
+		return 0;
+	}
 
-	nft_rule_list_get(h);
+	if (verbose)
+		h->ops->print_rule(r, 0, FMT_PRINT_RULE);
+
+	if (!nft_rule_list_get(h))
+		return 0;
 
 	nftnl_rule_list_add_tail(r, h->rule_cache);
 
@@ -1184,18 +1237,19 @@
 }
 
 void
-nft_rule_print_save(const void *data,
-		    struct nftnl_rule *r, enum nft_rule_print type,
+nft_rule_print_save(const struct nftnl_rule *r, enum nft_rule_print type,
 		    unsigned int format)
 {
 	const char *chain = nftnl_rule_get_str(r, NFTNL_RULE_CHAIN);
 	int family = nftnl_rule_get_u32(r, NFTNL_RULE_FAMILY);
+	struct iptables_command_state cs = {};
 	struct nft_family_ops *ops;
 
 	ops = nft_family_ops_lookup(family);
+	ops->rule_to_cs(r, &cs);
 
-	if (!(format & FMT_NOCOUNTS) && ops->save_counters)
-		ops->save_counters(data);
+	if (!(format & (FMT_NOCOUNTS | FMT_C_COUNTS)) && ops->save_counters)
+		ops->save_counters(&cs);
 
 	/* print chain name */
 	switch(type) {
@@ -1207,9 +1261,11 @@
 		break;
 	}
 
-	if (ops->save_firewall)
-		ops->save_firewall(data, format);
+	if (ops->save_rule)
+		ops->save_rule(&cs, format);
 
+	if (ops->clear_cs)
+		ops->clear_cs(&cs);
 }
 
 static int nftnl_chain_list_cb(const struct nlmsghdr *nlh, void *data)
@@ -1274,32 +1330,15 @@
 	[NF_ACCEPT] = "ACCEPT",
 };
 
-static void nft_chain_print_save(struct nftnl_chain *c, bool basechain)
-{
-	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);
-
-	/* print chain name */
-	if (basechain) {
-		uint32_t pol = NF_ACCEPT;
-
-		/* no default chain policy? don't crash, display accept */
-		if (nftnl_chain_get(c, NFTNL_CHAIN_POLICY))
-			pol = nftnl_chain_get_u32(c, NFTNL_CHAIN_POLICY);
-
-		printf(":%s %s [%"PRIu64":%"PRIu64"]\n", chain, policy_name[pol],
-					     pkts, bytes);
-	} else
-		printf(":%s - [%"PRIu64":%"PRIu64"]\n", chain, pkts, bytes);
-}
-
 int nft_chain_save(struct nft_handle *h, struct nftnl_chain_list *list,
 		   const char *table)
 {
 	struct nftnl_chain_list_iter *iter;
+	struct nft_family_ops *ops;
 	struct nftnl_chain *c;
 
+	ops = nft_family_ops_lookup(h->family);
+
 	iter = nftnl_chain_list_iter_create(list);
 	if (iter == NULL)
 		return 0;
@@ -1308,13 +1347,21 @@
 	while (c != NULL) {
 		const char *chain_table =
 			nftnl_chain_get_str(c, NFTNL_CHAIN_TABLE);
-		bool basechain = false;
+		const char *policy = NULL;
 
 		if (strcmp(table, chain_table) != 0)
 			goto next;
 
-		basechain = nft_chain_builtin(c);
-		nft_chain_print_save(c, basechain);
+		if (nft_chain_builtin(c)) {
+			uint32_t pol = NF_ACCEPT;
+
+			if (nftnl_chain_get(c, NFTNL_CHAIN_POLICY))
+				pol = nftnl_chain_get_u32(c, NFTNL_CHAIN_POLICY);
+			policy = policy_name[pol];
+		}
+
+		if (ops->save_chain)
+			ops->save_chain(c, policy);
 next:
 		c = nftnl_chain_list_iter_next(iter);
 	}
@@ -1380,7 +1427,7 @@
 	return list;
 }
 
-int nft_rule_save(struct nft_handle *h, const char *table, bool counters)
+int nft_rule_save(struct nft_handle *h, const char *table, unsigned int format)
 {
 	struct nftnl_rule_list *list;
 	struct nftnl_rule_list_iter *iter;
@@ -1398,15 +1445,11 @@
 	while (r != NULL) {
 		const char *rule_table =
 			nftnl_rule_get_str(r, NFTNL_RULE_TABLE);
-		struct iptables_command_state cs = {};
 
 		if (strcmp(table, rule_table) != 0)
 			goto next;
 
-		nft_rule_to_iptables_command_state(r, &cs);
-
-		nft_rule_print_save(&cs, r, NFT_RULE_APPEND,
-				    counters ? 0 : FMT_NOCOUNTS);
+		nft_rule_print_save(r, NFT_RULE_APPEND, format);
 
 next:
 		r = nftnl_rule_list_iter_next(iter);
@@ -1448,7 +1491,6 @@
 	struct nft_handle *h = d->handle;
 	const char *table = d->table;
 	const char *chain = d->chain;
-	int ret;
 
 	if (strcmp(table, table_name) != 0)
 		return 0;
@@ -1456,13 +1498,8 @@
 	if (strcmp(chain, chain_name) != 0)
 		return 0;
 
-	if (!nftnl_chain_is_set(c, NFTNL_CHAIN_HOOKNUM)) {
-		ret = batch_chain_add(h, NFT_COMPAT_CHAIN_USER_FLUSH, c);
-		if (ret < 0)
-			return ret;
-
-		nftnl_chain_list_del(c);
-	}
+	if (!nftnl_chain_is_set(c, NFTNL_CHAIN_HOOKNUM))
+		__nft_rule_flush(h, table, chain);
 
 	return 0;
 }
@@ -1483,9 +1520,10 @@
 	return 1;
 }
 
-int nft_rule_flush(struct nft_handle *h, const char *chain, const char *table)
+int nft_rule_flush(struct nft_handle *h, const char *chain, const char *table,
+		   bool verbose)
 {
-	int ret;
+	int ret = 0;
 	struct nftnl_chain_list *list;
 	struct nftnl_chain_list_iter *iter;
 	struct nftnl_chain *c;
@@ -1497,13 +1535,15 @@
 
 	list = nftnl_chain_list_get(h);
 	if (list == NULL) {
-		ret = 0;
+		ret = 1;
 		goto err;
 	}
 
 	iter = nftnl_chain_list_iter_create(list);
-	if (iter == NULL)
+	if (iter == NULL) {
+		ret = 1;
 		goto err;
+	}
 
 	c = nftnl_chain_list_iter_next(iter);
 	while (c != NULL) {
@@ -1518,6 +1558,9 @@
 		if (chain != NULL && strcmp(chain, chain_name) != 0)
 			goto next;
 
+		if (verbose)
+			fprintf(stdout, "Flushing chain `%s'\n", chain_name);
+
 		__nft_rule_flush(h, table_name, chain_name);
 
 		if (chain != NULL)
@@ -1565,7 +1608,8 @@
 #define NLM_F_NONREC	0x100	/* Do not delete recursively    */
 #endif
 
-int nft_chain_user_del(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)
 {
 	struct nftnl_chain_list *list;
 	struct nftnl_chain_list_iter *iter;
@@ -1600,6 +1644,9 @@
 		if (chain != NULL && strcmp(chain, chain_name) != 0)
 			goto next;
 
+		if (verbose)
+			fprintf(stdout, "Deleting chain `%s'\n", chain);
+
 		ret = batch_chain_add(h, NFT_COMPAT_CHAIN_USER_DEL, c);
 
 		if (ret < 0)
@@ -1672,6 +1719,21 @@
 	return nft_chain_list_find(list, table, chain);
 }
 
+bool nft_chain_exists(struct nft_handle *h,
+		      const char *table, const char *chain)
+{
+	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)
 {
@@ -1777,12 +1839,15 @@
 		const char *this_tablename =
 			nftnl_table_get(t, NFTNL_TABLE_NAME);
 
-		if (strcmp(tablename, this_tablename) == 0)
-			return true;
+		if (strcmp(tablename, this_tablename) == 0) {
+			ret = true;
+			break;
+		}
 
 		t = nftnl_table_list_iter_next(iter);
 	}
 
+	nftnl_table_list_iter_destroy(iter);
 	nftnl_table_list_free(list);
 
 err:
@@ -1815,6 +1880,7 @@
 		t = nftnl_table_list_iter_next(iter);
 	}
 
+	nftnl_table_list_iter_destroy(iter);
 	nftnl_table_list_free(list);
 	return 0;
 }
@@ -1833,7 +1899,7 @@
 	batch_table_add(h, NFT_COMPAT_TABLE_FLUSH, t);
 
 	_t = nft_table_builtin_find(h, table);
-	assert(t);
+	assert(_t);
 	_t->initialized = false;
 
 	flush_chain_cache(h, table);
@@ -1878,9 +1944,11 @@
 		t = nftnl_table_list_iter_next(iter);
 	}
 
-	h->rule_cache = nftnl_rule_list_alloc();
-	if (h->rule_cache == NULL)
-		return -1;
+	if (!h->rule_cache) {
+		h->rule_cache = nftnl_rule_list_alloc();
+		if (h->rule_cache == NULL)
+			return -1;
+	}
 
 err_table_iter:
 	nftnl_table_list_iter_destroy(iter);
@@ -1963,7 +2031,7 @@
 		   const char *table, void *data, bool verbose)
 {
 	struct nftnl_rule_list *list;
-	int ret;
+	struct nftnl_rule *r;
 
 	nft_fn = nft_rule_check;
 
@@ -1971,11 +2039,15 @@
 	if (list == NULL)
 		return 0;
 
-	ret = nft_rule_find(h, list, chain, table, data, -1) ? 1 : 0;
-	if (ret == 0)
+	r = nft_rule_find(h, list, chain, table, data, -1);
+	if (r == NULL) {
 		errno = ENOENT;
+		return 0;
+	}
+	if (verbose)
+		h->ops->print_rule(r, 0, FMT_PRINT_RULE);
 
-	return ret;
+	return 1;
 }
 
 int nft_rule_delete(struct nft_handle *h, const char *chain,
@@ -1996,6 +2068,8 @@
 		ret =__nft_rule_del(h, list, r);
 		if (ret < 0)
 			errno = ENOMEM;
+		if (verbose)
+			h->ops->print_rule(r, 0, FMT_PRINT_RULE);
 	} else
 		errno = ENOENT;
 
@@ -2021,6 +2095,9 @@
 		return NULL;
 	}
 
+	if (verbose)
+		h->ops->print_rule(r, 0, FMT_PRINT_RULE);
+
 	return r;
 }
 
@@ -2092,8 +2169,6 @@
 
 	r = nft_rule_find(h, list, chain, table, NULL, rulenum);
 	if (r != NULL) {
-		ret = 1;
-
 		DEBUGP("deleting rule by number %d\n", rulenum);
 		ret = __nft_rule_del(h, list, r);
 		if (ret < 0)
@@ -2143,7 +2218,7 @@
 	struct nftnl_rule_list *list;
 	struct nftnl_rule_list_iter *iter;
 	struct nftnl_rule *r;
-	int rule_ctr = 0, ret = 0;
+	int rule_ctr = 0;
 
 	list = nft_rule_list_get(h);
 	if (list == NULL)
@@ -2151,7 +2226,7 @@
 
 	iter = nftnl_rule_list_iter_create(list);
 	if (iter == NULL)
-		goto err;
+		return 0;
 
 	r = nftnl_rule_list_iter_next(iter);
 	while (r != NULL) {
@@ -2172,21 +2247,51 @@
 		}
 
 		cb(r, rule_ctr, format);
-		if (rulenum > 0 && rule_ctr == rulenum) {
-			ret = 1;
+		if (rulenum > 0)
 			break;
-		}
 
 next:
 		r = nftnl_rule_list_iter_next(iter);
 	}
 
 	nftnl_rule_list_iter_destroy(iter);
-err:
-	if (ret == 0)
-		errno = ENOENT;
+	return 1;
+}
 
-	return ret;
+static int nft_rule_count(struct nft_handle *h,
+			  const char *chain, const char *table)
+{
+	struct nftnl_rule_list_iter *iter;
+	struct nftnl_rule_list *list;
+	struct nftnl_rule *r;
+	int rule_ctr = 0;
+
+	list = nft_rule_list_get(h);
+	if (list == NULL)
+		return 0;
+
+	iter = nftnl_rule_list_iter_create(list);
+	if (iter == NULL)
+		return 0;
+
+	r = nftnl_rule_list_iter_next(iter);
+	while (r != NULL) {
+		const char *rule_table =
+			nftnl_rule_get_str(r, NFTNL_RULE_TABLE);
+		const char *rule_chain =
+			nftnl_rule_get_str(r, NFTNL_RULE_CHAIN);
+
+		if (strcmp(table, rule_table) != 0 ||
+		    strcmp(chain, rule_chain) != 0)
+			goto next;
+
+		rule_ctr++;
+next:
+		r = nftnl_rule_list_iter_next(iter);
+	}
+
+	nftnl_rule_list_iter_destroy(iter);
+	return rule_ctr;
 }
 
 int nft_rule_list(struct nft_handle *h, const char *chain, const char *table,
@@ -2204,8 +2309,10 @@
 		/* Force table and chain creation, otherwise first iptables -L
 		 * lists no table/chains.
 		 */
-		if (!list_empty(&h->obj_list))
+		if (!list_empty(&h->obj_list)) {
 			nft_commit(h);
+			flush_chain_cache(h, NULL);
+		}
 	}
 
 	ops = nft_family_ops_lookup(h->family);
@@ -2217,7 +2324,7 @@
 
 	if (chain && rulenum) {
 		__nft_rule_list(h, chain, table,
-				rulenum, format, ops->print_firewall);
+				rulenum, format, ops->print_rule);
 		return 1;
 	}
 
@@ -2227,7 +2334,7 @@
 	if (iter == NULL)
 		goto err;
 
-	if (ops->print_table_header)
+	if (!chain && ops->print_table_header)
 		ops->print_table_header(table);
 
 	c = nftnl_chain_list_iter_next(iter);
@@ -2245,47 +2352,52 @@
 			.bcnt = nftnl_chain_get_u64(c, NFTNL_CHAIN_BYTES),
 		};
 		bool basechain = false;
+		uint32_t entries;
 
 		if (nftnl_chain_get(c, NFTNL_CHAIN_HOOKNUM))
 			basechain = true;
 
 		if (strcmp(table, chain_table) != 0)
 			goto next;
-		if (chain && strcmp(chain, chain_name) != 0)
-			goto next;
+		if (chain) {
+			if (strcmp(chain, chain_name) != 0)
+				goto next;
+			else if (ops->print_table_header)
+				ops->print_table_header(table);
+		}
 
 		if (found)
 			printf("\n");
 
+		entries = nft_rule_count(h, chain_name, table);
 		ops->print_header(format, chain_name, policy_name[policy],
-				  &ctrs, basechain, refs);
+				  &ctrs, basechain, refs - entries, entries);
 
 		__nft_rule_list(h, chain_name, table,
-				rulenum, format, ops->print_firewall);
+				rulenum, format, ops->print_rule);
+
+		found = true;
 
 		/* we printed the chain we wanted, stop processing. */
 		if (chain)
 			break;
 
-		found = true;
-
 next:
 		c = nftnl_chain_list_iter_next(iter);
 	}
 
 	nftnl_chain_list_iter_destroy(iter);
 err:
+	if (chain && !found)
+		return 0;
+
 	return 1;
 }
 
 static void
 list_save(struct nftnl_rule *r, unsigned int num, unsigned int format)
 {
-	struct iptables_command_state cs = {};
-
-	nft_rule_to_iptables_command_state(r, &cs);
-
-	nft_rule_print_save(&cs, r, NFT_RULE_APPEND, !(format & FMT_NOCOUNTS));
+	nft_rule_print_save(r, NFT_RULE_APPEND, format);
 }
 
 static int
@@ -2340,8 +2452,26 @@
 {
 	struct nftnl_chain_list *list;
 	struct nftnl_chain_list_iter *iter;
+	unsigned int format = 0;
 	struct nftnl_chain *c;
-	int ret = 1;
+	int ret = 0;
+
+	/* If built-in chains don't exist for this table, create them */
+	if (nft_xtables_config_load(h, XTABLES_CONFIG_DEFAULT, 0) < 0) {
+		nft_xt_builtin_init(h, table);
+		/* Force table and chain creation, otherwise first iptables -L
+		 * lists no table/chains.
+		 */
+		if (!list_empty(&h->obj_list)) {
+			nft_commit(h);
+			flush_chain_cache(h, NULL);
+		}
+	}
+
+	if (!nft_is_table_compatible(h, table)) {
+		xtables_error(OTHER_PROBLEM, "table `%s' is incompatible, use 'nft' tool.\n", table);
+		return 0;
+	}
 
 	list = nft_chain_dump(h);
 
@@ -2354,6 +2484,11 @@
 	if (iter == NULL)
 		goto err;
 
+	if (counters < 0)
+		format = FMT_C_COUNTS;
+	else if (counters == 0)
+		format = FMT_NOCOUNTS;
+
 	c = nftnl_chain_list_iter_next(iter);
 	while (c != NULL) {
 		const char *chain_table =
@@ -2367,7 +2502,7 @@
 			goto next;
 
 		ret = __nft_rule_list(h, chain_name, table, rulenum,
-				      counters ? 0 : FMT_NOCOUNTS, list_save);
+				      format, list_save);
 
 		/* we printed the chain we wanted, stop processing. */
 		if (chain)
@@ -2456,10 +2591,11 @@
 	case NFT_COMPAT_TABLE_FLUSH:
 		nftnl_table_free(o->table);
 		break;
-	case NFT_COMPAT_CHAIN_ADD:
+	case NFT_COMPAT_CHAIN_ZERO:
 	case NFT_COMPAT_CHAIN_USER_ADD:
-	case NFT_COMPAT_CHAIN_USER_DEL:
 		break;
+	case NFT_COMPAT_CHAIN_ADD:
+	case NFT_COMPAT_CHAIN_USER_DEL:
 	case NFT_COMPAT_CHAIN_USER_FLUSH:
 	case NFT_COMPAT_CHAIN_UPDATE:
 	case NFT_COMPAT_CHAIN_RENAME:
@@ -2507,6 +2643,7 @@
 						   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);
@@ -2708,6 +2845,7 @@
 	      "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" },
@@ -2858,8 +2996,8 @@
 	return h->config_done;
 }
 
-int nft_chain_zero_counters(struct nft_handle *h, const char *chain, 
-			    const char *table)
+int nft_chain_zero_counters(struct nft_handle *h, const char *chain,
+			    const char *table, bool verbose)
 {
 	struct nftnl_chain_list *list;
 	struct nftnl_chain_list_iter *iter;
@@ -2887,12 +3025,15 @@
 		if (chain != NULL && strcmp(chain, chain_name) != 0)
 			goto next;
 
+		if (verbose)
+			fprintf(stdout, "Zeroing chain `%s'\n", chain_name);
+
 		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);
 
-		ret = batch_chain_add(h, NFT_COMPAT_CHAIN_ADD, c);
+		ret = batch_chain_add(h, NFT_COMPAT_CHAIN_ZERO, c);
 
 		if (chain != NULL)
 			break;
@@ -2929,8 +3070,9 @@
 };
 
 
-static int nft_is_expr_compatible(const char *name)
+static int nft_is_expr_compatible(const struct nftnl_expr *expr)
 {
+	const char *name = nftnl_expr_get_str(expr, NFTNL_EXPR_NAME);
 	int i;
 
 	for (i = 0; i < NFT_COMPAT_EXPR_MAX; i++) {
@@ -2938,6 +3080,11 @@
 			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;
 }
 
@@ -2953,9 +3100,7 @@
 
 	expr = nftnl_expr_iter_next(iter);
 	while (expr != NULL) {
-		const char *name = nftnl_expr_get_str(expr, NFTNL_EXPR_NAME);
-
-		if (nft_is_expr_compatible(name) == 0) {
+		if (nft_is_expr_compatible(expr) == 0) {
 			expr = nftnl_expr_iter_next(iter);
 			continue;
 		}
@@ -3021,7 +3166,12 @@
 
 	chain = nftnl_chain_list_iter_next(iter);
 	while (chain != NULL) {
-		if (!nft_chain_builtin(chain))
+		const char *chain_table;
+
+		chain_table = nftnl_chain_get_str(chain, NFTNL_CHAIN_TABLE);
+
+		if (strcmp(chain_table, tablename) ||
+		    !nft_chain_builtin(chain))
 			goto next;
 
 		ret = nft_is_chain_compatible(h, chain);
@@ -3041,16 +3191,9 @@
 	struct nftnl_rule_list *list;
 	struct nftnl_rule_list_iter *iter;
 	struct nftnl_rule *rule;
-	int ret = 0, i;
+	int ret = 0;
 
-	for (i = 0; i < TABLES_MAX; i++) {
-		if (!h->tables[i].name)
-			continue;
-		if (strcmp(h->tables[i].name, tablename) == 0)
-			break;
-	}
-
-	if (i == TABLES_MAX)
+	if (!nft_table_builtin_find(h, tablename))
 		return false;
 
 	ret = nft_are_chains_compatible(h, tablename);
@@ -3067,9 +3210,15 @@
 
 	rule = nftnl_rule_list_iter_next(iter);
 	while (rule != NULL) {
+		const char *table = nftnl_rule_get_str(rule, NFTNL_RULE_TABLE);
+
+		if (strcmp(table, tablename))
+			goto next_rule;
+
 		ret = nft_is_rule_compatible(rule);
 		if (ret != 0)
 			break;
+next_rule:
 		rule = nftnl_rule_list_iter_next(iter);
 	}
 
diff --git a/iptables/nft.h b/iptables/nft.h
index ffae84f..7419ec2 100644
--- a/iptables/nft.h
+++ b/iptables/nft.h
@@ -68,6 +68,7 @@
 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);
 void nft_table_new(struct nft_handle *h, const char *table);
+struct builtin_table *nft_table_builtin_find(struct nft_handle *h, const char *table);
 
 /*
  * Operations with chains.
@@ -79,11 +80,13 @@
 struct nftnl_chain *nft_chain_list_find(struct nftnl_chain_list *list, const char *table, const char *chain);
 int nft_chain_save(struct nft_handle *h, struct nftnl_chain_list *list, const char *table);
 int nft_chain_user_add(struct nft_handle *h, const char *chain, const char *table);
-int nft_chain_user_del(struct nft_handle *h, const char *chain, const char *table);
+int nft_chain_user_del(struct nft_handle *h, const char *chain, const char *table, bool verbose);
 int nft_chain_user_flush(struct nft_handle *h, struct nftnl_chain_list *list,
 			 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);
+int nft_chain_zero_counters(struct nft_handle *h, const char *chain, const char *table, bool verbose);
+struct builtin_chain *nft_chain_builtin_find(struct builtin_table *t, const char *chain);
+bool nft_chain_exists(struct nft_handle *h, const char *table, const char *chain);
 
 /*
  * Operations with rule-set.
@@ -98,8 +101,8 @@
 int nft_rule_replace(struct nft_handle *h, const char *chain, const char *table, void *data, int rulenum, bool verbose);
 int nft_rule_list(struct nft_handle *h, const char *chain, const char *table, int rulenum, unsigned int format);
 int nft_rule_list_save(struct nft_handle *h, const char *chain, const char *table, int rulenum, int counters);
-int nft_rule_save(struct nft_handle *h, const char *table, bool counters);
-int nft_rule_flush(struct nft_handle *h, const char *chain, const char *table);
+int nft_rule_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);
 
 /*
@@ -119,8 +122,7 @@
 	NFT_RULE_DEL,
 };
 
-void nft_rule_print_save(const void *data,
-			 struct nftnl_rule *r, enum nft_rule_print type,
+void nft_rule_print_save(const struct nftnl_rule *r, enum nft_rule_print type,
 			 unsigned int format);
 
 uint32_t nft_invflags2cmp(uint32_t invflags, uint32_t flag);
@@ -144,9 +146,12 @@
 /* For xtables.c */
 int do_commandx(struct nft_handle *h, int argc, char *argv[], char **table, bool restore);
 /* For xtables-arptables.c */
-int do_commandarp(struct nft_handle *h, int argc, char *argv[], char **table);
+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 do_commandeb(struct nft_handle *h, int argc, char *argv[], char **table);
+int nft_init_eb(struct nft_handle *h, const char *pname);
+int ebt_get_current_chain(const char *chain);
+int do_commandeb(struct nft_handle *h, int argc, char *argv[], char **table, bool restore);
 
 /*
  * Parse config for tables and chain helper functions
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
index a984a45..b6eb01c 100755
--- a/iptables/tests/shell/run-tests.sh
+++ b/iptables/tests/shell/run-tests.sh
@@ -65,13 +65,13 @@
 
 	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
 
-	rc_got=$?
-	echo -en "\033[1A\033[K" # clean the [EXECUTING] foobar line
-
 	if [ "$rc_got" == "$rc_spec" ] ; then
 		msg_info "[OK]          $testfile"
 		((ok++))
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..67265c8
--- /dev/null
+++ b/iptables/tests/shell/testcases/arptables/0001-arptables-save-restore_0
@@ -0,0 +1,58 @@
+#!/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 --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 -s 10.0.0.0/8 -j ACCEPT
+-A INPUT -d 192.168.123.1 -j ACCEPT
+-A INPUT -j foo
+-A INPUT 
+-A OUTPUT -o lo -j ACCEPT
+-A OUTPUT -o eth134 -j mangle --mangle-ip-s 10.0.0.1
+-A OUTPUT -o eth432 -j CLASSIFY --set-class feed:babe
+-A OUTPUT -o eth432 --opcode 1 -j CLASSIFY --set-class feed:babe
+-A foo -i lo -j ACCEPT
+-A foo --h-length 6 -j ACCEPT
+-A foo --opcode 1 -j ACCEPT
+-A foo --h-type 1 --proto-type 0x800 -j ACCEPT
+-A foo -i lo --h-length 6 --opcode 1 --h-type 1 --proto-type 0x800 -j ACCEPT
+'
+
+diff -u <(echo -e "$DUMP") <($XT_MULTI arptables-save)
+
+# 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)
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..b2ed95e
--- /dev/null
+++ b/iptables/tests/shell/testcases/arptables/0002-arptables-restore-defaults_0
@@ -0,0 +1,28 @@
+#!/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/chain/0004newchain_0 b/iptables/tests/shell/testcases/chain/0002newchain_0
similarity index 100%
rename from iptables/tests/shell/testcases/chain/0004newchain_0
rename to iptables/tests/shell/testcases/chain/0002newchain_0
diff --git a/iptables/tests/shell/testcases/chain/0005rename_1 b/iptables/tests/shell/testcases/chain/0003rename_1
similarity index 100%
rename from iptables/tests/shell/testcases/chain/0005rename_1
rename to iptables/tests/shell/testcases/chain/0003rename_1
diff --git a/iptables/tests/shell/testcases/ebtables/0001-ebtables-basic_0 b/iptables/tests/shell/testcases/ebtables/0001-ebtables-basic_0
index 05a2715..b0db216 100755
--- a/iptables/tests/shell/testcases/ebtables/0001-ebtables-basic_0
+++ b/iptables/tests/shell/testcases/ebtables/0001-ebtables-basic_0
@@ -28,6 +28,36 @@
 		exit 1
 	fi
 
+	$XT_MULTI ebtables -L FOO | grep -q 'entries: 0'
+	if [ $? -ne 0 ]; then
+		echo "Unexpected entries count in empty unreferenced chain"
+		$XT_MULTI ebtables -L
+		exit 1
+	fi
+
+	$XT_MULTI ebtables -A FORWARD -j FOO
+	$XT_MULTI ebtables -L FORWARD | grep -q 'entries: 1'
+	if [ $? -ne 0 ]; then
+		echo "Unexpected entries count in FORWARD chain"
+		$XT_MULTI ebtables -L
+		exit 1
+	fi
+
+	$XT_MULTI ebtables -L FOO | grep -q 'entries: 0'
+	if [ $? -ne 0 ]; then
+		echo "Unexpected entries count in empty referenced chain"
+		$XT_MULTI ebtables -L
+		exit 1
+	fi
+
+	$XT_MULTI ebtables -A FOO -j ACCEPT
+	$XT_MULTI ebtables -L FOO | grep -q 'entries: 1'
+	if [ $? -ne 0 ]; then
+		echo "Unexpected entries count in non-empty referenced chain"
+		$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
 
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..eeb7d83
--- /dev/null
+++ b/iptables/tests/shell/testcases/ebtables/0002-ebtables-save-restore_0
@@ -0,0 +1,112 @@
+#!/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 -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
+
+# compare against stored ebtables dump
+
+DUMP='*filter
+:INPUT ACCEPT
+:FORWARD DROP
+:OUTPUT ACCEPT
+: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:0:1 -j ACCEPT
+-A foo -p ARP --arp-mac-dst fe:ed:ba:0:0:0/ff:ff:ff:0:0:0 -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
+-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..c858054
--- /dev/null
+++ b/iptables/tests/shell/testcases/ebtables/0003-ebtables-restore-defaults_0
@@ -0,0 +1,33 @@
+#!/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/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/ip6tables/0004-return-codes_0 b/iptables/tests/shell/testcases/ip6tables/0004-return-codes_0
new file mode 100755
index 0000000..f023b79
--- /dev/null
+++ b/iptables/tests/shell/testcases/ip6tables/0004-return-codes_0
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+# make sure error return codes are as expected useful cases
+# (e.g. commands to check ruleset state)
+
+global_rc=0
+
+cmd() { # (rc, cmd, [args ...])
+	rc_exp=$1; shift
+
+	$XT_MULTI "$@"
+	rc=$?
+
+	[ $rc -eq $rc_exp ] || {
+		echo "---> expected $rc_exp, got $rc for command '$@'"
+		global_rc=1
+	}
+}
+
+# test chain creation
+cmd 0 ip6tables -N foo
+cmd 1 ip6tables -N foo
+# iptables-nft allows this - bug or feature?
+#cmd 2 ip6tables -N "invalid name"
+
+# test rule adding
+cmd 0 ip6tables -A INPUT -j ACCEPT
+cmd 1 ip6tables -A noexist -j ACCEPT
+
+# test rule checking
+cmd 0 ip6tables -C INPUT -j ACCEPT
+cmd 1 ip6tables -C FORWARD -j ACCEPT
+cmd 1 ip6tables -C nonexist -j ACCEPT
+cmd 2 ip6tables -C INPUT -j foobar
+cmd 2 ip6tables -C INPUT -m foobar -j ACCEPT
+cmd 3 ip6tables -t foobar -C INPUT -j ACCEPT
+
+exit $global_rc
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..ce3bef3
--- /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/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/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/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..2e80595
--- /dev/null
+++ b/iptables/tests/shell/testcases/iptables/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 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)
+
+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'"
+
+diff -u <(echo -e "$EXPECT") <($XT_MULTI iptables -v -F)
+
+EXPECT="Zeroing chain \`INPUT'
+Zeroing chain \`FORWARD'
+Zeroing chain \`OUTPUT'"
+
+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)
+
+$XT_MULTI iptables -N foo
+diff -u <(echo "Deleting chain \`foo'") <($XT_MULTI iptables -v -X foo)
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..34dffee
--- /dev/null
+++ b/iptables/tests/shell/testcases/iptables/0004-return-codes_0
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+# make sure error return codes are as expected useful cases
+# (e.g. commands to check ruleset state)
+
+global_rc=0
+
+cmd() { # (rc, cmd, [args ...])
+	rc_exp=$1; shift
+
+	$XT_MULTI "$@"
+	rc=$?
+
+	[ $rc -eq $rc_exp ] || {
+		echo "---> expected $rc_exp, got $rc for command '$@'"
+		global_rc=1
+	}
+}
+
+# test chain creation
+cmd 0 iptables -N foo
+cmd 1 iptables -N foo
+# iptables-nft allows this - bug or feature?
+#cmd 2 iptables -N "invalid name"
+
+# test rule adding
+cmd 0 iptables -A INPUT -j ACCEPT
+cmd 1 iptables -A noexist -j ACCEPT
+
+# test rule checking
+cmd 0 iptables -C INPUT -j ACCEPT
+cmd 1 iptables -C FORWARD -j ACCEPT
+cmd 1 iptables -C nonexist -j ACCEPT
+cmd 2 iptables -C INPUT -j foobar
+cmd 2 iptables -C INPUT -m foobar -j ACCEPT
+cmd 3 iptables -t foobar -C INPUT -j ACCEPT
+
+exit $global_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..4319ea5
--- /dev/null
+++ b/iptables/tests/shell/testcases/nft-only/0001compat_0
@@ -0,0 +1,21 @@
+#!/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)
+	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
+	;;
+*)
+	echo skip $XT_MULTI
+	;;
+esac
+
+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..406b608
--- /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..67af9fd
--- /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/xshared.c b/iptables/xshared.c
index 06db72d..b16f5fa 100644
--- a/iptables/xshared.c
+++ b/iptables/xshared.c
@@ -1,4 +1,5 @@
 #include <config.h>
+#include <ctype.h>
 #include <getopt.h>
 #include <errno.h>
 #include <libgen.h>
@@ -359,9 +360,337 @@
 	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;
+}
+
 inline bool xs_has_arg(int argc, char *argv[])
 {
 	return optind < argc &&
 	       argv[optind][0] != '-' &&
 	       argv[optind][0] != '!';
 }
+
+/* global new argv and argc */
+char *newargv[255];
+int newargc = 0;
+
+/* saved newargv and newargc from save_argv() */
+char *oldargv[255];
+int oldargc = 0;
+
+/* arg meta data, were they quoted, frinstance */
+int newargvattr[255];
+
+/* function adding one argument to newargv, updating newargc
+ * returns true if argument added, false otherwise */
+int add_argv(const char *what, int quoted)
+{
+	DEBUGP("add_argv: %s\n", what);
+	if (what && newargc + 1 < ARRAY_SIZE(newargv)) {
+		newargv[newargc] = strdup(what);
+		newargvattr[newargc] = quoted;
+		newargv[++newargc] = NULL;
+		return 1;
+	} else {
+		xtables_error(PARAMETER_PROBLEM,
+			      "Parser cannot handle more arguments\n");
+	}
+}
+
+void free_argv(void)
+{
+	while (newargc)
+		free(newargv[--newargc]);
+	while (oldargc)
+		free(oldargv[--oldargc]);
+}
+
+/* Save parsed rule for comparison with next rule to perform action aggregation
+ * on duplicate conditions.
+ */
+void save_argv(void)
+{
+	unsigned int i;
+
+	while (oldargc)
+		free(oldargv[--oldargc]);
+
+	oldargc = newargc;
+	newargc = 0;
+	for (i = 0; i < oldargc; i++) {
+		oldargv[i] = newargv[i];
+	}
+}
+
+void add_param_to_argv(char *parsestart, int line)
+{
+	int quote_open = 0, escaped = 0, param_len = 0;
+	char param_buffer[1024], *curchar;
+
+	/* After fighting with strtok enough, here's now
+	 * a 'real' parser. According to Rusty I'm now no
+	 * longer a real hacker, but I can live with that */
+
+	for (curchar = parsestart; *curchar; curchar++) {
+		if (quote_open) {
+			if (escaped) {
+				param_buffer[param_len++] = *curchar;
+				escaped = 0;
+				continue;
+			} else if (*curchar == '\\') {
+				escaped = 1;
+				continue;
+			} else if (*curchar == '"') {
+				quote_open = 0;
+				*curchar = '"';
+			} else {
+				param_buffer[param_len++] = *curchar;
+				continue;
+			}
+		} else {
+			if (*curchar == '"') {
+				quote_open = 1;
+				continue;
+			}
+		}
+
+		switch (*curchar) {
+		case '"':
+			break;
+		case ' ':
+		case '\t':
+		case '\n':
+			if (!param_len) {
+				/* two spaces? */
+				continue;
+			}
+			break;
+		default:
+			/* regular character, copy to buffer */
+			param_buffer[param_len++] = *curchar;
+
+			if (param_len >= sizeof(param_buffer))
+				xtables_error(PARAMETER_PROBLEM,
+					      "Parameter too long!");
+			continue;
+		}
+
+		param_buffer[param_len] = '\0';
+
+		/* check if table name specified */
+		if ((param_buffer[0] == '-' &&
+		     param_buffer[1] != '-' &&
+		     strchr(param_buffer, 't')) ||
+		    (!strncmp(param_buffer, "--t", 3) &&
+		     !strncmp(param_buffer, "--table", strlen(param_buffer)))) {
+			xtables_error(PARAMETER_PROBLEM,
+				      "The -t option (seen in line %u) cannot be used in %s.\n",
+				      line, xt_params->program_name);
+		}
+
+		add_argv(param_buffer, 0);
+		param_len = 0;
+	}
+}
+
+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)
+{
+	struct option *opts = xt_params->opts;
+	size_t size;
+
+	cs->jumpto = xt_parse_target(optarg);
+	/* TRY_LOAD (may be chain name) */
+	cs->target = xtables_find_target(cs->jumpto, XTF_TRY_LOAD);
+
+	if (cs->target == NULL)
+		return;
+
+	size = XT_ALIGN(sizeof(struct xt_entry_target)) + cs->target->size;
+
+	cs->target->t = xtables_calloc(1, size);
+	cs->target->t->u.target_size = size;
+	if (cs->target->real_name == NULL) {
+		strcpy(cs->target->t->u.user.name, cs->jumpto);
+	} else {
+		/* Alias support for userspace side */
+		strcpy(cs->target->t->u.user.name, cs->target->real_name);
+		if (!(cs->target->ext_flags & XTABLES_EXT_ALIAS))
+			fprintf(stderr, "Notice: The %s target is converted into %s target "
+				"in rule listing and saving.\n",
+				cs->jumpto, cs->target->real_name);
+	}
+	cs->target->t->u.user.revision = cs->target->revision;
+	xs_init_target(cs->target);
+
+	if (cs->target->x6_options != NULL)
+		opts = xtables_options_xfrm(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;
+}
diff --git a/iptables/xshared.h b/iptables/xshared.h
index c056d94..4265f98 100644
--- a/iptables/xshared.h
+++ b/iptables/xshared.h
@@ -7,9 +7,16 @@
 #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(stdout, x, ## args)
+#else
+#define DEBUGP(x, args...)
+#endif
+
 enum {
 	OPT_NONE        = 0,
 	OPT_NUMERIC     = 1 << 0,
@@ -84,6 +91,7 @@
 		struct ebt_entry eb;
 		struct ipt_entry fw;
 		struct ip6t_entry fw6;
+		struct arpt_entry arp;
 	};
 	int invert;
 	int c;
@@ -143,8 +151,32 @@
 
 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 xs_has_arg(int argc, char *argv[]);
 
 extern const struct xtables_afinfo *afinfo;
 
+extern char *newargv[];
+extern int newargc;
+
+extern char *oldargv[];
+extern int oldargc;
+
+extern int newargvattr[];
+
+int add_argv(const char *what, int quoted);
+void free_argv(void);
+void save_argv(void);
+void add_param_to_argv(char *parsestart, int line);
+
+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);
+
 #endif /* IPTABLES_XSHARED_H */
diff --git a/iptables/xtables-arp-standalone.c b/iptables/xtables-arp-standalone.c
index 6553d28..eca7bb9 100644
--- a/iptables/xtables-arp-standalone.c
+++ b/iptables/xtables-arp-standalone.c
@@ -47,24 +47,11 @@
 {
 	int ret;
 	char *table = "filter";
-	struct nft_handle h = {
-		.family = NFPROTO_ARP,
-	};
+	struct nft_handle h;
 
-	arptables_globals.program_name = "arptables";
-	ret = xtables_init_all(&arptables_globals, NFPROTO_ARP);
-	if (ret < 0) {
-		fprintf(stderr, "%s/%s Failed to initialize arptables-compat\n",
-			arptables_globals.program_name,
-			arptables_globals.program_version);
-		exit(1);
-	}
+	nft_init_arp(&h, "arptables");
 
-#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
-	init_extensionsa();
-#endif
-
-	ret = do_commandarp(&h, argc, argv, &table);
+	ret = do_commandarp(&h, argc, argv, &table, false);
 	if (ret)
 		ret = nft_commit(&h);
 
diff --git a/iptables/xtables-arp.c b/iptables/xtables-arp.c
index eb03beb..6939a61 100644
--- a/iptables/xtables-arp.c
+++ b/iptables/xtables-arp.c
@@ -149,8 +149,7 @@
 
 int RUNTIME_NF_ARP_NUMHOOKS = 3;
 
-static struct option *opts = original_opts;
-static unsigned int global_option_offset;
+#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 = {
@@ -588,16 +587,15 @@
 host_to_addr(const char *name, unsigned int *naddr)
 {
 	struct in_addr *addr;
-	struct addrinfo hints;
+	struct addrinfo hints = {
+		.ai_flags	= AI_CANONNAME,
+		.ai_family	= AF_INET,
+		.ai_socktype	= SOCK_RAW,
+	};;
 	struct addrinfo *res, *p;
 	int err;
 	unsigned int i;
 
-	memset(&hints, 0, sizeof(hints));
-	hints.ai_flags	  = AI_CANONNAME;
-	hints.ai_family	  = AF_INET;
-	hints.ai_socktype = SOCK_RAW;
-
 	*naddr = 0;
 	err = getaddrinfo(name, NULL, &hints, &res);
 	if (err != 0)
@@ -757,27 +755,6 @@
 	return rulenum;
 }
 
-static const char *
-parse_target(const char *targetname)
-{
-	const char *ptr;
-
-	if (strlen(targetname) < 1)
-		xtables_error(PARAMETER_PROBLEM,
-			      "Invalid target name (too short)");
-
-	if (strlen(targetname)+1 > sizeof(arpt_chainlabel))
-		xtables_error(PARAMETER_PROBLEM,
-			      "Invalid target name `%s' (%zu chars max)",
-			      targetname, sizeof(arpt_chainlabel)-1);
-
-	for (ptr = targetname; *ptr; ptr++)
-		if (isspace(*ptr))
-			xtables_error(PARAMETER_PROBLEM,
-				      "Invalid target name `%s'", targetname);
-	return targetname;
-}
-
 static void
 set_option(unsigned int *options, unsigned int option, u_int16_t *invflg,
 	   int invert)
@@ -824,46 +801,11 @@
 	return nft_rule_list(h, chain, table, rulenum, format);
 }
 
-static struct xtables_target *command_jump(struct arpt_entry *fw,
-					   const char *jumpto)
-{
-	struct xtables_target *target;
-	size_t size;
-
-	/* XTF_TRY_LOAD (may be chain name) */
-	target = xtables_find_target(jumpto, XTF_TRY_LOAD);
-
-	if (!target)
-		return NULL;
-
-	size = XT_ALIGN(sizeof(struct xt_entry_target))
-		+ target->size;
-
-	target->t = xtables_calloc(1, size);
-	target->t->u.target_size = size;
-	strncpy(target->t->u.user.name, jumpto, sizeof(target->t->u.user.name) - 1);
-	target->t->u.user.name[sizeof(target->t->u.user.name)-1] = '\0';
-	target->t->u.user.revision = target->revision;
-
-	xs_init_target(target);
-
-	if (target->x6_options != NULL)
-		opts = xtables_options_xfrm(arptables_globals.orig_opts,
-					    opts, target->x6_options,
-					    &target->option_offset);
-	else
-		opts = xtables_merge_options(arptables_globals.orig_opts,
-					     opts, target->extra_opts,
-					     &target->option_offset);
-
-	return target;
-}
-
 static int
 append_entry(struct nft_handle *h,
 	     const char *chain,
 	     const char *table,
-	     struct arptables_command_state *cs,
+	     struct iptables_command_state *cs,
 	     int rulenum,
 	     unsigned int nsaddrs,
 	     const struct in_addr saddrs[],
@@ -875,9 +817,9 @@
 	int ret = 1;
 
 	for (i = 0; i < nsaddrs; i++) {
-		cs->fw.arp.src.s_addr = saddrs[i].s_addr;
+		cs->arp.arp.src.s_addr = saddrs[i].s_addr;
 		for (j = 0; j < ndaddrs; j++) {
-			cs->fw.arp.tgt.s_addr = daddrs[j].s_addr;
+			cs->arp.arp.tgt.s_addr = daddrs[j].s_addr;
 			if (append) {
 				ret = nft_rule_append(h, chain, table, cs, 0,
 						      verbose);
@@ -894,14 +836,14 @@
 static int
 replace_entry(const char *chain,
 	      const char *table,
-	      struct arptables_command_state *cs,
+	      struct iptables_command_state *cs,
 	      unsigned int rulenum,
 	      const struct in_addr *saddr,
 	      const struct in_addr *daddr,
 	      bool verbose, struct nft_handle *h)
 {
-	cs->fw.arp.src.s_addr = saddr->s_addr;
-	cs->fw.arp.tgt.s_addr = daddr->s_addr;
+	cs->arp.arp.src.s_addr = saddr->s_addr;
+	cs->arp.arp.tgt.s_addr = daddr->s_addr;
 
 	return nft_rule_replace(h, chain, table, cs, rulenum, verbose);
 }
@@ -909,7 +851,7 @@
 static int
 delete_entry(const char *chain,
 	     const char *table,
-	     struct arptables_command_state *cs,
+	     struct iptables_command_state *cs,
 	     unsigned int nsaddrs,
 	     const struct in_addr saddrs[],
 	     unsigned int ndaddrs,
@@ -920,9 +862,9 @@
 	int ret = 1;
 
 	for (i = 0; i < nsaddrs; i++) {
-		cs->fw.arp.src.s_addr = saddrs[i].s_addr;
+		cs->arp.arp.src.s_addr = saddrs[i].s_addr;
 		for (j = 0; j < ndaddrs; j++) {
-			cs->fw.arp.tgt.s_addr = daddrs[j].s_addr;
+			cs->arp.arp.tgt.s_addr = daddrs[j].s_addr;
 			ret = nft_rule_delete(h, chain, table, cs, verbose);
 		}
 	}
@@ -930,9 +872,40 @@
 	return ret;
 }
 
-int do_commandarp(struct nft_handle *h, int argc, char *argv[], char **table)
+int nft_init_arp(struct nft_handle *h, const char *pname)
 {
-	struct arptables_command_state cs;
+	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
+
+	memset(h, 0, sizeof(*h));
+	h->family = NFPROTO_ARP;
+
+	if (nft_init(h, xtables_arp) < 0)
+		xtables_error(OTHER_PROBLEM,
+			      "Could not initialize nftables layer.");
+
+	h->ops = nft_family_ops_lookup(h->family);
+	if (h->ops == NULL)
+		xtables_error(PARAMETER_PROBLEM, "Unknown family");
+
+	return 0;
+}
+
+int do_commandarp(struct nft_handle *h, int argc, char *argv[], char **table,
+		  bool restore)
+{
+	struct iptables_command_state cs = {
+		.jumpto = "",
+	};
 	int invert = 0;
 	unsigned int nsaddrs = 0, ndaddrs = 0;
 	struct in_addr *saddrs = NULL, *daddrs = NULL;
@@ -946,14 +919,6 @@
 	int ret = 1;
 	struct xtables_target *t;
 
-	memset(&cs, 0, sizeof(cs));
-	cs.jumpto = "";
-
-	opts = original_opts;
-	global_option_offset = 0;
-
-	xtables_globals.orig_opts = original_opts;
-
 	/* re-set optind to 0 in case do_command gets called
 	 * a second time */
 	optind = 0;
@@ -967,6 +932,7 @@
 	    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) {
@@ -1090,47 +1056,47 @@
 			break;
 		case 's':
 			check_inverse(optarg, &invert, &optind, argc);
-			set_option(&options, OPT_S_IP, &cs.fw.arp.invflags,
+			set_option(&options, OPT_S_IP, &cs.arp.arp.invflags,
 				   invert);
 			shostnetworkmask = argv[optind-1];
 			break;
 
 		case 'd':
 			check_inverse(optarg, &invert, &optind, argc);
-			set_option(&options, OPT_D_IP, &cs.fw.arp.invflags,
+			set_option(&options, OPT_D_IP, &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.fw.arp.invflags,
+			set_option(&options, OPT_S_MAC, &cs.arp.arp.invflags,
 				   invert);
 			if (getmac_and_mask(argv[optind - 1],
-			    cs.fw.arp.src_devaddr.addr, cs.fw.arp.src_devaddr.mask))
+			    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.fw.arp.invflags,
+			set_option(&options, OPT_D_MAC, &cs.arp.arp.invflags,
 				   invert);
 
 			if (getmac_and_mask(argv[optind - 1],
-			    cs.fw.arp.tgt_devaddr.addr, cs.fw.arp.tgt_devaddr.mask))
+			    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.fw.arp.invflags,
+			set_option(&options, OPT_H_LENGTH, &cs.arp.arp.invflags,
 				   invert);
-			getlength_and_mask(argv[optind - 1], &cs.fw.arp.arhln,
-					   &cs.fw.arp.arhln_mask);
+			getlength_and_mask(argv[optind - 1], &cs.arp.arp.arhln,
+					   &cs.arp.arp.arhln_mask);
 
-			if (cs.fw.arp.arhln != 6) {
+			if (cs.arp.arp.arhln != 6) {
 				xtables_error(PARAMETER_PROBLEM,
 					      "Only harware address length of"
 					      " 6 is supported currently.");
@@ -1142,20 +1108,20 @@
 			xtables_error(PARAMETER_PROBLEM, "not supported");
 /*
 			check_inverse(optarg, &invert, &optind, argc);
-			set_option(&options, OPT_P_LENGTH, &cs.fw.arp.invflags,
+			set_option(&options, OPT_P_LENGTH, &cs.arp.arp.invflags,
 				   invert);
 
-			getlength_and_mask(argv[optind - 1], &cs.fw.arp.arpln,
-					   &cs.fw.arp.arpln_mask);
+			getlength_and_mask(argv[optind - 1], &cs.arp.arp.arpln,
+					   &cs.arp.arp.arpln_mask);
 			break;
 */
 
 		case 4:/* opcode */
 			check_inverse(optarg, &invert, &optind, argc);
-			set_option(&options, OPT_OPCODE, &cs.fw.arp.invflags,
+			set_option(&options, OPT_OPCODE, &cs.arp.arp.invflags,
 				   invert);
-			if (get16_and_mask(argv[optind - 1], &cs.fw.arp.arpop,
-					   &cs.fw.arp.arpop_mask, 10)) {
+			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++)
@@ -1163,65 +1129,64 @@
 						break;
 				if (i == NUMOPCODES)
 					xtables_error(PARAMETER_PROBLEM, "Problem with specified opcode");
-				cs.fw.arp.arpop = htons(i+1);
+				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.fw.arp.invflags,
+			set_option(&options, OPT_H_TYPE, &cs.arp.arp.invflags,
 				   invert);
-			if (get16_and_mask(argv[optind - 1], &cs.fw.arp.arhrd,
-					   &cs.fw.arp.arhrd_mask, 16)) {
+			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.fw.arp.arhrd = htons(1);
+				cs.arp.arp.arhrd = htons(1);
 			}
 			break;
 
 		case 6:/* proto-type */
 			check_inverse(optarg, &invert, &optind, argc);
-			set_option(&options, OPT_P_TYPE, &cs.fw.arp.invflags,
+			set_option(&options, OPT_P_TYPE, &cs.arp.arp.invflags,
 				   invert);
-			if (get16_and_mask(argv[optind - 1], &cs.fw.arp.arpro,
-					   &cs.fw.arp.arpro_mask, 0)) {
+			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.fw.arp.arpro = htons(0x800);
+				cs.arp.arp.arpro = htons(0x800);
 			}
 			break;
 
 		case 'j':
-			set_option(&options, OPT_JUMP, &cs.fw.arp.invflags,
+			set_option(&options, OPT_JUMP, &cs.arp.arp.invflags,
 				   invert);
-			cs.jumpto = parse_target(optarg);
-			cs.target = command_jump(&cs.fw, cs.jumpto);
+			command_jump(&cs);
 			break;
 
 		case 'i':
 			check_inverse(optarg, &invert, &optind, argc);
-			set_option(&options, OPT_VIANAMEIN, &cs.fw.arp.invflags,
+			set_option(&options, OPT_VIANAMEIN, &cs.arp.arp.invflags,
 				   invert);
 			parse_interface(argv[optind-1],
-					cs.fw.arp.iniface,
-					cs.fw.arp.iniface_mask);
-/*			cs.fw.nfcache |= NFC_IP_IF_IN; */
+					cs.arp.arp.iniface,
+					cs.arp.arp.iniface_mask);
+/*			cs.arp.nfcache |= NFC_IP_IF_IN; */
 			break;
 
 		case 'o':
 			check_inverse(optarg, &invert, &optind, argc);
-			set_option(&options, OPT_VIANAMEOUT, &cs.fw.arp.invflags,
+			set_option(&options, OPT_VIANAMEOUT, &cs.arp.arp.invflags,
 				   invert);
 			parse_interface(argv[optind-1],
-					cs.fw.arp.outiface,
-					cs.fw.arp.outiface_mask);
-			/* cs.fw.nfcache |= NFC_IP_IF_OUT; */
+					cs.arp.arp.outiface,
+					cs.arp.arp.outiface_mask);
+			/* cs.arp.nfcache |= NFC_IP_IF_OUT; */
 			break;
 
 		case 'v':
 			if (!verbose)
 				set_option(&options, OPT_VERBOSE,
-					   &cs.fw.arp.invflags, invert);
+					   &cs.arp.arp.invflags, invert);
 			verbose++;
 			break;
 
@@ -1244,7 +1209,7 @@
 		break;
 
 		case 'n':
-			set_option(&options, OPT_NUMERIC, &cs.fw.arp.invflags,
+			set_option(&options, OPT_NUMERIC, &cs.arp.arp.invflags,
 				   invert);
 			break;
 
@@ -1264,7 +1229,7 @@
 			exit(0);
 
 		case '0':
-			set_option(&options, OPT_LINENUMBERS, &cs.fw.arp.invflags,
+			set_option(&options, OPT_LINENUMBERS, &cs.arp.arp.invflags,
 				   invert);
 			break;
 
@@ -1274,7 +1239,7 @@
 
 		case 'c':
 
-			set_option(&options, OPT_COUNTERS, &cs.fw.arp.invflags,
+			set_option(&options, OPT_COUNTERS, &cs.arp.arp.invflags,
 				   invert);
 			pcnt = optarg;
 			if (xs_has_arg(argc, argv))
@@ -1284,12 +1249,12 @@
 					      "-%c requires packet and byte counter",
 					      opt2char(OPT_COUNTERS));
 
-			if (sscanf(pcnt, "%llu", &cs.fw.counters.pcnt) != 1)
+			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.fw.counters.bcnt) != 1)
+			if (sscanf(bcnt, "%llu", &cs.arp.counters.bcnt) != 1)
 				xtables_error(PARAMETER_PROBLEM,
 					      "-%c byte counter not numeric",
 					      opt2char(OPT_COUNTERS));
@@ -1313,7 +1278,7 @@
 		default:
 			if (cs.target) {
 				xtables_option_tpcall(c, argv,
-						      invert, cs.target, &cs.fw);
+						      invert, cs.target, &cs.arp);
 			}
 			break;
 		}
@@ -1341,14 +1306,14 @@
 
 	if (shostnetworkmask)
 		parse_hostnetworkmask(shostnetworkmask, &saddrs,
-				      &(cs.fw.arp.smsk), &nsaddrs);
+				      &(cs.arp.arp.smsk), &nsaddrs);
 
 	if (dhostnetworkmask)
 		parse_hostnetworkmask(dhostnetworkmask, &daddrs,
-				      &(cs.fw.arp.tmsk), &ndaddrs);
+				      &(cs.arp.arp.tmsk), &ndaddrs);
 
 	if ((nsaddrs > 1 || ndaddrs > 1) &&
-	    (cs.fw.arp.invflags & (ARPT_INV_SRCIP | ARPT_INV_TGTIP)))
+	    (cs.arp.arp.invflags & (ARPT_INV_SRCIP | ARPT_INV_TGTIP)))
 		xtables_error(PARAMETER_PROBLEM, "! not allowed with multiple"
 				" source or destination IP addresses");
 
@@ -1363,14 +1328,6 @@
 				"chain name `%s' too long (must be under %i chars)",
 				chain, ARPT_FUNCTION_MAXNAMELEN);
 
-	if (nft_init(h, xtables_arp) < 0)
-		xtables_error(OTHER_PROBLEM,
-			      "Could not initialize nftables layer.");
-
-	h->ops = nft_family_ops_lookup(h->family);
-	if (h->ops == NULL)
-		xtables_error(PARAMETER_PROBLEM, "Unknown family");
-
 	if (command == CMD_APPEND
 	    || command == CMD_DELETE
 	    || command == CMD_INSERT
@@ -1394,17 +1351,6 @@
 						opt2char(OPT_VIANAMEIN),
 						chain);
 		}
-
-		if (!cs.target && strlen(cs.jumpto) != 0) {
-			size_t size;
-
-			cs.target = xtables_find_target(XT_STANDARD_TARGET,
-							XTF_LOAD_MUST_SUCCEED);
-			size = sizeof(struct arpt_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);
-		}
 	}
 
 	switch (command) {
@@ -1439,10 +1385,11 @@
 				   options&OPT_LINENUMBERS);
 		break;
 	case CMD_FLUSH:
-		ret = nft_rule_flush(h, chain, *table);
+		ret = nft_rule_flush(h, chain, *table, options & OPT_VERBOSE);
 		break;
 	case CMD_ZERO:
-		ret = nft_chain_zero_counters(h, chain, *table);
+		ret = nft_chain_zero_counters(h, chain, *table,
+					      options & OPT_VERBOSE);
 		break;
 	case CMD_LIST|CMD_ZERO:
 		ret = list_entries(h, chain, *table, rulenum,
@@ -1451,13 +1398,15 @@
 				   /*options&OPT_EXPANDED*/0,
 				   options&OPT_LINENUMBERS);
 		if (ret)
-			ret = nft_chain_zero_counters(h, chain, *table);
+			ret = nft_chain_zero_counters(h, chain, *table,
+						      options & OPT_VERBOSE);
 		break;
 	case CMD_NEW_CHAIN:
 		ret = nft_chain_user_add(h, chain, *table);
 		break;
 	case CMD_DELETE_CHAIN:
-		ret = nft_chain_user_del(h, chain, *table);
+		ret = nft_chain_user_del(h, chain, *table,
+					 options & OPT_VERBOSE);
 		break;
 	case CMD_RENAME_CHAIN:
 		ret = nft_chain_user_rename(h, chain, *table, newname);
@@ -1473,6 +1422,16 @@
 		exit_tryhelp(2);
 	}
 
+	if (nsaddrs)
+		free(saddrs);
+	if (ndaddrs)
+		free(daddrs);
+
+	if (cs.target)
+		free(cs.target->t);
+
+	xtables_free_opts(1);
+
 /*	if (verbose > 1)
 		dump_entries(*handle);*/
 
diff --git a/iptables/xtables-eb-standalone.c b/iptables/xtables-eb-standalone.c
index 914d137..84ce0b6 100644
--- a/iptables/xtables-eb-standalone.c
+++ b/iptables/xtables-eb-standalone.c
@@ -41,29 +41,15 @@
 
 #include "xtables-multi.h"
 
-extern struct xtables_globals ebtables_globals;
-
 int xtables_eb_main(int argc, char *argv[])
 {
 	int ret;
 	char *table = "filter";
-	struct nft_handle h = {
-		.family = NFPROTO_BRIDGE,
-	};
+	struct nft_handle h;
 
-	ebtables_globals.program_name = "ebtables";
-	ret = xtables_init_all(&ebtables_globals, NFPROTO_BRIDGE);
-	if (ret < 0) {
-		fprintf(stderr, "%s/%s Failed to initialize ebtables-compat\n",
-			ebtables_globals.program_name,
-			ebtables_globals.program_version);
-		exit(1);
-	}
+	nft_init_eb(&h, "ebtables");
 
-#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
-	init_extensionsb();
-#endif
-	ret = do_commandeb(&h, argc, argv, &table);
+	ret = do_commandeb(&h, argc, argv, &table, false);
 	if (ret)
 		ret = nft_commit(&h);
 
diff --git a/iptables/xtables-eb-translate.c b/iptables/xtables-eb-translate.c
index 42b88e3..f98c385 100644
--- a/iptables/xtables-eb-translate.c
+++ b/iptables/xtables-eb-translate.c
@@ -14,7 +14,6 @@
 
 #include <linux/netfilter_bridge.h>
 #include <linux/netfilter/nf_tables.h>
-#include <ebtables/ethernetdb.h>
 #include <libiptc/libxtc.h>
 
 #include "xshared.h"
@@ -130,72 +129,6 @@
 #define prog_name ebtables_globals.program_name
 #define prog_vers ebtables_globals.program_version
 
-#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;
-}
-
-/*
- * More glue code.
- */
-static struct xtables_target *command_jump(struct iptables_command_state *cs,
-					   const char *jumpto)
-{
-	struct xtables_target *target;
-	size_t size;
-
-	/* XTF_TRY_LOAD (may be chain name) */
-	target = xtables_find_target(jumpto, XTF_TRY_LOAD);
-
-	if (!target)
-		return NULL;
-
-	size = XT_ALIGN(sizeof(struct xt_entry_target))
-		+ target->size;
-
-	target->t = xtables_calloc(1, size);
-	target->t->u.target_size = size;
-	snprintf(target->t->u.user.name,
-		 sizeof(target->t->u.user.name), "%s", jumpto);
-	target->t->u.user.name[sizeof(target->t->u.user.name)-1] = '\0';
-	target->t->u.user.revision = target->revision;
-
-	xs_init_target(target);
-
-	opts = merge_options(opts, target->extra_opts, &target->option_offset);
-	if (opts == NULL)
-		xtables_error(OTHER_PROBLEM, "Can't alloc memory");
-
-	return target;
-}
-
 static void print_help(void)
 {
 	fprintf(stderr, "%s: Translate ebtables command to nft syntax\n"
@@ -286,9 +219,10 @@
 	int rule_nr_end = 0;
 	int ret = 0;
 	unsigned int flags = 0;
-	struct xtables_target *t, *w;
-	struct xtables_match *m;
-	struct iptables_command_state cs;
+	struct iptables_command_state cs = {
+		.argv		= argv,
+		.eb.bitmask	= EBT_NOPROTO,
+	};
 	char command = 'h';
 	const char *chain = NULL;
 	int exec_style = EXEC_STYLE_PRG;
@@ -299,36 +233,8 @@
 		.table          = *table,
         };
 
-	memset(&cs, 0, sizeof(cs));
-	cs.argv = argv;
-
-	if (nft_init(h, xtables_bridge) < 0)
-		xtables_error(OTHER_PROBLEM,
-			      "Could not initialize nftables layer.");
-
-	h->ops = nft_family_ops_lookup(h->family);
-	if (h->ops == NULL)
-		xtables_error(PARAMETER_PROBLEM, "Unknown family");
-
-	/* manually registering ebt matches, given the original ebtables parser
-	 * don't use '-m matchname' and the match can't loaded dinamically when
-	 * the user calls it.
-	 */
-	ebt_load_match_extensions();
-
-	/* clear mflags in case do_commandeb 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;
-	}
-
 	/* prevent getopt to spoil our error reporting */
 	opterr = false;
-	cs.eb.bitmask = EBT_NOPROTO;
 
 	printf("nft ");
 	/* Getopt saves the day */
@@ -425,7 +331,6 @@
 			if (OPT_COMMANDS)
 				xtables_error(PARAMETER_PROBLEM,
 					      "Multiple commands are not allowed");
-			command = 'V';
 			if (exec_style == EXEC_STYLE_DAEMON)
 				xtables_error(PARAMETER_PROBLEM,
 					      "%s %s\n", prog_name, prog_vers);
@@ -507,7 +412,7 @@
 			} else if (c == 'j') {
 				ebt_check_option2(&flags, OPT_JUMP);
 				cs.jumpto = parse_target(optarg);
-				cs.target = command_jump(&cs, cs.jumpto);
+				cs.target = ebt_command_jump(cs.jumpto);
 				break;
 			} else if (c == 's') {
 				ebt_check_option2(&flags, OPT_SOURCE);
@@ -559,16 +464,16 @@
 				xtables_error(PARAMETER_PROBLEM,
 					      "Problem with the specified protocol");
 			if (*buffer != '\0') {
-				struct ethertypeent *ent;
+				struct xt_ethertypeent *ent;
 
 				if (!strcasecmp(optarg, "LENGTH")) {
 					cs.eb.bitmask |= EBT_802_3;
 					break;
 				}
-				ent = getethertypebyname(optarg);
+				ent = xtables_getethertypebyname(optarg);
 				if (!ent)
 					xtables_error(PARAMETER_PROBLEM,
-						      "Problem with the specified Ethernet protocol '%s', perhaps "_PATH_ETHERTYPES " is missing", optarg);
+						      "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;
@@ -621,34 +526,13 @@
 			optind--;
 			continue;
 		default:
-			/* Is it a target option? */
-			if (cs.target != NULL && cs.target->parse != NULL) {
-				int opt_offset = cs.target->option_offset;
-				if (cs.target->parse(c - opt_offset,
-						     argv, ebt_invert,
-						     &cs.target->tflags,
-						     NULL, &cs.target->t))
-					goto check_extension;
-			}
+			ebt_check_inverse2(optarg, argc, argv);
 
-			/* Is it a match_option? */
-			for (m = xtables_matches; m; m = m->next) {
-				if (m->parse(c - m->option_offset, argv, ebt_check_inverse2(optarg, argc, argv), &m->mflags, NULL, &m->m)) {
-					ebt_add_match(m, &cs);
-					goto check_extension;
-				}
-			}
+			if (ebt_command_default(&cs))
+				xtables_error(PARAMETER_PROBLEM,
+					      "Unknown argument: '%s'",
+					      argv[optind - 1]);
 
-			/* Is it a watcher option? */
-			for (w = xtables_targets; w; w = w->next) {
-				if (w->parse(c - w->option_offset, argv,
-					     ebt_invert, &w->tflags,
-					     NULL, &w->t)) {
-					ebt_add_watcher(w, &cs);
-					goto check_extension;
-				}
-			}
-check_extension:
 			if (command != 'A' && command != 'I' &&
 			    command != 'D')
 				xtables_error(PARAMETER_PROBLEM,
@@ -700,20 +584,11 @@
 {
 	int ret;
 	char *table = "filter";
-	struct nft_handle h = {
-		.family = NFPROTO_BRIDGE,
-	};
+	struct nft_handle h;
 
-	ebtables_globals.program_name = argv[0];
-	ret = xtables_init_all(&ebtables_globals, NFPROTO_BRIDGE);
-	if (ret < 0) {
-		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
-			ebtables_globals.program_name,
-			ebtables_globals.program_version);
-		exit(EXIT_FAILURE);
-	}
-
+	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");
diff --git a/iptables/xtables-eb.c b/iptables/xtables-eb.c
index 2f27656..64f332c 100644
--- a/iptables/xtables-eb.c
+++ b/iptables/xtables-eb.c
@@ -37,7 +37,6 @@
 
 #include <linux/netfilter_bridge.h>
 #include <linux/netfilter/nf_tables.h>
-#include <ebtables/ethernetdb.h>
 #include <libiptc/libxtc.h>
 #include "xshared.h"
 #include "nft.h"
@@ -203,7 +202,7 @@
 	return ret;
 }
 
-static int get_current_chain(const char *chain)
+int ebt_get_current_chain(const char *chain)
 {
 	if (!chain)
 		return -1;
@@ -380,33 +379,22 @@
 /*
  * More glue code.
  */
-static struct xtables_target *command_jump(struct iptables_command_state *cs,
-					   const char *jumpto)
+struct xtables_target *ebt_command_jump(const char *jumpto)
 {
 	struct xtables_target *target;
-	size_t size;
+	unsigned int verdict;
 
-	/* XTF_TRY_LOAD (may be chain name) */
-	target = xtables_find_target(jumpto, XTF_TRY_LOAD);
+	/* Standard target? */
+	if (!ebt_fill_target(jumpto, &verdict))
+		jumpto = "standard";
 
-	if (!target)
-		return NULL;
-
-	size = XT_ALIGN(sizeof(struct xt_entry_target))
-		+ target->size;
-
-	target->t = xtables_calloc(1, size);
-	target->t->u.target_size = size;
-	snprintf(target->t->u.user.name,
-		 sizeof(target->t->u.user.name), "%s", jumpto);
-	target->t->u.user.name[sizeof(target->t->u.user.name)-1] = '\0';
-	target->t->u.user.revision = target->revision;
-
-	xs_init_target(target);
-
-	opts = merge_options(opts, target->extra_opts, &target->option_offset);
-	if (opts == NULL)
-		xtables_error(OTHER_PROBLEM, "Can't alloc memory");
+	/* For ebtables, all targets are preloaded. Hence it is either in
+	 * xtables_targets or a custom chain to jump to, in which case
+	 * returning NULL is fine. */
+	for (target = xtables_targets; target; target = target->next) {
+		if (!strcmp(target->name, jumpto))
+			break;
+	}
 
 	return target;
 }
@@ -668,29 +656,30 @@
 	ebt_load_target("dnat");
 	ebt_load_target("snat");
 	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 *i, **rule_matches = &cs->matches;
+	struct xtables_rule_match **rule_matches = &cs->matches;
 	struct xtables_match *newm;
-	struct ebt_match *newnode;
-
-	/* match already in rule_matches, skip inclusion */
-	for (i = *rule_matches; i; i = i->next) {
-		if (strcmp(m->name, i->match->name) == 0) {
-			i->match->mflags |= m->mflags;
-			return;
-		}
-	}
+	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));
@@ -700,51 +689,143 @@
 	newnode->ismatch = true;
 	newnode->u.match = newm;
 
-	if (cs->match_list == NULL)
-		cs->match_list = newnode;
-	else
-		cs->match_list->next = newnode;
+	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 *i, *newnode;
+	struct ebt_match *newnode, **matchp;
+	struct xtables_target *clone;
 
-	for (i = cs->match_list; i; i = i->next) {
-		if (i->ismatch)
-			continue;
-		if (strcmp(i->u.watcher->name, watcher->name) == 0) {
-			i->u.watcher->tflags |= watcher->tflags;
-			return;
-		}
-	}
+	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 = watcher;
+	newnode->u.watcher = clone;
 
-	if (cs->match_list == NULL)
-		cs->match_list = newnode;
-	else
-		cs->match_list->next = newnode;
+	for (matchp = &cs->match_list; *matchp; matchp = &(*matchp)->next)
+		;
+	*matchp = newnode;
 }
 
-int do_commandeb(struct nft_handle *h, int argc, char *argv[], char **table)
+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
+
+	memset(h, 0, sizeof(*h));
+
+	h->family = NFPROTO_BRIDGE;
+
+	if (nft_init(h, xtables_bridge) < 0)
+		xtables_error(OTHER_PROBLEM,
+			      "Could not initialize nftables layer.");
+	h->ops = nft_family_ops_lookup(h->family);
+	if (!h->ops)
+		xtables_error(PARAMETER_PROBLEM, "Unknown family");
+
+	/* 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;
+}
+
+int do_commandeb(struct nft_handle *h, int argc, char *argv[], char **table,
+		 bool restore)
 {
 	char *buffer;
 	int c, i;
-	int zerochain = -1; /* Needed for the -Z option (we can have -Z <this> -L <that>) */
 	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, *w;
-	struct xtables_match *m;
-	struct iptables_command_state cs;
+	struct xtables_target *t;
+	struct iptables_command_state cs = {
+		.argv = argv,
+		.eb.bitmask = EBT_NOPROTO,
+	};
 	char command = 'h';
 	const char *chain = NULL;
 	const char *policy = NULL;
@@ -752,36 +833,9 @@
 	struct xtables_rule_match *xtrm_i;
 	struct ebt_match *match;
 
-	memset(&cs, 0, sizeof(cs));
-	cs.argv = argv;
-
-	if (nft_init(h, xtables_bridge) < 0)
-		xtables_error(OTHER_PROBLEM,
-			      "Could not initialize nftables layer.");
-
-	h->ops = nft_family_ops_lookup(h->family);
-	if (h->ops == NULL)
-		xtables_error(PARAMETER_PROBLEM, "Unknown family");
-
-	/* manually registering ebt matches, given the original ebtables parser
-	 * don't use '-m matchname' and the match can't loaded dinamically when
-	 * the user calls it.
-	 */
-	ebt_load_match_extensions();
-
-	/* clear mflags in case do_commandeb 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;
-	}
-
 	/* prevent getopt to spoil our error reporting */
+	optind = 0;
 	opterr = false;
-	cs.eb.bitmask = EBT_NOPROTO;
 
 	/* Getopt saves the day */
 	while ((c = getopt_long(argc, argv,
@@ -813,7 +867,7 @@
 			if (optarg && (optarg[0] == '-' || !strcmp(optarg, "!")))
 				xtables_error(PARAMETER_PROBLEM, "No chain name specified");
 			chain = optarg;
-			selected_chain = get_current_chain(chain);
+			selected_chain = ebt_get_current_chain(chain);
 			flags |= OPT_COMMAND;
 
 			if (c == 'N') {
@@ -825,7 +879,7 @@
 					chain = argv[optind];
 					optind++;
 				}
-				ret = nft_chain_user_del(h, chain, *table);
+				ret = nft_chain_user_del(h, chain, *table, 0);
 				break;
 			}
 
@@ -912,7 +966,6 @@
 			if (OPT_COMMANDS)
 				xtables_error(PARAMETER_PROBLEM,
 					      "Multiple commands are not allowed");
-			command = 'V';
 			printf("%s %s (nf_tables)\n", prog_name, prog_vers);
 			exit(0);
 		case 'h': /* Help */
@@ -1014,7 +1067,7 @@
 			} else if (c == 'j') {
 				ebt_check_option2(&flags, OPT_JUMP);
 				cs.jumpto = parse_target(optarg);
-				cs.target = command_jump(&cs, cs.jumpto);
+				cs.target = ebt_command_jump(cs.jumpto);
 				break;
 			} else if (c == 's') {
 				ebt_check_option2(&flags, OPT_SOURCE);
@@ -1066,16 +1119,16 @@
 				xtables_error(PARAMETER_PROBLEM,
 					      "Problem with the specified protocol");
 			if (*buffer != '\0') {
-				struct ethertypeent *ent;
+				struct xt_ethertypeent *ent;
 
 				if (!strcasecmp(optarg, "LENGTH")) {
 					cs.eb.bitmask |= EBT_802_3;
 					break;
 				}
-				ent = getethertypebyname(optarg);
+				ent = xtables_getethertypebyname(optarg);
 				if (!ent)
 					xtables_error(PARAMETER_PROBLEM,
-						      "Problem with the specified Ethernet protocol '%s', perhaps "_PATH_ETHERTYPES " is missing", optarg);
+						      "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;
@@ -1138,7 +1191,9 @@
 			break;*/
 		/*case 7 :*/ /* atomic-init */
 		/*case 10:*/ /* atomic-save */
-		/*case 11:*/ /* init-table */
+		case 11: /* init-table */
+			nft_table_flush(h, *table);
+			return 1;
 		/*
 			replace->command = c;
 			if (OPT_COMMANDS)
@@ -1176,49 +1231,13 @@
 			optind--;
 			continue;
 		default:
-			/* Is it a target option? */
-			if (cs.target != NULL && cs.target->parse != NULL) {
-				int opt_offset = cs.target->option_offset;
-				if (cs.target->parse(c - opt_offset,
-						     argv, ebt_invert,
-						     &cs.target->tflags,
-						     NULL, &cs.target->t))
-					goto check_extension;
-			}
+			ebt_check_inverse2(optarg, argc, argv);
 
-			/* Is it a match_option? */
-			for (m = xtables_matches; m; m = m->next) {
-				if (m->parse(c - m->option_offset, argv, ebt_check_inverse2(optarg, argc, argv), &m->mflags, NULL, &m->m)) {
-					ebt_add_match(m, &cs);
-					goto check_extension;
-				}
-			}
+			if (ebt_command_default(&cs))
+				xtables_error(PARAMETER_PROBLEM,
+					      "Unknown argument: '%s'",
+					      argv[optind - 1]);
 
-			/* Is it a watcher option? */
-			for (w = xtables_targets; w; w = w->next) {
-				if (w->parse(c - w->option_offset, argv,
-					     ebt_invert, &w->tflags,
-					     NULL, &w->t)) {
-					ebt_add_watcher(w, &cs);
-					goto check_extension;
-				}
-			}
-			/*
-			if (w == NULL && c == '?')
-				ebt_print_error2("Unknown argument: '%s'", argv[optind - 1], (char)optopt, (char)c);
-			else if (w == NULL) {
-				if (!strcmp(t->name, "standard"))
-					ebt_print_error2("Unknown argument: don't forget the -t option");
-				else
-					ebt_print_error2("Target-specific option does not correspond with specified target");
-			}
-			if (ebt_errormsg[0] != '\0')
-				return -1;
-			if (w->used == 0) {
-				ebt_add_watcher(new_entry, w);
-				w->used = 1;
-			}*/
-check_extension:
 			if (command != 'A' && command != 'I' &&
 			    command != 'D' && command != 'C')
 				xtables_error(PARAMETER_PROBLEM,
@@ -1274,28 +1293,24 @@
 			xtables_error(PARAMETER_PROBLEM, "Wrong policy");
 	} else if (command == 'L') {
 		ret = list_rules(h, chain, *table, rule_nr,
-				 flags&OPT_VERBOSE,
-				 flags&OPT_NUMERIC,
+				 0,
+				 0,
 				 /*flags&OPT_EXPANDED*/0,
 				 flags&LIST_N,
 				 flags&LIST_C);
-		if (!(flags & OPT_ZERO))
-			exit(0);
 	}
 	if (flags & OPT_ZERO) {
-		selected_chain = zerochain;
-		ret = nft_chain_zero_counters(h, chain, *table);
+		ret = nft_chain_zero_counters(h, chain, *table, 0);
 	} else if (command == 'F') {
-		ret = nft_rule_flush(h, chain, *table);
+		ret = nft_rule_flush(h, chain, *table, 0);
 	} else if (command == 'A') {
-		ret = append_entry(h, chain, *table, &cs, 0,
-				   flags&OPT_VERBOSE, true);
+		ret = append_entry(h, chain, *table, &cs, 0, 0, true);
 	} else if (command == 'I') {
 		ret = append_entry(h, chain, *table, &cs, rule_nr - 1,
-				   flags&OPT_VERBOSE, false);
+				   0, false);
 	} else if (command == 'D') {
 		ret = delete_entry(h, chain, *table, &cs, rule_nr - 1,
-				   rule_nr_end, flags&OPT_VERBOSE);
+				   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')
diff --git a/iptables/xtables-monitor.8.in b/iptables/xtables-monitor.8.in
index 202842b..b647a79 100644
--- a/iptables/xtables-monitor.8.in
+++ b/iptables/xtables-monitor.8.in
@@ -25,10 +25,10 @@
 using the TRACE target.
 .TP
 \fB\-4\fP
-Restrict output to ipv4.
+Restrict output to IPv4.
 .TP
 \fB\-6\fP
-Restrict output to ipv6.
+Restrict output to IPv6.
 .SH EXAMPLE OUTPUT
 .TP
 .B xtables-monitor \-\-trace
@@ -55,10 +55,10 @@
 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 hown by line six.
+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 usally a good idea to only select packets
+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
diff --git a/iptables/xtables-monitor.c b/iptables/xtables-monitor.c
index 08292a7..3b1ca77 100644
--- a/iptables/xtables-monitor.c
+++ b/iptables/xtables-monitor.c
@@ -73,12 +73,9 @@
 
 static int rule_cb(const struct nlmsghdr *nlh, void *data)
 {
-	struct arptables_command_state cs_arp = {};
-	struct iptables_command_state cs = {};
 	uint32_t type = nlh->nlmsg_type & 0xFF;
 	const struct cb_arg *arg = data;
 	struct nftnl_rule *r;
-	void *fw = NULL;
 	uint8_t family;
 
 	r = nftnl_rule_alloc();
@@ -98,21 +95,16 @@
 	case AF_INET:
 	case AF_INET6:
 		printf("-%c ", family == AF_INET ? '4' : '6');
-		nft_rule_to_iptables_command_state(r, &cs);
-		fw = &cs;
 		break;
 	case NFPROTO_ARP:
 		printf("-0 ");
-		nft_rule_to_arptables_command_state(r, &cs_arp);
-		fw = &cs_arp;
 		break;
 	default:
 		goto err_free;
 	}
 
 	printf("-t %s ", nftnl_rule_get_str(r, NFTNL_RULE_TABLE));
-	nft_rule_print_save(fw, r,
-			    type == NFT_MSG_NEWRULE ? NFT_RULE_APPEND :
+	nft_rule_print_save(r, type == NFT_MSG_NEWRULE ? NFT_RULE_APPEND :
 							   NFT_RULE_DEL,
 			    counters ? 0 : FMT_NOCOUNTS);
 err_free:
@@ -576,6 +568,7 @@
 	{.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},
 };
 
@@ -585,10 +578,10 @@
 			  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  taht modify the ruleset\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"
+	       "        --ipv4     -4    only monitor IPv4\n"
+	       "        --ipv6     -6    only monitor IPv6\n"
 	       "	--counters -c    show counters in rules\n"
 
 	       , xtables_globals.program_name);
@@ -600,7 +593,7 @@
 	struct mnl_socket *nl;
 	char buf[MNL_SOCKET_BUFFER_SIZE];
 	uint32_t nfgroup = 0;
-	struct cb_arg cb_arg;
+	struct cb_arg cb_arg = {};
 	int ret, c;
 
 	xtables_globals.program_name = "xtables-monitor";
@@ -617,7 +610,6 @@
 	init_extensions4();
 #endif
 
-	memset(&cb_arg, 0, sizeof(cb_arg));
 	opterr = 0;
 	while ((c = getopt_long(argc, argv, "ceht46V", options, NULL)) != -1) {
 		switch (c) {
@@ -644,7 +636,7 @@
 			exit(0);
 		default:
 			fprintf(stderr, "xtables-monitor %s: Bad argument.\n", IPTABLES_VERSION);
-			fprintf(stderr, "Try `xtables-monitor -h' for more information.");
+			fprintf(stderr, "Try `xtables-monitor -h' for more information.\n");
 			exit(PARAMETER_PROBLEM);
 		}
 	}
diff --git a/iptables/xtables-multi.h b/iptables/xtables-multi.h
index 82ee9c9..0fedb43 100644
--- a/iptables/xtables-multi.h
+++ b/iptables/xtables-multi.h
@@ -15,7 +15,11 @@
 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
diff --git a/iptables/xtables-nft-multi.c b/iptables/xtables-nft-multi.c
index 187da81..e2b7c64 100644
--- a/iptables/xtables-nft-multi.c
+++ b/iptables/xtables-nft-multi.c
@@ -31,8 +31,18 @@
 	{"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},
 };
diff --git a/iptables/xtables-restore.c b/iptables/xtables-restore.c
index f127093..d9faa4d 100644
--- a/iptables/xtables-restore.c
+++ b/iptables/xtables-restore.c
@@ -16,14 +16,9 @@
 #include "libiptc/libiptc.h"
 #include "xtables-multi.h"
 #include "nft.h"
+#include "nft-bridge.h"
 #include <libnftnl/chain.h>
 
-#ifdef DEBUG
-#define DEBUGP(x, args...) fprintf(stderr, x, ## args)
-#else
-#define DEBUGP(x, args...)
-#endif
-
 static int counters, verbose, noflush;
 
 /* Keeping track of external matches and targets.  */
@@ -61,110 +56,6 @@
 			"	   [ --ipv6 ]\n", name);
 }
 
-static int parse_counters(char *string, struct xt_counters *ctr)
-{
-	unsigned long long pcnt, bcnt;
-	int ret;
-
-	ret = sscanf(string, "[%llu:%llu]", &pcnt, &bcnt);
-	ctr->pcnt = pcnt;
-	ctr->bcnt = bcnt;
-	return ret == 2;
-}
-
-/* global new argv and argc */
-static char *newargv[255];
-static int newargc;
-
-/* function adding one argument to newargv, updating newargc 
- * returns true if argument added, false otherwise */
-static int add_argv(char *what) {
-	DEBUGP("add_argv: %s\n", what);
-	if (what && newargc + 1 < ARRAY_SIZE(newargv)) {
-		newargv[newargc] = strdup(what);
-		newargv[++newargc] = NULL;
-		return 1;
-	} else {
-		xtables_error(PARAMETER_PROBLEM,
-			"Parser cannot handle more arguments\n");
-		return 0;
-	}
-}
-
-static void free_argv(void) {
-	int i;
-
-	for (i = 0; i < newargc; i++)
-		free(newargv[i]);
-}
-
-static void add_param_to_argv(char *parsestart)
-{
-	int quote_open = 0, escaped = 0, param_len = 0;
-	char param_buffer[1024], *curchar;
-
-	/* After fighting with strtok enough, here's now
-	 * a 'real' parser. According to Rusty I'm now no
-	 * longer a real hacker, but I can live with that */
-
-	for (curchar = parsestart; *curchar; curchar++) {
-		if (quote_open) {
-			if (escaped) {
-				param_buffer[param_len++] = *curchar;
-				escaped = 0;
-				continue;
-			} else if (*curchar == '\\') {
-				escaped = 1;
-				continue;
-			} else if (*curchar == '"') {
-				quote_open = 0;
-				*curchar = ' ';
-			} else {
-				param_buffer[param_len++] = *curchar;
-				continue;
-			}
-		} else {
-			if (*curchar == '"') {
-				quote_open = 1;
-				continue;
-			}
-		}
-
-		if (*curchar == ' '
-		    || *curchar == '\t'
-		    || * curchar == '\n') {
-			if (!param_len) {
-				/* two spaces? */
-				continue;
-			}
-
-			param_buffer[param_len] = '\0';
-
-			/* check if table name specified */
-			if ((param_buffer[0] == '-' &&
-			     param_buffer[1] != '-' &&
-			     strchr(param_buffer, 't')) ||
-			    (!strncmp(param_buffer, "--t", 3) &&
-			     !strncmp(param_buffer, "--table", strlen(param_buffer)))) {
-				xtables_error(PARAMETER_PROBLEM,
-				"The -t option (seen in line %u) cannot be "
-				"used in xtables-restore.\n", line);
-				exit(1);
-			}
-
-			add_argv(param_buffer);
-			param_len = 0;
-		} else {
-			/* regular character, copy to buffer */
-			param_buffer[param_len++] = *curchar;
-
-			if (param_len >= sizeof(param_buffer))
-				xtables_error(PARAMETER_PROBLEM,
-				   "Parameter too long!");
-		}
-	}
-}
-
 static struct nftnl_chain_list *get_chain_list(struct nft_handle *h)
 {
 	struct nftnl_chain_list *chain_list;
@@ -215,7 +106,7 @@
 {
 	char buffer[10240];
 	int in_table = 0;
-	char curtable[XT_TABLE_MAXNAMELEN + 1];
+	struct builtin_table *curtable = NULL;
 	const struct xtc_ops *ops = &xtc_ops;
 	struct nftnl_chain_list *chain_list = NULL;
 
@@ -253,7 +144,7 @@
 			}
 			in_table = 0;
 
-		} else if ((buffer[0] == '*') && (!in_table)) {
+		} else if ((buffer[0] == '*') && (!in_table || !p->commit)) {
 			/* New table */
 			char *table;
 
@@ -265,8 +156,11 @@
 					xt_params->program_name, line);
 				exit(1);
 			}
-			strncpy(curtable, table, XT_TABLE_MAXNAMELEN);
-			curtable[XT_TABLE_MAXNAMELEN] = '\0';
+			curtable = nft_table_builtin_find(h, table);
+			if (!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))
 				continue;
@@ -288,6 +182,7 @@
 			/* New chain. */
 			char *policy, *chain = NULL;
 			struct xt_counters count = {};
+			bool chain_exists = false;
 
 			chain = strtok(buffer+1, " \t\n");
 			DEBUGP("line %u, chain '%s'\n", line, chain);
@@ -300,16 +195,18 @@
 
 			if (noflush == 0) {
 				if (cb->chain_del)
-					cb->chain_del(chain_list, curtable,
+					cb->chain_del(chain_list, curtable->name,
 						      chain);
-			} else {
+			} else if (nft_chain_list_find(chain_list,
+						       curtable->name, chain)) {
+				chain_exists = true;
 				/* Apparently -n still flushes existing user
 				 * defined chains that are redefined. Otherwise,
 				 * leave them as is.
 				 */
 				if (cb->chain_user_flush)
 					cb->chain_user_flush(h, chain_list,
-							     curtable, chain);
+							     curtable->name, chain);
 			}
 
 			if (strlen(chain) >= XT_EXTENSION_MAXNAMELEN)
@@ -327,7 +224,7 @@
 				exit(1);
 			}
 
-			if (strcmp(policy, "-") != 0) {
+			if (nft_chain_builtin_find(curtable, chain)) {
 				if (counters) {
 					char *ctrs;
 					ctrs = strtok(NULL, " \t\n");
@@ -339,7 +236,8 @@
 
 				}
 				if (cb->chain_set &&
-				    cb->chain_set(h, curtable, chain, policy, &count) < 0) {
+				    cb->chain_set(h, curtable->name,
+					          chain, policy, &count) < 0) {
 					xtables_error(OTHER_PROBLEM,
 						      "Can't set policy `%s'"
 						      " on `%s' line %u: %s\n",
@@ -351,8 +249,10 @@
 				ret = 1;
 
 			} else {
-				if (cb->chain_user_add &&
-				    cb->chain_user_add(h, chain, curtable) < 0) {
+				if (!chain_exists &&
+				    cb->chain_user_add &&
+				    cb->chain_user_add(h, chain,
+						       curtable->name) < 0) {
 					if (errno == EEXIST)
 						continue;
 
@@ -366,7 +266,6 @@
 
 		} else if (in_table) {
 			int a;
-			char *ptr = buffer;
 			char *pcnt = NULL;
 			char *bcnt = NULL;
 			char *parsestart;
@@ -376,7 +275,8 @@
 
 			if (buffer[0] == '[') {
 				/* we have counters in our input */
-				ptr = strchr(buffer, ']');
+				char *ptr = strchr(buffer, ']');
+
 				if (!ptr)
 					xtables_error(PARAMETER_PROBLEM,
 						   "Bad line %u: need ]\n",
@@ -401,20 +301,20 @@
 				parsestart = buffer;
 			}
 
-			add_argv(argv[0]);
-			add_argv("-t");
-			add_argv(curtable);
+			add_argv(argv[0], 0);
+			add_argv("-t", 0);
+			add_argv(curtable->name, 0);
 
 			if (counters && pcnt && bcnt) {
-				add_argv("--set-counters");
-				add_argv((char *) pcnt);
-				add_argv((char *) bcnt);
+				add_argv("--set-counters", 0);
+				add_argv((char *) pcnt, 0);
+				add_argv((char *) bcnt, 0);
 			}
 
-			add_param_to_argv(parsestart);
+			add_param_to_argv(parsestart, line);
 
 			DEBUGP("calling do_command4(%u, argv, &%s, handle):\n",
-				newargc, curtable);
+				newargc, curtable->name);
 
 			for (a = 0; a < newargc; a++)
 				DEBUGP("argv[%u]: %s\n", a, newargv[a]);
@@ -437,7 +337,8 @@
 			free_argv();
 			fflush(stdout);
 		}
-		if (p->tablename && (strcmp(p->tablename, curtable) != 0))
+		if (p->tablename && curtable &&
+		    (strcmp(p->tablename, curtable->name) != 0))
 			continue;
 		if (!ret) {
 			fprintf(stderr, "%s: line %u failed\n",
@@ -445,10 +346,13 @@
 			exit(1);
 		}
 	}
-	if (in_table) {
+	if (in_table && p->commit) {
 		fprintf(stderr, "%s: COMMIT expected at line %u\n",
 				xt_params->program_name, line + 1);
 		exit(1);
+	} else if (in_table && cb->commit && !cb->commit(h)) {
+		xtables_error(OTHER_PROBLEM, "%s: final implicit COMMIT failed",
+			      xt_params->program_name);
 	}
 }
 
@@ -461,7 +365,9 @@
 		.restore = true,
 	};
 	int c;
-	struct nft_xt_restore_parse p = {};
+	struct nft_xt_restore_parse p = {
+		.commit = true,
+	};
 
 	line = 0;
 
@@ -513,6 +419,8 @@
 				break;
 			case 'w': /* fallthrough.  Ignored by xt-restore */
 			case 'W':
+				if (!optarg && xs_has_arg(argc, argv))
+					optind++;
 				break;
 			default:
 				fprintf(stderr,
@@ -581,3 +489,75 @@
 	return xtables_restore_main(NFPROTO_IPV6, "ip6tables-restore",
 				    argc, argv);
 }
+
+struct nft_xt_restore_cb ebt_restore_cb = {
+	.chain_list	= get_chain_list,
+	.commit		= nft_commit,
+	.table_new	= nft_table_new,
+	.table_flush	= nft_table_flush,
+	.chain_user_flush = nft_chain_user_flush,
+	.chain_del	= chain_delete,
+	.do_command	= do_commandeb,
+	.chain_set	= nft_chain_set,
+	.chain_user_add	= nft_chain_user_add,
+};
+
+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,
+	};
+	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");
+	xtables_restore_parse(&h, &p, &ebt_restore_cb, argc, argv);
+	nft_fini(&h);
+
+	return 0;
+}
+
+struct nft_xt_restore_cb arp_restore_cb = {
+	.chain_list	= get_chain_list,
+	.commit		= nft_commit,
+	.table_new	= nft_table_new,
+	.table_flush	= nft_table_flush,
+	.chain_user_flush = nft_chain_user_flush,
+	.chain_del	= chain_delete,
+	.do_command	= do_commandarp,
+	.chain_set	= nft_chain_set,
+	.chain_user_add	= nft_chain_user_add,
+};
+
+int xtables_arp_restore_main(int argc, char *argv[])
+{
+	struct nft_xt_restore_parse p = {
+		.in = stdin,
+	};
+	struct nft_handle h;
+
+	nft_init_arp(&h, "arptables-restore");
+	xtables_restore_parse(&h, &p, &arp_restore_cb, argc, argv);
+	nft_fini(&h);
+
+	return 0;
+}
diff --git a/iptables/xtables-save.c b/iptables/xtables-save.c
index c19c999..53ce4b8 100644
--- a/iptables/xtables-save.c
+++ b/iptables/xtables-save.c
@@ -49,13 +49,10 @@
 	struct nftnl_chain_list *chain_list;
 
 
-	if (!nft_table_find(h, tablename)) {
-		printf("Table `%s' does not exist\n", tablename);
-		return 1;
-	}
-
 	if (!nft_is_table_compatible(h, tablename)) {
-		printf("# Table `%s' is incompatible, use 'nft' tool.\n", tablename);
+		if (!nft_table_builtin_find(h, tablename))
+			printf("# Table `%s' is incompatible, use 'nft' tool.\n",
+			       tablename);
 		return 0;
 	}
 
@@ -70,7 +67,7 @@
 	/* Dump out chain names first,
 	 * thereby preventing dependency conflicts */
 	nft_chain_save(h, chain_list, tablename);
-	nft_rule_save(h, tablename, counters);
+	nft_rule_save(h, tablename, counters ? 0 : FMT_NOCOUNTS);
 
 	now = time(NULL);
 	printf("COMMIT\n");
@@ -89,6 +86,11 @@
 		return !!ret;
 	}
 
+	if (!nft_table_find(h, tablename)) {
+		printf("Table `%s' does not exist\n", tablename);
+		return 1;
+	}
+
 	ret = __do_output(h, tablename, counters);
 	nft_check_xt_legacy(h->family, true);
 	return ret;
@@ -203,12 +205,12 @@
 		exit(EXIT_FAILURE);
 	}
 
-	if (dump) {
-		do_output(&h, tablename, show_counters);
+	ret = do_output(&h, tablename, show_counters);
+	nft_fini(&h);
+	if (dump)
 		exit(0);
-	}
 
-	return do_output(&h, tablename, show_counters);
+	return ret;
 }
 
 int xtables_ip4_save_main(int argc, char *argv[])
@@ -220,3 +222,111 @@
 {
 	return xtables_save_main(NFPROTO_IPV6, "ip6tables-save", argc, argv);
 }
+
+static int __ebt_save(struct nft_handle *h, const char *tablename, bool counters)
+{
+	struct nftnl_chain_list *chain_list;
+	static bool first = true;
+	time_t now;
+
+	if (!nft_table_find(h, tablename)) {
+		printf("Table `%s' does not exist\n", tablename);
+		return 1;
+	}
+
+	if (!nft_is_table_compatible(h, tablename)) {
+		printf("# Table `%s' is incompatible, use 'nft' tool.\n", tablename);
+		return 0;
+	}
+
+	chain_list = nft_chain_dump(h);
+
+	if (first) {
+		now = time(NULL);
+		printf("# Generated by ebtables-save v%s on %s",
+		       IPTABLES_VERSION, ctime(&now));
+		first = false;
+	}
+	printf("*%s\n", tablename);
+
+	/* Dump out chain names first,
+	 * thereby preventing dependency conflicts */
+	nft_chain_save(h, chain_list, tablename);
+	nft_rule_save(h, tablename,
+		      FMT_EBT_SAVE | (counters ? 0 : FMT_NOCOUNTS));
+	printf("\n");
+	return 0;
+}
+
+int xtables_eb_save_main(int argc_, char *argv_[])
+{
+	const char *ctr = getenv("EBTABLES_SAVE_COUNTER");
+	struct nft_handle h = {
+		.family	= NFPROTO_BRIDGE,
+	};
+	int c;
+
+	if (ctr && strcmp(ctr, "yes"))
+		ctr = NULL;
+
+	xtables_globals.program_name = "ebtables-save";
+	c = xtables_init_all(&xtables_globals, h.family);
+	if (c < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
+				xtables_globals.program_name,
+				xtables_globals.program_version);
+		exit(1);
+	}
+
+	if (nft_init(&h, xtables_bridge) < 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_for_each_table(&h, __ebt_save, !!ctr);
+	nft_fini(&h);
+	return 0;
+}
+
+int xtables_arp_save_main(int argc, char **argv)
+{
+	struct nft_handle h = {
+		.family	= NFPROTO_ARP,
+	};
+	int c;
+
+	xtables_globals.program_name = "arptables-save";
+	c = xtables_init_all(&xtables_globals, h.family);
+	if (c < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize xtables\n",
+				xtables_globals.program_name,
+				xtables_globals.program_version);
+		exit(1);
+	}
+
+	if (nft_init(&h, xtables_arp) < 0) {
+		fprintf(stderr, "%s/%s Failed to initialize nft: %s\n",
+				xtables_globals.program_name,
+				xtables_globals.program_version,
+				strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+
+	if (!nft_table_find(&h, "filter"))
+		return 0;
+
+	if (!nft_is_table_compatible(&h, "filter")) {
+		printf("# Table `filter' is incompatible, use 'nft' tool.\n");
+		return 0;
+	}
+
+	printf("*filter\n");
+	nft_chain_save(&h, nft_chain_dump(&h), "filter");
+	nft_rule_save(&h, "filter", FMT_NOCOUNTS);
+	printf("\n");
+	nft_fini(&h);
+	return 0;
+}
diff --git a/iptables/xtables.c b/iptables/xtables.c
index 2a4e0ec..e0343db 100644
--- a/iptables/xtables.c
+++ b/iptables/xtables.c
@@ -156,9 +156,9 @@
 /* -f */ IPT_INV_FRAG,
 };
 
-#define opts xtables_globals.opts
-#define prog_name xtables_globals.program_name
-#define prog_vers xtables_globals.program_version
+#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)
@@ -363,27 +363,6 @@
 	return rulenum;
 }
 
-static const char *
-parse_target(const char *targetname)
-{
-	const char *ptr;
-
-	if (strlen(targetname) < 1)
-		xtables_error(PARAMETER_PROBLEM,
-			   "Invalid target name (too short)");
-
-	if (strlen(targetname) >= XT_EXTENSION_MAXNAMELEN)
-		xtables_error(PARAMETER_PROBLEM,
-			   "Invalid target name `%s' (%u chars max)",
-			   targetname, XT_EXTENSION_MAXNAMELEN - 1);
-
-	for (ptr = targetname; *ptr; ptr++)
-		if (isspace(*ptr))
-			xtables_error(PARAMETER_PROBLEM,
-				   "Invalid target name `%s'", targetname);
-	return targetname;
-}
-
 static void
 set_option(unsigned int *options, unsigned int option, uint8_t *invflg,
 	   int invert)
@@ -599,88 +578,7 @@
 	if (counters)
 	    counters = -1;		/* iptables -c format */
 
-	nft_rule_list_save(h, chain, table, rulenum, counters);
-
-	/* iptables does not return error if rule number not found */
-	return 1;
-}
-
-static void command_jump(struct iptables_command_state *cs)
-{
-	size_t size;
-
-	set_option(&cs->options, OPT_JUMP, &cs->fw.ip.invflags, cs->invert);
-	cs->jumpto = parse_target(optarg);
-	/* TRY_LOAD (may be chain name) */
-	cs->target = xtables_find_target(cs->jumpto, XTF_TRY_LOAD);
-
-	if (cs->target == NULL)
-		return;
-
-	size = XT_ALIGN(sizeof(struct xt_entry_target))
-		+ cs->target->size;
-
-	cs->target->t = xtables_calloc(1, size);
-	cs->target->t->u.target_size = size;
-	if (cs->target->real_name == NULL) {
-		strcpy(cs->target->t->u.user.name, cs->jumpto);
-	} else {
-		/* Alias support for userspace side */
-		strcpy(cs->target->t->u.user.name, cs->target->real_name);
-		if (!(cs->target->ext_flags & XTABLES_EXT_ALIAS))
-			fprintf(stderr, "Notice: The %s target is converted into %s target "
-				"in rule listing and saving.\n",
-				cs->jumpto, cs->target->real_name);
-	}
-	cs->target->t->u.user.revision = cs->target->revision;
-	xs_init_target(cs->target);
-
-	if (cs->target->x6_options != NULL)
-		opts = xtables_options_xfrm(xtables_globals.orig_opts, opts,
-					    cs->target->x6_options,
-					    &cs->target->option_offset);
-	else
-		opts = xtables_merge_options(xtables_globals.orig_opts, opts,
-					     cs->target->extra_opts,
-					     &cs->target->option_offset);
-	if (opts == NULL)
-		xtables_error(OTHER_PROBLEM, "can't alloc memory!");
-}
-
-static void command_match(struct iptables_command_state *cs)
-{
-	struct xtables_match *m;
-	size_t size;
-
-	if (cs->invert)
-		xtables_error(PARAMETER_PROBLEM,
-			   "unexpected ! flag before --match");
-
-	m = xtables_find_match(optarg, XTF_LOAD_MUST_SUCCEED, &cs->matches);
-	size = XT_ALIGN(sizeof(struct xt_entry_match)) + m->size;
-	m->m = xtables_calloc(1, size);
-	m->m->u.match_size = size;
-	if (m->real_name == NULL) {
-		strcpy(m->m->u.user.name, m->name);
-	} else {
-		strcpy(m->m->u.user.name, m->real_name);
-		if (!(m->ext_flags & XTABLES_EXT_ALIAS))
-			fprintf(stderr, "Notice: the %s match is converted into %s match "
-				"in rule listing and saving.\n", m->name, m->real_name);
-	}
-	m->m->u.user.revision = m->revision;
-	xs_init_match(m);
-	if (m == m->next)
-		return;
-	/* Merge options for non-cloned matches */
-	if (m->x6_options != NULL)
-		opts = xtables_options_xfrm(xtables_globals.orig_opts, opts,
-					    m->x6_options, &m->option_offset);
-	else if (m->extra_opts != NULL)
-		opts = xtables_merge_options(xtables_globals.orig_opts, opts,
-					     m->extra_opts, &m->option_offset);
-	if (opts == NULL)
-		xtables_error(OTHER_PROBLEM, "can't alloc memory!");
+	return nft_rule_list_save(h, chain, table, rulenum, counters);
 }
 
 void do_parse(struct nft_handle *h, int argc, char *argv[],
@@ -915,11 +813,13 @@
 			set_option(&cs->options, OPT_JUMP, &args->invflags,
 				   cs->invert);
 			args->goto_set = true;
-			cs->jumpto = parse_target(optarg);
+			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);
 			break;
 
@@ -979,6 +879,10 @@
 			if (cs->invert)
 				xtables_error(PARAMETER_PROBLEM,
 					   "unexpected ! flag before --table");
+			if (!nft_table_builtin_find(h, optarg))
+				xtables_error(VERSION_PROBLEM,
+					      "table '%s' does not exist",
+					      optarg);
 			p->table = optarg;
 			break;
 
@@ -1159,12 +1063,18 @@
 					   p->chain);
 		}
 
-		/*
-		 * Contrary to what iptables does, we assume that any jumpto
-		 * is a custom chain jumps (if no target is found). Later on,
-		 * nf_table will spot the error if the chain does not exists.
-		 */
+		if (!nft_chain_exists(h, p->table, p->chain))
+			xtables_error(OTHER_PROBLEM,
+				      "Chain '%s' does not exist", cs->jumpto);
+
+		if (!cs->target && strlen(cs->jumpto) > 0 &&
+		    !nft_chain_exists(h, p->table, cs->jumpto))
+			xtables_error(PARAMETER_PROBLEM,
+				      "Chain '%s' does not exist", cs->jumpto);
 	}
+	if (p->command == CMD_NEW_CHAIN &&
+	    nft_chain_exists(h, p->table, p->chain))
+		xtables_error(OTHER_PROBLEM, "Chain already exists");
 }
 
 int do_commandx(struct nft_handle *h, int argc, char *argv[], char **table,
@@ -1213,10 +1123,12 @@
 				cs.options&OPT_VERBOSE, h, false);
 		break;
 	case CMD_FLUSH:
-		ret = nft_rule_flush(h, p.chain, p.table);
+		ret = nft_rule_flush(h, p.chain, p.table,
+				     cs.options & OPT_VERBOSE);
 		break;
 	case CMD_ZERO:
-		ret = nft_chain_zero_counters(h, p.chain, p.table);
+		ret = nft_chain_zero_counters(h, p.chain, p.table,
+					      cs.options & OPT_VERBOSE);
 		break;
 	case CMD_ZERO_NUM:
 		ret = nft_rule_zero_counters(h, p.chain, p.table,
@@ -1231,8 +1143,8 @@
 				   cs.options & OPT_EXPANDED,
 				   cs.options & OPT_LINENUMBERS);
 		if (ret && (p.command & CMD_ZERO)) {
-			ret = nft_chain_zero_counters(h, p.chain,
-						      p.table);
+			ret = nft_chain_zero_counters(h, p.chain, p.table,
+						      cs.options & OPT_VERBOSE);
 		}
 		if (ret && (p.command & CMD_ZERO_NUM)) {
 			ret = nft_rule_zero_counters(h, p.chain, p.table,
@@ -1246,8 +1158,8 @@
 		ret = list_rules(h, p.chain, p.table, p.rulenum,
 				 cs.options & OPT_VERBOSE);
 		if (ret && (p.command & CMD_ZERO)) {
-			ret = nft_chain_zero_counters(h, p.chain,
-						      p.table);
+			ret = nft_chain_zero_counters(h, p.chain, p.table,
+						      cs.options & OPT_VERBOSE);
 		}
 		if (ret && (p.command & CMD_ZERO_NUM)) {
 			ret = nft_rule_zero_counters(h, p.chain, p.table,
@@ -1259,16 +1171,14 @@
 		ret = nft_chain_user_add(h, p.chain, p.table);
 		break;
 	case CMD_DELETE_CHAIN:
-		ret = nft_chain_user_del(h, p.chain, p.table);
+		ret = nft_chain_user_del(h, p.chain, p.table,
+					 cs.options & OPT_VERBOSE);
 		break;
 	case CMD_RENAME_CHAIN:
 		ret = nft_chain_user_rename(h, p.chain, p.table, p.newname);
 		break;
 	case CMD_SET_POLICY:
 		ret = nft_chain_set(h, p.table, p.chain, p.policy, NULL);
-		if (ret < 0)
-			xtables_error(PARAMETER_PROBLEM, "Wrong policy `%s'\n",
-				      p.policy);
 		break;
 	default:
 		/* We should never reach this... */
diff --git a/libiptc/libiptc.c b/libiptc/libiptc.c
index b28ca0a..68ebc27 100644
--- a/libiptc/libiptc.c
+++ b/libiptc/libiptc.c
@@ -1122,8 +1122,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;
@@ -1156,7 +1157,8 @@
 		strcpy(head->name.target.u.user.name, ERROR_TARGET);
 		head->name.target.u.target_size =
 				ALIGN(sizeof(struct xt_error_target));
-		strcpy(head->name.errorname, c->name);
+		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;
@@ -1276,7 +1278,7 @@
 
 /* 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;
 
@@ -1287,14 +1289,14 @@
 	}
 	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;
 
@@ -1343,8 +1345,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;
 	}
@@ -1682,8 +1684,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;
diff --git a/libxtables/Makefile.am b/libxtables/Makefile.am
index 4267cb5..8ff6b0c 100644
--- a/libxtables/Makefile.am
+++ b/libxtables/Makefile.am
@@ -4,7 +4,7 @@
 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
+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
diff --git a/iptables/getethertype.c b/libxtables/getethertype.c
similarity index 87%
rename from iptables/getethertype.c
rename to libxtables/getethertype.c
index 027ef4a..59949b7 100644
--- a/iptables/getethertype.c
+++ b/libxtables/getethertype.c
@@ -42,27 +42,26 @@
 #include <string.h>
 #include <netinet/ether.h>
 #include <net/ethernet.h>
-
-#include <ebtables/ethernetdb.h>
+#include <xtables.h>
 
 #define	MAXALIASES	35
 
 static FILE *etherf = NULL;
 static char line[BUFSIZ + 1];
-static struct ethertypeent et_ent;
+static struct xt_ethertypeent et_ent;
 static char *ethertype_aliases[MAXALIASES];
 static int ethertype_stayopen;
 
-void setethertypeent(int f)
+static void setethertypeent(int f)
 {
 	if (etherf == NULL)
-		etherf = fopen(_PATH_ETHERTYPES, "r");
+		etherf = fopen(XT_PATH_ETHERTYPES, "r");
 	else
 		rewind(etherf);
 	ethertype_stayopen |= f;
 }
 
-void endethertypeent(void)
+static void endethertypeent(void)
 {
 	if (etherf) {
 		fclose(etherf);
@@ -71,14 +70,15 @@
 	ethertype_stayopen = 0;
 }
 
-struct ethertypeent *getethertypeent(void)
+
+static struct xt_ethertypeent *getethertypeent(void)
 {
 	char *e;
 	char *endptr;
 	register char *cp, **q;
 
 	if (etherf == NULL
-	    && (etherf = fopen(_PATH_ETHERTYPES, "r")) == NULL) {
+	    && (etherf = fopen(XT_PATH_ETHERTYPES, "r")) == NULL) {
 		return (NULL);
 	}
 
@@ -127,10 +127,9 @@
 	return (&et_ent);
 }
 
-
-struct ethertypeent *getethertypebyname(const char *name)
+struct xt_ethertypeent *xtables_getethertypebyname(const char *name)
 {
-	register struct ethertypeent *e;
+	register struct xt_ethertypeent *e;
 	register char **cp;
 
 	setethertypeent(ethertype_stayopen);
@@ -147,9 +146,9 @@
 	return (e);
 }
 
-struct ethertypeent *getethertypebynumber(int type)
+struct xt_ethertypeent *xtables_getethertypebynumber(int type)
 {
-	register struct ethertypeent *e;
+	register struct xt_ethertypeent *e;
 
 	setethertypeent(ethertype_stayopen);
 	while ((e = getethertypeent()) != NULL)
diff --git a/libxtables/xtables.c b/libxtables/xtables.c
index f3966f1..34a084f 100644
--- a/libxtables/xtables.c
+++ b/libxtables/xtables.c
@@ -21,6 +21,7 @@
 #include <fcntl.h>
 #include <inttypes.h>
 #include <netdb.h>
+#include <spawn.h>
 #include <stdarg.h>
 #include <stdbool.h>
 #include <stdio.h>
@@ -119,8 +120,10 @@
 	 * Since @oldopts also has @orig_opts already (and does so at the
 	 * start), skip these entries.
 	 */
-	oldopts += num_oold;
-	num_old -= num_oold;
+	if (oldopts != NULL) {
+		oldopts += num_oold;
+		num_old -= num_oold;
+	}
 
 	merge = malloc(sizeof(*mp) * (num_oold + num_old + num_new + 1));
 	if (merge == NULL)
@@ -139,8 +142,10 @@
 		mp->val += *option_offset;
 
 	/* Third, the old options */
-	memcpy(mp, oldopts, sizeof(*mp) * num_old);
-	mp += num_old;
+	if (oldopts != NULL) {
+		memcpy(mp, oldopts, sizeof(*mp) * num_old);
+		mp += num_old;
+	}
 	xtables_free_opts(0);
 
 	/* Clear trailing entry */
@@ -358,6 +363,7 @@
 	char *buf = NULL;
 	char *argv[4];
 	int status;
+	pid_t pid;
 
 	/* If they don't explicitly set it, read out of kernel */
 	if (!modprobe) {
@@ -378,18 +384,11 @@
 	 */
 	fflush(stdout);
 
-	switch (vfork()) {
-	case 0:
-		execv(argv[0], argv);
-
-		/* not usually reached */
-		_exit(1);
-	case -1:
+	if (posix_spawn(&pid, argv[0], NULL, NULL, argv, NULL)) {
 		free(buf);
 		return -1;
-
-	default: /* parent */
-		wait(&status);
+	} else {
+		waitpid(pid, &status, 0);
 	}
 
 	free(buf);
@@ -488,7 +487,7 @@
 	bool ret;
 
 	ret = xtables_strtoul(s, end, &v, min, max);
-	if (value != NULL)
+	if (ret && value != NULL)
 		*value = v;
 	return ret;
 }
@@ -921,6 +920,12 @@
 		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",
@@ -1108,6 +1113,12 @@
 		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",
diff --git a/libxtables/xtoptions.c b/libxtables/xtoptions.c
index ba3128b..d329f2f 100644
--- a/libxtables/xtoptions.c
+++ b/libxtables/xtoptions.c
@@ -91,8 +91,10 @@
 	 * Since @oldopts also has @orig_opts already (and does so at the
 	 * start), skip these entries.
 	 */
-	oldopts += num_orig;
-	num_old -= num_orig;
+	if (oldopts != NULL) {
+		oldopts += num_orig;
+		num_old -= num_orig;
+	}
 
 	merge = malloc(sizeof(*mp) * (num_orig + num_old + num_new + 1));
 	if (merge == NULL)
@@ -114,8 +116,10 @@
 	}
 
 	/* Third, the old options */
-	memcpy(mp, oldopts, sizeof(*mp) * num_old);
-	mp += num_old;
+	if (oldopts != NULL) {
+		memcpy(mp, oldopts, sizeof(*mp) * num_old);
+		mp += num_old;
+	}
 	xtables_free_opts(0);
 
 	/* Clear trailing entry */
@@ -282,7 +286,7 @@
 static void xtopt_parse_mint(struct xt_option_call *cb)
 {
 	const struct xt_option_entry *entry = cb->entry;
-	const char *arg = cb->arg;
+	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);
@@ -844,7 +848,7 @@
 	 * a *RC option type.
 	 */
 	cb->nvals = 1;
-	if (entry->type <= ARRAY_SIZE(xtopt_subparse) &&
+	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. */
diff --git a/utils/nfnl_osf.c b/utils/nfnl_osf.c
index 720e3a3..0ea33fc 100644
--- a/utils/nfnl_osf.c
+++ b/utils/nfnl_osf.c
@@ -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,7 +343,7 @@
 	pend = xt_osf_strchr(pbeg, OSFPDEL);
 	if (pend) {
 		*pend = '\0';
-		cnt = snprintf(obuf, sizeof(obuf), "%s,", pbeg);
+		snprintf(obuf, sizeof(obuf), "%s,", pbeg);
 		pbeg = pend + 1;
 	}
 
@@ -352,25 +351,23 @@
 	if (pend) {
 		*pend = '\0';
 		if (pbeg[0] == '@' || pbeg[0] == '*')
-			cnt = snprintf(f.genre, sizeof(f.genre), "%s", pbeg + 1);
+			snprintf(f.genre, sizeof(f.genre), "%s", pbeg + 1);
 		else
-			cnt = snprintf(f.genre, sizeof(f.genre), "%s", pbeg);
+			snprintf(f.genre, sizeof(f.genre), "%s", pbeg);
 		pbeg = pend + 1;
 	}
 
 	pend = xt_osf_strchr(pbeg, OSFPDEL);
 	if (pend) {
 		*pend = '\0';
-		cnt = snprintf(f.version, sizeof(f.version), "%s", pbeg);
+		snprintf(f.version, sizeof(f.version), "%s", 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;
+		snprintf(f.subtype, sizeof(f.subtype), "%s", pbeg);
 	}
 
 	xt_osf_parse_opt(f.opt, &f.opt_num, obuf, sizeof(obuf));
@@ -384,7 +381,7 @@
 
 	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)
diff --git a/xlate-test.py b/xlate-test.py
index 803c5b7..f365a70 100755
--- a/xlate-test.py
+++ b/xlate-test.py
@@ -40,7 +40,7 @@
     for line in payload:
         if line.startswith(keywords):
             tests += 1
-            process = Popen([ os.path.abspath(os.path.curdir) + "/iptables/xtables-compat-multi" ] + shlex.split(line), stdout=PIPE, stderr=PIPE)
+            process = Popen([ os.path.abspath(os.path.curdir) + "/iptables/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")