From: Štěpán Balážik Date: Wed, 1 Apr 2026 12:41:51 +0000 (+0200) Subject: Reimplement 'reclimit/ans4' using ControllableAsyncServer X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=8c5e4a73edd91b621b3fd51d61cec13abe153d79;p=thirdparty%2Fbind9.git Reimplement 'reclimit/ans4' using ControllableAsyncServer Replace the Perl implementation with a Python one. One behavior of the old server is dropped: the AA flag is only cleared on referrals rather than on every response with an empty answer section. --- diff --git a/bin/tests/system/reclimit/ans4/ans.pl b/bin/tests/system/reclimit/ans4/ans.pl deleted file mode 100644 index d5002aae8cd..00000000000 --- a/bin/tests/system/reclimit/ans4/ans.pl +++ /dev/null @@ -1,240 +0,0 @@ -#!/usr/bin/env 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. - -use strict; -use warnings; - -use IO::File; -use IO::Socket; -use Net::DNS; - -my $localaddr = "10.53.0.4"; -my $limit = getlimit(); -my $no_more_waiting = 0; -my @delayed_response; -my $timeout; - -my $localport = int($ENV{'PORT'}); -if (!$localport) { $localport = 5300; } - -my $udpsock = IO::Socket::INET->new(LocalAddr => "$localaddr", - LocalPort => $localport, Proto => "udp", Reuse => 1) 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; - -my $count = 0; -my $send_response = 1; - -sub getlimit { - if ( -e "ans.limit") { - open(FH, "<", "ans.limit"); - my $line = ; - chomp $line; - close FH; - if ($line =~ /^\d+$/) { - return $line; - } - } - - return 0; -} - -# If $wait == 0 is returned, returned reply will be sent immediately. -# If $wait == 1 is returned, sending the returned reply might be delayed; see -# comments inside handle_UDP() for details. -sub reply_handler { - my ($qname, $qclass, $qtype) = @_; - my ($rcode, @ans, @auth, @add, $wait); - - print ("request: $qname/$qtype\n"); - STDOUT->flush(); - - $wait = 0; - $count += 1; - - if ($qname eq "count" ) { - if ($qtype eq "TXT") { - my ($ttl, $rdata) = (0, "$count"); - my $rr = new Net::DNS::RR("$qname $ttl $qclass $qtype $rdata"); - push @ans, $rr; - print ("\tcount: $count\n"); - } - $rcode = "NOERROR"; - } elsif ($qname eq "reset" ) { - $count = 0; - $send_response = 1; - $limit = getlimit(); - $rcode = "NOERROR"; - print ("\tlimit: $limit\n"); - } elsif ($qname eq "direct.example.org" ) { - if ($qtype eq "A") { - my ($ttl, $rdata) = (3600, $localaddr); - my $rr = new Net::DNS::RR("$qname $ttl $qclass $qtype $rdata"); - push @ans, $rr; - print ("\twait=$wait ans: $qname $ttl $qclass $qtype $rdata\n"); - } - $rcode = "NOERROR"; - } elsif ($qname eq "indirect1.example.org" || - $qname eq "indirect2.example.org" || - $qname eq "indirect3.example.org" || - $qname eq "indirect4.example.org" || - $qname eq "indirect5.example.org" || - $qname eq "indirect6.example.org" || - $qname eq "indirect7.example.org" || - $qname eq "indirect8.example.org") { - if ($qtype eq "A") { - my ($ttl, $rdata) = (3600, $localaddr); - my $rr = new Net::DNS::RR("$qname $ttl $qclass $qtype $rdata"); - push @ans, $rr; - print ("\twait=$wait ans: $qname $ttl $qclass $qtype $rdata\n"); - } - $rcode = "NOERROR"; - } elsif ($qname =~ /^ns1\.(\d+)\.example\.org$/) { - my $next = $1 + 1; - $wait = 1; - if ($limit == 0) { - my $rr = new Net::DNS::RR("$1.example.org 86400 $qclass NS ns1.$next.example.org"); - push @auth, $rr; - print ("\twait=$wait auth: $1.example.org 86400 $qclass NS ns1.$next.example.org\n"); - } else { - $send_response = 1; - if ($qtype eq "A") { - my ($ttl, $rdata) = (3600, $localaddr); - my $rr = new Net::DNS::RR("$qname $ttl $qclass $qtype $rdata"); - print("\tresponse: $qname $ttl $qclass $qtype $rdata\n"); - push @ans, $rr; - } - } - $rcode = "NOERROR"; - } elsif ($qname eq "direct.example.net" ) { - if ($qtype eq "A") { - my ($ttl, $rdata) = (3600, $localaddr); - my $rr = new Net::DNS::RR("$qname $ttl $qclass $qtype $rdata"); - push @ans, $rr; - print ("\twait=$wait ans: $qname $ttl $qclass $qtype $rdata\n"); - } - $rcode = "NOERROR"; - } elsif( $qname =~ /^ns1\.(\d+)\.example\.net$/ ) { - my $next = ($1 + 1) * 16; - for (my $i = 1; $i < 16; $i++) { - my $s = $next + $i; - my $rr = new Net::DNS::RR("$1.example.net 86400 $qclass NS ns1.$s.example.net"); - push @auth, $rr; - print ("\twait=$wait auth: $1.example.net 86400 $qclass NS ns1.$s.example.net\n"); - $rr = new Net::DNS::RR("ns1.$s.example.net 86400 $qclass A 10.53.0.7"); - print ("\twait=$wait add: ns1.$s.example.net 86400 $qclass A 10.53.0.7\n"); - push @add, $rr; - } - $rcode = "NOERROR"; - } else { - $rcode = "NXDOMAIN"; - print ("\twait=$wait NXDOMAIN\n"); - } - - return ($rcode, \@ans, \@auth, \@add, $wait); -} - -sub handleUDP { - my ($buf, $peer) = @_; - my ($request, $rcode, $ans, $auth, $add, $wait); - - $request = new Net::DNS::Packet(\$buf, 0); - $@ and die $@; - - my ($question) = $request->question; - my $qname = $question->qname; - my $qclass = $question->qclass; - my $qtype = $question->qtype; - - ($rcode, $ans, $auth, $add, $wait) = reply_handler($qname, $qclass, $qtype); - - my $reply = $request->reply(); - - $reply->header->rcode($rcode); - $reply->header->aa(@$ans ? 1 : 0); - $reply->header->id($request->header->id); - $reply->{answer} = $ans if $ans; - $reply->{authority} = $auth if $auth; - $reply->{additional} = $add if $add; - - if ($wait) { - # reply_handler() asked us to delay sending this reply until - # another reply with $wait == 1 is generated or a timeout - # occurs. - if (@delayed_response) { - # A delayed reply is already queued, so we can now send - # both the delayed reply and the current reply. - send_delayed_response(); - return $reply; - } elsif ($no_more_waiting) { - # It was determined before that there is no point in - # waiting for "accompanying" queries. Thus, send the - # current reply immediately. - return $reply; - } else { - # No delayed reply is queued and the client is expected - # to send an "accompanying" query shortly. Do not send - # the current reply right now, just save it for later - # and wait for an "accompanying" query to be received. - @delayed_response = ($reply, $peer); - $timeout = 0.5; - return; - } - } else { - # Send reply immediately. - return $reply; - } -} - -sub send_delayed_response { - my ($reply, $peer) = @delayed_response; - # Truncation to 512 bytes is required for triggering "NS explosion" on - # builds without IPv6 support - $udpsock->send($reply->data(512), 0, $peer); - undef @delayed_response; - undef $timeout; - print ("send_delayed_response\n"); -} - -# Main -my $rin; -my $rout; -for (;;) { - $rin = ''; - vec($rin, fileno($udpsock), 1) = 1; - - select($rout = $rin, undef, undef, $timeout); - - if (vec($rout, fileno($udpsock), 1)) { - my ($buf, $peer, $reply); - $udpsock->recv($buf, 512); - $peer = $udpsock->peername(); - $reply = handleUDP($buf, $peer); - # Truncation to 512 bytes is required for triggering "NS - # explosion" on builds without IPv6 support - $udpsock->send($reply->data(512), 0, $peer) if $reply; - } else { - # An "accompanying" query was expected to come in, but did not. - # Assume the client never sends "accompanying" queries to - # prevent pointlessly waiting for them ever again. - $no_more_waiting = 1; - # Send the delayed reply to the query which caused us to wait. - send_delayed_response(); - } -} diff --git a/bin/tests/system/reclimit/ans4/ans.py b/bin/tests/system/reclimit/ans4/ans.py new file mode 100644 index 00000000000..53bccff1a09 --- /dev/null +++ b/bin/tests/system/reclimit/ans4/ans.py @@ -0,0 +1,44 @@ +""" +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. +""" + +import dns.rcode + +from isctest.asyncserver import ControllableAsyncDnsServer + +from ..reclimit_ans import ( + DirectExampleHandler, + FallbackNxdomainHandler, + IndirectExampleOrgHandler, + LimitControlCommand, + Ns1ExampleOrgHandler, + ReclimitStateHandler, +) + + +def main() -> None: + server = ControllableAsyncDnsServer( + default_aa=True, default_rcode=dns.rcode.NOERROR + ) + server.install_response_handlers( + state_handler := ReclimitStateHandler(), + DirectExampleHandler(state_handler, 4), + IndirectExampleOrgHandler(state_handler, 4), + Ns1ExampleOrgHandler(state_handler), + FallbackNxdomainHandler(state_handler), + ) + server.install_control_command(LimitControlCommand(state_handler)) + server.run() + + +if __name__ == "__main__": + main() diff --git a/bin/tests/system/reclimit/tests.sh b/bin/tests/system/reclimit/tests.sh index f7b86200115..09bee7a413a 100644 --- a/bin/tests/system/reclimit/tests.sh +++ b/bin/tests/system/reclimit/tests.sh @@ -39,6 +39,13 @@ ns3_sends_aaaa_queries() { fi } +set_limit() { + IP=$1 + LIMIT=$2 + LOGID=$3 + dig_with_opts @${IP} $LIMIT.limit._control TXT >dig.out.limit.${LOGID} +} + # Check whether the number of queries ans2 received from ns3 (this value is # read from dig output stored in file $1) is as expected. The expected query # count is variable: @@ -70,7 +77,7 @@ n=$((n + 1)) echo_i "attempt excessive-depth lookup ($n)" ret=0 echo "1000" >ans2/ans.limit -echo "1000" >ans4/ans.limit +set_limit 10.53.0.4 1000 $n dig_with_opts @10.53.0.2 reset >/dev/null || ret=1 dig_with_opts @10.53.0.4 reset >/dev/null || ret=1 dig_with_opts @10.53.0.3 indirect1.example.org >dig.out.1.test$n || ret=1 @@ -85,7 +92,7 @@ n=$((n + 1)) echo_i "attempt permissible lookup ($n)" ret=0 echo "12" >ans2/ans.limit -echo "12" >ans4/ans.limit +set_limit 10.53.0.4 12 $n ns3_reset dig_with_opts @10.53.0.2 reset >/dev/null || ret=1 dig_with_opts @10.53.0.4 reset >/dev/null || ret=1 @@ -119,7 +126,7 @@ n=$((n + 1)) echo_i "attempt permissible lookup ($n)" ret=0 echo "5" >ans2/ans.limit -echo "5" >ans4/ans.limit +set_limit 10.53.0.4 5 $n ns3_reset dig_with_opts @10.53.0.2 reset >/dev/null || ret=1 dig_with_opts @10.53.0.4 reset >/dev/null || ret=1 @@ -137,7 +144,7 @@ n=$((n + 1)) echo_i "attempt excessive-queries lookup ($n)" ret=0 echo "13" >ans2/ans.limit -echo "13" >ans4/ans.limit +set_limit 10.53.0.4 13 $n cp ns3/named3.conf ns3/named.conf ns3_reset dig_with_opts @10.53.0.2 reset >/dev/null || ret=1