From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-lj1-f172.google.com (mail-lj1-f172.google.com [209.85.208.172]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2C01237F328 for ; Wed, 5 Aug 2026 06:05:11 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.172 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785909914; cv=none; b=Pvrh8ir9oin9XDq7co3Y1YwWarjnV3mbwfzSG7ViZieDB/TY/xAxITATCVRLNQCHhuvc4n5PSEpFVS+DYWfKz9X0P3ArpH2OPqNJGQLLbFNFuowy9eeRuttGHqA+o8w4/V5FIPG0m3Kiik1b+N8eqHMYOJTrh+EavLKRV1Q3LQk= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785909914; c=relaxed/simple; bh=bBUIb6c6mdg44l4b86guDEjHx2iXxEqjN7CxTB293lw=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=LEj2ENmm0yh9xWYaBYjN8XdX+FQ5zs9gB+0rCtT7ioI0TtFRb/dRXm+lSZz+Pz4GvMeFuCQxvlMjE/PmM9qqqsGcE9ylwL1BIfPVqlohW8MuriipknMu6ZjMcrCddw8I949I91gWaOvs7diQIfucy8ZIKBxBPoH1ol9mJ4RGPn4= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=ND6R4G09; arc=none smtp.client-ip=209.85.208.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="ND6R4G09" Received: by mail-lj1-f172.google.com with SMTP id 38308e7fff4ca-39c7fd45465so5696421fa.1 for ; Tue, 04 Aug 2026 23:05:11 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785909910; x=1786514710; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to:content-type; bh=bydW1aFXYq/H3/grM3GNxELGvfwDk9bqHM9XnIheb34=; b=ND6R4G09BnjwmD/wUoIbvC32O11Hh0tfixdQyocTGsGDl5Q3VHSW0ebvQ5R2h0M9do KHgaspkSp6IebfEwlqnuML5zKYNyGjA5qhaOlGBRfngD5W+VlCq7XYqEwMkBOY6Bdfb1 h/VAcIq9wVseQcaeAF806Y2zDZpVCVqguXhLvSKECnLWuUuYlvYhYnwHipO5+jL9h/wP PPrJyAWHyFkIed+XY4epLfv65BtSQcMe3Wye4FF3Ix0C15hRvnWeKYZaEQm0LU3e20jx vzc34pNQMKCpiaj+oowBi+nUWDR2Yy7QBgnH1dTNM7iqIlI3eFEHMEEo+Hrfz23HLA17 JSxQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785909910; x=1786514710; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=bydW1aFXYq/H3/grM3GNxELGvfwDk9bqHM9XnIheb34=; b=VQZA/IPifynhNxkrP8ER4d1ISJl4W8bAY65wE56KJNvHcK0YNMk0yiqYqWW5Cl0Z+9 qRQlrj6om6bp7yzW7We+ziA7Lc/QRfuftgIK9Xrz/m7rZbcWLrocxwwIL+ADfUgfrENB yKnWDnCZeVAbD1+STbKR54RQS3vad7EfbdfliGwlWN7NbDcCcfzQ8M2WPXtMAQUzH/t6 NESswAh2us/wEN1VK7pKJz3KQ3zEQ3Rx8ZbMec620T+w3YJzBRyCgOzdpMqYZRL4sLdx NLxKmVNg69EGgVR5R1F5vTkh9/qUGS8Ops3o2l2hmZiNu2CJNVxwug8qp3oQDcUzW4XC TZOA== X-Forwarded-Encrypted: i=1; AHgh+RphQjmMs0C3Ny6dGWyZRSRTOOjLUhYQj+D/hZGIvzEp2PxrdohG95EDa7FyLFQUikKgFB78nVf1qKHczEs=@vger.kernel.org X-Gm-Message-State: AOJu0YzaNxzB55Z6rsbIaDxKY2eQMJawgeOlUQT0A93mDYxJdKfqZjwd qf1ibDr9YtVgpAgTTQNZKcMZhKCH2PM2zVysvpztEgjJUMfS+jy2UPCx X-Gm-Gg: AR+sD12+uFHesQDVdQCuWNAO61hV7bXjS4bie/uTCvwNFiZVq1HJ/maFfU/lgvVzIiA fjaQRyQ2Nt7sdwM37J7An9u5d7CLjcL4rV/np9oORL+1DH193x9+51Er078shbjIQjJrsf+Clg1 308Ve3tKxGWOwP5wk+X4FIehaFzmMFW7j6tZ+7T0ZGjw+1g+i4EDqDDi2aaOsWsHOTsl/ZWCFpH cGU3n/V/DOcDr+nezho+t3f1DpPlyVrCi05+lfPQ+2VRWAY+8iULdOk9m4uOQ0zc72vhsS28NJx I8qSttv6OixMFuITrb8xXL8I28ZKGDogDRQMQMvPpRRpLgmMoFSOSXU8wO91l/evZbnFy021YQ2 7MnWKeFXc4+e/Y6TfNcQ10ZZMP4ok+ECYTh7YM8Lu7Fwf23amXc9OOO0sdHM4ck7xzUFhFbJ5Ut 4Cz9fX66u/xm6hG5QAZCn3WlZNmpxuPHthgTUS6JoI65GSR8vqhKX6v8gShGiBVJf+q5kPSWkfc dJ+0LdKxgrWVO4nXFIJpXj9LWmZqpspQ76XjB0CoI/EAS5a X-Received: by 2002:a05:651c:2117:b0:39c:7922:b43d with SMTP id 38308e7fff4ca-39fbb250562mr4695371fa.13.1785909909655; Tue, 04 Aug 2026 23:05:09 -0700 (PDT) Received: from va-HP-Pavilion-Desktop-595-p0xxx.mshome.net ([193.0.148.216]) by smtp.gmail.com with ESMTPSA id 38308e7fff4ca-39fb98b48d9sm6210401fa.7.2026.08.04.23.05.08 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 04 Aug 2026 23:05:09 -0700 (PDT) From: Valery Borovsky To: pablo@netfilter.org, fw@strlen.de Cc: netfilter-devel@vger.kernel.org, linux-kselftest@vger.kernel.org, shuah@kernel.org, linux-kernel@vger.kernel.org, Valery Borovsky Subject: [PATCH] selftests: netfilter: add functional test for nft ct timeout policies Date: Wed, 5 Aug 2026 09:05:07 +0300 Message-ID: <20260805060507.777983-1-vebohr@gmail.com> X-Mailer: git-send-email 2.53.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 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 --- 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 - </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