]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Test named reconfiguration during zone transfer's SOA request
authorAram Sargsyan <aram@isc.org>
Wed, 21 May 2025 14:57:47 +0000 (14:57 +0000)
committerOndřej Surý <ondrej@isc.org>
Wed, 28 May 2025 16:20:13 +0000 (16:20 +0000)
This new test checks that named can correctly process an interrupted
SOA request during zone transfer, caused by reconfiguration.

Co-authored-by: Michał Kępień <michal@isc.org>
bin/tests/system/xfer/ans9/ans.py [new file with mode: 0644]
bin/tests/system/xfer/ns6/named.conf.in
bin/tests/system/xfer/tests.sh
bin/tests/system/xfer/tests_sh_xfer.py

diff --git a/bin/tests/system/xfer/ans9/ans.py b/bin/tests/system/xfer/ans9/ans.py
new file mode 100644 (file)
index 0000000..56c80be
--- /dev/null
@@ -0,0 +1,111 @@
+"""
+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.
+"""
+
+from typing import AsyncGenerator
+
+import dns.message
+import dns.rdataclass
+import dns.rdatatype
+import dns.rrset
+
+from isctest.asyncserver import (
+    ControllableAsyncDnsServer,
+    DomainHandler,
+    QueryContext,
+    ResponseAction,
+    DnsResponseSend,
+    ToggleResponsesCommand,
+)
+
+
+class AXFRServer(DomainHandler):
+    """
+    Yield SOA and AXFR responses. Every new AXFR response increments the SOA
+    version.
+    """
+
+    domains = ["xfr-and-reconfig"]
+
+    def __init__(self) -> None:
+        super().__init__()
+        self.soa_version = 0
+
+    async def get_responses(
+        self, qctx: QueryContext
+    ) -> AsyncGenerator[ResponseAction, None]:
+        # This is oversimplified because I am lazy - we are appending the SOA
+        # RRset to the ANSWER section for _every_ QTYPE.  named is only
+        # expected to send a SOA query over UDP and then an AXFR query over
+        # TCP.  Responses to both of those start with a SOA RRset in the ANSWER
+        # section :-)
+        soa_message = dns.message.make_response(qctx.query)
+        soa_rrset = dns.rrset.from_text(
+            qctx.qname,
+            300,
+            dns.rdataclass.IN,
+            dns.rdatatype.SOA,
+            f". . {self.soa_version} 0 0 0 0",
+        )
+        soa_message.answer.append(soa_rrset)
+
+        yield DnsResponseSend(soa_message, authoritative=True)
+
+        if qctx.qtype == dns.rdatatype.SOA:
+            # If QTYPE=SOA, the SOA record is the complete response.
+            return
+
+        if qctx.qtype != dns.rdatatype.AXFR:
+            # If QTYPE=AXFR, we will continue cramming RRsets into the ANSWER
+            # section of a subsequent DNS message below.
+            #
+            # If QTYPE was not SOA or AXFR, abort.  Yeah, we just sent a broken
+            # response by yielding DnsResponseSend() with a SOA RRset in the
+            # ANSWER section above.  We will have to carry that burden for the
+            # rest of our lives.
+            return
+
+        # Send just the obligatory NS RRset at zone apex in the next message.
+        # This is stupidly inefficient, but makes looping below simpler as we
+        # will already have been done with the mandatory stuff by then.
+        ns_message = dns.message.make_response(qctx.query)
+        ns_rrset = dns.rrset.from_text(
+            qctx.qname, 300, dns.rdataclass.IN, dns.rdatatype.NS, "."
+        )
+        ns_message.answer.append(ns_rrset)
+
+        yield DnsResponseSend(ns_message, authoritative=True)
+
+        # Generate the AXFR with a txt rrset.
+        txt_message = dns.message.make_response(qctx.query)
+        txt_rrset = dns.rrset.from_text(
+            qctx.qname,
+            300,
+            dns.rdataclass.IN,
+            dns.rdatatype.TXT,
+            "foo bar",
+        )
+        txt_message.answer.append(txt_rrset)
+
+        yield DnsResponseSend(txt_message, authoritative=True)
+
+        # Finish the AXFR transaction by sending the second SOA RRset.
+        yield DnsResponseSend(soa_message, authoritative=True)
+
+        # This makes sure that the next SOA request causes a new zone transfer
+        self.soa_version += 1
+
+
+if __name__ == "__main__":
+    server = ControllableAsyncDnsServer([ToggleResponsesCommand])
+    server.install_response_handler(AXFRServer())
+    server.run()
index 6a51f6e82d0616a0b30d0097921f35ac11bbcd11..9261f0dd4e12c77330ea1880c8b5bad621e63de9 100644 (file)
@@ -111,3 +111,10 @@ zone "ixfr-too-many-diffs" {
        primaries { 10.53.0.1; };
        file "ixfr-too-many-diffs.bk";
 };
+
+zone "xfr-and-reconfig" {
+       type secondary;
+       primaries { 10.53.0.9; };
+       file "xfr-and-reconfig.bk";
+       request-ixfr no; # ans9 supports only axfr
+};
index 8098da23d62d31045de9d65d489d01eedd592e6b..1a5b1d0b7e3ce7c401fbc781e4328379974546fd 100755 (executable)
@@ -19,6 +19,10 @@ DIGOPTS="+tcp +noadd +nosea +nostat +noquest +nocomm +nocmd -p ${PORT}"
 RNDCCMD="$RNDC -c ../_common/rndc.conf -p ${CONTROLPORT} -s"
 NS_PARAMS="-m record -c named.conf -d 99 -g -T maxcachesize=2097152"
 
+dig_with_opts() (
+  "$DIG" -p "$PORT" "$@"
+)
+
 status=0
 n=0
 
@@ -752,5 +756,44 @@ if [ $tmp -eq 0 ]; then
 fi
 status=$((status + tmp))
 
+nextpart ns6/named.run >/dev/null
+
+sendcmd() (
+  dig_with_opts "@${1}" "${2}._control." TXT +time=5 +tries=1 +tcp >/dev/null 2>&1
+)
+
+# See #5307#note_558185
+n=$((n + 1))
+echo_i "test reconfiguration when zone transfer is in the middle of a SOA query (part 1) ($n)"
+tmp=0
+# Check that xfr-and-reconfig has been successfully transferred by the secondary.
+grep -F 'zone xfr-and-reconfig/IN: zone transfer finished: success' ns6/named.run 2>&1 >/dev/null || tmp=0
+# Make ans6 receive queries without responding to them.
+sendcmd 10.53.0.9 "disable.send-responses"
+sleep 1
+# Try to reload the zone from an unresponsive primary.
+$RNDCCMD 10.53.0.6 reload xfr-and-reconfig 2>&1 | sed 's/^/ns6 /' | cat_i
+sleep 1
+# Reconfigure named while zone transfer attempt is in progress.
+$RNDCCMD 10.53.0.6 reconfig 2>&1 | sed 's/^/ns6 /' | cat_i
+# Confirm that the ongoing SOA request was canceled, caused by the reconfiguratoin.
+retry_quiet 60 wait_for_message "refresh: request result: shutting down" || tmp=1
+if test $tmp != 0; then echo_i "failed"; fi
+status=$((status + tmp))
+
+nextpart ns6/named.run >/dev/null
+
+n=$((n + 1))
+echo_i "test reconfiguration when zone transfer is in the middle of a SOA query (part 2) ($n)"
+tmp=0
+# Make ans6 receive queries and respond to them.
+sendcmd 10.53.0.9 "enable.send-responses"
+sleep 1
+# Try to reload the zone from the primary.
+$RNDCCMD 10.53.0.6 reload xfr-and-reconfig 2>&1 | sed 's/^/ns6 /' | cat_i
+retry_quiet 60 wait_for_message "zone xfr-and-reconfig/IN: Transfer started." || tmp=1
+if test $tmp != 0; then echo_i "failed"; fi
+status=$((status + tmp))
+
 echo_i "exit status: $status"
 [ $status -eq 0 ] || exit 1
index 0b82e0bf12e0dd11c0856dd176f23044e0b39a5d..d94f90b98113c2c8754a045be410e84689f44c35 100644 (file)
@@ -51,6 +51,7 @@ pytestmark = pytest.mark.extra_artifacts(
         "ns6/primary.db.jnl",
         "ns6/sec.bk",
         "ns6/xot-primary-try-next.bk",
+        "ns6/xfr-and-reconfig.bk",
         "ns7/edns-expire.bk",
         "ns7/primary2.db",
         "ns7/sec.bk",