Blame SOURCES/0028-nft-Fix-for-concurrent-noflush-restore-calls.patch

87db66
From 575a1e5589f813af7e838c045863b510b4740353 Mon Sep 17 00:00:00 2001
87db66
From: Phil Sutter <phil@nwl.cc>
87db66
Date: Mon, 5 Oct 2020 16:06:49 +0200
87db66
Subject: [PATCH] nft: Fix for concurrent noflush restore calls
87db66
87db66
Transaction refresh was broken with regards to nft_chain_restore(): It
87db66
created a rule flush batch object only if the chain was found in cache
87db66
and a chain add object only if the chain was not found. Yet with
87db66
concurrent ruleset updates, one has to expect both situations:
87db66
87db66
* If a chain vanishes, the rule flush job must be skipped and instead
87db66
  the chain add job become active.
87db66
87db66
* If a chain appears, the chain add job must be skipped and instead
87db66
  rules flushed.
87db66
87db66
Change the code accordingly: Create both batch objects and set their
87db66
'skip' field depending on the situation in cache and adjust both in
87db66
nft_refresh_transaction().
87db66
87db66
As a side-effect, the implicit rule flush becomes explicit and all
87db66
handling of implicit batch jobs is dropped along with the related field
87db66
indicating such.
87db66
87db66
Reuse the 'implicit' parameter of __nft_rule_flush() to control the
87db66
initial 'skip' field value instead.
87db66
87db66
A subtle caveat is vanishing of existing chains: Creating the chain add
87db66
job based on the chain in cache causes a netlink message containing that
87db66
chain's handle which the kernel dislikes. Therefore unset the chain's
87db66
handle in that case.
87db66
87db66
Fixes: 58d7de0181f61 ("xtables: handle concurrent ruleset modifications")
87db66
Signed-off-by: Phil Sutter <phil@nwl.cc>
87db66
(cherry picked from commit dac904bdcd9a18aabafee7275ccf0c2bd53800f3)
87db66
87db66
Conflicts:
87db66
	iptables/nft.c
87db66
-> Upstream changed good/bad return codes of nft_chain_restore()
87db66
   function.
87db66
87db66
Signed-off-by: Phil Sutter <psutter@redhat.com>
87db66
---
87db66
 iptables/nft.c                                | 58 ++++++++++---------
87db66
 .../ipt-restore/0016-concurrent-restores_0    | 53 +++++++++++++++++
87db66
 2 files changed, 83 insertions(+), 28 deletions(-)
87db66
 create mode 100755 iptables/tests/shell/testcases/ipt-restore/0016-concurrent-restores_0
87db66
87db66
diff --git a/iptables/nft.c b/iptables/nft.c
87db66
index d661ac2cafda6..dc5490c085364 100644
87db66
--- a/iptables/nft.c
87db66
+++ b/iptables/nft.c
87db66
@@ -283,7 +283,6 @@ struct obj_update {
87db66
 	struct list_head	head;
87db66
 	enum obj_update_type	type:8;
87db66
 	uint8_t			skip:1;
87db66
-	uint8_t			implicit:1;
87db66
 	unsigned int		seq;
87db66
 	union {
87db66
 		struct nftnl_table	*table;
87db66
@@ -1650,7 +1649,7 @@ int nft_rule_save(struct nft_handle *h, const char *table, unsigned int format)
87db66
 
87db66
 static void
87db66
 __nft_rule_flush(struct nft_handle *h, const char *table,
87db66
-		 const char *chain, bool verbose, bool implicit)
87db66
+		 const char *chain, bool verbose, bool skip)
87db66
 {
87db66
 	struct obj_update *obj;
87db66
 	struct nftnl_rule *r;
87db66
@@ -1672,7 +1671,7 @@ __nft_rule_flush(struct nft_handle *h, const char *table,
87db66
 		return;
87db66
 	}
87db66
 
87db66
-	obj->implicit = implicit;
87db66
+	obj->skip = skip;
87db66
 }
87db66
 
87db66
 int nft_rule_flush(struct nft_handle *h, const char *chain, const char *table,
87db66
@@ -1768,17 +1767,12 @@ int nft_chain_user_add(struct nft_handle *h, const char *chain, const char *tabl
87db66
 int nft_chain_restore(struct nft_handle *h, const char *chain, const char *table)
87db66
 {
87db66
 	struct nftnl_chain_list *list;
87db66
+	struct obj_update *obj;
87db66
 	struct nftnl_chain *c;
87db66
 	bool created = false;
87db66
 
87db66
 	c = nft_chain_find(h, table, chain);
87db66
-	if (c) {
87db66
-		/* Apparently -n still flushes existing user defined
87db66
-		 * chains that are redefined.
87db66
-		 */
87db66
-		if (h->noflush)
87db66
-			__nft_rule_flush(h, table, chain, false, true);
87db66
-	} else {
87db66
+	if (!c) {
87db66
 		c = nftnl_chain_alloc();
87db66
 		if (!c)
87db66
 			return -1;
87db66
@@ -1786,20 +1780,26 @@ int nft_chain_restore(struct nft_handle *h, const char *chain, const char *table
87db66
 		nftnl_chain_set_str(c, NFTNL_CHAIN_TABLE, table);
87db66
 		nftnl_chain_set_str(c, NFTNL_CHAIN_NAME, chain);
87db66
 		created = true;
87db66
-	}
87db66
 
87db66
-	if (h->family == NFPROTO_BRIDGE)
87db66
-		nftnl_chain_set_u32(c, NFTNL_CHAIN_POLICY, NF_ACCEPT);
87db66
+		list = nft_chain_list_get(h, table, chain);
87db66
+		if (list)
87db66
+			nftnl_chain_list_add(c, list);
87db66
+	} else {
87db66
+		/* If the chain should vanish meanwhile, kernel genid changes
87db66
+		 * and the transaction is refreshed enabling the chain add
87db66
+		 * object. With the handle still set, kernel interprets it as a
87db66
+		 * chain replace job and errors since it is not found anymore.
87db66
+		 */
87db66
+		nftnl_chain_unset(c, NFTNL_CHAIN_HANDLE);
87db66
+	}
87db66
 
87db66
-	if (!created)
87db66
-		return 0;
87db66
+	__nft_rule_flush(h, table, chain, false, created);
87db66
 
87db66
-	if (!batch_chain_add(h, NFT_COMPAT_CHAIN_USER_ADD, c))
87db66
+	obj = batch_chain_add(h, NFT_COMPAT_CHAIN_USER_ADD, c);
87db66
+	if (!obj)
87db66
 		return -1;
87db66
 
87db66
-	list = nft_chain_list_get(h, table, chain);
87db66
-	if (list)
87db66
-		nftnl_chain_list_add(c, list);
87db66
+	obj->skip = !created;
87db66
 
87db66
 	return 0;
87db66
 }
87db66
@@ -2693,11 +2693,6 @@ static void nft_refresh_transaction(struct nft_handle *h)
87db66
 	h->error.lineno = 0;
87db66
 
87db66
 	list_for_each_entry_safe(n, tmp, &h->obj_list, head) {
87db66
-		if (n->implicit) {
87db66
-			batch_obj_del(h, n);
87db66
-			continue;
87db66
-		}
87db66
-
87db66
 		switch (n->type) {
87db66
 		case NFT_COMPAT_TABLE_FLUSH:
87db66
 			tablename = nftnl_table_get_str(n->table, NFTNL_TABLE_NAME);
87db66
@@ -2723,14 +2718,22 @@ static void nft_refresh_transaction(struct nft_handle *h)
87db66
 
87db66
 			c = nft_chain_find(h, tablename, chainname);
87db66
 			if (c) {
87db66
-				/* -restore -n flushes existing rules from redefined user-chain */
87db66
-				__nft_rule_flush(h, tablename,
87db66
-						 chainname, false, true);
87db66
 				n->skip = 1;
87db66
 			} else if (!c) {
87db66
 				n->skip = 0;
87db66
 			}
87db66
 			break;
87db66
+		case NFT_COMPAT_RULE_FLUSH:
87db66
+			tablename = nftnl_rule_get_str(n->rule, NFTNL_RULE_TABLE);
87db66
+			if (!tablename)
87db66
+				continue;
87db66
+
87db66
+			chainname = nftnl_rule_get_str(n->rule, NFTNL_RULE_CHAIN);
87db66
+			if (!chainname)
87db66
+				continue;
87db66
+
87db66
+			n->skip = !nft_chain_find(h, tablename, chainname);
87db66
+			break;
87db66
 		case NFT_COMPAT_TABLE_ADD:
87db66
 		case NFT_COMPAT_CHAIN_ADD:
87db66
 		case NFT_COMPAT_CHAIN_ZERO:
87db66
@@ -2742,7 +2745,6 @@ static void nft_refresh_transaction(struct nft_handle *h)
87db66
 		case NFT_COMPAT_RULE_INSERT:
87db66
 		case NFT_COMPAT_RULE_REPLACE:
87db66
 		case NFT_COMPAT_RULE_DELETE:
87db66
-		case NFT_COMPAT_RULE_FLUSH:
87db66
 		case NFT_COMPAT_SET_ADD:
87db66
 			break;
87db66
 		}
87db66
diff --git a/iptables/tests/shell/testcases/ipt-restore/0016-concurrent-restores_0 b/iptables/tests/shell/testcases/ipt-restore/0016-concurrent-restores_0
87db66
new file mode 100755
87db66
index 0000000000000..53ec12fa368af
87db66
--- /dev/null
87db66
+++ b/iptables/tests/shell/testcases/ipt-restore/0016-concurrent-restores_0
87db66
@@ -0,0 +1,53 @@
87db66
+#!/bin/bash
87db66
+
87db66
+set -e
87db66
+
87db66
+RS="*filter
87db66
+:INPUT ACCEPT [12024:3123388]
87db66
+:FORWARD ACCEPT [0:0]
87db66
+:OUTPUT ACCEPT [12840:2144421]
87db66
+:FOO - [0:0]
87db66
+:BAR0 - [0:0]
87db66
+:BAR1 - [0:0]
87db66
+:BAR2 - [0:0]
87db66
+:BAR3 - [0:0]
87db66
+:BAR4 - [0:0]
87db66
+:BAR5 - [0:0]
87db66
+:BAR6 - [0:0]
87db66
+:BAR7 - [0:0]
87db66
+:BAR8 - [0:0]
87db66
+:BAR9 - [0:0]
87db66
+"
87db66
+
87db66
+RS1="$RS
87db66
+-X BAR3
87db66
+-X BAR6
87db66
+-X BAR9
87db66
+-A FOO -s 9.9.0.1/32 -j BAR1
87db66
+-A FOO -s 9.9.0.2/32 -j BAR2
87db66
+-A FOO -s 9.9.0.4/32 -j BAR4
87db66
+-A FOO -s 9.9.0.5/32 -j BAR5
87db66
+-A FOO -s 9.9.0.7/32 -j BAR7
87db66
+-A FOO -s 9.9.0.8/32 -j BAR8
87db66
+COMMIT
87db66
+"
87db66
+
87db66
+RS2="$RS
87db66
+-X BAR2
87db66
+-X BAR5
87db66
+-X BAR7
87db66
+-A FOO -s 9.9.0.1/32 -j BAR1
87db66
+-A FOO -s 9.9.0.3/32 -j BAR3
87db66
+-A FOO -s 9.9.0.4/32 -j BAR4
87db66
+-A FOO -s 9.9.0.6/32 -j BAR6
87db66
+-A FOO -s 9.9.0.8/32 -j BAR8
87db66
+-A FOO -s 9.9.0.9/32 -j BAR9
87db66
+COMMIT
87db66
+"
87db66
+
87db66
+for n in $(seq 1 10); do
87db66
+	$XT_MULTI iptables-restore --noflush -w <<< "$RS1" &
87db66
+	$XT_MULTI iptables-restore --noflush -w <<< "$RS2" &
87db66
+	wait -n
87db66
+	wait -n
87db66
+done
87db66
-- 
87db66
2.28.0
87db66