Commit 87ed5e5efc for bind

commit 87ed5e5efce75fd75464b30aeb0e4d1cf8178f11
Author: Nicki Křížek <nicki@isc.org>
Date:   Mon Sep 21 12:49:33 2026 +0000

    Replace ditch.pl with isctest.tools.ditch

    The query-burst tool used by fetchlimit and serve_stale becomes a
    dnspython script with the same options.  It was the only reason the
    fetchlimit test required the Perl Net::DNS module, so that mark goes
    with it.

    Assisted-by: Claude:claude-fable-5-1

diff --git a/bin/tests/system/ditch.pl b/bin/tests/system/ditch.pl
deleted file mode 100644
index f77d02224d..0000000000
--- a/bin/tests/system/ditch.pl
+++ /dev/null
@@ -1,94 +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.
-
-# This is a tool for sending queries via UDP to specified address and
-# port, then exiting without waiting for a response.
-#
-# Usage: ditch.pl [-s <address>] [-p <port>] [filename]
-#
-# Input (in filename, if specified, otherwise stdin) is a series of one
-# or more DNS names and types to send as queries, e.g.:
-#
-# www.example.com A
-# www.example.org MX
-#
-# If not specified, address defaults to 127.0.0.1, port to 53.
-
-require 5.006.001;
-
-use strict;
-use Getopt::Std;
-use Net::DNS;
-use Net::DNS::Packet;
-use IO::File;
-use IO::Socket;
-
-sub usage {
-    print ("Usage: ditch.pl [-s address] [-p port] [-b source_port] [file]\n");
-    exit 1;
-}
-
-my %options={};
-getopts("s:p:b:", \%options);
-
-my $addr = "127.0.0.1";
-$addr = $options{s} if defined $options{s};
-
-my $port = 53;
-$port = $options{p} if defined $options{p};
-
-my $source_port = 0;
-$source_port = $options{b} if defined $options{b};
-
-my $file = "STDIN";
-if (@ARGV >= 1) {
-    my $filename = shift @ARGV;
-    open FH, "<$filename" or die "$filename: $!";
-    $file = "FH";
-}
-
-my $input = "";
-while (defined(my $line = <$file>) ) {
-    chomp $line;
-    next if ($line =~ m/^ *#/);
-    my @tokens = split (' ', $line);
-
-    my $packet;
-    if ($Net::DNS::VERSION > 0.68) {
-            $packet = new Net::DNS::Packet();
-            $@ and die $@;
-    } else {
-            my $err;
-            ($packet, $err) = new Net::DNS::Packet();
-            $err and die $err;
-    }
-
-    my $q = new Net::DNS::Question($tokens[0], $tokens[1], "IN");
-    $packet->header->rd(1);
-    $packet->push(question => $q);
-
-    my $sock = IO::Socket::INET->new(
-        PeerAddr => $addr,
-        PeerPort => $port,
-        Proto => "udp",
-        LocalPort => $source_port,
-        ) or die "$!";
-
-    my $bytes = $sock->send($packet->data);
-    #print ("sent $bytes bytes to $addr:$port:\n");
-    #print ("  ", unpack("H* ", $packet->data), "\n");
-
-    $sock->close;
-}
-
-close $file;
diff --git a/bin/tests/system/fetchlimit/tests.sh b/bin/tests/system/fetchlimit/tests.sh
index 9ca4b70df7..03c456c6bc 100644
--- a/bin/tests/system/fetchlimit/tests.sh
+++ b/bin/tests/system/fetchlimit/tests.sh
@@ -46,7 +46,7 @@ burst() {
       echo "${num}${2}${3}.lamesub.example A" >>burst.input.$$
     fi
   done
-  $PERL ../ditch.pl -p ${PORT} -s ${server} -b ${EXTRAPORT8} burst.input.$$
+  $PYTHON -m isctest.tools.ditch -p ${PORT} -s ${server} -b ${EXTRAPORT8} burst.input.$$
   rm -f burst.input.$$
 }

diff --git a/bin/tests/system/fetchlimit/tests_sh_fetchlimit.py b/bin/tests/system/fetchlimit/tests_sh_fetchlimit.py
index 3ff7552d8d..1c68ed714e 100644
--- a/bin/tests/system/fetchlimit/tests_sh_fetchlimit.py
+++ b/bin/tests/system/fetchlimit/tests_sh_fetchlimit.py
@@ -11,9 +11,7 @@

 import pytest

-import isctest.mark
-
-EXTRA_ARTIFACTS = pytest.mark.extra_artifacts(
+pytestmark = pytest.mark.extra_artifacts(
     [
         "dig.out.*",
         "wait_for_message.*",
@@ -24,11 +22,6 @@ EXTRA_ARTIFACTS = pytest.mark.extra_artifacts(
     ]
 )

-pytestmark = [
-    isctest.mark.requires_net_dns,
-    EXTRA_ARTIFACTS,
-]
-

 def test_fetchlimit(run_tests_sh):
     run_tests_sh()
diff --git a/bin/tests/system/isctest/tools/ditch.py b/bin/tests/system/isctest/tools/ditch.py
new file mode 100644
index 0000000000..cec223121a
--- /dev/null
+++ b/bin/tests/system/isctest/tools/ditch.py
@@ -0,0 +1,84 @@
+# 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 DNS queries over UDP without waiting for the replies.
+
+Read "name type" lines from a file or stdin and fire one query per
+line at the server, all from the same source port; the replies are
+never read.  This is how a test bursts a resolver with queries, e.g.
+to drive it into its fetch limits.
+"""
+
+from collections.abc import Iterable
+
+import argparse
+import fileinput
+import socket
+
+import dns.inet
+import dns.message
+
+
+def send_queries(
+    lines: Iterable[str], address: str, port: int, source_port: int = 0
+) -> int:
+    """
+    Send one recursive query per "name type" line to address:port over
+    UDP without reading any replies.  Blank lines and lines starting
+    with "#" are skipped.  Return the number of queries sent.
+    """
+    family = dns.inet.af_for_address(address)
+    sent = 0
+    with socket.socket(family, socket.SOCK_DGRAM) as sock:
+        sock.bind(("", source_port))
+        for line in lines:
+            fields = line.split()
+            if not fields or fields[0].startswith("#"):
+                continue
+            name, rdtype = fields[:2]
+            query = dns.message.make_query(name, rdtype)
+            sock.sendto(query.to_wire(), (address, port))
+            sent += 1
+    return sent
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser(prog="ditch", 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=53, help="server port (default: %(default)s)"
+    )
+    parser.add_argument(
+        "-b",
+        "--source-port",
+        type=int,
+        default=0,
+        help="source port to send from (default: any)",
+    )
+    parser.add_argument(
+        "file",
+        nargs="?",
+        default="-",
+        help="file with one 'name type' query per line (default: stdin)",
+    )
+    args = parser.parse_args()
+    with fileinput.input(files=args.file, encoding="utf-8") as lines:
+        send_queries(lines, args.server, args.port, args.source_port)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/bin/tests/system/serve_stale/tests.sh b/bin/tests/system/serve_stale/tests.sh
index d349744503..9606c41b3e 100755
--- a/bin/tests/system/serve_stale/tests.sh
+++ b/bin/tests/system/serve_stale/tests.sh
@@ -2599,7 +2599,7 @@ burst() {
     num=$((num - 1))
     echo "fetch${num}.example A" >>burst.input.$$
   done
-  $PERL ../ditch.pl -p ${PORT} -s 10.53.0.3 -b ${EXTRAPORT8} burst.input.$$
+  $PYTHON -m isctest.tools.ditch -p ${PORT} -s 10.53.0.3 -b ${EXTRAPORT8} burst.input.$$
   rm -f burst.input.$$
 }