]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Reimplement 'resolver/ans2' server using AsyncDnsServer
authorŠtěpán Balážik <stepan@isc.org>
Thu, 25 Dec 2025 16:03:20 +0000 (17:03 +0100)
committerŠtěpán Balážik <stepan@isc.org>
Sat, 24 Jan 2026 12:04:09 +0000 (13:04 +0100)
Ensure packet-for-packet compatibility with the old server including
bugs.

bin/tests/system/resolver/ans2/ans.pl [deleted file]
bin/tests/system/resolver/ans2/ans.py [new file with mode: 0644]
bin/tests/system/resolver/tests_resolver.py

diff --git a/bin/tests/system/resolver/ans2/ans.pl b/bin/tests/system/resolver/ans2/ans.pl
deleted file mode 100644 (file)
index 8ec4b0a..0000000
+++ /dev/null
@@ -1,195 +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.
-
-#
-# Ad hoc name server
-#
-
-use IO::File;
-use IO::Socket;
-use Net::DNS;
-use Net::DNS::Packet;
-
-print "Using Net::DNS $Net::DNS::VERSION\n";
-
-my $localport = int($ENV{'PORT'});
-if (!$localport) { $localport = 5300; }
-
-my $sock = IO::Socket::INET->new(LocalAddr => "10.53.0.2",
-   LocalPort => $localport, Proto => "udp") or die "$!";
-
-my $pidf = new IO::File "ans.pid", "w" or die "cannot open pid file: $!";
-print $pidf "$$\n" or die "cannot write pid file: $!";
-$pidf->close or die "cannot close pid file: $!";
-sub rmpid { unlink "ans.pid"; exit 1; };
-
-$SIG{INT} = \&rmpid;
-$SIG{TERM} = \&rmpid;
-
-for (;;) {
-       $sock->recv($buf, 512);
-
-       print "**** request from " , $sock->peerhost, " port ", $sock->peerport, "\n";
-
-       my $packet;
-
-       if ($Net::DNS::VERSION > 0.68) {
-               $packet = new Net::DNS::Packet(\$buf, 0);
-               $@ and die $@;
-       } else {
-               my $err;
-               ($packet, $err) = new Net::DNS::Packet(\$buf, 0);
-               $err and die $err;
-       }
-
-       print "REQUEST:\n";
-       $packet->print;
-
-       $packet->header->qr(1);
-
-       my @questions = $packet->question;
-       my $qname = $questions[0]->qname;
-       my $qtype = $questions[0]->qtype;
-
-       if ($qname eq "com" && $qtype eq "NS") {
-               $packet->header->aa(1);
-               $packet->push("answer", new Net::DNS::RR("com 300 NS a.root-servers.nil."));
-       } elsif ($qname eq "example.com" && $qtype eq "NS") {
-               $packet->header->aa(1);
-               $packet->push("answer", new Net::DNS::RR("example.com 300 NS a.root-servers.nil."));
-       } elsif ($qname eq "cname1.example.com") {
-               # Data for the "cname + other data / 1" test
-               $packet->push("answer", new Net::DNS::RR("cname1.example.com 300 CNAME cname1.example.com"));
-               $packet->push("answer", new Net::DNS::RR("cname1.example.com 300 A 1.2.3.4"));
-       } elsif ($qname eq "cname2.example.com") {
-               # Data for the "cname + other data / 2" test: same RRs in opposite order
-               $packet->push("answer", new Net::DNS::RR("cname2.example.com 300 A 1.2.3.4"));
-               $packet->push("answer", new Net::DNS::RR("cname2.example.com 300 CNAME cname2.example.com"));
-       } elsif ($qname =~ /redirect\.com/) {
-               $packet->push("authority", new Net::DNS::RR("redirect.com 300 NS ns.redirect.com"));
-               $packet->push("additional", new Net::DNS::RR("ns.redirect.com 300 A 10.53.0.6"));
-       } elsif ($qname =~ /\.tld1/) {
-               $packet->push("authority", new Net::DNS::RR("tld1 300 NS ns.tld1"));
-               $packet->push("additional", new Net::DNS::RR("ns.tld1 300 A 10.53.0.6"));
-       } elsif ($qname =~ /\.tld2/) {
-               $packet->push("authority", new Net::DNS::RR("tld2 300 NS ns.tld2"));
-               $packet->push("additional", new Net::DNS::RR("ns.tld2 300 A 10.53.0.7"));
-       } elsif ($qname eq "org" && $qtype eq "NS") {
-               $packet->header->aa(1);
-               $packet->push("answer", new Net::DNS::RR("org 300 NS a.root-servers.nil."));
-       } elsif ($qname eq "example.org" && $qtype eq "NS") {
-               $packet->header->aa(1);
-               $packet->push("answer", new Net::DNS::RR("example.org 300 NS a.root-servers.nil."));
-       } elsif (($qname eq "baddname.example.org" || $qname eq "gooddname.example.org") && $qtype eq "NS") {
-               $packet->header->aa(1);
-               $packet->push("answer", new Net::DNS::RR("example.org 300 NS a.root-servers.nil."));
-       } elsif ($qname eq "www.example.org" ||
-                $qname eq "badcname.example.org" ||
-                $qname eq "goodcname.example.org" ||
-                $qname eq "foo.baddname.example.org" ||
-                $qname eq "foo.gooddname.example.org") {
-               # Data for address/alias filtering.
-               $packet->header->aa(1);
-               if ($qtype eq "A") {
-                       $packet->push("answer",
-                                     new Net::DNS::RR($qname .
-                                                      " 300 A 192.0.2.1"));
-               } elsif ($qtype eq "AAAA") {
-                       $packet->push("answer",
-                                     new Net::DNS::RR($qname .
-                                               " 300 AAAA 2001:db8:beef::1"));
-               }
-       } elsif ($qname eq "net" && $qtype eq "NS") {
-               $packet->header->aa(1);
-               $packet->push("answer", new Net::DNS::RR("net 300 NS a.root-servers.nil."));
-       } elsif ($qname eq "noresponse.exampleudp.net") {
-               next;
-       } elsif ($qname =~ /example\.net/) {
-               $packet->push("authority", new Net::DNS::RR("example.net 300 NS ns.example.net"));
-               $packet->push("additional", new Net::DNS::RR("ns.example.net 300 A 10.53.0.3"));
-       } elsif ($qname =~ /exampleudp\.net/) {
-               $packet->push("authority", new Net::DNS::RR("exampleudp.net 300 NS ns.exampleudp.net"));
-               $packet->push("additional", new Net::DNS::RR("ns.exampleudp.net 300 A 10.53.0.2"));
-       } elsif ($qname =~ /lame\.example\.org/) {
-               $packet->header->ad(0);
-               $packet->header->aa(0);
-               $packet->push("authority", new Net::DNS::RR("lame.example.org 300 NS ns.lame.example.org"));
-               $packet->push("additional", new Net::DNS::RR("ns.lame.example.org 300 A 10.53.0.3"));
-       } elsif ($qname =~ /sub\.example\.org/) {
-               # Data for CNAME/DNAME filtering.  The final answers are
-               # expected to be accepted regardless of the filter setting.
-               $packet->push("authority", new Net::DNS::RR("sub.example.org 300 NS ns.sub.example.org"));
-               $packet->push("additional", new Net::DNS::RR("ns.sub.example.org 300 A 10.53.0.3"));
-       } elsif ($qname =~ /glue-in-answer\.example\.org/) {
-               $packet->push("answer", new Net::DNS::RR("ns.glue-in-answer.example.org 300 A 10.53.0.3"));
-               $packet->push("authority", new Net::DNS::RR("glue-in-answer.example.org 300 NS ns.glue-in-answer.example.org"));
-               $packet->push("additional", new Net::DNS::RR("ns.glue-in-answer.example.org 300 A 10.53.0.3"));
-       } elsif ($qname =~ /\.broken/ || $qname =~ /^broken/) {
-               # Delegation to broken TLD.
-               $packet->push("authority", new Net::DNS::RR("broken 300 NS ns.broken"));
-               $packet->push("additional", new Net::DNS::RR("ns.broken 300 A 10.53.0.4"));
-       } elsif ($qname =~ /\.partial-formerr/) {
-               $packet->header->rcode("FORMERR");
-       } elsif ($qname eq "gl6412") {
-               if ($qtype eq "SOA") {
-                       $packet->push("answer",
-                             new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0"));
-               } elsif ($qtype eq "NS") {
-                       $packet->push("answer",
-                             new Net::DNS::RR($qname . " 300 NS ns2" . $qname));
-                       $packet->push("answer",
-                             new Net::DNS::RR($qname . " 300 NS ns3" . $qname));
-               } else {
-                       $packet->push("authority",
-                             new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0"));
-               }
-       } elsif ($qname eq "a.gl6412" || $qname eq "a.a.gl6412") {
-               $packet->push("authority",
-                             new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0"));
-       } elsif ($qname eq "ns2.gl6412") {
-               if ($qtype eq "A") {
-                       $packet->push("answer",
-                                     new Net::DNS::RR($qname . " 300 A 10.53.0.2"));
-               } else {
-                       $packet->push("authority",
-                                     new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0"));
-               }
-       } elsif ($qname eq "ns3.gl6412") {
-               if ($qtype eq "A") {
-                       $packet->push("answer",
-                                     new Net::DNS::RR($qname . " 300 A 10.53.0.3"));
-               } else {
-                       $packet->push("authority",
-                                     new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0"));
-               }
-       } elsif ($qname eq "zoneversion") {
-               $packet->push("authority", new Net::DNS::RR(". 300 SOA . . 0 0 0 0 0"));
-               if ($Net::DNS::VERSION >= 1.49) {
-                       $packet->edns->option('ZONEVERSION' => [0, 1, '01022304'] )
-               } elsif ($Net::DNS::VERSION >= 1.35) {
-                       $packet->edns->option('19' => {'BASE16' => '000101022304'} )
-               } else {
-                       $packet->edns->option('19' => pack 'H*', '000101022304')
-               }
-       } else {
-               # Data for the "bogus referrals" test
-               $packet->push("authority", new Net::DNS::RR("below.www.example.com 300 NS ns.below.www.example.com"));
-               $packet->push("additional", new Net::DNS::RR("ns.below.www.example.com 300 A 10.53.0.3"));
-       }
-
-       $sock->send($packet->data);
-
-       print "RESPONSE:\n";
-       $packet->print;
-       print "\n";
-}
diff --git a/bin/tests/system/resolver/ans2/ans.py b/bin/tests/system/resolver/ans2/ans.py
new file mode 100644 (file)
index 0000000..69b94d1
--- /dev/null
@@ -0,0 +1,240 @@
+"""
+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, Tuple, Union
+
+import dns.edns
+import dns.name
+import dns.rcode
+import dns.rrset
+import dns.rdatatype
+
+from isctest.asyncserver import (
+    AsyncDnsServer,
+    DnsResponseSend,
+    DomainHandler,
+    IgnoreAllQueries,
+    QnameHandler,
+    QnameQtypeHandler,
+    QueryContext,
+    StaticResponseHandler,
+    ResponseHandler,
+)
+
+from resolver_ans import (
+    DelegationHandler,
+    Gl6412AHandler,
+    Gl6412Handler,
+    Gl6412Ns2Handler,
+    Gl6412Ns3Handler,
+    rrset,
+    setup_delegation,
+    soa_rrset,
+)
+
+
+class BadGoodDnameNsHandler(QnameQtypeHandler, StaticResponseHandler):
+    qnames = [
+        "baddname.example.org.",
+        "gooddname.example.org.",
+    ]
+    qtypes = [dns.rdatatype.NS]
+    answer = [rrset("example.org.", dns.rdatatype.NS, "a.root-servers.nil.")]
+    authoritative = True
+
+
+def _cname_rrsets(
+    qname: Union[dns.name.Name, str],
+) -> Tuple[dns.rrset.RRset, dns.rrset.RRset]:
+    return (
+        rrset(qname, dns.rdatatype.CNAME, f"{qname}"),
+        rrset(qname, dns.rdatatype.A, "1.2.3.4"),
+    )
+
+
+class Cname1Handler(QnameHandler, StaticResponseHandler):
+    qnames = ["cname1.example.com."]
+    # Data for the "cname + other data / 1" test
+    answer = _cname_rrsets(qnames[0])
+    authoritative = False
+
+
+class Cname2Handler(QnameHandler, StaticResponseHandler):
+    qnames = ["cname2.example.com."]
+    # Data for the "cname + other data / 2" test: same RRs in opposite order
+    answer = tuple(reversed(_cname_rrsets(qnames[0])))
+    authoritative = False
+
+
+class ExampleOrgHandler(QnameHandler):
+    qnames = [
+        "www.example.org",
+        "badcname.example.org",
+        "goodcname.example.org",
+        "foo.baddname.example.org",
+        "foo.gooddname.example.org",
+    ]
+
+    async def get_responses(
+        self, qctx: QueryContext
+    ) -> AsyncGenerator[DnsResponseSend, None]:
+        # Data for address/alias filtering.
+        if qctx.qtype == dns.rdatatype.A:
+            a_rrset = rrset(qctx.qname, dns.rdatatype.A, "192.0.2.1")
+            qctx.response.answer.append(a_rrset)
+        elif qctx.qtype == dns.rdatatype.AAAA:
+            aaaa_rrset = rrset(qctx.qname, dns.rdatatype.AAAA, "2001:db8:beef::1")
+            qctx.response.answer.append(aaaa_rrset)
+        yield DnsResponseSend(qctx.response, authoritative=True)
+
+
+class NoResponseExampleUdpHandler(QnameHandler, IgnoreAllQueries):
+    qnames = ["noresponse.exampleudp.net."]
+
+
+class RootNsHandler(QnameQtypeHandler):
+    qnames = [
+        "example.com.",
+        "com.",
+        "example.org.",
+        "org.",
+        "net.",
+    ]
+
+    qtypes = [dns.rdatatype.NS]
+
+    async def get_responses(
+        self, qctx: QueryContext
+    ) -> AsyncGenerator[DnsResponseSend, None]:
+        root_ns = rrset(qctx.qname, dns.rdatatype.NS, "a.root-servers.nil.")
+        qctx.response.answer.append(root_ns)
+        yield DnsResponseSend(qctx.response, authoritative=True)
+
+
+class ZoneVersionHandler(QnameHandler):
+    qnames = ["zoneversion."]
+
+    async def get_responses(
+        self, qctx: QueryContext
+    ) -> AsyncGenerator[DnsResponseSend, None]:
+        qctx.response.authority.append(soa_rrset("."))
+        zoneversion_opt = dns.edns.GenericOption(19, bytes.fromhex("000101022304"))  # type: ignore
+        qctx.response.use_edns(edns=0, options=[zoneversion_opt])
+        yield DnsResponseSend(qctx.response, authoritative=False)
+
+
+class Ns2Delegation(DelegationHandler):
+    domains = ["exampleudp.net."]
+    server_number = 2
+
+
+class Ns3Delegation(DelegationHandler):
+    domains = [
+        "example.net.",
+        "lame.example.org.",
+        "sub.example.org.",
+    ]
+    server_number = 3
+
+
+class Ns3GlueInAnswerDelegation(DelegationHandler):
+    domains = ["glue-in-answer.example.org."]
+    server_number = 3
+
+    async def get_responses(
+        self, qctx: QueryContext
+    ) -> AsyncGenerator[DnsResponseSend, None]:
+        async for dns_response in super().get_responses(qctx):
+            dns_response.response.answer += dns_response.response.additional
+            yield dns_response
+
+
+class Ns4Delegation(DelegationHandler):
+    domains = ["broken."]
+    server_number = 4
+
+
+class Ns6Delegation(DelegationHandler):
+    domains = [
+        "redirect.com.",
+        "tld1.",
+    ]
+    server_number = 6
+
+
+class Ns7Delegation(DelegationHandler):
+    domains = ["tld2."]
+    server_number = 7
+
+
+class PartialFormerrHandler(DomainHandler, StaticResponseHandler):
+    domains = ["partial-formerr."]
+    authoritative = False
+    rcode = dns.rcode.FORMERR
+
+
+class FallbackHandler(ResponseHandler):
+    async def get_responses(
+        self, qctx: QueryContext
+    ) -> AsyncGenerator[DnsResponseSend, None]:
+        setup_delegation(qctx, "below.www.example.com.", 3)
+        yield DnsResponseSend(qctx.response, authoritative=False)
+
+
+# XXX: This handler is here to provide bug-for-bug compatibility with the old server.
+class XXXBuggyTldNsHandler(QnameQtypeHandler, FallbackHandler):
+    qnames = [
+        "tld1.",
+        "tld2.",
+    ]
+
+    qtypes = [dns.rdatatype.NS]
+
+
+def main() -> None:
+    server = AsyncDnsServer(default_rcode=dns.rcode.NOERROR)
+
+    # Install QnameHandlers first
+    server.install_response_handlers(
+        BadGoodDnameNsHandler(),
+        Cname1Handler(),
+        Cname2Handler(),
+        ExampleOrgHandler(),
+        Gl6412AHandler(),
+        Gl6412Handler(),
+        Gl6412Ns2Handler(),
+        Gl6412Ns3Handler(),
+        NoResponseExampleUdpHandler(),
+        RootNsHandler(),
+        ZoneVersionHandler(),
+        XXXBuggyTldNsHandler(),
+    )
+
+    # Then install DomainHandlers
+    server.install_response_handlers(
+        Ns2Delegation(),
+        Ns3Delegation(),
+        Ns3GlueInAnswerDelegation(),
+        Ns4Delegation(),
+        Ns6Delegation(),
+        Ns7Delegation(),
+        PartialFormerrHandler(),
+    )
+
+    # Finally, install the fallback handler
+    server.install_response_handler(FallbackHandler())
+    server.run()
+
+
+if __name__ == "__main__":
+    main()
index 1465269ebd64a633c7e2d5bf52bbc63272d9adf9..a7d1cdef43fa7b228b1518096fe0220a7d8a70a8 100644 (file)
@@ -11,7 +11,6 @@
 
 import time
 
-
 import isctest