* [PATCH] selftests: netfilter: add functional test for nft ct timeout policies
@ 2026-08-05 6:05 Valery Borovsky
0 siblings, 0 replies; only message in thread
From: Valery Borovsky @ 2026-08-05 6:05 UTC (permalink / raw)
To: pablo, fw
Cc: netfilter-devel, linux-kselftest, shuah, linux-kernel,
Valery Borovsky
There is no selftest coverage for nftables ct timeout objects: no test
under tools/testing/selftests/net/netfilter/ exercises "ct timeout set"
or checks that a named policy reaches the conntrack state machine.
Add nft_ct_timeout.sh with three subtests, using two netns joined by a
veth pair and reading the remaining flow lifetime from "conntrack -L":
1. test_policy_applied: a TCP connection matched by a "ct timeout set"
rule gets the ESTABLISHED timeout carried by the policy (120s here)
instead of the kernel default, which is 5 days for that state (see
nf_conntrack_proto_tcp.c, tcp_timeouts[TCP_CONNTRACK_ESTABLISHED]).
2. test_default_preserved: a connection on a port not covered by any
rule keeps the default. The expected value is read from
nf_conntrack_tcp_timeout_established rather than hardcoded, so a
host that lowered the sysctl does not fail the test spuriously.
3. test_policy_reload: after the table is deleted and reloaded, a new
connection through the rule receives the policy timeout again,
covering the create/assign/destroy/re-create object lifecycle.
The test checks kernel behaviour rather than the parsing round-trip,
which the nftables.git functional tests already cover.
Signed-off-by: Valery Borovsky <vebohr@gmail.com>
---
This replaces the nft_ct timeout regression test posted in April:
https://lore.kernel.org/netfilter-devel/20260422131818.106417-1-vebohr@gmail.com/
Florian's review of that patch asked for a functional test for timeout
policies rather than a regression script, and noted that the nftables.git
tests only cover the parsing side. This is that test: it checks that the
policy reaches the conntrack state machine, and does not re-check parsing.
The executable bit is set this time.
Test output on x86_64 (nft 1.1.6, conntrack-tools 1.4.9):
PASS: test_policy_applied: timeout 119s <= policy 120s
PASS: test_default_preserved: timeout 431999s matches default 432000s
PASS: test_policy_reload: timeout 119s <= policy 120s
.../testing/selftests/net/netfilter/Makefile | 1 +
.../selftests/net/netfilter/nft_ct_timeout.sh | 217 ++++++++++++++++++
2 files changed, 218 insertions(+)
create mode 100755 tools/testing/selftests/net/netfilter/nft_ct_timeout.sh
diff --git a/tools/testing/selftests/net/netfilter/Makefile b/tools/testing/selftests/net/netfilter/Makefile
index f88dd4ef8d26..39101af5d84a 100644
--- a/tools/testing/selftests/net/netfilter/Makefile
+++ b/tools/testing/selftests/net/netfilter/Makefile
@@ -25,6 +25,7 @@ TEST_PROGS := \
nft_audit.sh \
nft_concat_range.sh \
nft_conntrack_helper.sh \
+ nft_ct_timeout.sh \
nft_fib.sh \
nft_fib_nexthop.sh \
nft_flowtable.sh \
diff --git a/tools/testing/selftests/net/netfilter/nft_ct_timeout.sh b/tools/testing/selftests/net/netfilter/nft_ct_timeout.sh
new file mode 100755
index 000000000000..a41dad2329cc
--- /dev/null
+++ b/tools/testing/selftests/net/netfilter/nft_ct_timeout.sh
@@ -0,0 +1,217 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Functional test for nftables ct timeout objects.
+#
+# Verifies that a ct timeout policy actually changes the conntrack
+# state-machine timeout of the connections it is attached to:
+#
+# 1. test_policy_applied - a connection matching a "ct timeout set" rule
+# gets the per-state timeout from the named
+# policy, i.e. <= POLICY_ESTAB_SECS.
+#
+# 2. test_default_preserved - a connection not matching any ct timeout rule
+# keeps the kernel default ESTABLISHED timeout.
+#
+# 3. test_policy_reload - after the table is deleted and reloaded, a new
+# connection through the rule still receives the
+# policy timeout rather than the default.
+#
+# The remaining lifetime of a flow is the third field of a "conntrack -L"
+# line:
+#
+# tcp 6 117 ESTABLISHED src=10.0.1.1 dst=10.0.1.2 sport=... dport=...
+# ^^^
+#
+# Topology: two netns connected by a veth pair.
+# ns1 (10.0.1.1/24) --veth-- ns2 (10.0.1.2/24)
+# The ruleset is loaded in ns1, so ns1's conntrack table is used throughout.
+
+source lib.sh
+
+checktool "nft --version" "run test without nft"
+checktool "conntrack --version" "run test without conntrack"
+checktool "socat -h" "run test without socat"
+
+# TCP port covered by the ct timeout policy.
+PORT_POLICY=12345
+# TCP port not covered by any rule; uses the kernel default timeouts.
+PORT_DEFAULT=12346
+
+# ESTABLISHED timeout carried by the policy, in seconds. Must be well below
+# the kernel default (5 days) so the two cases cannot be confused.
+POLICY_ESTAB_SECS=120
+
+ret=0
+
+cleanup()
+{
+ ip netns pids "$ns1" 2>/dev/null | xargs -r kill
+ ip netns pids "$ns2" 2>/dev/null | xargs -r kill
+ cleanup_all_ns
+}
+
+load_ruleset()
+{
+ ip netns exec "$ns1" nft -f - <<EOF
+table ip ct_timeout_test {
+ ct timeout tcp_policy {
+ protocol tcp;
+ policy = { established: ${POLICY_ESTAB_SECS}s };
+ }
+
+ chain output {
+ type filter hook output priority filter; policy accept;
+ ip daddr 10.0.1.2 tcp dport $PORT_POLICY ct timeout set "tcp_policy"
+ }
+}
+EOF
+}
+
+listener_ready()
+{
+ ip netns exec "$ns2" ss -lnt -o "sport = :$1" | grep -q "$1"
+}
+
+conn_established()
+{
+ ip netns exec "$ns1" conntrack -L -p tcp --dport "$1" 2>/dev/null |
+ grep -q ESTABLISHED
+}
+
+# Prints the remaining lifetime, in seconds, of the first ESTABLISHED TCP
+# flow in ns1 matching the given destination port.
+established_timeout()
+{
+ ip netns exec "$ns1" conntrack -L -p tcp --dport "$1" 2>/dev/null |
+ awk '/ESTABLISHED/ { print $3; exit }'
+}
+
+# Opens a TCP connection from ns1 and holds it open in the background.
+# Prints the pid of the holder so the caller can tear it down.
+open_connection()
+{
+ # stdout/stderr must be redirected, else the background job keeps the
+ # command substitution that calls this function blocked until it exits.
+ ip netns exec "$ns1" bash -c "
+ exec 3<>/dev/tcp/10.0.1.2/$1 || exit 1
+ sleep 60
+ " >/dev/null 2>&1 &
+ echo $!
+}
+
+close_connection()
+{
+ kill "$1" 2>/dev/null
+ wait "$1" 2>/dev/null
+}
+
+# Asserts that the flow on $1 has a timeout matching expectation $2 ("policy"
+# or "default"), using $3 as the test name.
+check_timeout()
+{
+ local dport=$1 expect=$2 name=$3
+ local tval
+
+ if ! busywait "$BUSYWAIT_TIMEOUT" conn_established "$dport"; then
+ echo "FAIL: $name: connection did not reach ESTABLISHED"
+ ret=1
+ return
+ fi
+
+ tval=$(established_timeout "$dport")
+ if [ -z "$tval" ]; then
+ echo "FAIL: $name: could not read conntrack timeout"
+ ret=1
+ return
+ fi
+
+ if [ "$expect" = "policy" ]; then
+ if [ "$tval" -le "$POLICY_ESTAB_SECS" ]; then
+ echo "PASS: $name: timeout ${tval}s <= policy ${POLICY_ESTAB_SECS}s"
+ else
+ echo "FAIL: $name: timeout ${tval}s exceeds policy ${POLICY_ESTAB_SECS}s"
+ ret=1
+ fi
+ else
+ if [ "$tval" -gt "$POLICY_ESTAB_SECS" ] &&
+ [ "$tval" -le "$default_estab" ]; then
+ echo "PASS: $name: timeout ${tval}s matches default ${default_estab}s"
+ else
+ echo "FAIL: $name: timeout ${tval}s is not the default ${default_estab}s"
+ ret=1
+ fi
+ fi
+}
+
+trap cleanup EXIT
+
+setup_ns ns1 ns2
+
+if ! ip link add veth0 netns "$ns1" type veth peer name veth0 netns "$ns2" \
+ >/dev/null 2>&1; then
+ echo "SKIP: No virtual ethernet pair device support in kernel"
+ exit $ksft_skip
+fi
+
+ip -net "$ns1" link set veth0 up
+ip -net "$ns2" link set veth0 up
+ip -net "$ns1" addr add 10.0.1.1/24 dev veth0
+ip -net "$ns2" addr add 10.0.1.2/24 dev veth0
+
+# SYSTEM:"cat" keeps each accepted connection open: cat blocks reading the
+# socket, so the flow stays ESTABLISHED until the client goes away. Sinking
+# to /dev/null instead would close it immediately and land in CLOSE_WAIT.
+ip netns exec "$ns2" socat TCP-LISTEN:$PORT_POLICY,reuseaddr,fork SYSTEM:"cat" &>/dev/null &
+ip netns exec "$ns2" socat TCP-LISTEN:$PORT_DEFAULT,reuseaddr,fork SYSTEM:"cat" &>/dev/null &
+
+busywait "$BUSYWAIT_TIMEOUT" listener_ready "$PORT_POLICY"
+busywait "$BUSYWAIT_TIMEOUT" listener_ready "$PORT_DEFAULT"
+
+# Loading the ruleset pulls in conntrack, so the sysctls below exist only
+# after this point.
+if ! load_ruleset; then
+ echo "SKIP: Could not load ct timeout ruleset"
+ exit $ksft_skip
+fi
+
+# Read the default rather than hardcoding it: a host that lowered
+# nf_conntrack_tcp_timeout_established must not fail test 2 spuriously.
+default_estab=$(ip netns exec "$ns1" \
+ cat /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established \
+ 2>/dev/null)
+
+if [ -z "$default_estab" ]; then
+ echo "SKIP: conntrack tcp timeout sysctl not available"
+ exit $ksft_skip
+fi
+
+if [ "$default_estab" -le "$POLICY_ESTAB_SECS" ]; then
+ echo "SKIP: default ESTABLISHED timeout ${default_estab}s is not above the policy value"
+ exit $ksft_skip
+fi
+
+# Test 1: a flow matched by the rule gets the policy timeout.
+conn_pid=$(open_connection "$PORT_POLICY")
+check_timeout "$PORT_POLICY" policy test_policy_applied
+close_connection "$conn_pid"
+
+# Test 2: a flow not matched by any rule keeps the kernel default.
+conn_pid=$(open_connection "$PORT_DEFAULT")
+check_timeout "$PORT_DEFAULT" default test_default_preserved
+close_connection "$conn_pid"
+
+# Test 3: the policy survives a delete/reload cycle of the whole table.
+ip netns exec "$ns1" conntrack -F 2>/dev/null
+ip netns exec "$ns1" nft delete table ip ct_timeout_test
+
+if ! load_ruleset; then
+ echo "FAIL: test_policy_reload: could not reload ct timeout ruleset"
+ exit $ksft_fail
+fi
+
+conn_pid=$(open_connection "$PORT_POLICY")
+check_timeout "$PORT_POLICY" policy test_policy_reload
+close_connection "$conn_pid"
+
+exit $ret
--
2.53.0
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2026-08-05 6:05 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-05 6:05 [PATCH] selftests: netfilter: add functional test for nft ct timeout policies Valery Borovsky
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.