From: Štěpán Balážik Date: Thu, 25 Dec 2025 16:03:20 +0000 (+0100) Subject: Reimplement 'resolver/ans2' server using AsyncDnsServer X-Git-Tag: v9.21.18~11^2~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d203a39314489d0958db11da8e105215dde4d80b;p=thirdparty%2Fbind9.git Reimplement 'resolver/ans2' server using AsyncDnsServer Ensure packet-for-packet compatibility with the old server including bugs. --- diff --git a/bin/tests/system/resolver/ans2/ans.pl b/bin/tests/system/resolver/ans2/ans.pl deleted file mode 100644 index 8ec4b0a59b1..00000000000 --- a/bin/tests/system/resolver/ans2/ans.pl +++ /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 index 00000000000..69b94d15c0e --- /dev/null +++ b/bin/tests/system/resolver/ans2/ans.py @@ -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() diff --git a/bin/tests/system/resolver/tests_resolver.py b/bin/tests/system/resolver/tests_resolver.py index 1465269ebd6..a7d1cdef43f 100644 --- a/bin/tests/system/resolver/tests_resolver.py +++ b/bin/tests/system/resolver/tests_resolver.py @@ -11,7 +11,6 @@ import time - import isctest