Commit d3665612f0 for bind
commit d3665612f0bb6f3d825f57c0d767b8a20c3a2450
Author: Nicki Křížek <nicki@isc.org>
Date: Thu Sep 10 15:28:20 2026 +0000
Replace testsock.pl and testsock6.pl with a Python tool
Both interface-presence probes become one isctest.tools.testsock
module: without arguments it sweeps the 10.53.0.* addresses read from
ifconfig.sh.in (formerly testsock.pl), with explicit addresses it
checks just those (formerly testsock6.pl). The pytest runner performs
the check in-process; start.pl and the legacy tests.sh call sites run
the tool with $PYTHON -m.
The IO::Socket::IP availability gate in testsock6() is gone with the
Perl: the probe always runs now, so the IPv6 test sections it guards
can no longer be silently skipped for lack of a Perl module.
Assisted-by: Claude:claude-fable-5
diff --git a/bin/tests/system/conf.sh b/bin/tests/system/conf.sh
index 1b180dda1a..2f78df6372 100644
--- a/bin/tests/system/conf.sh
+++ b/bin/tests/system/conf.sh
@@ -25,11 +25,7 @@ fi
export PYTHONPATH="$TOP_SRCDIR/bin/tests/system${PYTHONPATH:+:$PYTHONPATH}"
testsock6() {
- if test -n "$PERL" && $PERL -e "use IO::Socket::IP;" 2>/dev/null; then
- $PERL "$TOP_SRCDIR/bin/tests/system/testsock6.pl" "$@"
- else
- false
- fi
+ $PYTHON -m isctest.tools.testsock "$@"
}
echofail() {
diff --git a/bin/tests/system/conftest.py b/bin/tests/system/conftest.py
index d3a25cb3aa..1713c42e44 100644
--- a/bin/tests/system/conftest.py
+++ b/bin/tests/system/conftest.py
@@ -12,6 +12,7 @@
from pathlib import Path
from re import compile as Re
+import errno
import filecmp
import os
import shutil
@@ -28,6 +29,7 @@ pytest.register_assert_rewrite("isctest")
from isctest.vars.build import SYSTEM_TEST_DIR_GIT_PATH
import isctest
+import isctest.tools.testsock
# pylint: enable=wrong-import-position
@@ -585,12 +587,14 @@ def system_test(
"""
def check_net_interfaces():
+ port = int(os.environ["PORT"])
try:
- isctest.run.perl(
- f"{os.environ['srcdir']}/testsock.pl", ["-p", os.environ["PORT"]]
- )
- except subprocess.CalledProcessError as exc:
- isctest.log.error("testsock.pl: exited with code %d", exc.returncode)
+ for check_port in range(port, port + isctest.vars.ports.PORTS_PER_TEST):
+ isctest.tools.testsock.check_ipv4_interfaces(check_port)
+ except OSError as exc:
+ isctest.log.error("testsock: %s", exc)
+ if exc.errno == errno.EADDRINUSE:
+ raise RuntimeError(f"test port range is in use: {exc}") from exc
pytest.skip("Network interface aliases not set up.")
def setup_test():
diff --git a/bin/tests/system/dns64/tests.sh b/bin/tests/system/dns64/tests.sh
index 310ccfc11a..d8ecc3ba74 100644
--- a/bin/tests/system/dns64/tests.sh
+++ b/bin/tests/system/dns64/tests.sh
@@ -1479,7 +1479,7 @@ n=$((n + 1))
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
-if $PERL ../testsock6.pl fd92:7065:b8e:fffe::10.53.0.4 2>/dev/null; then
+if testsock6 fd92:7065:b8e:fffe::10.53.0.4 2>/dev/null; then
echo_i "checking resolver-use-dns64 ($n)"
ret=0
$DIG $DIGOPTS @10.53.0.3 no-aaaa aaaa >dig.out.ns3.test$n || ret=1
diff --git a/bin/tests/system/ifconfig.sh.in b/bin/tests/system/ifconfig.sh.in
index 126777c658..aea9a3f6ca 100755
--- a/bin/tests/system/ifconfig.sh.in
+++ b/bin/tests/system/ifconfig.sh.in
@@ -28,7 +28,7 @@
# interface MTU.
#
# See also org.isc.bind.system (a version of this script for use on macOS)
-# and testsock.pl (which checks the interfaces are configured)
+# and isctest/tools/testsock.py (which checks the interfaces are configured)
#
sys=@SYSTEM@
@@ -172,7 +172,7 @@ sequence() (
#
# See also `org.isc.bind.system`.
#
-# This `max` setting is grepped out for use by testsock.pl
+# This `max` setting is read by isctest/tools/testsock.py
#
max=11
case $1 in
diff --git a/bin/tests/system/isctest/tools/testsock.py b/bin/tests/system/isctest/tools/testsock.py
new file mode 100644
index 0000000000..862be20671
--- /dev/null
+++ b/bin/tests/system/isctest/tools/testsock.py
@@ -0,0 +1,106 @@
+# 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.
+
+"""
+Check that the test network interfaces are up.
+
+Try to bind() a UDP socket on each of the given addresses (IPv4 or IPv6).
+
+When no address is specified, check the 10.53.0.* test addresses.
+"""
+
+from collections.abc import Iterable
+from pathlib import Path
+
+import argparse
+import re
+import socket
+import sys
+
+import dns.inet
+
+# ifconfig.sh.in sets the test interfaces up; its max= setting is the
+# authoritative count of the 10.53.0.* addresses.
+IFCONFIG_SCRIPT = Path(__file__).resolve().parents[2] / "ifconfig.sh.in"
+
+
+def check_addr(address: str, port: int = 0) -> None:
+ """
+ Try to bind a UDP socket to the given address and port; raise
+ OSError on failure and ValueError for a malformed address.
+ """
+ try:
+ family = dns.inet.af_for_address(address)
+ except ValueError:
+ raise ValueError(f"{address}: not an IPv4 or IPv6 address") from None
+ with socket.socket(family, socket.SOCK_DGRAM) as sock:
+ try:
+ sock.bind((address, port))
+ except OSError as exc:
+ raise OSError(
+ exc.errno, f"bind({address}, {port}): {exc.strerror}"
+ ) from exc
+
+
+def interface_ids() -> range:
+ """
+ Return the range of configured test interface ids, read from the
+ max= setting in ifconfig.sh.in.
+ """
+ matches = re.findall(
+ r"^max=(\d+)\s*$",
+ IFCONFIG_SCRIPT.read_text(encoding="utf-8"),
+ flags=re.MULTILINE,
+ )
+ if not matches:
+ raise RuntimeError(f"could not find max IP address in {IFCONFIG_SCRIPT}")
+ return range(1, int(matches[-1]) + 1)
+
+
+def check_ipv4_interfaces(port: int = 0, server_id: int | None = None) -> None:
+ """
+ Check that the 10.53.0.* test interfaces (or just 10.53.0.<server_id>)
+ are up and the given port can be bound on them; raise OSError on
+ failure.
+ """
+ ids: Iterable[int]
+ if server_id is not None:
+ ids = [server_id]
+ else:
+ ids = interface_ids()
+ for interface_id in ids:
+ check_addr(f"10.53.0.{interface_id}", port)
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser(prog="testsock", description=__doc__)
+ parser.add_argument(
+ "-p", "--port", type=int, default=0, help="UDP port to bind (default: any)"
+ )
+ parser.add_argument(
+ "-i", "--id", type=int, help="check only the 10.53.0.<id> interface"
+ )
+ parser.add_argument(
+ "address", nargs="*", help="addresses to check instead of 10.53.0.*"
+ )
+ args = parser.parse_args()
+ try:
+ if args.address:
+ for address in args.address:
+ check_addr(address, args.port)
+ else:
+ check_ipv4_interfaces(args.port, args.id)
+ except (OSError, ValueError) as exc:
+ sys.exit(f"testsock: {exc}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/bin/tests/system/org.isc.bind.system b/bin/tests/system/org.isc.bind.system
index 48a5756eaa..bf68419ca8 100644
--- a/bin/tests/system/org.isc.bind.system
+++ b/bin/tests/system/org.isc.bind.system
@@ -11,7 +11,7 @@
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.
-# see also ifconfig.sh.in and testsock.pl
+# see also ifconfig.sh.in and isctest/tools/testsock.py
ifup() {
/sbin/ifconfig lo0 10.53.$1.$3 alias
diff --git a/bin/tests/system/start.pl b/bin/tests/system/start.pl
index 57d99270ef..551b6da369 100755
--- a/bin/tests/system/start.pl
+++ b/bin/tests/system/start.pl
@@ -100,6 +100,14 @@ my $DIG = $ENV{'DIG'};
my $PERL = $ENV{'PERL'};
my $PYTHON = $ENV{'PYTHON'};
+# Make the isctest package importable, for the standalone helper tools
+# ($PYTHON -m isctest.tools.<name>).
+if (defined $ENV{'PYTHONPATH'}) {
+ $ENV{'PYTHONPATH'} = "$srcdir:$ENV{'PYTHONPATH'}";
+} else {
+ $ENV{'PYTHONPATH'} = $srcdir;
+}
+
# Start the server(s)
my @ns;
@@ -173,7 +181,7 @@ sub check_ns_port {
my $tries = 0;
while (1) {
- my $return = system("$PERL $srcdir/testsock.pl -p $port $options");
+ my $return = system("$PYTHON -m isctest.tools.testsock -p $port $options");
if ($return == 0) {
last;
diff --git a/bin/tests/system/testsock.pl b/bin/tests/system/testsock.pl
deleted file mode 100755
index e793874d93..0000000000
--- a/bin/tests/system/testsock.pl
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/usr/bin/perl
-
-# 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.
-
-# Test whether the interfaces on 10.53.0.* are up.
-
-require 5.001;
-
-use Cwd 'abs_path';
-use File::Basename;
-use Socket;
-use Getopt::Long;
-
-my $port = 0;
-my $id = 0;
-GetOptions("p=i" => \$port,
- "i=i" => \$id);
-
-my @ids;
-if ($id != 0) {
- @ids = ($id);
-} else {
- my $dir = dirname(abs_path($0));
- my $fn = "$dir/ifconfig.sh.in";
- open FH, "< $fn" or die "open < $fn: $!\n";
- while (<FH>) {
- @ids = (1..$1)
- if /^max=(\d+)\s*$/;
- }
- close FH;
- die "could not find max IP address in $fn\n"
- unless @ids > 1;
-}
-
-foreach $id (@ids) {
- my $addr = pack("C4", 10, 53, 0, $id);
- my $sa = pack_sockaddr_in($port, $addr);
- socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname("tcp"))
- or die "$0: socket: $!\n";
- setsockopt(SOCK, SOL_SOCKET, SO_REUSEADDR, pack("l", 1));
-
- bind(SOCK, $sa)
- or die sprintf("$0: bind(%s, %d): $!\n",
- inet_ntoa($addr), $port);
- close(SOCK);
-}
diff --git a/bin/tests/system/testsock6.pl b/bin/tests/system/testsock6.pl
deleted file mode 100644
index 9d4e5b7a8f..0000000000
--- a/bin/tests/system/testsock6.pl
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/perl
-
-# 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.
-
-require 5.001;
-
-use IO::Socket::IP;
-
-foreach $addr (@ARGV) {
- my $sock;
- $sock = IO::Socket::IP->new(LocalAddr => $addr,
- Domain => PF_INET6,
- LocalPort => 0,
- Proto => tcp)
- or die "Can't bind : $@\n";
- close($sock);
-}