xtables-restore: support atomic commit

Use new services in nf_tables to support atomic commit.

Commit per table, although we support global commit at once,
call commit for each table to emulate iptables-restore
behaviour by now.

Keep table dormant/wake up code in iptables/nft.c as it can
be used in the future.

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
diff --git a/include/linux/netfilter/nf_tables.h b/include/linux/netfilter/nf_tables.h
index bdab3f2..5385bf3 100644
--- a/include/linux/netfilter/nf_tables.h
+++ b/include/linux/netfilter/nf_tables.h
@@ -35,6 +35,8 @@
 	NFT_MSG_NEWSETELEM,
 	NFT_MSG_GETSETELEM,
 	NFT_MSG_DELSETELEM,
+	NFT_MSG_COMMIT,
+	NFT_MSG_ABORT,
 	NFT_MSG_MAX,
 };
 
@@ -83,12 +85,18 @@
 };
 #define NFTA_CHAIN_MAX		(__NFTA_CHAIN_MAX - 1)
 
+enum {
+	NFT_RULE_F_COMMIT	= (1 << 0),
+	NFT_RULE_F_MASK		= NFT_RULE_F_COMMIT,
+};
+
 enum nft_rule_attributes {
 	NFTA_RULE_UNSPEC,
 	NFTA_RULE_TABLE,
 	NFTA_RULE_CHAIN,
 	NFTA_RULE_HANDLE,
 	NFTA_RULE_EXPRESSIONS,
+	NFTA_RULE_FLAGS,
 	__NFTA_RULE_MAX
 };
 #define NFTA_RULE_MAX		(__NFTA_RULE_MAX - 1)
diff --git a/iptables/nft.c b/iptables/nft.c
index fd19ff5..f42e437 100644
--- a/iptables/nft.c
+++ b/iptables/nft.c
@@ -946,6 +946,10 @@
 		flags |= NLM_F_REPLACE;
 	}
 
+	if (h->commit) {
+		nft_rule_attr_set_u32(r, NFT_RULE_ATTR_FLAGS,
+				      NFT_RULE_F_COMMIT);
+	}
 	nlh = nft_rule_nlmsg_build_hdr(buf, NFT_MSG_NEWRULE,
 				       h->family, flags, h->seq);
 
@@ -1626,6 +1630,11 @@
 	nft_rule_attr_set(r, NFT_RULE_ATTR_TABLE, (char *)table);
 	nft_rule_attr_set(r, NFT_RULE_ATTR_CHAIN, (char *)chain);
 
+	if (h->commit) {
+		nft_rule_attr_set_u32(r, NFT_RULE_ATTR_FLAGS,
+					 NFT_RULE_F_COMMIT);
+	}
+
 	/* Delete all rules in this table + chain */
 	nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_DELRULE, h->family,
 					NLM_F_ACK, h->seq);
@@ -2773,6 +2782,10 @@
 	if (r != NULL) {
 		ret = 1;
 
+		if (h->commit) {
+			nft_rule_attr_set_u32(r, NFT_RULE_ATTR_FLAGS,
+						 NFT_RULE_F_COMMIT);
+		}
 		DEBUGP("deleting rule\n");
 		__nft_rule_del(h, r);
 	} else
@@ -2802,6 +2815,10 @@
 	if (r != NULL) {
 		ret = 1;
 
+		if (h->commit) {
+			nft_rule_attr_set_u32(r, NFT_RULE_ATTR_FLAGS,
+						 NFT_RULE_F_COMMIT);
+		}
 		DEBUGP("deleting rule by number %d\n", rulenum);
 		__nft_rule_del(h, r);
 	} else
@@ -2834,6 +2851,10 @@
 			(unsigned long long)
 			nft_rule_attr_get_u64(r, NFT_RULE_ATTR_HANDLE));
 
+		if (h->commit) {
+			nft_rule_attr_set_u32(r, NFT_RULE_ATTR_FLAGS,
+						 NFT_RULE_F_COMMIT);
+		}
 		ret = nft_rule_add(h, chain, table, cs, true,
 				   nft_rule_attr_get_u64(r, NFT_RULE_ATTR_HANDLE),
 				   verbose);
@@ -3435,6 +3456,41 @@
 	return 1;
 }
 
+static int nft_action(struct nft_handle *h, int type)
+{
+	char buf[MNL_SOCKET_BUFFER_SIZE];
+	struct nlmsghdr *nlh;
+	uint32_t seq;
+	int ret;
+
+	nlh = mnl_nlmsg_put_header(buf);
+	nlh->nlmsg_type = (NFNL_SUBSYS_NFTABLES<< 8) | type;
+	nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+	nlh->nlmsg_seq = seq = time(NULL);
+
+	struct nfgenmsg *nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg));
+	nfg->nfgen_family = AF_INET;
+	nfg->version = NFNETLINK_V0;
+	nfg->res_id = 0;
+
+	ret = mnl_talk(h, nlh, NULL, NULL);
+	if (ret < 0) {
+		if (errno != EEXIST)
+			perror("mnl-talk:nft_commit");
+	}
+	return ret;
+}
+
+int nft_commit(struct nft_handle *h)
+{
+	return nft_action(h, NFT_MSG_COMMIT);
+}
+
+int nft_abort(struct nft_handle *h)
+{
+	return nft_action(h, NFT_MSG_ABORT);
+}
+
 int nft_compatible_revision(const char *name, uint8_t rev, int opt)
 {
 	struct mnl_socket *nl;
diff --git a/iptables/nft.h b/iptables/nft.h
index f7ed0a3..834fff0 100644
--- a/iptables/nft.h
+++ b/iptables/nft.h
@@ -8,6 +8,7 @@
 	struct mnl_socket	*nl;
 	uint32_t		portid;
 	uint32_t		seq;
+	bool			commit;
 };
 
 int nft_init(struct nft_handle *h);
@@ -56,6 +57,12 @@
 int nft_rule_flush(struct nft_handle *h, const char *chain, const char *table);
 
 /*
+ * global commit and abort
+ */
+int nft_commit(struct nft_handle *h);
+int nft_abort(struct nft_handle *h);
+
+/*
  * revision compatibility.
  */
 int nft_compatible_revision(const char *name, uint8_t rev, int opt);
diff --git a/iptables/xtables-restore.c b/iptables/xtables-restore.c
index 9778a9f..ca9e0c0 100644
--- a/iptables/xtables-restore.c
+++ b/iptables/xtables-restore.c
@@ -164,6 +164,7 @@
 {
 	struct nft_handle h = {
 		.family = AF_INET,	/* default to IPv4 */
+		.commit	= true,
 	};
 	char buffer[10240];
 	int c;
@@ -253,10 +254,14 @@
 			continue;
 		} else if ((strcmp(buffer, "COMMIT\n") == 0) && (in_table)) {
 			if (!testing) {
-				if (nft_table_wake_dormant(&h, curtable) < 0) {
-					fprintf(stderr, "Failed to wake up "
-						"dormant table `%s'\n",
-						curtable);
+				/* Commit per table, although we support
+				 * global commit at once, stick by now to
+				 * the existing behaviour.
+				 */
+				if (nft_commit(&h)) {
+					fprintf(stderr, "Failed to commit "
+							"table %s\n",
+							curtable);
 				}
 				DEBUGP("Calling commit\n");
 				ret = 1;
@@ -288,7 +293,6 @@
 			if (tablename && (strcmp(tablename, table) != 0))
 				continue;
 
-			nft_table_set_dormant(&h, table);
 			if (noflush == 0) {
 				DEBUGP("Cleaning all chains of table '%s'\n",
 					table);
@@ -426,6 +430,14 @@
 				DEBUGP("argv[%u]: %s\n", a, newargv[a]);
 
 			ret = do_commandx(&h, newargc, newargv, &newargv[2]);
+			if (ret < 0) {
+				ret = nft_abort(&h);
+				if (ret < 0) {
+					fprintf(stderr, "failed to abort "
+							"commit operation\n");
+				}
+				exit(1);
+			}
 
 			free_argv();
 			fflush(stdout);
diff --git a/iptables/xtables-standalone.c b/iptables/xtables-standalone.c
index f746c90..2841611 100644
--- a/iptables/xtables-standalone.c
+++ b/iptables/xtables-standalone.c
@@ -46,6 +46,8 @@
 	char *table = "filter";
 	struct nft_handle h;
 
+	memset(&h, 0, sizeof(h));
+
 	iptables_globals.program_name = "xtables";
 	ret = xtables_init_all(&xtables_globals, NFPROTO_IPV4);
 	if (ret < 0) {