diff --git a/bin/tests/system/serve-stale/ans2/ans.pl b/bin/tests/system/serve-stale/ans2/ans.pl index fed26967fe..6fac57c139 100644 --- a/bin/tests/system/serve-stale/ans2/ans.pl +++ b/bin/tests/system/serve-stale/ans2/ans.pl @@ -44,11 +44,14 @@ my $udpsock = IO::Socket::INET->new(LocalAddr => "$localaddr", LocalPort => $localport, Proto => "udp", Reuse => 1) or die "$!"; # -# Delegation +# Delegations # my $SOA = "example 300 IN SOA . . 0 0 0 0 300"; my $NS = "example 300 IN NS ns.example"; my $A = "ns.example 300 IN A $localaddr"; +my $ssSOA = "delegated.serve.stale 300 IN SOA . . 0 0 0 0 300"; +my $ssNS = "delegated.serve.stale 300 IN NS ns.delegated.serve.stale"; +my $ssA = "ns.delegated.serve.stale 300 IN A $localaddr"; # # Slow delegation @@ -66,6 +69,7 @@ my $TXT = "data.example 2 IN TXT \"A text record with a 2 second ttl\""; my $LONGTXT = "longttl.example 600 IN TXT \"A text record with a 600 second ttl\""; my $CAA = "othertype.example 2 IN CAA 0 issue \"ca1.example.net\""; my $negSOA = "example 2 IN SOA . . 0 0 0 0 300"; +my $ssnegSOA = "delegated.serve.stale 2 IN SOA . . 0 0 0 0 300"; my $CNAME = "cname.example 7 IN CNAME target.example"; my $TARGET = "target.example 9 IN A $localaddr"; my $SHORTCNAME = "shortttl.cname.example 1 IN CNAME longttl.target.example"; @@ -223,6 +227,38 @@ sub reply_handler { push @auth, $rr; } $rcode = "NOERROR"; + } elsif ($qname eq "ns.delegated.serve.stale" ) { + if ($qtype eq "A") { + my $rr = new Net::DNS::RR($ssA); + push @ans, $rr; + } else { + my $rr = new Net::DNS::RR($ssSOA); + push @auth, $rr; + } + $rcode = "NOERROR"; + } elsif ($qname eq "delegated.serve.stale") { + if ($qtype eq "NS") { + my $rr = new Net::DNS::RR($ssNS); + push @auth, $rr; + $rr = new Net::DNS::RR($ssA); + push @add, $rr; + } elsif ($qtype eq "SOA") { + my $rr = new Net::DNS::RR($ssSOA); + push @ans, $rr; + } else { + my $rr = new Net::DNS::RR($ssSOA); + push @auth, $rr; + } + $rcode = "NOERROR"; + } elsif ($qname eq "www.delegated.serve.stale") { + if ($qtype eq "A") { + my $rr = new Net::DNS::RR("www.delegated.serve.stale 2 IN A 10.53.0.99"); + push @ans, $rr; + } else { + my $rr = new Net::DNS::RR($ssnegSOA); + push @auth, $rr; + } + $rcode = "NOERROR"; } elsif ($qname eq "ns.slow" ) { if ($qtype eq "A") { my $rr = new Net::DNS::RR($slowA); diff --git a/bin/tests/system/serve-stale/ns3/named9.conf.in b/bin/tests/system/serve-stale/ns3/named9.conf.in new file mode 100644 index 0000000000..cb09713519 --- /dev/null +++ b/bin/tests/system/serve-stale/ns3/named9.conf.in @@ -0,0 +1,49 @@ +/* + * 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. + */ + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +options { + query-source address 10.53.0.3; + notify-source 10.53.0.3; + transfer-source 10.53.0.3; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.3; }; + listen-on-v6 { none; }; + recursion yes; + dnssec-validation no; + stale-answer-enable yes; + stale-cache-enable yes; + stale-answer-ttl 3; + stale-answer-client-timeout 0; +}; + +zone "." { + type secondary; + primaries { 10.53.0.1; }; + file "root.bk"; +}; + +zone "serve.stale" IN { + type primary; + notify no; + file "serve.stale.db"; +}; diff --git a/bin/tests/system/serve-stale/ns3/serve.stale.db b/bin/tests/system/serve-stale/ns3/serve.stale.db index 704f451012..09a696563d 100644 --- a/bin/tests/system/serve-stale/ns3/serve.stale.db +++ b/bin/tests/system/serve-stale/ns3/serve.stale.db @@ -16,3 +16,6 @@ ns.serve.stale. IN A 10.53.0.6 $ORIGIN serve.stale. test IN NS nss1.example.nxd. test IN NS nss2.example.nxd. + +delegated IN NS ns2.delegated.serve.stale. +ns2.delegated IN A 10.53.0.2 diff --git a/bin/tests/system/serve-stale/tests.sh b/bin/tests/system/serve-stale/tests.sh index 42b94dbeb5..3e3267051f 100755 --- a/bin/tests/system/serve-stale/tests.sh +++ b/bin/tests/system/serve-stale/tests.sh @@ -2506,5 +2506,33 @@ grep "2001:aaaa" dig.out.2.test$n >/dev/null || ret=1 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) +n=$((n + 1)) +echo_i "check serve-stale (stale-answer-client-timeout 0) with a delegation ($n)" +ret=0 +# configure ns3 with stale-answer-client-timeout 0 and a delegated zone +copy_setports ns3/named9.conf.in ns3/named.conf +rndc_reload ns3 10.53.0.3 +# flush cache, enable ans2 responses, make sure serve-stale is on +$RNDCCMD 10.53.0.3 flush >rndc.out.test$n.1 2>&1 || ret=1 +$DIG -p ${PORT} @10.53.0.2 txt enable >/dev/null || ret=1 +$RNDCCMD 10.53.0.3 serve-stale on >rndc.out.test$n.2 2>&1 || ret=1 +# prime the cache with the A response +$DIG -p ${PORT} @10.53.0.3 www.delegated.serve.stale >dig.out.1.test$n || ret=1 +grep -F "status: NOERROR" dig.out.1.test$n >/dev/null || ret=1 +grep -F "10.53.0.99" dig.out.1.test$n >/dev/null || ret=1 +# disable responses from the auth server +$DIG -p ${PORT} @10.53.0.2 txt disable >/dev/null || ret=1 +# wait two seconds for the previous answer to become stale +sleep 2 +# resend the query; we should immediately get a stale answer +$DIG -p ${PORT} @10.53.0.3 www.delegated.serve.stale >dig.out.2.test$n || ret=1 +grep -F "status: NOERROR" dig.out.2.test$n >/dev/null || ret=1 +grep -F "EDE: 3 (Stale Answer): (stale data prioritized over lookup)" dig.out.2.test$n >/dev/null || ret=1 +grep -F "10.53.0.99" dig.out.2.test$n >/dev/null || ret=1 +# re-enable responses +$DIG -p ${PORT} @10.53.0.2 txt enable >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + echo_i "exit status: $status" [ $status -eq 0 ] || exit 1 diff --git a/lib/ns/query.c b/lib/ns/query.c index 3759ee371b..a61fd92bce 100644 --- a/lib/ns/query.c +++ b/lib/ns/query.c @@ -6206,11 +6206,18 @@ query_lookup(query_ctx_t *qctx) { } } else if (stale_timeout) { if (qctx->options.stalefirst) { - if (!stale_found && !answer_found) { - /* - * We have nothing useful in cache to return - * immediately. - */ + /* + * If 'qctx->zdb' is set, this was a cache lookup after + * an authoritative lookup returned a delegation (in + * order to find a better answer). But we still can + * return without getting any usable answer here, as + * query_notfound() should handle it from here. + * Otherwise, if nothing useful was found in cache then + * recursively call query_lookup() again without the + * 'stalefirst' option set. + */ + if (!stale_found && !answer_found && qctx->zdb == NULL) + { qctx_clean(qctx); qctx_freedata(qctx); dns_db_attach(qctx->client->view->cachedb, @@ -8936,7 +8943,25 @@ query_zone_delegation(query_ctx_t *qctx) { dns_db_attach(qctx->view->cachedb, &qctx->db); qctx->is_zone = false; - return query_lookup(qctx); + /* + * Since 'qctx->is_zone' is now false, we should reconsider + * setting the 'stalefirst' option, which is usually set in + * the beginning in ns__query_start(). + */ + if (qctx->view->staleanswerclienttimeout == 0 && + dns_view_staleanswerenabled(qctx->view)) + { + qctx->options.stalefirst = true; + } + + result = query_lookup(qctx); + + /* + * After fetch completes, this option is not expected to be set. + */ + qctx->options.stalefirst = false; + + return result; } return query_prepare_delegation_response(qctx);