From: Štěpán Balážik Date: Tue, 2 Sep 2025 10:39:33 +0000 (+0200) Subject: Use isctest.asyncserver in the "statistics" test X-Git-Tag: v9.21.15~25^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2ba372620746b560241ee1f0e58ae39da2c8e802;p=thirdparty%2Fbind9.git Use isctest.asyncserver in the "statistics" test Reimplement the custom server using the asyncserver Python module. Behavior change: The new server doesn't copy CD and DO flags and a client cookie to responses. --- diff --git a/bin/tests/system/statistics/ans4/ans.pl b/bin/tests/system/statistics/ans4/ans.pl deleted file mode 100644 index 3a37a828146..00000000000 --- a/bin/tests/system/statistics/ans4/ans.pl +++ /dev/null @@ -1,118 +0,0 @@ -#!/usr/bin/perl -w - -# 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; - -my $localport = int($ENV{'PORT'}); -if (!$localport) { $localport = 5300; } - -my $sock = IO::Socket::INET->new(LocalAddr => "10.53.0.4", - 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; - - my $donotrespond = 0; - - if ($qname eq "foo.info") { - $donotrespond = 1; - } 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 eq "www.example.org" || $qname eq "www.example.net" || - $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 "badcname.example.net" || - $qname eq "goodcname.example.net") { - # Data for CNAME/DNAME filtering. We need to make one-level - # delegation to avoid automatic acceptance for subdomain aliases - $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 =~ /^nodata\.example\.net$/i) { - $packet->header->aa(1); - } elsif ($qname =~ /^nxdomain\.example\.net$/i) { - $packet->header->aa(1); - $packet->header->rcode(NXDOMAIN); - } 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")); - } 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")); - } - - if ($donotrespond == 0) { - $sock->send($packet->data); - print "RESPONSE:\n"; - $packet->print; - print "\n"; - } -} diff --git a/bin/tests/system/statistics/ans4/ans.py b/bin/tests/system/statistics/ans4/ans.py new file mode 100644 index 00000000000..d3de81bc701 --- /dev/null +++ b/bin/tests/system/statistics/ans4/ans.py @@ -0,0 +1,178 @@ +""" +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 + +from isctest.asyncserver import ( + AsyncDnsServer, + DnsResponseSend, + DomainHandler, + IgnoreAllQueries, + QnameHandler, + QueryContext, + ResponseHandler, +) + + +def setup_delegation(qctx: QueryContext, owner: str) -> None: + ns_name = f"ns.{owner}" + ns_rrset = dns.rrset.from_text(owner, 300, qctx.qclass, dns.rdatatype.NS, ns_name) + a_rrset = dns.rrset.from_text( + ns_name, 300, qctx.qclass, dns.rdatatype.A, "10.53.0.3" + ) + qctx.response.authority.append(ns_rrset) + qctx.response.additional.append(a_rrset) + + +class BadGoodCnameHandler(QnameHandler): + qnames = [ + "badcname.example.net.", + "goodcname.example.net.", + ] + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + # Data for CNAME/DNAME filtering. We need to make one-level + # delegation to avoid automatic acceptance for subdomain aliases + setup_delegation(qctx, "example.net.") + yield DnsResponseSend(qctx.response, authoritative=False) + + +class Cname1Handler(QnameHandler): + qnames = ["cname1.example.com."] + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + # Data for the "cname + other data / 1" test + cname_rrset = dns.rrset.from_text( + qctx.qname, 300, qctx.qclass, dns.rdatatype.CNAME, "cname1.example.com." + ) + a_rrset = dns.rrset.from_text( + qctx.qname, 300, qctx.qclass, dns.rdatatype.A, "1.2.3.4" + ) + qctx.response.answer.append(cname_rrset) + qctx.response.answer.append(a_rrset) + yield DnsResponseSend(qctx.response, authoritative=False) + + +class Cname2Handler(QnameHandler): + qnames = ["cname2.example.com."] + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + # Data for the "cname + other data / 2" test: same RRs in opposite order + a_rrset = dns.rrset.from_text( + qctx.qname, 300, qctx.qclass, dns.rdatatype.A, "1.2.3.4" + ) + cname_rrset = dns.rrset.from_text( + qctx.qname, 300, qctx.qclass, dns.rdatatype.CNAME, "cname2.example.com." + ) + qctx.response.answer.append(a_rrset) + qctx.response.answer.append(cname_rrset) + yield DnsResponseSend(qctx.response, authoritative=False) + + +class ExampleHandler(QnameHandler): + qnames = [ + "www.example.com.", + "www.example.net.", + "badcname.example.org.", + "goodcname.example.org.", + "foo.badcname.example.org.", + "foo.goodcname.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 = dns.rrset.from_text( + qctx.qname, 300, qctx.qclass, qctx.qtype, "192.0.2.1" + ) + qctx.response.answer.append(a_rrset) + elif qctx.qtype == dns.rdatatype.AAAA: + aaaa_rrset = dns.rrset.from_text( + qctx.qname, 300, qctx.qclass, qctx.qtype, "2001:db8:beef::1" + ) + qctx.response.answer.append(aaaa_rrset) + yield DnsResponseSend(qctx.response, authoritative=True) + + +class FooInfoHandler(QnameHandler, IgnoreAllQueries): + qnames = ["foo.info."] + + +class NoDataHandler(DomainHandler): + domains = ["nodata.example.net."] + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + yield DnsResponseSend(qctx.response, authoritative=True) + + +class NxdomainHandler(DomainHandler): + domains = ["nxdomain.example.net."] + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + qctx.response.set_rcode(dns.rcode.NXDOMAIN) + yield DnsResponseSend(qctx.response, authoritative=True) + + +class SubHandler(DomainHandler): + domains = ["sub.example.org."] + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + # Data for CNAME/DNAME filtering. The final answers are + # expected to be accepted regardless of the filter setting. + setup_delegation(qctx, "sub.example.org.") + yield DnsResponseSend(qctx.response, authoritative=False) + + +class FallbackHandler(ResponseHandler): + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + setup_delegation(qctx, "below.www.example.com.") + yield DnsResponseSend(qctx.response, authoritative=False) + + +def main() -> None: + server = AsyncDnsServer(default_rcode=dns.rcode.NOERROR) + for handler in ( + BadGoodCnameHandler, + Cname1Handler, + Cname2Handler, + ExampleHandler, + FooInfoHandler, + NoDataHandler, + NxdomainHandler, + SubHandler, + FallbackHandler, + ): + server.install_response_handler(handler()) + server.run() + + +if __name__ == "__main__": + main()