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