From: Štěpán Balážik Date: Sat, 25 Apr 2026 14:00:38 +0000 (+0200) Subject: Port TCP request statistics checks to Python X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=f2316d7dc6a717342ab0e941c023e61edae07214;p=thirdparty%2Fbind9.git Port TCP request statistics checks to Python Add a helper that runs `rndc stats` and reads the TCP request counter from named.stats, then use it to port the resolver and forwarder checks from the shell script to tests_tcp.py. Record named.stats as an extra artifact so the generated statistics remain available after test runs. --- diff --git a/bin/tests/system/tcp/tests.sh b/bin/tests/system/tcp/tests.sh index 18276c8441b..3e0f203c5e3 100644 --- a/bin/tests/system/tcp/tests.sh +++ b/bin/tests/system/tcp/tests.sh @@ -27,50 +27,6 @@ rndccmd() { status=0 n=0 -n=$((n + 1)) -echo_i "initializing TCP statistics ($n)" -ret=0 -rndccmd 10.53.0.1 stats || ret=1 -rndccmd 10.53.0.2 stats || ret=1 -mv ns1/named.stats ns1/named.stats.test$n -mv ns2/named.stats ns2/named.stats.test$n -ntcp10="$(grep "TCP requests received" ns1/named.stats.test$n | tail -1 | awk '{print $1}')" -ntcp20="$(grep "TCP requests received" ns2/named.stats.test$n | tail -1 | awk '{print $1}')" -if [ $ret != 0 ]; then echo_i "failed"; fi -status=$((status + ret)) - -n=$((n + 1)) -echo_i "checking TCP request statistics (resolver) ($n)" -ret=0 -dig_with_opts @10.53.0.3 txt.example. >dig.out.test$n -sleep 1 -rndccmd 10.53.0.1 stats || ret=1 -rndccmd 10.53.0.2 stats || ret=1 -mv ns1/named.stats ns1/named.stats.test$n -mv ns2/named.stats ns2/named.stats.test$n -ntcp11="$(grep "TCP requests received" ns1/named.stats.test$n | tail -1 | awk '{print $1}')" -ntcp21="$(grep "TCP requests received" ns2/named.stats.test$n | tail -1 | awk '{print $1}')" -if [ "$ntcp10" -ge "$ntcp11" ]; then ret=1; fi -if [ "$ntcp20" -ne "$ntcp21" ]; then ret=1; fi -if [ $ret != 0 ]; then echo_i "failed"; fi -status=$((status + ret)) - -n=$((n + 1)) -echo_i "checking TCP request statistics (forwarder) ($n)" -ret=0 -dig_with_opts @10.53.0.4 txt.example. >dig.out.test$n -sleep 1 -rndccmd 10.53.0.1 stats || ret=1 -rndccmd 10.53.0.2 stats || ret=1 -mv ns1/named.stats ns1/named.stats.test$n -mv ns2/named.stats ns2/named.stats.test$n -ntcp12="$(grep "TCP requests received" ns1/named.stats.test$n | tail -1 | awk '{print $1}')" -ntcp22="$(grep "TCP requests received" ns2/named.stats.test$n | tail -1 | awk '{print $1}')" -if [ "$ntcp11" -ne "$ntcp12" ]; then ret=1; fi -if [ "$ntcp21" -ge "$ntcp22" ]; then ret=1; fi -if [ $ret != 0 ]; then echo_i "failed"; fi -status=$((status + ret)) - # -------- TCP high-water tests ---------- refresh_tcp_stats() { rndccmd 10.53.0.5 status >rndc.out.$n || ret=1 diff --git a/bin/tests/system/tcp/tests_sh_tcp.py b/bin/tests/system/tcp/tests_sh_tcp.py index 0e0c9fc0e50..f2430db9eee 100644 --- a/bin/tests/system/tcp/tests_sh_tcp.py +++ b/bin/tests/system/tcp/tests_sh_tcp.py @@ -17,7 +17,6 @@ pytestmark = pytest.mark.extra_artifacts( "rndc.out.*", "ans*/ans.run", "ans*/ans.run.prev", - "ns*/named.stats.*", ] ) diff --git a/bin/tests/system/tcp/tests_tcp.py b/bin/tests/system/tcp/tests_tcp.py index 940b89f7c74..be2605884ff 100644 --- a/bin/tests/system/tcp/tests_tcp.py +++ b/bin/tests/system/tcp/tests_tcp.py @@ -18,6 +18,7 @@ import time import dns.message import dns.name import dns.query +import dns.rcode import dns.rrset import pytest @@ -25,7 +26,7 @@ from isctest.instance import NamedInstance import isctest -pytestmark = pytest.mark.extra_artifacts(["ans*/ans.run"]) +pytestmark = pytest.mark.extra_artifacts(["ns*/named.stats"]) TIMEOUT: int = 10 @@ -36,6 +37,15 @@ def create_socket(host: str, port: int) -> socket.socket: return sock +def tcp_requests_received(ns: NamedInstance) -> int: + ns.rndc("stats") + stats = isctest.text.TextFile(str(ns.directory / "named.stats")) + matches = stats.grep("TCP requests received") + assert matches, f"'TCP requests received' not found in {stats}" + # `rndc stats` appends to the file; only the last occurrence is current + return int(matches[-1].string.split()[0]) + + def tcp_round_trip( sock: socket.socket, msg: dns.message.Message ) -> dns.message.Message: @@ -133,3 +143,47 @@ def test_tcp_big(ns7: NamedInstance, named_port: int) -> None: "a.example.", "A", dnssec=False, use_edns=-1, ad=False ) tcp_round_trip(sock, msg) + + +def wait_for_stable_tcp_requests(ns: NamedInstance, timeout: int = 10) -> int: + """Read the TCP request counter until it stops changing. + + The counter is incremented on request receipt, so a client response + implies its upstream queries are already counted; this only needs to + absorb unsynchronized traffic such as the resolver's root priming query. + """ + last = -1 + + def stable() -> bool: + nonlocal last + previous, last = last, tcp_requests_received(ns) + return previous == last + + isctest.run.retry_with_timeout(stable, timeout=timeout) + return last + + +def test_tcp_request_statistics( + ns1: NamedInstance, ns2: NamedInstance, ns3: NamedInstance, ns4: NamedInstance +) -> None: + isctest.log.info("initializing TCP statistics") + ns1_tcp = tcp_requests_received(ns1) + ns2_tcp = tcp_requests_received(ns2) + + isctest.log.info("checking TCP request statistics (resolver)") + msg = isctest.query.create("txt.example.", "A") + isctest.query.udp(msg, ns3.ip, expected_rcode=dns.rcode.NXDOMAIN) + + ns1_tcp_after_resolver = wait_for_stable_tcp_requests(ns1) + ns2_tcp_after_resolver = wait_for_stable_tcp_requests(ns2) + assert ns1_tcp < ns1_tcp_after_resolver + assert ns2_tcp == ns2_tcp_after_resolver + + isctest.log.info("checking TCP request statistics (forwarder)") + msg = isctest.query.create("txt.example.", "A") + isctest.query.udp(msg, ns4.ip, expected_rcode=dns.rcode.NXDOMAIN) + + ns1_tcp_after_forwarder = wait_for_stable_tcp_requests(ns1) + ns2_tcp_after_forwarder = wait_for_stable_tcp_requests(ns2) + assert ns1_tcp_after_resolver == ns1_tcp_after_forwarder + assert ns2_tcp_after_resolver < ns2_tcp_after_forwarder