]> git.ipfire.org Git - thirdparty/nftables.git/commitdiff
tests: shell: add regression tests for set flush+add bugs
authorFlorian Westphal <fw@strlen.de>
Tue, 16 Sep 2025 16:52:53 +0000 (18:52 +0200)
committerFlorian Westphal <fw@strlen.de>
Wed, 8 Oct 2025 09:14:34 +0000 (11:14 +0200)
Create a helper file to:
1. create client <-> router <-> server topology
2. floodping from client to server
3. add a chain + set that contains both client and server
   addresses
4. a control counter that should never match
5. then, flush the set (not the ruleset) and re-add the
   addresses in one transaction

Report failure when counter had a match.

The test cases for the set types are done in separate files to take
advantage of run-tests.sh parallelization.

The expected behavior is that every ping packet is matched by the set.
The packet path should either match the old state, right before flush,
or the new state, after re-add.

As the flushed addresses are re-added in the same transaction we must
not observe in-limbo state where existing elements are deactivated but
new elements are not found.

Signed-off-by: Florian Westphal <fw@strlen.de>
13 files changed:
tests/shell/helpers/set_flush_add_atomic_helpers [new file with mode: 0644]
tests/shell/testcases/packetpath/dumps/set_flush_add_atomic_bitmap.nodump [new file with mode: 0644]
tests/shell/testcases/packetpath/dumps/set_flush_add_atomic_hash.nodump [new file with mode: 0644]
tests/shell/testcases/packetpath/dumps/set_flush_add_atomic_hash_fast.nodump [new file with mode: 0644]
tests/shell/testcases/packetpath/dumps/set_flush_add_atomic_pipapo.nodump [new file with mode: 0644]
tests/shell/testcases/packetpath/dumps/set_flush_add_atomic_rbtree.nodump [new file with mode: 0644]
tests/shell/testcases/packetpath/dumps/set_flush_add_atomic_rhash.nodump [new file with mode: 0644]
tests/shell/testcases/packetpath/set_flush_add_atomic_bitmap [new file with mode: 0755]
tests/shell/testcases/packetpath/set_flush_add_atomic_hash [new file with mode: 0755]
tests/shell/testcases/packetpath/set_flush_add_atomic_hash_fast [new file with mode: 0755]
tests/shell/testcases/packetpath/set_flush_add_atomic_pipapo [new file with mode: 0755]
tests/shell/testcases/packetpath/set_flush_add_atomic_rbtree [new file with mode: 0755]
tests/shell/testcases/packetpath/set_flush_add_atomic_rhash [new file with mode: 0755]

diff --git a/tests/shell/helpers/set_flush_add_atomic_helpers b/tests/shell/helpers/set_flush_add_atomic_helpers
new file mode 100644 (file)
index 0000000..fe895e9
--- /dev/null
@@ -0,0 +1,223 @@
+# Test skeleton for kernel fixes:
+# b2f742c846ca netfilter: nf_tables: restart set lookup on base_seq change
+# a60f7bf4a152 netfilter: nft_set_rbtree: continue traversal if element is inactive
+# .. and related patches.
+#
+# Generate traffic and then flush the set contents and replace
+# them with the same matching entries.
+#
+# Fail when a packet gets through.
+
+# global variables:
+# R, S, C (network namespaces).
+# ip_s (server address)
+
+# helpers:
+# set_flush_add_atomic_cleanup
+# set_flush_add_create_topo
+# set_flush_add_atomic_run_test
+
+[ -z "$TIMEOUT" ] && TIMEOUT=30
+
+set_flush_add_atomic_cleanup()
+{
+       local tmp="$1"
+       local i
+
+       rm -f "$tmp"
+
+       ip netns exec $R $NFT --debug netlink list ruleset
+
+       for i in $C $S $R;do
+               kill $(ip netns pid $i) 2>/dev/null
+               ip netns del $i
+       done
+}
+
+check_counter()
+{
+       local tmp="$1"
+       local then="$2"
+
+       if ip netns exec $R $NFT list chain ip filter block-spoofed | grep -q 'counter packets 0 bytes 0'; then
+               return 0
+       fi
+
+       local now=$(date +%s)
+       echo "$0 failed counter check after $((now-then))s"
+
+       rm -f "$tmp"
+       kill $(ip netns pid $C) 2>/dev/null
+       return 1
+}
+
+load_ruleset()
+{
+       local type="$1"
+       local flags="$2"
+       local elements="$3"
+       local expr="$4"
+
+ip netns exec $R $NFT -f - <<EOF
+table ip filter {
+  set match {
+    type $type
+    $flags
+    elements = { $elements }
+  }
+
+  set bogon {
+     type ipv4_addr . ipv4_addr . icmp_type
+     flags dynamic
+     size 8
+     timeout 5m
+  }
+
+  chain block-spoofed {
+    type filter hook prerouting priority filter; policy accept;
+    $expr @match accept
+    counter add @bogon { ip saddr . ip daddr . icmp type } comment "must not match"
+  }
+}
+EOF
+}
+
+reload_set()
+{
+       local tmp="$1"
+       local elements="$2"
+
+       while [ -f "$tmp" ]; do
+ip netns exec $R $NFT -f - <<EOF
+flush set ip filter match
+create element ip filter match { $elements }
+EOF
+               if [ $? -ne 0 ];then
+                       echo "reload of set failed unexpectedly"
+                       rm -f "$tmp"
+                       exit 1
+               fi
+
+       done
+}
+
+monitor_counter()
+{
+       local tmp="$1"
+       local then="$2"
+
+       while [ -f "$tmp" ]; do
+               sleep 1
+               check_counter "$tmp" "$then" || return 1
+       done
+
+       return 0
+}
+
+wait_for_timeout()
+{
+       local tmp="$1"
+       local rc=0
+
+       local then=$(date +%s)
+       local end=$((then+TIMEOUT))
+
+       while [ -f "$tmp" ];do
+               local now=$(date +%s)
+               [ "$now" -ge "$end" ] && break
+               sleep 1
+       done
+
+       test -f "$tmp" || rc=1
+       rm -f "$tmp"
+
+       return $rc
+}
+
+set_flush_add_create_topo()
+{
+       local test="$1"
+       local ip_r1=192.168.2.1
+       local ip_r2=192.168.3.1
+       # global
+       ip_c=192.168.2.30
+       ip_s=192.168.3.10
+
+       rnd=$(mktemp -u XXXXXXXX)
+       C="$test-client-$rnd"
+       R="$test-router-$rnd"
+       S="$test-server-$rnd"
+
+       ip netns add $S
+       ip netns add $R
+       ip netns add $C
+
+       ip link add veth0 netns $S type veth peer name rs netns $R
+       ip link add veth0 netns $C type veth peer name rc netns $R
+
+       ip -net $S link set veth0 up
+       ip -net $C link set veth0 up
+       ip -net $R link set rs up
+       ip -net $R link set rc up
+       ip -net $S link set lo up
+       ip -net $C link set lo up
+       ip -net $R link set lo up
+
+       for n in $S $R $C;do
+               ip netns exec $n sysctl -q net.ipv4.conf.all.rp_filter=0
+       done
+
+       ip netns exec $R sysctl -q net.ipv4.ip_forward=1
+
+       ip -net $S addr add ${ip_s}/24  dev veth0
+       ip -net $C addr add ${ip_c}/24  dev veth0
+       ip -net $R addr add ${ip_r1}/24 dev rc
+       ip -net $R addr add ${ip_r2}/24 dev rs
+
+       ip -net $C route add default via ${ip_r1} dev veth0
+       ip -net $S route add default via ${ip_r2} dev veth0
+
+       ip netns exec $S ping -q -c 1 -i 0.1 ${ip_c} || exit 1
+       ip netns exec $C ping -q -c 1 -i 0.1 ${ip_s} || exit 2
+}
+
+start_ping_flood()
+{
+       for i in $(seq 1 4);do
+               timeout $TIMEOUT ip netns exec $C ping -W 0.00001 -l 200 -q -f ${ip_s} &
+       done
+
+       wait
+}
+
+set_flush_add_atomic_run_test()
+{
+       local tmp="$1"
+       local type="$2"
+       local flags="$3"
+       local elements="$4"
+       local expr="$5"
+
+       local then=$(date +%s)
+       local now=$(date +%s)
+
+       load_ruleset "$type" "$flags" "$elements" "$expr"
+
+       # sanity check, counter must be 0, no parallel set flush/elem add yet.
+       check_counter "$tmp" "$then" || exit 3
+
+       start_ping_flood &
+
+       reload_set "$tmp" "$elements" &
+
+       monitor_counter "$tmp" "$then" &
+
+       wait_for_timeout "$tmp" || return 1
+       wait
+
+       check_counter "$tmp" "$then" || return 1
+
+       local now=$(date +%s)
+       echo "$0 test took $((now-then))s"
+       return 0
+}
diff --git a/tests/shell/testcases/packetpath/dumps/set_flush_add_atomic_bitmap.nodump b/tests/shell/testcases/packetpath/dumps/set_flush_add_atomic_bitmap.nodump
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/shell/testcases/packetpath/dumps/set_flush_add_atomic_hash.nodump b/tests/shell/testcases/packetpath/dumps/set_flush_add_atomic_hash.nodump
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/shell/testcases/packetpath/dumps/set_flush_add_atomic_hash_fast.nodump b/tests/shell/testcases/packetpath/dumps/set_flush_add_atomic_hash_fast.nodump
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/shell/testcases/packetpath/dumps/set_flush_add_atomic_pipapo.nodump b/tests/shell/testcases/packetpath/dumps/set_flush_add_atomic_pipapo.nodump
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/shell/testcases/packetpath/dumps/set_flush_add_atomic_rbtree.nodump b/tests/shell/testcases/packetpath/dumps/set_flush_add_atomic_rbtree.nodump
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/shell/testcases/packetpath/dumps/set_flush_add_atomic_rhash.nodump b/tests/shell/testcases/packetpath/dumps/set_flush_add_atomic_rhash.nodump
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/shell/testcases/packetpath/set_flush_add_atomic_bitmap b/tests/shell/testcases/packetpath/set_flush_add_atomic_bitmap
new file mode 100755 (executable)
index 0000000..65fc71f
--- /dev/null
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+. $NFT_TEST_BASEDIR/helpers/set_flush_add_atomic_helpers
+
+tmp=$(mktemp)
+
+cleanup()
+{
+       set_flush_add_atomic_cleanup "$tmp"
+}
+
+trap cleanup EXIT
+
+set_flush_add_create_topo "bitmap"
+
+set_flush_add_atomic_run_test "$tmp" "icmp_type" "" "echo-reply, echo-request" "icmp type" || exit 1
+
+exit 0
diff --git a/tests/shell/testcases/packetpath/set_flush_add_atomic_hash b/tests/shell/testcases/packetpath/set_flush_add_atomic_hash
new file mode 100755 (executable)
index 0000000..6f14ce1
--- /dev/null
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+. $NFT_TEST_BASEDIR/helpers/set_flush_add_atomic_helpers
+
+tmp=$(mktemp)
+
+cleanup()
+{
+       set_flush_add_atomic_cleanup "$tmp"
+}
+
+trap cleanup EXIT
+
+set_flush_add_create_topo "hash"
+
+set_flush_add_atomic_run_test "$tmp" "ipv4_addr . ipv4_addr" "size 2" "$ip_c . $ip_s, $ip_s . $ip_c" "ip saddr . ip daddr" || exit 1
+
+exit 0
diff --git a/tests/shell/testcases/packetpath/set_flush_add_atomic_hash_fast b/tests/shell/testcases/packetpath/set_flush_add_atomic_hash_fast
new file mode 100755 (executable)
index 0000000..12858d8
--- /dev/null
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+. $NFT_TEST_BASEDIR/helpers/set_flush_add_atomic_helpers
+
+tmp=$(mktemp)
+
+cleanup()
+{
+       set_flush_add_atomic_cleanup "$tmp"
+}
+
+trap cleanup EXIT
+
+set_flush_add_create_topo "hash_fast"
+
+set_flush_add_atomic_run_test "$tmp" "ipv4_addr" "size 2" "$ip_c, $ip_s" "ip saddr" || exit 1
+
+exit 0
diff --git a/tests/shell/testcases/packetpath/set_flush_add_atomic_pipapo b/tests/shell/testcases/packetpath/set_flush_add_atomic_pipapo
new file mode 100755 (executable)
index 0000000..5d6b39f
--- /dev/null
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+# NFT_TEST_REQUIRES(NFT_TEST_HAVE_pipapo)
+
+. $NFT_TEST_BASEDIR/helpers/set_flush_add_atomic_helpers
+
+tmp=$(mktemp)
+
+cleanup()
+{
+       set_flush_add_atomic_cleanup "$tmp"
+}
+
+trap cleanup EXIT
+
+set_flush_add_create_topo "pipapo"
+
+set_flush_add_atomic_run_test "$tmp" "ipv4_addr . ipv4_addr" "flags interval" "$ip_c . $ip_s, $ip_s . $ip_c" "ip saddr . ip daddr" || exit 1
+
+exit 0
diff --git a/tests/shell/testcases/packetpath/set_flush_add_atomic_rbtree b/tests/shell/testcases/packetpath/set_flush_add_atomic_rbtree
new file mode 100755 (executable)
index 0000000..c5cef3c
--- /dev/null
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+. $NFT_TEST_BASEDIR/helpers/set_flush_add_atomic_helpers
+
+tmp=$(mktemp)
+
+cleanup()
+{
+       set_flush_add_atomic_cleanup "$tmp"
+}
+
+trap cleanup EXIT
+
+set_flush_add_create_topo "rbtree"
+
+set_flush_add_atomic_run_test "$tmp" "ipv4_addr" "flags interval" "0.0.0.0-192.168.2.19, 192.168.2.21-255.255.255.255" "ip saddr" || exit 1
+
+exit 0
diff --git a/tests/shell/testcases/packetpath/set_flush_add_atomic_rhash b/tests/shell/testcases/packetpath/set_flush_add_atomic_rhash
new file mode 100755 (executable)
index 0000000..185d8e7
--- /dev/null
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+. $NFT_TEST_BASEDIR/helpers/set_flush_add_atomic_helpers
+
+tmp=$(mktemp)
+
+cleanup()
+{
+       set_flush_add_atomic_cleanup "$tmp"
+}
+
+trap cleanup EXIT
+
+set_flush_add_create_topo "rhash"
+
+set_flush_add_atomic_run_test "$tmp" "ipv4_addr" "flags dynamic" "$ip_c, $ip_s" "ip saddr" || exit 1
+
+exit 0