From: Štěpán Balážik Date: Sat, 25 Apr 2026 14:13:59 +0000 (+0200) Subject: Port the long TCP stream test to Python X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=561e443daf8f83c91be115f6828e277708f694ff;p=thirdparty%2Fbind9.git Port the long TCP stream test to Python 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. --- 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 index 7520c3a35f9..00000000000 --- a/bin/tests/system/tcp/1996-alloc_dnsbuf-crash-test.pkt +++ /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 index fc829f77f48..00000000000 --- a/bin/tests/system/tcp/tests.sh +++ /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 index 8aba3c456c0..00000000000 --- a/bin/tests/system/tcp/tests_sh_tcp.py +++ /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() diff --git a/bin/tests/system/tcp/tests_tcp.py b/bin/tests/system/tcp/tests_tcp.py index c4353657429..2045ba9940a 100644 --- a/bin/tests/system/tcp/tests_tcp.py +++ b/bin/tests/system/tcp/tests_tcp.py @@ -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)