]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Use isctest.asyncserver in the "ixfr" system test
authorŠtěpán Balážik <stepan@isc.org>
Tue, 23 Dec 2025 13:41:18 +0000 (14:41 +0100)
committerŠtěpán Balážik <stepan@isc.org>
Wed, 14 Jan 2026 11:29:59 +0000 (12:29 +0100)
Replace the usage of the `bin/tests/system/ans.pl` server with an
instance of ControllableAsyncServer.

bin/tests/system/ixfr/ans2/ans.py [new file with mode: 0644]
bin/tests/system/ixfr/ans2/startme [deleted file]
bin/tests/system/ixfr/tests.sh
bin/tests/system/ixfr/tests_sh_ixfr.py

diff --git a/bin/tests/system/ixfr/ans2/ans.py b/bin/tests/system/ixfr/ans2/ans.py
new file mode 100644 (file)
index 0000000..8659244
--- /dev/null
@@ -0,0 +1,265 @@
+"""
+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 abc
+
+import dns.rcode
+import dns.rdataclass
+import dns.rdatatype
+import dns.rrset
+
+from typing import AsyncGenerator, Collection, Iterable
+
+from isctest.asyncserver import (
+    ControllableAsyncDnsServer,
+    DnsResponseSend,
+    QueryContext,
+    ResponseHandler,
+    SwitchControlCommand,
+)
+
+
+def rrset(owner: str, rdtype: dns.rdatatype.RdataType, rdata: str) -> dns.rrset.RRset:
+    return dns.rrset.from_text(
+        owner,
+        300,
+        dns.rdataclass.IN,
+        rdtype,
+        rdata,
+    )
+
+
+def soa(serial: int, *, owner: str = "nil.") -> dns.rrset.RRset:
+    return rrset(
+        owner,
+        dns.rdatatype.SOA,
+        f"ns.nil. root.nil. {serial} 300 300 604800 300",
+    )
+
+
+def ns() -> dns.rrset.RRset:
+    return rrset(
+        "nil.",
+        dns.rdatatype.NS,
+        "ns.nil.",
+    )
+
+
+def a(address: str, *, owner: str) -> dns.rrset.RRset:
+    return rrset(
+        owner,
+        dns.rdatatype.A,
+        address,
+    )
+
+
+def txt(data: str, *, owner: str = "nil.") -> dns.rrset.RRset:
+    return rrset(
+        owner,
+        dns.rdatatype.TXT,
+        f'"{data}"',
+    )
+
+
+class SoaHandler(ResponseHandler):
+    def __init__(self, serial: int):
+        self._serial = serial
+
+    def match(self, qctx: QueryContext) -> bool:
+        return qctx.qtype == dns.rdatatype.SOA
+
+    async def get_responses(
+        self, qctx: QueryContext
+    ) -> AsyncGenerator[DnsResponseSend, None]:
+        qctx.response.answer.append(soa(self._serial))
+        yield DnsResponseSend(qctx.response)
+
+
+class AxfrHandler(ResponseHandler):
+    @property
+    @abc.abstractmethod
+    def answers(self) -> Iterable[Collection[dns.rrset.RRset]]:
+        """
+        Answer sections of response packets sent in response to
+        AXFR queries.
+        """
+        raise NotImplementedError
+
+    def match(self, qctx: QueryContext) -> bool:
+        return qctx.qtype == dns.rdatatype.AXFR
+
+    async def get_responses(
+        self, qctx: QueryContext
+    ) -> AsyncGenerator[DnsResponseSend, None]:
+        for answer in self.answers:
+            response = qctx.prepare_new_response()
+            for rrset_ in answer:
+                response.answer.append(rrset_)
+            yield DnsResponseSend(response)
+
+
+class IxfrHandler(ResponseHandler):
+    @property
+    @abc.abstractmethod
+    def answer(self) -> Collection[dns.rrset.RRset]:
+        """
+        Answer section of a response packet sent in response to
+        IXFR queries.
+        """
+        raise NotImplementedError
+
+    def match(self, qctx: QueryContext) -> bool:
+        return qctx.qtype == dns.rdatatype.IXFR
+
+    async def get_responses(
+        self, qctx: QueryContext
+    ) -> AsyncGenerator[DnsResponseSend, None]:
+        for rrset_ in self.answer:
+            qctx.response.answer.append(rrset_)
+        yield DnsResponseSend(qctx.response)
+
+
+class InitialAfxrHandler(AxfrHandler):
+    answers = (
+        (soa(1),),
+        (
+            ns(),
+            txt("initial AXFR"),
+            a("10.0.0.61", owner="a.nil."),
+            a("10.0.0.62", owner="b.nil."),
+        ),
+        (soa(1),),
+    )
+
+
+class SuccessfulIfxrHandler(IxfrHandler):
+    answer = (
+        soa(3),
+        soa(1),
+        a("10.0.0.61", owner="a.nil."),
+        txt("initial AXFR"),
+        soa(2),
+        txt("successful IXFR"),
+        a("10.0.1.61", owner="a.nil."),
+        soa(2),
+        soa(3),
+        soa(3),
+    )
+
+
+class NotExactIxfrHandler(IxfrHandler):
+    answer = (
+        soa(4),
+        soa(3),
+        txt("delete-nonexistent-txt-record"),
+        soa(4),
+        txt("this-txt-record-would-be-added"),
+        soa(4),
+    )
+
+
+class FallbackNotExactAxfrHandler(AxfrHandler):
+    answers = (
+        (soa(3),),
+        (
+            ns(),
+            txt("fallback AXFR"),
+        ),
+        (soa(3),),
+    )
+
+
+class TooManyRecordsIxfrHandler(IxfrHandler):
+    answer = (
+        soa(4),
+        soa(3),
+        soa(4),
+        txt("text 1"),
+        txt("text 2"),
+        txt("text 3"),
+        txt("text 4"),
+        txt("text 5"),
+        txt("text 6: causing too many records"),
+        soa(4),
+    )
+
+
+class FallbackTooManyRecordsAxfrHandler(AxfrHandler):
+    answers = (
+        (
+            soa(3),
+            ns(),
+            txt("fallback AXFR on too many records"),
+        ),
+        (soa(3),),
+    )
+
+
+class BadSoaOwnerIxfrHandler(IxfrHandler):
+    answer = (
+        soa(4),
+        soa(3),
+        soa(4, owner="bad-owner."),
+        txt("serial 4, malformed IXFR", owner="test.nil."),
+        soa(4),
+    )
+
+
+class FallbackBadSoaOwnerAxfrHandler(AxfrHandler):
+    answers = (
+        (soa(4),),
+        (
+            ns(),
+            txt("serial 4, fallback AXFR", owner="test.nil."),
+        ),
+        (soa(4),),
+    )
+
+
+def main() -> None:
+    server = ControllableAsyncDnsServer(
+        default_aa=True, default_rcode=dns.rcode.NOERROR
+    )
+    switch_command = SwitchControlCommand(
+        {
+            "initial_axfr": (
+                SoaHandler(1),
+                InitialAfxrHandler(),
+            ),
+            "successful_ixfr": (
+                SoaHandler(3),
+                SuccessfulIfxrHandler(),
+            ),
+            "not_exact": (
+                SoaHandler(4),
+                NotExactIxfrHandler(),
+                FallbackNotExactAxfrHandler(),
+            ),
+            "too_many_records": (
+                SoaHandler(4),
+                TooManyRecordsIxfrHandler(),
+                FallbackTooManyRecordsAxfrHandler(),
+            ),
+            "bad_soa_owner": (
+                SoaHandler(4),
+                BadSoaOwnerIxfrHandler(),
+                FallbackBadSoaOwnerAxfrHandler(),
+            ),
+        }
+    )
+    server.install_control_command(switch_command)
+    server.run()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/bin/tests/system/ixfr/ans2/startme b/bin/tests/system/ixfr/ans2/startme
deleted file mode 100644 (file)
index e69de29..0000000
index 864adefb09b7d4e2464debe90d4f8ec2c7f81552..ef783ab6dea95afbba883f85e9d29f45a8b89701 100644 (file)
@@ -32,27 +32,16 @@ n=0
 DIGOPTS="+tcp +noadd +nosea +nostat +noquest +nocomm +nocmd -p ${PORT}"
 RNDCCMD="$RNDC -p ${CONTROLPORT} -c ../_common/rndc.conf -s"
 
-sendcmd() {
-  send 10.53.0.2 "${EXTRAPORT1}"
+switch_responses() {
+  RESPONSES_KEY="${1}"
+  $DIG $DIGOPTS "@10.53.0.2" "${RESPONSES_KEY}.switch._control." TXT +time=5 +tries=1 +tcp >/dev/null 2>&1
 }
 
 n=$((n + 1))
 echo_i "testing initial AXFR ($n)"
 ret=0
 
-sendcmd <<EOF
-/SOA/
-nil.           300     SOA     ns.nil. root.nil. 1 300 300 604800 300
-/AXFR/
-nil.           300     SOA     ns.nil. root.nil. 1 300 300 604800 300
-/AXFR/
-nil.           300     NS      ns.nil.
-nil.           300     TXT     "initial AXFR"
-a.nil.         60      A       10.0.0.61
-b.nil.         60      A       10.0.0.62
-/AXFR/
-nil.           300     SOA     ns.nil. root.nil. 1 300 300 604800 300
-EOF
+switch_responses "initial_axfr"
 
 sleep 1
 
@@ -84,21 +73,7 @@ ret=0
 # We change the IP address of a.nil., and the TXT record at the apex.
 # Then we do a SOA-only update.
 
-sendcmd <<EOF
-/SOA/
-nil.           300     SOA     ns.nil. root.nil. 3 300 300 604800 300
-/IXFR/
-nil.           300     SOA     ns.nil. root.nil. 3 300 300 604800 300
-nil.           300     SOA     ns.nil. root.nil. 1 300 300 604800 300
-a.nil.         60      A       10.0.0.61
-nil.           300     TXT     "initial AXFR"
-nil.           300     SOA     ns.nil. root.nil. 2 300 300 604800 300
-nil.           300     TXT     "successful IXFR"
-a.nil.         60      A       10.0.1.61
-nil.           300     SOA     ns.nil. root.nil. 2 300 300 604800 300
-nil.           300     SOA     ns.nil. root.nil. 3 300 300 604800 300
-nil.           300     SOA     ns.nil. root.nil. 3 300 300 604800 300
-EOF
+switch_responses "successful_ixfr"
 
 sleep 1
 
@@ -115,25 +90,7 @@ echo_i "testing AXFR fallback after IXFR failure (not exact error) ($n)"
 ret=0
 
 # Provide a broken IXFR response and a working fallback AXFR response
-
-sendcmd <<EOF
-/SOA/
-nil.           300     SOA     ns.nil. root.nil. 4 300 300 604800 300
-/IXFR/
-nil.           300     SOA     ns.nil. root.nil. 4 300 300 604800 300
-nil.           300     SOA     ns.nil. root.nil. 3 300 300 604800 300
-nil.           300     TXT     "delete-nonexistent-txt-record"
-nil.           300     SOA     ns.nil. root.nil. 4 300 300 604800 300
-nil.           300     TXT     "this-txt-record-would-be-added"
-nil.           300     SOA     ns.nil. root.nil. 4 300 300 604800 300
-/AXFR/
-nil.           300     SOA     ns.nil. root.nil. 3 300 300 604800 300
-/AXFR/
-nil.           300     NS      ns.nil.
-nil.           300     TXT     "fallback AXFR"
-/AXFR/
-nil.           300     SOA     ns.nil. root.nil. 3 300 300 604800 300
-EOF
+switch_responses "not_exact"
 
 sleep 1
 
@@ -150,28 +107,7 @@ echo_i "testing AXFR fallback after IXFR failure (too many records) ($n)"
 ret=0
 
 # Provide an IXFR response that would cause a "too many records" condition
-
-sendcmd <<EOF
-/SOA/
-nil.           300     SOA     ns.nil. root.nil. 4 300 300 604800 300
-/IXFR/
-nil.           300     SOA     ns.nil. root.nil. 4 300 300 604800 300
-nil.           300     SOA     ns.nil. root.nil. 3 300 300 604800 300
-nil.           300     SOA     ns.nil. root.nil. 4 300 300 604800 300
-nil.           300     TXT     "text 1"
-nil.           300     TXT     "text 2"
-nil.           300     TXT     "text 3"
-nil.           300     TXT     "text 4"
-nil.           300     TXT     "text 5"
-nil.           300     TXT     "text 6: causing too many records"
-nil.           300     SOA     ns.nil. root.nil. 4 300 300 604800 300
-/AXFR/
-nil.           300     SOA     ns.nil. root.nil. 3 300 300 604800 300
-nil.           300     NS      ns.nil.
-nil.           300     TXT     "fallback AXFR on too many records"
-/AXFR/
-nil.           300     SOA     ns.nil. root.nil. 3 300 300 604800 300
-EOF
+switch_responses "too_many_records"
 
 sleep 1
 
@@ -196,23 +132,7 @@ ret=0
 nextpart ns1/named.run >/dev/null
 
 # Provide a broken IXFR response and a working fallback AXFR response.
-sendcmd <<EOF
-/SOA/
-nil.           300     SOA     ns.nil. root.nil. 4 300 300 604800 300
-/IXFR/
-nil.           300     SOA     ns.nil. root.nil. 4 300 300 604800 300
-nil.           300     SOA     ns.nil. root.nil. 3 300 300 604800 300
-bad-owner.     300     SOA     ns.nil. root.nil. 4 300 300 604800 300
-test.nil.      300     TXT     "serial 4, malformed IXFR"
-nil.           300     SOA     ns.nil. root.nil. 4 300 300 604800 300
-/AXFR/
-nil.           300     SOA     ns.nil. root.nil. 4 300 300 604800 300
-/AXFR/
-nil.           300     NS      ns.nil.
-test.nil.              300     TXT     "serial 4, fallback AXFR"
-/AXFR/
-nil.           300     SOA     ns.nil. root.nil. 4 300 300 604800 300
-EOF
+switch_responses "bad_soa_owner"
 $RNDCCMD 10.53.0.1 refresh nil | sed 's/^/ns1 /' | cat_i
 
 # A broken server would accept the malformed IXFR and apply its contents to the
index 171a0c6d2dbd7224eeb226aaf4cc7dfe7c07e4ca..a8b8143bfc7821403c4997fb65d66de531c8a960 100644 (file)
@@ -11,6 +11,9 @@
 
 import pytest
 
+# isctest.asyncserver requires dnspython >= 2.0.0
+pytest.importorskip("dns", minversion="2.0.0")
+
 pytestmark = pytest.mark.extra_artifacts(
     [
         "dig.out*",