]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Port the long TCP stream test to Python
authorŠtěpán Balážik <stepan@isc.org>
Sat, 25 Apr 2026 14:13:59 +0000 (16:13 +0200)
committerŠtěpán Balážik <stepan@isc.org>
Sat, 25 Apr 2026 18:28:02 +0000 (20:28 +0200)
Previously, the packet.pl script was used to send the a series of frames
to named; this worked by accident as most of these were refused by the
kernel with EAGAIN. packet.pl prints a dot every 1000 packets, so this
slowed the script donw and allowed some frames to get through.

Reimplement the test in Python: build the packet with dnspython, send
~6 MiB of data over TCP discarding all replies and then check if the
server is still alive.

bin/tests/system/tcp/1996-alloc_dnsbuf-crash-test.pkt [deleted file]
bin/tests/system/tcp/tests.sh [deleted file]
bin/tests/system/tcp/tests_sh_tcp.py [deleted file]
bin/tests/system/tcp/tests_tcp.py

diff --git a/bin/tests/system/tcp/1996-alloc_dnsbuf-crash-test.pkt b/bin/tests/system/tcp/1996-alloc_dnsbuf-crash-test.pkt
deleted file mode 100644 (file)
index 7520c3a..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-# Transaction ID
-0001
-# Standard query
-0000
-# Questions: 1, Additional: 1
-0001 0000 0000 0000
-# QNAME: www.isc.org
-03 697363 03 6F7267 00
-# Type: AXFR
-00fc
-# Class: IN
-0001
diff --git a/bin/tests/system/tcp/tests.sh b/bin/tests/system/tcp/tests.sh
deleted file mode 100644 (file)
index fc829f7..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/bin/sh
-
-# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
-#
-# SPDX-License-Identifier: MPL-2.0
-#
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0.  If a copy of the MPL was not distributed with this
-# file, you can obtain one at https://mozilla.org/MPL/2.0/.
-#
-# See the COPYRIGHT file distributed with this work for additional
-# information regarding copyright ownership.
-
-set -e
-
-# shellcheck source=../conf.sh
-. ../conf.sh
-
-dig_with_opts() {
-  "${DIG}" -p "${PORT}" "$@"
-}
-
-rndccmd() {
-  "${RNDC}" -p "${CONTROLPORT}" -c ../_common/rndc.conf -s "$@"
-}
-
-status=0
-n=0
-
-####################################################
-# NOTE: The next test resets the debug level to 1. #
-####################################################
-
-n=$((n + 1))
-echo_i "checking that BIND 9 doesn't crash on long TCP messages ($n)"
-ret=0
-# Avoid logging useless information.
-rndccmd 10.53.0.1 trace 1 || ret=1
-{ $PERL ../packet.pl -a "10.53.0.1" -p "${PORT}" -t tcp -r 300000 1996-alloc_dnsbuf-crash-test.pkt || ret=1; } | cat_i
-dig_with_opts +tcp @10.53.0.1 txt.example >dig.out.test$n || ret=1
-if [ $ret != 0 ]; then echo_i "failed"; fi
-status=$((status + ret))
-
-echo_i "exit status: $status"
-[ $status -eq 0 ] || exit 1
diff --git a/bin/tests/system/tcp/tests_sh_tcp.py b/bin/tests/system/tcp/tests_sh_tcp.py
deleted file mode 100644 (file)
index 8aba3c4..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
-#
-# SPDX-License-Identifier: MPL-2.0
-#
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0.  If a copy of the MPL was not distributed with this
-# file, you can obtain one at https://mozilla.org/MPL/2.0/.
-#
-# See the COPYRIGHT file distributed with this work for additional
-# information regarding copyright ownership.
-
-import pytest
-
-pytestmark = pytest.mark.extra_artifacts(
-    [
-        "dig.out.*",
-    ]
-)
-
-
-def test_tcp(run_tests_sh):
-    run_tests_sh()
index c4353657429eaed4288595a6a72c7e91ea61f5b6..2045ba9940a957abfbedf742252aa6e44c917b32 100644 (file)
@@ -16,6 +16,7 @@ from types import TracebackType
 from typing import NamedTuple
 
 import asyncio
+import contextlib
 import socket
 import struct
 import time
@@ -243,6 +244,46 @@ def wait_for_tcp_status(
     return status
 
 
+async def send_long_tcp_stream(
+    host: str, port: int, message: dns.message.Message, min_bytes: int
+) -> None:
+    frame = message.to_wire(prepend_length=True)
+    chunk_frames = max(1, 65536 // len(frame))
+    frames_remaining = (min_bytes + len(frame) - 1) // len(frame)
+
+    async def discard_stream(reader: asyncio.StreamReader) -> None:
+        with contextlib.suppress(OSError):
+            while await reader.read(65535):
+                pass
+
+    async def run() -> None:
+        reader, writer = await asyncio.open_connection(host, port)
+        discard_task = asyncio.create_task(discard_stream(reader))
+        try:
+            remaining = frames_remaining
+            while remaining > 0:
+                frames = min(chunk_frames, remaining)
+                writer.write(frame * frames)
+                await writer.drain()
+                remaining -= frames
+
+            writer.write_eof()
+            await writer.drain()
+
+            writer.close()
+            with contextlib.suppress(ConnectionError, OSError):
+                await writer.wait_closed()
+            await discard_task
+        finally:
+            writer.close()
+            if not discard_task.done():
+                discard_task.cancel()
+            with contextlib.suppress(asyncio.CancelledError):
+                await discard_task
+
+    await asyncio.wait_for(run(), timeout=TIMEOUT)
+
+
 def test_tcp_garbage(named_port: int) -> None:
     with create_socket("10.53.0.7", named_port) as sock:
         msg = isctest.query.create(
@@ -424,3 +465,22 @@ def test_tcp_high_water(named_port: int, ns5: NamedInstance) -> None:
             check_tcp_response(ns5.ip)
 
     asyncio.run(run())
+
+
+def test_long_tcp_messages(named_port: int, ns1: NamedInstance) -> None:
+    isctest.log.info("checking that BIND 9 doesn't crash on long TCP messages")
+    ns1.rndc("trace 1")
+    stream_bytes = 6 * 1024 * 1024
+    msg = isctest.query.create(
+        "isc.org.",
+        "AXFR",
+        dnssec=False,
+        use_edns=False,
+        rd=False,
+        ad=False,
+        message_id=1,
+    )
+    asyncio.run(send_long_tcp_stream(ns1.ip, named_port, msg, stream_bytes))
+
+    msg = isctest.query.create("txt.example.", "A")
+    isctest.query.tcp(msg, ns1.ip)