From: Aram Sargsyan Date: Wed, 21 May 2025 14:57:47 +0000 (+0000) Subject: Test named reconfiguration during zone transfer's SOA request X-Git-Tag: v9.21.9~33^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=aa6ca3e77682462ed3af8bc42ea8590addba6626;p=thirdparty%2Fbind9.git Test named reconfiguration during zone transfer's SOA request 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ń --- diff --git a/bin/tests/system/xfer/ans9/ans.py b/bin/tests/system/xfer/ans9/ans.py new file mode 100644 index 00000000000..56c80becb9b --- /dev/null +++ b/bin/tests/system/xfer/ans9/ans.py @@ -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() diff --git a/bin/tests/system/xfer/ns6/named.conf.in b/bin/tests/system/xfer/ns6/named.conf.in index 6a51f6e82d0..9261f0dd4e1 100644 --- a/bin/tests/system/xfer/ns6/named.conf.in +++ b/bin/tests/system/xfer/ns6/named.conf.in @@ -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 +}; diff --git a/bin/tests/system/xfer/tests.sh b/bin/tests/system/xfer/tests.sh index 8098da23d62..1a5b1d0b7e3 100755 --- a/bin/tests/system/xfer/tests.sh +++ b/bin/tests/system/xfer/tests.sh @@ -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 diff --git a/bin/tests/system/xfer/tests_sh_xfer.py b/bin/tests/system/xfer/tests_sh_xfer.py index 0b82e0bf12e..d94f90b9811 100644 --- a/bin/tests/system/xfer/tests_sh_xfer.py +++ b/bin/tests/system/xfer/tests_sh_xfer.py @@ -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",