Commit 70d7c2feac for bind

commit 70d7c2feac1bb2ee06d345392f47fc62a98d3da3
Author: Michal Nowak <mnowak@isc.org>
Date:   Mon Oct 5 13:01:41 2026 +0000

    Rewrite pipequeries in pure Python

    The pipelined test used a dedicated C helper built on top of libdns's
    request manager just to send a handful of queries over a single TCP
    connection before reading any response.  dnspython does this with a
    fraction of the code and without compiling a test-only binary.

    It also sidesteps the FreeBSD "address in use" failures: netmgr binds
    outgoing TCP sockets to port 0 through uv_tcp_bind(), which sets
    SO_REUSEADDR, and on FreeBSD that lets the kernel pick a source port
    still held in TIME_WAIT by a previous connection to the same server.
    The Python tool lets connect() choose the port itself, without
    SO_REUSEADDR, so the four-tuple is always free.

    Assisted-by: Claude:claude-opus-5-5

diff --git a/REUSE.toml b/REUSE.toml
index 06414e623c..2602a08198 100644
--- a/REUSE.toml
+++ b/REUSE.toml
@@ -87,9 +87,7 @@ path = [
 	"bin/tests/system/nsupdate/verylarge.j2",
 	"bin/tests/system/org.isc.bind.system.plist",
 	"bin/tests/system/pipelined/input",
-	"bin/tests/system/pipelined/inputb",
 	"bin/tests/system/pipelined/ref",
-	"bin/tests/system/pipelined/refb",
 	"bin/tests/system/requirements.txt",
 	"bin/tests/system/rsabigexponent/ns2/dsset-example.in",
 	"bin/tests/system/run.gdb",
diff --git a/bin/tests/system/isctest/tools/pipequeries.py b/bin/tests/system/isctest/tools/pipequeries.py
new file mode 100644
index 0000000000..e2336e0383
--- /dev/null
+++ b/bin/tests/system/isctest/tools/pipequeries.py
@@ -0,0 +1,83 @@
+# 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.
+
+"""
+Send A queries for the names read from stdin over a single TCP
+connection, all of them before reading any response, and print the
+answer sections in the order the responses arrive.
+"""
+
+import argparse
+import random
+import socket
+import sys
+import time
+
+import dns.message
+import dns.query
+import dns.rcode
+
+TIMEOUT = 30
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser(prog="pipequeries", description=__doc__)
+    parser.add_argument(
+        "-s",
+        "--server",
+        default="127.0.0.1",
+        help="server address (default: %(default)s)",
+    )
+    parser.add_argument(
+        "-p",
+        "--port",
+        type=int,
+        default=5300,
+        help="server port (default: %(default)s)",
+    )
+    args = parser.parse_args()
+
+    # Use sequential IDs from a random start, so that repeated runs do
+    # not reuse the same ID sequence, just like ditch.py does.
+    first_id = random.getrandbits(16)
+    pending = {}
+    for offset, qname in enumerate(sys.stdin.read().split()):
+        msgid = (first_id + offset) & 0xFFFF
+        query = dns.message.make_query(qname, "A", id=msgid)
+        pending[msgid] = query
+
+    expiration = time.time() + TIMEOUT
+    with socket.create_connection((args.server, args.port), timeout=TIMEOUT) as sock:
+        # socket.create_connection() leaves the socket in timeout mode,
+        # where every recv() call would get its own TIMEOUT; switch to
+        # non-blocking mode so that dns.query.send_tcp() and
+        # dns.query.receive_tcp() honor the shared expiration deadline.
+        sock.setblocking(False)
+        for query in pending.values():
+            dns.query.send_tcp(sock, query, expiration)
+
+        while pending:
+            response, _ = dns.query.receive_tcp(sock, expiration)
+            sent = pending.get(response.id)
+            if sent is None or not sent.is_response(response):
+                sys.exit(f"I:unexpected response:\n{response}")
+            del pending[response.id]
+            if response.rcode() != dns.rcode.NOERROR:
+                sys.exit(f"I:response rcode: {dns.rcode.to_text(response.rcode())}")
+            count = response.section_count(dns.message.ANSWER)
+            if count != 1:
+                print(f"I:response answer count ({count}!=1)", file=sys.stderr)
+            for rrset in response.answer:
+                print(rrset.to_text(), flush=True)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/bin/tests/system/isctest/vars/basic.py b/bin/tests/system/isctest/vars/basic.py
index bf33f56978..fcee93fd47 100644
--- a/bin/tests/system/isctest/vars/basic.py
+++ b/bin/tests/system/isctest/vars/basic.py
@@ -46,7 +46,6 @@ BASIC_VARS = {
     "VERIFY": f"{BUILD_VARS['TOP_BUILDDIR']}/dnssec-verify",
     "WIREFORMAT": f"{BUILD_VARS['TOP_BUILDDIR']}/named-wireformat",
     "BIGKEY": f"{BUILD_VARS['TOP_BUILDDIR']}/bigkey",
-    "PIPEQUERIES": f"{BUILD_VARS['TOP_BUILDDIR']}/pipequeries",
     "TMPDIR": os.getenv("TMPDIR", "/tmp"),
     "KRB5_CONFIG": "/dev/null",  # we don't want a KRB5_CONFIG setting breaking the tests
     "LC_ALL": "C",
diff --git a/bin/tests/system/meson.build b/bin/tests/system/meson.build
index fa3e5f2d37..5a2bec5d4d 100644
--- a/bin/tests/system/meson.build
+++ b/bin/tests/system/meson.build
@@ -22,7 +22,6 @@ subdir('isctest' / 'vars' / '.build_vars')
 system_test_binaries += {
     # 'bigkey': files('rsabigexponent' / 'bigkey.c'),
     'feature-test': files('feature-test.c'),
-    'pipequeries': files('pipelined' / 'pipequeries.c'),
 }

 system_test_libraries += {
diff --git a/bin/tests/system/pipelined/.gitignore b/bin/tests/system/pipelined/.gitignore
deleted file mode 100644
index 7d8bd02e19..0000000000
--- a/bin/tests/system/pipelined/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-pipequeries
diff --git a/bin/tests/system/pipelined/inputb b/bin/tests/system/pipelined/inputb
deleted file mode 100644
index 6ea367e278..0000000000
--- a/bin/tests/system/pipelined/inputb
+++ /dev/null
@@ -1,8 +0,0 @@
-e.examplea
-e.exampleb
-f.examplea
-f.exampleb
-g.examplea
-g.exampleb
-h.examplea
-h.exampleb
diff --git a/bin/tests/system/pipelined/pipequeries.c b/bin/tests/system/pipelined/pipequeries.c
deleted file mode 100644
index 7255adbfed..0000000000
--- a/bin/tests/system/pipelined/pipequeries.c
+++ /dev/null
@@ -1,293 +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.
- */
-
-#include <inttypes.h>
-#include <stdbool.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <isc/base64.h>
-#include <isc/commandline.h>
-#include <isc/hash.h>
-#include <isc/lib.h>
-#include <isc/log.h>
-#include <isc/loop.h>
-#include <isc/managers.h>
-#include <isc/mem.h>
-#include <isc/net.h>
-#include <isc/netmgr.h>
-#include <isc/parseint.h>
-#include <isc/result.h>
-#include <isc/sockaddr.h>
-#include <isc/util.h>
-
-#include <dns/dispatch.h>
-#include <dns/fixedname.h>
-#include <dns/lib.h>
-#include <dns/message.h>
-#include <dns/name.h>
-#include <dns/rdataset.h>
-#include <dns/request.h>
-#include <dns/result.h>
-#include <dns/types.h>
-#include <dns/view.h>
-
-#define CHECKM(str, x)                                       \
-	{                                                    \
-		if ((x) != ISC_R_SUCCESS) {                  \
-			fprintf(stderr, "I:%s: %s\n", (str), \
-				isc_result_totext(x));       \
-			exit(EXIT_FAILURE);                  \
-		}                                            \
-	}
-
-#define RUNCHECK(x) RUNTIME_CHECK((x) == ISC_R_SUCCESS)
-
-#define PORT	5300
-#define TIMEOUT 30
-
-static dns_requestmgr_t *requestmgr = NULL;
-static bool have_src = false;
-static isc_sockaddr_t srcaddr;
-static isc_sockaddr_t dstaddr;
-static int onfly;
-
-static void
-recvresponse(void *arg) {
-	isc_result_t result;
-	dns_request_t *request = (dns_request_t *)arg;
-	dns_message_t *query = dns_request_getarg(request);
-	dns_message_t *response = NULL;
-	isc_buffer_t outbuf;
-	char output[1024];
-
-	result = dns_request_getresult(request);
-	if (result != ISC_R_SUCCESS) {
-		fprintf(stderr, "I:request event result: %s\n",
-			isc_result_totext(result));
-		exit(EXIT_FAILURE);
-	}
-
-	dns_message_create(isc_g_mctx, NULL, NULL, DNS_MESSAGE_INTENTPARSE,
-			   &response);
-
-	result = dns_request_getresponse(request, response,
-					 DNS_MESSAGEPARSE_PRESERVEORDER);
-	CHECKM("dns_request_getresponse", result);
-
-	if (response->rcode != dns_rcode_noerror) {
-		result = dns_result_fromrcode(response->rcode);
-		fprintf(stderr, "I:response rcode: %s\n",
-			isc_result_totext(result));
-		exit(EXIT_FAILURE);
-	}
-	if (response->counts[DNS_SECTION_ANSWER] != 1U) {
-		fprintf(stderr, "I:response answer count (%u!=1)\n",
-			response->counts[DNS_SECTION_ANSWER]);
-	}
-
-	isc_buffer_init(&outbuf, output, sizeof(output));
-	result = dns_message_sectiontotext(
-		response, DNS_SECTION_ANSWER, &dns_master_style_simple,
-		DNS_MESSAGETEXTFLAG_NOCOMMENTS, &outbuf);
-	CHECKM("dns_message_sectiontotext", result);
-	printf("%.*s", (int)isc_buffer_usedlength(&outbuf),
-	       (char *)isc_buffer_base(&outbuf));
-	fflush(stdout);
-
-	dns_message_detach(&query);
-	dns_message_detach(&response);
-	dns_request_destroy(&request);
-
-	if (--onfly == 0) {
-		isc_loopmgr_shutdown();
-	}
-	return;
-}
-
-static isc_result_t
-sendquery(void) {
-	dns_request_t *request = NULL;
-	dns_message_t *message = NULL;
-	dns_name_t *qname = NULL;
-	dns_rdataset_t *qrdataset = NULL;
-	isc_result_t result;
-	dns_fixedname_t queryname;
-	isc_buffer_t buf;
-	static char host[256];
-	int c;
-
-	c = scanf("%255s", host);
-	if (c == EOF) {
-		return ISC_R_NOMORE;
-	}
-
-	onfly++;
-
-	dns_fixedname_init(&queryname);
-	isc_buffer_init(&buf, host, strlen(host));
-	isc_buffer_add(&buf, strlen(host));
-	result = dns_name_fromtext(dns_fixedname_name(&queryname), &buf,
-				   dns_rootname, 0);
-	CHECKM("dns_name_fromtext", result);
-
-	dns_message_create(isc_g_mctx, NULL, NULL, DNS_MESSAGE_INTENTRENDER,
-			   &message);
-
-	message->opcode = dns_opcode_query;
-	message->flags |= DNS_MESSAGEFLAG_RD;
-	message->rdclass = dns_rdataclass_in;
-	message->id = (unsigned short)(random() & 0xFFFF);
-
-	dns_message_gettempname(message, &qname);
-
-	dns_message_gettemprdataset(message, &qrdataset);
-
-	dns_name_clone(dns_fixedname_name(&queryname), qname);
-	dns_rdataset_makequestion(qrdataset, dns_rdataclass_in,
-				  dns_rdatatype_a);
-	ISC_LIST_APPEND(qname->list, qrdataset, link);
-	dns_message_addname(message, qname, DNS_SECTION_QUESTION);
-
-	result = dns_request_create(
-		requestmgr, message, have_src ? &srcaddr : NULL, &dstaddr, NULL,
-		NULL, DNS_REQUESTOPT_TCP, NULL, TIMEOUT, TIMEOUT, 0, 0,
-		isc_loop_main(), recvresponse, message, &request);
-	CHECKM("dns_request_create", result);
-
-	return ISC_R_SUCCESS;
-}
-
-static void
-sendqueries(void *arg) {
-	isc_result_t result;
-
-	UNUSED(arg);
-
-	do {
-		result = sendquery();
-	} while (result == ISC_R_SUCCESS);
-
-	if (onfly == 0) {
-		isc_loopmgr_shutdown();
-	}
-	return;
-}
-
-static void
-teardown_view(void *arg) {
-	dns_view_t *view = arg;
-	dns_view_detach(&view);
-}
-
-static void
-teardown_requestmgr(void *arg) {
-	dns_requestmgr_t *mgr = arg;
-
-	dns_requestmgr_shutdown(mgr);
-	dns_requestmgr_detach(&mgr);
-}
-
-static void
-teardown_dispatchv4(void *arg) {
-	dns_dispatch_t *dispatchv4 = arg;
-	dns_dispatch_detach(&dispatchv4);
-}
-
-static void
-teardown_dispatchmgr(void *arg) {
-	dns_dispatchmgr_t *dispatchmgr = arg;
-	dns_dispatchmgr_detach(&dispatchmgr);
-}
-
-int
-main(int argc, char *argv[]) {
-	isc_sockaddr_t bind_any;
-	struct in_addr inaddr;
-	isc_result_t result;
-	dns_dispatchmgr_t *dispatchmgr = NULL;
-	dns_dispatch_t *dispatchv4 = NULL;
-	dns_view_t *view = NULL;
-	uint16_t port = PORT;
-	int c;
-
-	isc_commandline_errprint = false;
-	while ((c = isc_commandline_parse(argc, argv, "p:r:")) != -1) {
-		switch (c) {
-		case 'p':
-			result = isc_parse_uint16(&port,
-						  isc_commandline_argument, 10);
-			if (result != ISC_R_SUCCESS) {
-				fprintf(stderr, "bad port '%s'\n",
-					isc_commandline_argument);
-				exit(EXIT_FAILURE);
-			}
-			break;
-		case 'r':
-			fprintf(stderr, "The -r option has been deprecated.\n");
-			break;
-		case '?':
-			fprintf(stderr, "%s: invalid argument '%c'", argv[0],
-				c);
-			break;
-		default:
-			break;
-		}
-	}
-
-	argc -= isc_commandline_index;
-	argv += isc_commandline_index;
-	POST(argv);
-
-	if (argc > 0) {
-		have_src = true;
-	}
-
-	isc_sockaddr_any(&bind_any);
-
-	result = ISC_R_FAILURE;
-	if (inet_pton(AF_INET, "10.53.0.7", &inaddr) != 1) {
-		CHECKM("inet_pton", result);
-	}
-	isc_sockaddr_fromin(&srcaddr, &inaddr, 0);
-
-	result = ISC_R_FAILURE;
-	if (inet_pton(AF_INET, "10.53.0.4", &inaddr) != 1) {
-		CHECKM("inet_pton", result);
-	}
-	isc_sockaddr_fromin(&dstaddr, &inaddr, port);
-
-	isc_managers_create(1);
-
-	RUNCHECK(dns_dispatchmgr_create(isc_g_mctx, &dispatchmgr));
-
-	RUNCHECK(dns_dispatch_createudp(
-		dispatchmgr, have_src ? &srcaddr : &bind_any, &dispatchv4));
-	RUNCHECK(dns_requestmgr_create(isc_g_mctx, dispatchmgr, dispatchv4,
-				       NULL, &requestmgr));
-
-	dns_view_create(isc_g_mctx, NULL, dns_rdataclass_in, "_test", &view);
-
-	isc_loopmgr_setup(sendqueries, NULL);
-	isc_loopmgr_teardown(teardown_view, view);
-	isc_loopmgr_teardown(teardown_requestmgr, requestmgr);
-	isc_loopmgr_teardown(teardown_dispatchv4, dispatchv4);
-	isc_loopmgr_teardown(teardown_dispatchmgr, dispatchmgr);
-
-	isc_loopmgr_run();
-
-	isc_managers_destroy();
-
-	return 0;
-}
diff --git a/bin/tests/system/pipelined/refb b/bin/tests/system/pipelined/refb
deleted file mode 100644
index a24c6bc4b8..0000000000
--- a/bin/tests/system/pipelined/refb
+++ /dev/null
@@ -1,8 +0,0 @@
-e.examplea. 10.0.1.5
-e.exampleb. 10.0.2.5
-f.examplea. 10.0.1.6
-f.exampleb. 10.0.2.6
-g.examplea. 10.0.1.7
-g.exampleb. 10.0.2.7
-h.examplea. 10.0.1.8
-h.exampleb. 10.0.2.8
diff --git a/bin/tests/system/pipelined/tests.sh b/bin/tests/system/pipelined/tests.sh
index 8b87df1fce..1dcd38a6e7 100644
--- a/bin/tests/system/pipelined/tests.sh
+++ b/bin/tests/system/pipelined/tests.sh
@@ -24,7 +24,7 @@ rndccmd() {
 }

 pipequeries() {
-  "$PIPEQUERIES" -p "${PORT}"
+  "$PYTHON" -m isctest.tools.pipequeries -s 10.53.0.4 -p "${PORT}"
 }

 status=0