summaryrefslogtreecommitdiffstats
path: root/bin/tests/system/serve-stale
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:59:48 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:59:48 +0000
commit3b9b6d0b8e7f798023c9d109c490449d528fde80 (patch)
tree2e1c188dd7b8d7475cd163de9ae02c428343669b /bin/tests/system/serve-stale
parentInitial commit. (diff)
downloadbind9-upstream.tar.xz
bind9-upstream.zip
Adding upstream version 1:9.18.19.upstream/1%9.18.19upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'bin/tests/system/serve-stale')
-rw-r--r--bin/tests/system/serve-stale/ans2/ans.pl331
-rw-r--r--bin/tests/system/serve-stale/clean.sh22
-rw-r--r--bin/tests/system/serve-stale/ns1/named1.conf.in44
-rw-r--r--bin/tests/system/serve-stale/ns1/named2.conf.in44
-rw-r--r--bin/tests/system/serve-stale/ns1/named3.conf.in43
-rw-r--r--bin/tests/system/serve-stale/ns1/named4.conf.in49
-rw-r--r--bin/tests/system/serve-stale/ns1/root.db18
-rw-r--r--bin/tests/system/serve-stale/ns1/stale.test.db19
-rw-r--r--bin/tests/system/serve-stale/ns3/named1.conf.in41
-rw-r--r--bin/tests/system/serve-stale/ns3/named2.conf.in51
-rw-r--r--bin/tests/system/serve-stale/ns3/named3.conf.in48
-rw-r--r--bin/tests/system/serve-stale/ns3/named4.conf.in50
-rw-r--r--bin/tests/system/serve-stale/ns3/named5.conf.in49
-rw-r--r--bin/tests/system/serve-stale/ns3/named6.conf.in46
-rw-r--r--bin/tests/system/serve-stale/ns3/named7.conf.in55
-rw-r--r--bin/tests/system/serve-stale/ns3/named8.conf.in47
-rw-r--r--bin/tests/system/serve-stale/ns3/root.db13
-rw-r--r--bin/tests/system/serve-stale/ns4/named.conf.in42
-rw-r--r--bin/tests/system/serve-stale/ns5/named.conf.in43
-rw-r--r--bin/tests/system/serve-stale/setup.sh21
-rwxr-xr-xbin/tests/system/serve-stale/tests.sh2705
-rw-r--r--bin/tests/system/serve-stale/tests_sh_serve_stale.py14
22 files changed, 3795 insertions, 0 deletions
diff --git a/bin/tests/system/serve-stale/ans2/ans.pl b/bin/tests/system/serve-stale/ans2/ans.pl
new file mode 100644
index 0000000..3fdc1fc
--- /dev/null
+++ b/bin/tests/system/serve-stale/ans2/ans.pl
@@ -0,0 +1,331 @@
+#!/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 Getopt::Long;
+use Net::DNS;
+use Time::HiRes qw(usleep nanosleep);
+
+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;
+
+# If send_response is set, the server will respond, otherwise the query will
+# be dropped.
+my $send_response = 1;
+# If slow_response is set, a lookup for the CNAME target (target.example) is
+# delayed. Other lookups will not be delayed.
+my $slow_response = 0;
+
+my $localaddr = "10.53.0.2";
+
+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 "$!";
+
+#
+# Delegation
+#
+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";
+
+#
+# Slow delegation
+#
+my $slowSOA = "slow 300 IN SOA . . 0 0 0 0 300";
+my $slowNS = "slow 300 IN NS ns.slow";
+my $slowA = "ns.slow 300 IN A $localaddr";
+my $slowTXT = "data.slow 2 IN TXT \"A slow text record with a 2 second ttl\"";
+my $slownegSOA = "slow 2 IN SOA . . 0 0 0 0 300";
+
+#
+# Records to be TTL stretched
+#
+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 $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";
+my $LONGTARGET = "longttl.target.example 600 IN A $localaddr";
+
+sub reply_handler {
+ my ($qname, $qclass, $qtype) = @_;
+ my ($rcode, @ans, @auth, @add);
+
+ print ("request: $qname/$qtype\n");
+ STDOUT->flush();
+
+ # Control whether we send a response or not.
+ # We always respond to control commands.
+ if ($qname eq "enable" ) {
+ if ($qtype eq "TXT") {
+ $send_response = 1;
+ my $rr = new Net::DNS::RR("$qname 0 $qclass TXT \"$send_response\"");
+ push @ans, $rr;
+ }
+ $rcode = "NOERROR";
+ return ($rcode, \@ans, \@auth, \@add, { aa => 1 });
+ } elsif ($qname eq "disable" ) {
+ if ($qtype eq "TXT") {
+ $send_response = 0;
+ my $rr = new Net::DNS::RR("$qname 0 $qclass TXT \"$send_response\"");
+ push @ans, $rr;
+ }
+ $rcode = "NOERROR";
+ return ($rcode, \@ans, \@auth, \@add, { aa => 1 });
+ } elsif ($qname eq "slowdown" ) {
+ if ($qtype eq "TXT") {
+ $send_response = 1;
+ $slow_response = 1;
+ my $rr = new Net::DNS::RR("$qname 0 $qclass TXT \"$send_response\"");
+ push @ans, $rr;
+ }
+ $rcode = "NOERROR";
+ return ($rcode, \@ans, \@auth, \@add, { aa => 1 });
+ }
+
+ # If we are not responding to queries we are done.
+ return if (!$send_response);
+
+ if (index($qname, "latency") == 0) {
+ # simulate network latency before answering
+ print " Sleeping 50 milliseconds\n";
+ select(undef, undef, undef, 0.05);
+ }
+
+ # Construct the response and send it.
+ if ($qname eq "ns.example" ) {
+ if ($qtype eq "A") {
+ my $rr = new Net::DNS::RR($A);
+ push @ans, $rr;
+ } else {
+ my $rr = new Net::DNS::RR($SOA);
+ push @auth, $rr;
+ }
+ $rcode = "NOERROR";
+ } elsif ($qname eq "example") {
+ if ($qtype eq "NS") {
+ my $rr = new Net::DNS::RR($NS);
+ push @auth, $rr;
+ $rr = new Net::DNS::RR($A);
+ push @add, $rr;
+ } elsif ($qtype eq "SOA") {
+ my $rr = new Net::DNS::RR($SOA);
+ push @ans, $rr;
+ } else {
+ my $rr = new Net::DNS::RR($SOA);
+ push @auth, $rr;
+ }
+ $rcode = "NOERROR";
+ } elsif ($qname eq "nodata.example") {
+ my $rr = new Net::DNS::RR($negSOA);
+ push @auth, $rr;
+ $rcode = "NOERROR";
+ } elsif ($qname eq "data.example") {
+ if ($qtype eq "TXT") {
+ my $rr = new Net::DNS::RR($TXT);
+ push @ans, $rr;
+ } else {
+ my $rr = new Net::DNS::RR($negSOA);
+ push @auth, $rr;
+ }
+ $rcode = "NOERROR";
+ } elsif ($qname eq "a-only.example") {
+ if ($qtype eq "A") {
+ my $rr = new Net::DNS::RR("a-only.example 2 IN A $localaddr");
+ push @ans, $rr;
+ } else {
+ my $rr = new Net::DNS::RR($negSOA);
+ push @auth, $rr;
+ }
+ $rcode = "NOERROR";
+ } elsif ($qname eq "cname.example") {
+ if ($qtype eq "A") {
+ my $rr = new Net::DNS::RR($CNAME);
+ push @ans, $rr;
+ } else {
+ my $rr = new Net::DNS::RR($negSOA);
+ push @auth, $rr;
+ }
+ $rcode = "NOERROR";
+ } elsif ($qname eq "target.example") {
+ if ($slow_response) {
+ print " Sleeping 3 seconds\n";
+ sleep(3);
+ }
+ if ($qtype eq "A") {
+ my $rr = new Net::DNS::RR($TARGET);
+ push @ans, $rr;
+ } else {
+ my $rr = new Net::DNS::RR($negSOA);
+ push @auth, $rr;
+ }
+ $rcode = "NOERROR";
+ } elsif ($qname eq "shortttl.cname.example") {
+ if ($qtype eq "A") {
+ my $rr = new Net::DNS::RR($SHORTCNAME);
+ push @ans, $rr;
+ } else {
+ my $rr = new Net::DNS::RR($negSOA);
+ push @auth, $rr;
+ }
+ $rcode = "NOERROR";
+ } elsif ($qname eq "longttl.target.example") {
+ if ($slow_response) {
+ print " Sleeping 3 seconds\n";
+ sleep(3);
+ }
+ if ($qtype eq "A") {
+ my $rr = new Net::DNS::RR($LONGTARGET);
+ push @ans, $rr;
+ } else {
+ my $rr = new Net::DNS::RR($negSOA);
+ push @auth, $rr;
+ }
+ $rcode = "NOERROR";
+ } elsif ($qname eq "longttl.example") {
+ if ($qtype eq "TXT") {
+ my $rr = new Net::DNS::RR($LONGTXT);
+ push @ans, $rr;
+ } else {
+ my $rr = new Net::DNS::RR($negSOA);
+ push @auth, $rr;
+ }
+ $rcode = "NOERROR";
+ } elsif ($qname eq "nxdomain.example") {
+ my $rr = new Net::DNS::RR($negSOA);
+ push @auth, $rr;
+ $rcode = "NXDOMAIN";
+ } elsif ($qname eq "othertype.example") {
+ if ($qtype eq "CAA") {
+ my $rr = new Net::DNS::RR($CAA);
+ push @ans, $rr;
+ } else {
+ my $rr = new Net::DNS::RR($negSOA);
+ push @auth, $rr;
+ }
+ $rcode = "NOERROR";
+ } elsif ($qname eq "ns.slow" ) {
+ if ($qtype eq "A") {
+ my $rr = new Net::DNS::RR($slowA);
+ push @ans, $rr;
+ } else {
+ my $rr = new Net::DNS::RR($slowSOA);
+ push @auth, $rr;
+ }
+ $rcode = "NOERROR";
+ } elsif ($qname eq "slow") {
+ if ($qtype eq "NS") {
+ my $rr = new Net::DNS::RR($slowNS);
+ push @auth, $rr;
+ $rr = new Net::DNS::RR($slowA);
+ push @add, $rr;
+ } elsif ($qtype eq "SOA") {
+ my $rr = new Net::DNS::RR($slowSOA);
+ push @ans, $rr;
+ } else {
+ my $rr = new Net::DNS::RR($slowSOA);
+ push @auth, $rr;
+ }
+ $rcode = "NOERROR";
+ } elsif ($qname eq "data.slow") {
+ if ($slow_response) {
+ print " Sleeping 3 seconds\n";
+ sleep(3);
+ # only one time
+ $slow_response = 0;
+ }
+ if ($qtype eq "TXT") {
+ my $rr = new Net::DNS::RR($slowTXT);
+ push @ans, $rr;
+ } else {
+ my $rr = new Net::DNS::RR($slownegSOA);
+ push @auth, $rr;
+ }
+ $rcode = "NOERROR";
+ } else {
+ my $rr = new Net::DNS::RR($SOA);
+ push @auth, $rr;
+ $rcode = "NXDOMAIN";
+ }
+
+ # mark the answer as authoritative (by setting the 'aa' flag)
+ return ($rcode, \@ans, \@auth, \@add, { aa => 1 });
+}
+
+GetOptions(
+ 'port=i' => \$localport,
+);
+
+my $rin;
+my $rout;
+
+for (;;) {
+ $rin = '';
+ vec($rin, fileno($udpsock), 1) = 1;
+
+ select($rout = $rin, undef, undef, undef);
+
+ if (vec($rout, fileno($udpsock), 1)) {
+ my ($buf, $request, $err);
+ $udpsock->recv($buf, 512);
+
+ if ($Net::DNS::VERSION > 0.68) {
+ $request = new Net::DNS::Packet(\$buf, 0);
+ $@ and die $@;
+ } else {
+ my $err;
+ ($request, $err) = new Net::DNS::Packet(\$buf, 0);
+ $err and die $err;
+ }
+
+ my @questions = $request->question;
+ my $qname = $questions[0]->qname;
+ my $qclass = $questions[0]->qclass;
+ my $qtype = $questions[0]->qtype;
+ my $id = $request->header->id;
+
+ my ($rcode, $ans, $auth, $add, $headermask) = reply_handler($qname, $qclass, $qtype);
+
+ if (!defined($rcode)) {
+ print " Silently ignoring query\n";
+ next;
+ }
+
+ my $reply = Net::DNS::Packet->new();
+ $reply->header->qr(1);
+ $reply->header->aa(1) if $headermask->{'aa'};
+ $reply->header->id($id);
+ $reply->header->rcode($rcode);
+ $reply->push("question", @questions);
+ $reply->push("answer", @$ans) if $ans;
+ $reply->push("authority", @$auth) if $auth;
+ $reply->push("additional", @$add) if $add;
+
+ my $num_chars = $udpsock->send($reply->data);
+ print " Sent $num_chars bytes via UDP\n";
+ }
+}
diff --git a/bin/tests/system/serve-stale/clean.sh b/bin/tests/system/serve-stale/clean.sh
new file mode 100644
index 0000000..b4a0d50
--- /dev/null
+++ b/bin/tests/system/serve-stale/clean.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+# 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.
+
+rm -f dig.out*
+rm -f ns*/named.conf
+rm -f ns*/root.bk
+rm -f rndc.out.test*
+rm -f */named.run */named.memstats
+rm -f ns*/managed-keys.bind*
+rm -f ns*/named_dump*
+rm -f ns*/named.stats*
+rm -f ns*/named.run.prev
diff --git a/bin/tests/system/serve-stale/ns1/named1.conf.in b/bin/tests/system/serve-stale/ns1/named1.conf.in
new file mode 100644
index 0000000..d25611d
--- /dev/null
+++ b/bin/tests/system/serve-stale/ns1/named1.conf.in
@@ -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.
+ */
+
+key rndc_key {
+ secret "1234abcd8765";
+ algorithm @DEFAULT_HMAC@;
+};
+
+controls {
+ inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+options {
+ query-source address 10.53.0.1;
+ notify-source 10.53.0.1;
+ transfer-source 10.53.0.1;
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { 10.53.0.1; };
+ listen-on-v6 { none; };
+ recursion yes;
+ dnssec-validation no;
+ max-stale-ttl 3600;
+ stale-answer-ttl 4;
+ stale-answer-enable yes;
+ stale-cache-enable yes;
+ stale-refresh-time 30;
+ servfail-ttl 0;
+};
+
+zone "." {
+ type primary;
+ file "root.db";
+};
diff --git a/bin/tests/system/serve-stale/ns1/named2.conf.in b/bin/tests/system/serve-stale/ns1/named2.conf.in
new file mode 100644
index 0000000..82e5e76
--- /dev/null
+++ b/bin/tests/system/serve-stale/ns1/named2.conf.in
@@ -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.
+ */
+
+key rndc_key {
+ secret "1234abcd8765";
+ algorithm @DEFAULT_HMAC@;
+};
+
+controls {
+ inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+options {
+ query-source address 10.53.0.1;
+ notify-source 10.53.0.1;
+ transfer-source 10.53.0.1;
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { 10.53.0.1; };
+ listen-on-v6 { none; };
+ recursion yes;
+ dnssec-validation no;
+ max-stale-ttl 3600;
+ stale-answer-ttl 4;
+ stale-answer-enable yes;
+ stale-cache-enable yes;
+ stale-refresh-time 0;
+ servfail-ttl 0;
+};
+
+zone "." {
+ type primary;
+ file "root.db";
+};
diff --git a/bin/tests/system/serve-stale/ns1/named3.conf.in b/bin/tests/system/serve-stale/ns1/named3.conf.in
new file mode 100644
index 0000000..4f6f175
--- /dev/null
+++ b/bin/tests/system/serve-stale/ns1/named3.conf.in
@@ -0,0 +1,43 @@
+/*
+ * 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.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+options {
+ query-source address 10.53.0.1;
+ notify-source 10.53.0.1;
+ transfer-source 10.53.0.1;
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { 10.53.0.1; };
+ listen-on-v6 { none; };
+ recursion yes;
+ dnssec-validation no;
+ max-stale-ttl 20;
+ stale-answer-ttl 3;
+ stale-answer-enable yes;
+ stale-cache-enable yes;
+ servfail-ttl 0;
+};
+
+zone "." {
+ type primary;
+ file "root.db";
+};
diff --git a/bin/tests/system/serve-stale/ns1/named4.conf.in b/bin/tests/system/serve-stale/ns1/named4.conf.in
new file mode 100644
index 0000000..f2cc2a8
--- /dev/null
+++ b/bin/tests/system/serve-stale/ns1/named4.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.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+options {
+ query-source address 10.53.0.1;
+ notify-source 10.53.0.1;
+ transfer-source 10.53.0.1;
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { 10.53.0.1; };
+ listen-on-v6 { none; };
+ recursion yes;
+ dnssec-validation no;
+ max-stale-ttl 20;
+ stale-answer-ttl 3;
+ stale-answer-enable yes;
+ stale-cache-enable yes;
+ stale-refresh-time 0;
+ servfail-ttl 0;
+};
+
+zone "." {
+ type primary;
+ file "root.db";
+};
+
+zone "stale.test" {
+ type primary;
+ file "stale.test.db";
+};
diff --git a/bin/tests/system/serve-stale/ns1/root.db b/bin/tests/system/serve-stale/ns1/root.db
new file mode 100644
index 0000000..aef8e31
--- /dev/null
+++ b/bin/tests/system/serve-stale/ns1/root.db
@@ -0,0 +1,18 @@
+; 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.
+
+. 300 SOA . . 0 0 0 0 0
+. 300 NS ns.nil.
+ns.nil. 300 A 10.53.0.1
+example. 300 NS ns.example.
+ns.example. 300 A 10.53.0.2
+slow. 300 NS ns.slow.
+ns.slow. 300 A 10.53.0.2
diff --git a/bin/tests/system/serve-stale/ns1/stale.test.db b/bin/tests/system/serve-stale/ns1/stale.test.db
new file mode 100644
index 0000000..d389e7c
--- /dev/null
+++ b/bin/tests/system/serve-stale/ns1/stale.test.db
@@ -0,0 +1,19 @@
+; 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.
+
+$ORIGIN stale.test.
+stale.test. 300 SOA . . 0 0 0 0 0
+stale.test. 300 NS ns.stale.test.
+ns.stale.test. 300 A 10.53.0.1
+cname1.stale.test. 1 CNAME a1.stale.test.
+a1.stale.test. 1 A 192.0.2.1
+cname2.stale.test. 1 CNAME a2.stale.test.
+a2.stale.test. 300 A 192.0.2.2
diff --git a/bin/tests/system/serve-stale/ns3/named1.conf.in b/bin/tests/system/serve-stale/ns3/named1.conf.in
new file mode 100644
index 0000000..5c7dddd
--- /dev/null
+++ b/bin/tests/system/serve-stale/ns3/named1.conf.in
@@ -0,0 +1,41 @@
+/*
+ * 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;
+ dump-file "named_dump3.db";
+ stale-cache-enable yes;
+ dnssec-validation no;
+};
+
+zone "." {
+ type secondary;
+ primaries { 10.53.0.1; };
+ file "root.bk";
+};
diff --git a/bin/tests/system/serve-stale/ns3/named2.conf.in b/bin/tests/system/serve-stale/ns3/named2.conf.in
new file mode 100644
index 0000000..0a316d8
--- /dev/null
+++ b/bin/tests/system/serve-stale/ns3/named2.conf.in
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+/*
+ * Test default stale-answer-client-timeout value
+ */
+
+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; };
+ dnssec-validation no;
+ recursion yes;
+ stale-answer-enable yes;
+ stale-cache-enable yes;
+ stale-answer-ttl 3;
+ stale-refresh-time 0;
+ stale-answer-client-timeout 1800; # 1.8 seconds
+ recursive-clients 10; # CVE-2022-3924
+ max-stale-ttl 3600;
+ resolver-query-timeout 30000; # 30 seconds
+ qname-minimization disabled;
+};
+
+zone "." {
+ type hint;
+ file "root.db";
+};
diff --git a/bin/tests/system/serve-stale/ns3/named3.conf.in b/bin/tests/system/serve-stale/ns3/named3.conf.in
new file mode 100644
index 0000000..cef5e52
--- /dev/null
+++ b/bin/tests/system/serve-stale/ns3/named3.conf.in
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+/*
+ * Test disable of stale-answer-client-timeout.
+ */
+
+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; };
+ dnssec-validation no;
+ recursion yes;
+ stale-answer-enable yes;
+ stale-cache-enable yes;
+ stale-answer-ttl 3;
+ stale-refresh-time 0;
+ max-stale-ttl 3600;
+ resolver-query-timeout 10000; # 10 seconds
+};
+
+zone "." {
+ type hint;
+ file "root.db";
+};
diff --git a/bin/tests/system/serve-stale/ns3/named4.conf.in b/bin/tests/system/serve-stale/ns3/named4.conf.in
new file mode 100644
index 0000000..abc1eb7
--- /dev/null
+++ b/bin/tests/system/serve-stale/ns3/named4.conf.in
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+/*
+ * Test stale-answer-client-timeout 0.
+ */
+
+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; };
+ dnssec-validation no;
+ recursion yes;
+ stale-answer-enable yes;
+ stale-cache-enable yes;
+ stale-answer-ttl 3;
+ stale-answer-client-timeout 0;
+ stale-refresh-time 0;
+ resolver-query-timeout 10000; # 10 seconds
+ max-stale-ttl 3600;
+ recursive-clients 10;
+};
+
+zone "." {
+ type hint;
+ file "root.db";
+};
diff --git a/bin/tests/system/serve-stale/ns3/named5.conf.in b/bin/tests/system/serve-stale/ns3/named5.conf.in
new file mode 100644
index 0000000..b695901
--- /dev/null
+++ b/bin/tests/system/serve-stale/ns3/named5.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.
+ */
+
+/*
+ * Test stale-answer-client-timeout 0.
+ */
+
+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; };
+ dnssec-validation no;
+ recursion yes;
+ stale-answer-enable yes;
+ stale-cache-enable yes;
+ stale-answer-ttl 3;
+ stale-answer-client-timeout 0;
+ stale-refresh-time 4;
+ resolver-query-timeout 10000; # 10 seconds
+ max-stale-ttl 3600;
+};
+
+zone "." {
+ type hint;
+ file "root.db";
+};
diff --git a/bin/tests/system/serve-stale/ns3/named6.conf.in b/bin/tests/system/serve-stale/ns3/named6.conf.in
new file mode 100644
index 0000000..307c3bc
--- /dev/null
+++ b/bin/tests/system/serve-stale/ns3/named6.conf.in
@@ -0,0 +1,46 @@
+/*
+ * 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; };
+ dnssec-validation no;
+ recursion yes;
+ stale-answer-enable no;
+ stale-cache-enable yes;
+ stale-answer-ttl 3;
+ stale-refresh-time 4;
+ resolver-query-timeout 10000; # 10 seconds
+ fetches-per-zone 1 fail;
+ fetches-per-server 1 fail;
+ max-stale-ttl 3600;
+};
+
+zone "." {
+ type hint;
+ file "root.db";
+};
diff --git a/bin/tests/system/serve-stale/ns3/named7.conf.in b/bin/tests/system/serve-stale/ns3/named7.conf.in
new file mode 100644
index 0000000..12d9ee3
--- /dev/null
+++ b/bin/tests/system/serve-stale/ns3/named7.conf.in
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+/*
+ * Test serve-stale interaction with fetch-limits (dual-mode).
+ */
+
+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; };
+ dnssec-validation no;
+ recursion yes;
+ /*
+ * stale-answer-enable is not strictly required because serving
+ * stale answers is enabled in the test via rndc.
+ */
+ stale-answer-enable yes;
+ stale-cache-enable yes;
+ stale-answer-ttl 3;
+ stale-refresh-time 4;
+ resolver-query-timeout 10000; # 10 seconds
+ fetches-per-zone 1 fail;
+ fetches-per-server 1 fail;
+ max-stale-ttl 3600;
+};
+
+zone "." {
+ type secondary;
+ primaries { 10.53.0.1; };
+ file "root.bk";
+};
diff --git a/bin/tests/system/serve-stale/ns3/named8.conf.in b/bin/tests/system/serve-stale/ns3/named8.conf.in
new file mode 100644
index 0000000..7b5792d
--- /dev/null
+++ b/bin/tests/system/serve-stale/ns3/named8.conf.in
@@ -0,0 +1,47 @@
+/*
+ * 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-client-timeout 1800;
+ prefetch 2 8;
+ dns64 2001:aaaa::/96 {
+ clients { any; };
+ mapped { any; };
+ };
+};
+
+zone "." {
+ type secondary;
+ primaries { 10.53.0.1; };
+ file "root.bk";
+};
diff --git a/bin/tests/system/serve-stale/ns3/root.db b/bin/tests/system/serve-stale/ns3/root.db
new file mode 100644
index 0000000..bbf039c
--- /dev/null
+++ b/bin/tests/system/serve-stale/ns3/root.db
@@ -0,0 +1,13 @@
+; 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.
+
+. 300 NS ns.nil.
+ns.nil. 300 A 10.53.0.1
diff --git a/bin/tests/system/serve-stale/ns4/named.conf.in b/bin/tests/system/serve-stale/ns4/named.conf.in
new file mode 100644
index 0000000..ac7dd99
--- /dev/null
+++ b/bin/tests/system/serve-stale/ns4/named.conf.in
@@ -0,0 +1,42 @@
+/*
+ * 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.4 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+options {
+ query-source address 10.53.0.4;
+ notify-source 10.53.0.4;
+ transfer-source 10.53.0.4;
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { 10.53.0.4; };
+ listen-on-v6 { none; };
+ recursion yes;
+ dnssec-validation no;
+ dump-file "named_dump.db";
+ stale-answer-enable no;
+ stale-cache-enable yes;
+};
+
+zone "." {
+ type secondary;
+ primaries { 10.53.0.1; };
+ file "root.bk";
+};
diff --git a/bin/tests/system/serve-stale/ns5/named.conf.in b/bin/tests/system/serve-stale/ns5/named.conf.in
new file mode 100644
index 0000000..9874728
--- /dev/null
+++ b/bin/tests/system/serve-stale/ns5/named.conf.in
@@ -0,0 +1,43 @@
+/*
+ * 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.5 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+options {
+ query-source address 10.53.0.5;
+ notify-source 10.53.0.5;
+ transfer-source 10.53.0.5;
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { 10.53.0.5; };
+ listen-on-v6 { none; };
+ recursion yes;
+ dnssec-validation no;
+ dump-file "named_dump.db";
+ stale-answer-enable yes;
+ stale-cache-enable no;
+ max-cache-ttl 24h;
+};
+
+zone "." {
+ type secondary;
+ primaries { 10.53.0.1; };
+ file "root.bk";
+};
diff --git a/bin/tests/system/serve-stale/setup.sh b/bin/tests/system/serve-stale/setup.sh
new file mode 100644
index 0000000..0a0dc47
--- /dev/null
+++ b/bin/tests/system/serve-stale/setup.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+# 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.
+
+. ../conf.sh
+
+$SHELL clean.sh
+
+copy_setports ns1/named1.conf.in ns1/named.conf
+copy_setports ns3/named1.conf.in ns3/named.conf
+copy_setports ns4/named.conf.in ns4/named.conf
+copy_setports ns5/named.conf.in ns5/named.conf
diff --git a/bin/tests/system/serve-stale/tests.sh b/bin/tests/system/serve-stale/tests.sh
new file mode 100755
index 0000000..8c5ce84
--- /dev/null
+++ b/bin/tests/system/serve-stale/tests.sh
@@ -0,0 +1,2705 @@
+#!/bin/sh
+
+# 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.
+
+set -e
+
+. ../conf.sh
+
+RNDCCMD="$RNDC -c ../common/rndc.conf -p ${CONTROLPORT} -s"
+DIG="$DIG +time=12 +tries=1"
+
+max_stale_ttl=$(sed -ne 's,^[[:space:]]*max-stale-ttl \([[:digit:]]*\).*,\1,p' $TOP_SRCDIR/bin/named/config.c)
+stale_answer_ttl=$(sed -ne 's,^[[:space:]]*stale-answer-ttl \([[:digit:]]*\).*,\1,p' $TOP_SRCDIR/bin/named/config.c)
+
+status=0
+n=0
+
+#
+# First test server with serve-stale options set.
+#
+echo_i "test server with serve-stale options set"
+
+n=$((n+1))
+echo_i "prime cache longttl.example TXT ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.1 longttl.example TXT > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache data.example TXT ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.1 data.example TXT > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache othertype.example CAA ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.1 othertype.example CAA > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache nodata.example TXT ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.1 nodata.example TXT > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache nxdomain.example TXT ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.1 nxdomain.example TXT > dig.out.test$n
+grep "status: NXDOMAIN" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "verify prime cache statistics ($n)"
+ret=0
+rm -f ns1/named.stats
+$RNDCCMD 10.53.0.1 stats > /dev/null 2>&1
+[ -f ns1/named.stats ] || ret=1
+cp ns1/named.stats ns1/named.stats.$n
+# Check first 10 lines of Cache DB statistics. After prime queries, we expect
+# two active TXT, one active Others, one nxrrset TXT, and one NXDOMAIN.
+grep -A 10 "++ Cache DB RRsets ++" ns1/named.stats.$n > ns1/named.stats.$n.cachedb || ret=1
+grep "1 Others" ns1/named.stats.$n.cachedb > /dev/null || ret=1
+grep "2 TXT" ns1/named.stats.$n.cachedb > /dev/null || ret=1
+grep "1 !TXT" ns1/named.stats.$n.cachedb > /dev/null || ret=1
+grep "1 NXDOMAIN" ns1/named.stats.$n.cachedb > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "disable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt disable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"0\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check 'rndc serve-stale status' ($n)"
+ret=0
+$RNDCCMD 10.53.0.1 serve-stale status > rndc.out.test$n 2>&1 || ret=1
+grep '_default: stale cache enabled; stale answers enabled (stale-answer-ttl=4 max-stale-ttl=3600 stale-refresh-time=30)' rndc.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+sleep 2
+
+# Run rndc dumpdb, test whether the stale data has correct comment printed.
+# The max-stale-ttl is 3600 seconds, so the comment should say the data is
+# stale for somewhere between 3500-3599 seconds.
+echo_i "check rndc dump stale data.example ($n)"
+rndc_dumpdb ns1 || ret=1
+awk '/; stale/ { x=$0; getline; print x, $0}' ns1/named_dump.db.test$n |
+ grep "; stale data\.example.*3[56]...*TXT.*A text record with a 2 second ttl" > /dev/null 2>&1 || ret=1
+# Also make sure the not expired data does not have a stale comment.
+awk '/; authanswer/ { x=$0; getline; print x, $0}' ns1/named_dump.db.test$n |
+ grep "; authanswer longttl\.example.*[56]...*TXT.*A text record with a 600 second ttl" > /dev/null 2>&1 || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+echo_i "sending queries for tests $((n+1))-$((n+5))..."
+$DIG -p ${PORT} @10.53.0.1 data.example TXT > dig.out.test$((n+1)) &
+$DIG -p ${PORT} @10.53.0.1 longttl.example TXT > dig.out.test$((n+2)) &
+$DIG -p ${PORT} @10.53.0.1 othertype.example CAA > dig.out.test$((n+3)) &
+$DIG -p ${PORT} @10.53.0.1 nodata.example TXT > dig.out.test$((n+4)) &
+$DIG -p ${PORT} @10.53.0.1 nxdomain.example TXT > dig.out.test$((n+5)) &
+
+wait
+
+n=$((n+1))
+echo_i "check stale data.example TXT ($n)"
+ret=0
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (resolver failure)" dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*4.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check non-stale longttl.example TXT ($n)"
+ret=0
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "EDE" dig.out.test$n > /dev/null && ret=1
+grep "longttl\.example\..*59[0-9].*IN.*TXT.*A text record with a 600 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check stale othertype.example CAA ($n)"
+ret=0
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (resolver failure)" dig.out.test$n > /dev/null || ret=1
+grep "othertype\.example\..*4.*IN.*CAA.*0.*issue" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check stale nodata.example TXT ($n)"
+ret=0
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (resolver failure)" dig.out.test$n > /dev/null || ret=1
+grep "example\..*4.*IN.*SOA" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check stale nxdomain.example TXT ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "verify stale cache statistics ($n)"
+ret=0
+rm -f ns1/named.stats
+$RNDCCMD 10.53.0.1 stats > /dev/null 2>&1
+[ -f ns1/named.stats ] || ret=1
+cp ns1/named.stats ns1/named.stats.$n
+# Check first 10 lines of Cache DB statistics. After serve-stale queries, we
+# expect one active TXT RRset, one stale TXT, one stale nxrrset TXT, and one
+# stale NXDOMAIN.
+grep -A 10 "++ Cache DB RRsets ++" ns1/named.stats.$n > ns1/named.stats.$n.cachedb || ret=1
+grep "1 TXT" ns1/named.stats.$n.cachedb > /dev/null || ret=1
+grep "1 #Others" ns1/named.stats.$n.cachedb > /dev/null || ret=1
+grep "1 #TXT" ns1/named.stats.$n.cachedb > /dev/null || ret=1
+grep "1 #!TXT" ns1/named.stats.$n.cachedb > /dev/null || ret=1
+status=$((status+ret))
+if [ $ret != 0 ]; then echo_i "failed"; fi
+
+# Test stale-refresh-time when serve-stale is enabled via configuration.
+# Steps for testing stale-refresh-time option (default).
+# 1. Prime cache data.example txt
+# 2. Disable responses from authoritative server.
+# 3. Sleep for TTL duration so rrset TTL expires (2 sec)
+# 4. Query data.example
+# 5. Check if response come from stale rrset (4 sec TTL)
+# 6. Enable responses from authoritative server.
+# 7. Query data.example
+# 8. Check if response come from stale rrset, since the query
+# is still within stale-refresh-time window.
+n=$((n+1))
+echo_i "check 'rndc serve-stale status' ($n)"
+ret=0
+$RNDCCMD 10.53.0.1 serve-stale status > rndc.out.test$n 2>&1 || ret=1
+grep '_default: stale cache enabled; stale answers enabled (stale-answer-ttl=4 max-stale-ttl=3600 stale-refresh-time=30)' rndc.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Step 1-3 done above.
+
+# Step 4.
+n=$((n+1))
+echo_i "sending query for test ($n)"
+$DIG -p ${PORT} @10.53.0.1 data.example TXT > dig.out.test$n
+
+# Step 5.
+echo_i "check stale data.example TXT (stale-refresh-time) ($n)"
+ret=0
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (query within stale refresh time window)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*4.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Step 6.
+n=$((n+1))
+echo_i "enable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt enable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Step 7.
+echo_i "sending query for test $((n+1))"
+$DIG -p ${PORT} @10.53.0.1 data.example TXT > dig.out.test$((n+1))
+
+# Step 8.
+n=$((n+1))
+echo_i "check stale data.example TXT comes from cache (stale-refresh-time) ($n)"
+ret=0
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (query within stale refresh time window)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*4.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+#
+# Test disabling serve-stale via rndc.
+#
+
+n=$((n+1))
+echo_i "updating ns1/named.conf ($n)"
+ret=0
+copy_setports ns1/named2.conf.in ns1/named.conf
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "running 'rndc reload' ($n)"
+ret=0
+rndc_reload ns1 10.53.0.1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check 'rndc serve-stale status' ($n)"
+ret=0
+$RNDCCMD 10.53.0.1 serve-stale status > rndc.out.test$n 2>&1 || ret=1
+grep '_default: stale cache enabled; stale answers enabled (stale-answer-ttl=4 max-stale-ttl=3600 stale-refresh-time=0)' rndc.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "disable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt disable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"0\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "running 'rndc serve-stale off' ($n)"
+ret=0
+$RNDCCMD 10.53.0.1 serve-stale off || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check 'rndc serve-stale status' ($n)"
+ret=0
+$RNDCCMD 10.53.0.1 serve-stale status > rndc.out.test$n 2>&1 || ret=1
+grep '_default: stale cache enabled; stale answers disabled (stale-answer-ttl=4 max-stale-ttl=3600 stale-refresh-time=0)' rndc.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+echo_i "sending queries for tests $((n+1))-$((n+4))..."
+$DIG -p ${PORT} @10.53.0.1 data.example TXT > dig.out.test$((n+1)) &
+$DIG -p ${PORT} @10.53.0.1 othertype.example CAA > dig.out.test$((n+2)) &
+$DIG -p ${PORT} @10.53.0.1 nodata.example TXT > dig.out.test$((n+3)) &
+$DIG -p ${PORT} @10.53.0.1 nxdomain.example TXT > dig.out.test$((n+4)) &
+
+wait
+
+n=$((n+1))
+echo_i "check stale data.example TXT (serve-stale off) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+grep "EDE" dig.out.test$n > /dev/null && ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check stale othertype.example CAA (serve-stale off) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+grep "EDE" dig.out.test$n > /dev/null && ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check stale nodata.example TXT (serve-stale off) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+grep "EDE" dig.out.test$n > /dev/null && ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check stale nxdomain.example TXT (serve-stale off) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+grep "EDE" dig.out.test$n > /dev/null && ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+#
+# Test enabling serve-stale via rndc.
+#
+n=$((n+1))
+echo_i "running 'rndc serve-stale on' ($n)"
+ret=0
+$RNDCCMD 10.53.0.1 serve-stale on || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check 'rndc serve-stale status' ($n)"
+ret=0
+$RNDCCMD 10.53.0.1 serve-stale status > rndc.out.test$n 2>&1 || ret=1
+grep '_default: stale cache enabled; stale answers enabled (stale-answer-ttl=4 max-stale-ttl=3600 stale-refresh-time=0)' rndc.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+echo_i "sending queries for tests $((n+1))-$((n+4))..."
+$DIG -p ${PORT} @10.53.0.1 data.example TXT > dig.out.test$((n+1)) &
+$DIG -p ${PORT} @10.53.0.1 othertype.example CAA > dig.out.test$((n+2)) &
+$DIG -p ${PORT} @10.53.0.1 nodata.example TXT > dig.out.test$((n+3)) &
+$DIG -p ${PORT} @10.53.0.1 nxdomain.example TXT > dig.out.test$((n+4)) &
+
+wait
+
+n=$((n+1))
+echo_i "check stale data.example TXT (serve-stale on) ($n)"
+ret=0
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (resolver failure)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*4.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check stale othertype.example CAA (serve-stale on) ($n)"
+ret=0
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (resolver failure)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "othertype\.example\..*4.*IN.*CAA.*0.*issue" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check stale nodata.example TXT (serve-stale on) ($n)"
+ret=0
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (resolver failure)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+grep "example\..*4.*IN.*SOA" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check stale nxdomain.example TXT (serve-stale on) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "running 'rndc serve-stale off' ($n)"
+ret=0
+$RNDCCMD 10.53.0.1 serve-stale off || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "running 'rndc serve-stale reset' ($n)"
+ret=0
+$RNDCCMD 10.53.0.1 serve-stale reset || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check 'rndc serve-stale status' ($n)"
+ret=0
+$RNDCCMD 10.53.0.1 serve-stale status > rndc.out.test$n 2>&1 || ret=1
+grep '_default: stale cache enabled; stale answers enabled (stale-answer-ttl=4 max-stale-ttl=3600 stale-refresh-time=0)' rndc.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+echo_i "sending queries for tests $((n+1))-$((n+4))..."
+$DIG -p ${PORT} @10.53.0.1 data.example TXT > dig.out.test$((n+1)) &
+$DIG -p ${PORT} @10.53.0.1 othertype.example CAA > dig.out.test$((n+2)) &
+$DIG -p ${PORT} @10.53.0.1 nodata.example TXT > dig.out.test$((n+3)) &
+$DIG -p ${PORT} @10.53.0.1 nxdomain.example TXT > dig.out.test$((n+4)) &
+
+wait
+
+n=$((n+1))
+echo_i "check stale data.example TXT (serve-stale reset) ($n)"
+ret=0
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (resolver failure)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*4.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check stale othertype.example CAA (serve-stale reset) ($n)"
+ret=0
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (resolver failure)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "othertype.example\..*4.*IN.*CAA.*0.*issue" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check stale nodata.example TXT (serve-stale reset) ($n)"
+ret=0
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (resolver failure)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+grep "example\..*4.*IN.*SOA" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check stale nxdomain.example TXT (serve-stale reset) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "running 'rndc serve-stale off' ($n)"
+ret=0
+$RNDCCMD 10.53.0.1 serve-stale off || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check 'rndc serve-stale status' ($n)"
+ret=0
+$RNDCCMD 10.53.0.1 serve-stale status > rndc.out.test$n 2>&1 || ret=1
+grep '_default: stale cache enabled; stale answers disabled (stale-answer-ttl=4 max-stale-ttl=3600 stale-refresh-time=0)' rndc.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+#
+# Update named.conf.
+# Test server with low max-stale-ttl.
+#
+echo_i "test server with serve-stale options set, low max-stale-ttl"
+
+n=$((n+1))
+echo_i "updating ns1/named.conf ($n)"
+ret=0
+copy_setports ns1/named3.conf.in ns1/named.conf
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "running 'rndc reload' ($n)"
+ret=0
+rndc_reload ns1 10.53.0.1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check 'rndc serve-stale status' ($n)"
+ret=0
+$RNDCCMD 10.53.0.1 serve-stale status > rndc.out.test$n 2>&1 || ret=1
+grep '_default: stale cache enabled; stale answers disabled (stale-answer-ttl=3 max-stale-ttl=20 stale-refresh-time=30)' rndc.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "flush cache, re-enable serve-stale and query again ($n)"
+ret=0
+$RNDCCMD 10.53.0.1 flushtree example > rndc.out.test$n.1 2>&1 || ret=1
+$RNDCCMD 10.53.0.1 serve-stale on > rndc.out.test$n.2 2>&1 || ret=1
+$DIG -p ${PORT} @10.53.0.1 data.example TXT > dig.out.test$n
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check 'rndc serve-stale status' ($n)"
+ret=0
+$RNDCCMD 10.53.0.1 serve-stale status > rndc.out.test$n 2>&1 || ret=1
+grep '_default: stale cache enabled; stale answers enabled (stale-answer-ttl=3 max-stale-ttl=20 stale-refresh-time=30)' rndc.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "enable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt enable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache longttl.example TXT (low max-stale-ttl) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.1 longttl.example TXT > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache data.example TXT (low max-stale-ttl) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.1 data.example TXT > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache othertype.example CAA (low max-stale-ttl) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.1 othertype.example CAA > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache nodata.example TXT (low max-stale-ttl) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.1 nodata.example TXT > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache nxdomain.example TXT (low max-stale-ttl) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.1 nxdomain.example TXT > dig.out.test$n
+grep "status: NXDOMAIN" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Keep track of time so we can access these RRset later, when we expect them
+# to become ancient.
+t1=$($PERL -e 'print time()')
+
+n=$((n+1))
+echo_i "verify prime cache statistics (low max-stale-ttl) ($n)"
+ret=0
+rm -f ns1/named.stats
+$RNDCCMD 10.53.0.1 stats > /dev/null 2>&1
+[ -f ns1/named.stats ] || ret=1
+cp ns1/named.stats ns1/named.stats.$n
+# Check first 10 lines of Cache DB statistics. After prime queries, we expect
+# two active TXT RRsets, one active Others, one nxrrset TXT, and one NXDOMAIN.
+grep -A 10 "++ Cache DB RRsets ++" ns1/named.stats.$n > ns1/named.stats.$n.cachedb || ret=1
+grep "2 TXT" ns1/named.stats.$n.cachedb > /dev/null || ret=1
+grep "1 Others" ns1/named.stats.$n.cachedb > /dev/null || ret=1
+grep "1 !TXT" ns1/named.stats.$n.cachedb > /dev/null || ret=1
+grep "1 NXDOMAIN" ns1/named.stats.$n.cachedb > /dev/null || ret=1
+status=$((status+ret))
+if [ $ret != 0 ]; then echo_i "failed"; fi
+
+n=$((n+1))
+echo_i "disable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt disable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"0\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+sleep 2
+
+echo_i "sending queries for tests $((n+1))-$((n+4))..."
+$DIG -p ${PORT} @10.53.0.1 data.example TXT > dig.out.test$((n+1)) &
+$DIG -p ${PORT} @10.53.0.1 othertype.example CAA > dig.out.test$((n+2)) &
+$DIG -p ${PORT} @10.53.0.1 nodata.example TXT > dig.out.test$((n+3)) &
+$DIG -p ${PORT} @10.53.0.1 nxdomain.example TXT > dig.out.test$((n+4)) &
+
+wait
+
+n=$((n+1))
+echo_i "check stale data.example TXT (low max-stale-ttl) ($n)"
+ret=0
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (resolver failure)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*3.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check stale othertype.example CAA (low max-stale-ttl) ($n)"
+ret=0
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (resolver failure)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "othertype\.example\..*3.*IN.*CAA.*0.*issue" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check stale nodata.example TXT (low max-stale-ttl) ($n)"
+ret=0
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (resolver failure)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+grep "example\..*3.*IN.*SOA" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check stale nxdomain.example TXT (low max-stale-ttl) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "verify stale cache statistics (low max-stale-ttl) ($n)"
+ret=0
+rm -f ns1/named.stats
+$RNDCCMD 10.53.0.1 stats > /dev/null 2>&1
+[ -f ns1/named.stats ] || ret=1
+cp ns1/named.stats ns1/named.stats.$n
+# Check first 10 lines of Cache DB statistics. After serve-stale queries, we
+# expect one active TXT RRset, one stale TXT, one stale nxrrset TXT, and one
+# stale NXDOMAIN.
+grep -A 10 "++ Cache DB RRsets ++" ns1/named.stats.$n > ns1/named.stats.$n.cachedb || ret=1
+grep "1 TXT" ns1/named.stats.$n.cachedb > /dev/null || ret=1
+grep "1 #TXT" ns1/named.stats.$n.cachedb > /dev/null || ret=1
+grep "1 #Others" ns1/named.stats.$n.cachedb > /dev/null || ret=1
+grep "1 #!TXT" ns1/named.stats.$n.cachedb > /dev/null || ret=1
+
+status=$((status+ret))
+if [ $ret != 0 ]; then echo_i "failed"; fi
+
+# Retrieve max-stale-ttl value.
+interval_to_ancient=$(grep 'max-stale-ttl' ns1/named3.conf.in | awk '{ print $2 }' | tr -d ';')
+# We add 2 seconds to it since this is the ttl value of the records being
+# tested.
+interval_to_ancient=$((interval_to_ancient + 2))
+t2=$($PERL -e 'print time()')
+elapsed=$((t2 - t1))
+
+# If elapsed time so far is less than max-stale-ttl + 2 seconds, then we sleep
+# enough to ensure that we'll ask for ancient RRsets in the next queries.
+if [ $elapsed -lt $interval_to_ancient ]; then
+ sleep $((interval_to_ancient - elapsed))
+fi
+
+echo_i "sending queries for tests $((n+1))-$((n+4))..."
+$DIG -p ${PORT} @10.53.0.1 data.example TXT > dig.out.test$((n+1)) &
+$DIG -p ${PORT} @10.53.0.1 othertype.example CAA > dig.out.test$((n+2)) &
+$DIG -p ${PORT} @10.53.0.1 nodata.example TXT > dig.out.test$((n+3)) &
+$DIG -p ${PORT} @10.53.0.1 nxdomain.example TXT > dig.out.test$((n+4)) &
+
+wait
+
+n=$((n+1))
+echo_i "check ancient data.example TXT (low max-stale-ttl) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+grep "EDE" dig.out.test$n > /dev/null && ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check ancient othertype.example CAA (low max-stale-ttl) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+grep "EDE" dig.out.test$n > /dev/null && ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check ancient nodata.example TXT (low max-stale-ttl) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+grep "EDE" dig.out.test$n > /dev/null && ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check ancient nxdomain.example TXT (low max-stale-ttl) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+grep "EDE" dig.out.test$n > /dev/null && ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Test stale-refresh-time when serve-stale is enabled via rndc.
+# Steps for testing stale-refresh-time option (default).
+# 1. Prime cache data.example txt
+# 2. Disable responses from authoritative server.
+# 3. Sleep for TTL duration so rrset TTL expires (2 sec)
+# 4. Query data.example
+# 5. Check if response come from stale rrset (3 sec TTL)
+# 6. Enable responses from authoritative server.
+# 7. Query data.example
+# 8. Check if response come from stale rrset, since the query
+# is within stale-refresh-time window.
+n=$((n+1))
+echo_i "flush cache, enable responses from authoritative server ($n)"
+ret=0
+$RNDCCMD 10.53.0.1 flushtree example > rndc.out.test$n.1 2>&1 || ret=1
+$DIG -p ${PORT} @10.53.0.2 txt enable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check 'rndc serve-stale status' ($n)"
+ret=0
+$RNDCCMD 10.53.0.1 serve-stale status > rndc.out.test$n 2>&1 || ret=1
+grep '_default: stale cache enabled; stale answers enabled (stale-answer-ttl=3 max-stale-ttl=20 stale-refresh-time=30)' rndc.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Step 1.
+n=$((n+1))
+echo_i "prime cache data.example TXT (stale-refresh-time rndc) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.1 data.example TXT > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*2.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Step 2.
+n=$((n+1))
+echo_i "disable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt disable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"0\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Step 3.
+sleep 2
+
+# Step 4.
+n=$((n+1))
+echo_i "sending query for test ($n)"
+$DIG -p ${PORT} @10.53.0.1 data.example TXT > dig.out.test$n
+
+# Step 5.
+echo_i "check stale data.example TXT (stale-refresh-time rndc) ($n)"
+ret=0
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (resolver failure)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*3.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Step 6.
+n=$((n+1))
+echo_i "enable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt enable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Step 7.
+echo_i "sending query for test $((n+1))"
+$DIG -p ${PORT} @10.53.0.1 data.example TXT > dig.out.test$((n+1))
+
+# Step 8.
+n=$((n+1))
+echo_i "check stale data.example TXT comes from cache (stale-refresh-time rndc) ($n)"
+ret=0
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (query within stale refresh time window)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*3.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Steps for testing stale-refresh-time option (disabled).
+# 1. Prime cache data.example txt
+# 2. Disable responses from authoritative server.
+# 3. Sleep for TTL duration so rrset TTL expires (2 sec)
+# 4. Query data.example
+# 5. Check if response come from stale rrset (3 sec TTL)
+# 6. Enable responses from authoritative server.
+# 7. Query data.example
+# 8. Check if response come from stale rrset, since the query
+# is within stale-refresh-time window.
+n=$((n+1))
+echo_i "updating ns1/named.conf ($n)"
+ret=0
+copy_setports ns1/named4.conf.in ns1/named.conf
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "running 'rndc reload' ($n)"
+ret=0
+rndc_reload ns1 10.53.0.1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check 'rndc serve-stale status' ($n)"
+ret=0
+$RNDCCMD 10.53.0.1 serve-stale status > rndc.out.test$n 2>&1 || ret=1
+grep '_default: stale cache enabled; stale answers enabled (stale-answer-ttl=3 max-stale-ttl=20 stale-refresh-time=0)' rndc.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "flush cache, enable responses from authoritative server ($n)"
+ret=0
+$RNDCCMD 10.53.0.1 flushtree example > rndc.out.test$n.1 2>&1 || ret=1
+$DIG -p ${PORT} @10.53.0.2 txt enable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Step 1.
+n=$((n+1))
+echo_i "prime cache data.example TXT (stale-refresh-time disabled) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.1 data.example TXT > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE" dig.out.test$n > /dev/null && ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*2.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Step 2.
+n=$((n+1))
+echo_i "disable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt disable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"0\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Step 3.
+sleep 2
+
+# Step 4.
+n=$((n+1))
+echo_i "sending query for test ($n)"
+$DIG -p ${PORT} @10.53.0.1 data.example TXT > dig.out.test$n
+
+# Step 5.
+echo_i "check stale data.example TXT (stale-refresh-time disabled) ($n)"
+ret=0
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (resolver failure)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*3.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Step 6.
+n=$((n+1))
+echo_i "enable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt enable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Step 7.
+echo_i "sending query for test $((n+1))"
+$DIG -p ${PORT} @10.53.0.1 data.example TXT > dig.out.test$((n+1))
+
+# Step 8.
+n=$((n+1))
+echo_i "check data.example TXT comes from authoritative (stale-refresh-time disabled) ($n)"
+ret=0
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE" dig.out.test$n > /dev/null && ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*2.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+#
+# Now test server with no serve-stale options set.
+#
+echo_i "test server with no serve-stale options set"
+
+n=$((n+1))
+echo_i "enable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt enable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache longttl.example TXT (max-stale-ttl default) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.3 longttl.example TXT > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache data.example TXT (max-stale-ttl default) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*2.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache othertype.example CAA (max-stale-ttl default) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.3 othertype.example CAA > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "othertype\.example\..*2.*IN.*CAA.*0.*issue" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache nodata.example TXT (max-stale-ttl default) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.3 nodata.example TXT > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+grep "example\..*2.*IN.*SOA" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache nxdomain.example TXT (max-stale-ttl default) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.3 nxdomain.example TXT > dig.out.test$n
+grep "status: NXDOMAIN" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+grep "example\..*2.*IN.*SOA" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "verify prime cache statistics (max-stale-ttl default) ($n)"
+ret=0
+rm -f ns3/named.stats
+$RNDCCMD 10.53.0.3 stats > /dev/null 2>&1
+[ -f ns3/named.stats ] || ret=1
+cp ns3/named.stats ns3/named.stats.$n
+# Check first 10 lines of Cache DB statistics. After prime queries, we expect
+# two active TXT RRsets, one active Others, one nxrrset TXT, and one NXDOMAIN.
+grep -A 10 "++ Cache DB RRsets ++" ns3/named.stats.$n > ns3/named.stats.$n.cachedb || ret=1
+grep "2 TXT" ns3/named.stats.$n.cachedb > /dev/null || ret=1
+grep "1 Others" ns3/named.stats.$n.cachedb > /dev/null || ret=1
+grep "1 !TXT" ns3/named.stats.$n.cachedb > /dev/null || ret=1
+grep "1 NXDOMAIN" ns3/named.stats.$n.cachedb > /dev/null || ret=1
+status=$((status+ret))
+if [ $ret != 0 ]; then echo_i "failed"; fi
+
+n=$((n+1))
+echo_i "disable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt disable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"0\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check 'rndc serve-stale status' ($n)"
+ret=0
+$RNDCCMD 10.53.0.3 serve-stale status > rndc.out.test$n 2>&1 || ret=1
+grep "_default: stale cache enabled; stale answers disabled (stale-answer-ttl=$stale_answer_ttl max-stale-ttl=$max_stale_ttl stale-refresh-time=30)" rndc.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+sleep 2
+
+echo_i "sending queries for tests $((n+1))-$((n+4))..."
+$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$((n+1)) &
+$DIG -p ${PORT} @10.53.0.3 othertype.example CAA > dig.out.test$((n+2)) &
+$DIG -p ${PORT} @10.53.0.3 nodata.example TXT > dig.out.test$((n+3)) &
+$DIG -p ${PORT} @10.53.0.3 nxdomain.example TXT > dig.out.test$((n+4)) &
+
+wait
+
+n=$((n+1))
+echo_i "check fail of data.example TXT (max-stale-ttl default) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+grep "EDE" dig.out.test$n > /dev/null && ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check fail of othertype.example CAA (max-stale-ttl default) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+grep "EDE" dig.out.test$n > /dev/null && ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check fail of nodata.example TXT (max-stale-ttl default) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+grep "EDE" dig.out.test$n > /dev/null && ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check fail of nxdomain.example TXT (max-stale-ttl default) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+grep "EDE" dig.out.test$n > /dev/null && ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "verify stale cache statistics (max-stale-ttl default) ($n)"
+ret=0
+rm -f ns3/named.stats
+$RNDCCMD 10.53.0.3 stats > /dev/null 2>&1
+[ -f ns3/named.stats ] || ret=1
+cp ns3/named.stats ns3/named.stats.$n
+# Check first 10 lines of Cache DB statistics. After last queries, we expect
+# one active TXT RRset, one stale TXT, one stale nxrrset TXT, and one stale
+# NXDOMAIN.
+grep -A 10 "++ Cache DB RRsets ++" ns3/named.stats.$n > ns3/named.stats.$n.cachedb || ret=1
+grep "1 TXT" ns3/named.stats.$n.cachedb > /dev/null || ret=1
+grep "1 #TXT" ns3/named.stats.$n.cachedb > /dev/null || ret=1
+grep "1 #Others" ns3/named.stats.$n.cachedb > /dev/null || ret=1
+grep "1 #!TXT" ns3/named.stats.$n.cachedb > /dev/null || ret=1
+
+status=$((status+ret))
+if [ $ret != 0 ]; then echo_i "failed"; fi
+
+n=$((n+1))
+echo_i "check 'rndc serve-stale on' ($n)"
+ret=0
+$RNDCCMD 10.53.0.3 serve-stale on > rndc.out.test$n 2>&1 || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check 'rndc serve-stale status' ($n)"
+ret=0
+$RNDCCMD 10.53.0.3 serve-stale status > rndc.out.test$n 2>&1 || ret=1
+grep "_default: stale cache enabled; stale answers enabled (stale-answer-ttl=$stale_answer_ttl max-stale-ttl=$max_stale_ttl stale-refresh-time=30)" rndc.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+sleep 2
+
+# Check that if we don't have stale data for a domain name, we will
+# not answer anything until the resolver query timeout.
+n=$((n+1))
+echo_i "check notincache.example TXT times out (max-stale-ttl default) ($n)"
+ret=0
+$DIG -p ${PORT} +tries=1 +timeout=3 @10.53.0.3 notfound.example TXT > dig.out.test$n 2>&1 && ret=1
+grep "timed out" dig.out.test$n > /dev/null || ret=1
+grep ";; no servers could be reached" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+echo_i "sending queries for tests $((n+1))-$((n+4))..."
+$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$((n+1)) &
+$DIG -p ${PORT} @10.53.0.3 othertype.example CAA > dig.out.test$((n+2)) &
+$DIG -p ${PORT} @10.53.0.3 nodata.example TXT > dig.out.test$((n+3)) &
+$DIG -p ${PORT} @10.53.0.3 nxdomain.example TXT > dig.out.test$((n+4)) &
+$DIG -p ${PORT} @10.53.0.3 notfound.example TXT > dig.out.test$((n+5)) &
+
+wait
+
+n=$((n+1))
+echo_i "check data.example TXT (max-stale-ttl default) ($n)"
+ret=0
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (resolver failure)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*30.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check othertype.example CAA (max-stale-ttl default) ($n)"
+ret=0
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (resolver failure)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "example\..*30.*IN.*CAA.*0.*issue" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check nodata.example TXT (max-stale-ttl default) ($n)"
+ret=0
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (resolver failure)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+grep "example\..*30.*IN.*SOA" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check nxdomain.example TXT (max-stale-ttl default) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# The notfound.example check is different than nxdomain.example because
+# we didn't send a prime query to add notfound.example to the cache.
+n=$((n+1))
+echo_i "check notfound.example TXT (max-stale-ttl default) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+grep "EDE" dig.out.test$n > /dev/null && ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+#
+# Now test server with serve-stale answers disabled.
+#
+echo_i "test server with serve-stale disabled"
+
+n=$((n+1))
+echo_i "enable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt enable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache longttl.example TTL (serve-stale answers disabled) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.4 longttl.example TXT > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache data.example TTL (serve-stale answers disabled) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.4 data.example TXT > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*2.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache othertype.example CAA (serve-stale answers disabled) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.4 othertype.example CAA > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "othertype\.example\..*2.*IN.*CAA.*0.*issue" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache nodata.example TXT (serve-stale answers disabled) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.4 nodata.example TXT > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+grep "example\..*2.*IN.*SOA" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache nxdomain.example TXT (serve-stale answers disabled) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.4 nxdomain.example TXT > dig.out.test$n
+grep "status: NXDOMAIN" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+grep "example\..*2.*IN.*SOA" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "verify prime cache statistics (serve-stale answers disabled) ($n)"
+ret=0
+rm -f ns4/named.stats
+$RNDCCMD 10.53.0.4 stats > /dev/null 2>&1
+[ -f ns4/named.stats ] || ret=1
+cp ns4/named.stats ns4/named.stats.$n
+# Check first 10 lines of Cache DB statistics. After prime queries, we expect
+# two active TXT RRsets, one active Others, one nxrrset TXT, and one NXDOMAIN.
+grep -A 10 "++ Cache DB RRsets ++" ns4/named.stats.$n > ns4/named.stats.$n.cachedb || ret=1
+grep "2 TXT" ns4/named.stats.$n.cachedb > /dev/null || ret=1
+grep "1 Others" ns4/named.stats.$n.cachedb > /dev/null || ret=1
+grep "1 !TXT" ns4/named.stats.$n.cachedb > /dev/null || ret=1
+grep "1 NXDOMAIN" ns4/named.stats.$n.cachedb > /dev/null || ret=1
+status=$((status+ret))
+if [ $ret != 0 ]; then echo_i "failed"; fi
+
+n=$((n+1))
+echo_i "disable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt disable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"0\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check 'rndc serve-stale status' ($n)"
+ret=0
+$RNDCCMD 10.53.0.4 serve-stale status > rndc.out.test$n 2>&1 || ret=1
+grep "_default: stale cache enabled; stale answers disabled (stale-answer-ttl=$stale_answer_ttl max-stale-ttl=$max_stale_ttl stale-refresh-time=30)" rndc.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+sleep 2
+
+echo_i "sending queries for tests $((n+1))-$((n+4))..."
+$DIG -p ${PORT} @10.53.0.4 data.example TXT > dig.out.test$((n+1)) &
+$DIG -p ${PORT} @10.53.0.4 othertype.example CAA > dig.out.test$((n+2)) &
+$DIG -p ${PORT} @10.53.0.4 nodata.example TXT > dig.out.test$((n+3)) &
+$DIG -p ${PORT} @10.53.0.4 nxdomain.example TXT > dig.out.test$((n+4)) &
+
+wait
+
+n=$((n+1))
+echo_i "check fail of data.example TXT (serve-stale answers disabled) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+grep "EDE" dig.out.test$n > /dev/null && ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check fail of othertype.example TXT (serve-stale answers disabled) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+grep "EDE" dig.out.test$n > /dev/null && ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check fail of nodata.example TXT (serve-stale answers disabled) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+grep "EDE" dig.out.test$n > /dev/null && ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check fail of nxdomain.example TXT (serve-stale answers disabled) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+grep "EDE" dig.out.test$n > /dev/null && ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "verify stale cache statistics (serve-stale answers disabled) ($n)"
+ret=0
+rm -f ns4/named.stats
+$RNDCCMD 10.53.0.4 stats > /dev/null 2>&1
+[ -f ns4/named.stats ] || ret=1
+cp ns4/named.stats ns4/named.stats.$n
+# Check first 10 lines of Cache DB statistics. After last queries, we expect
+# one active TXT RRset, one stale TXT, one stale nxrrset TXT, and one stale
+# NXDOMAIN.
+grep -A 10 "++ Cache DB RRsets ++" ns4/named.stats.$n > ns4/named.stats.$n.cachedb || ret=1
+grep "1 TXT" ns4/named.stats.$n.cachedb > /dev/null || ret=1
+grep "1 #TXT" ns4/named.stats.$n.cachedb > /dev/null || ret=1
+grep "1 #Others" ns4/named.stats.$n.cachedb > /dev/null || ret=1
+grep "1 #!TXT" ns4/named.stats.$n.cachedb > /dev/null || ret=1
+status=$((status+ret))
+if [ $ret != 0 ]; then echo_i "failed"; fi
+
+# Dump the cache.
+n=$((n+1))
+echo_i "dump the cache (serve-stale answers disabled) ($n)"
+ret=0
+rndc_dumpdb ns4 -cache || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+echo_i "stop ns4"
+stop_server --use-rndc --port ${CONTROLPORT} ns4
+
+# Load the cache as if it was five minutes (RBTDB_VIRTUAL) older. Since
+# max-stale-ttl defaults to a week, we need to adjust the date by one week and
+# five minutes.
+LASTWEEK=$(TZ=UTC perl -e 'my $now = time();
+ my $oneWeekAgo = $now - 604800;
+ my $fiveMinutesAgo = $oneWeekAgo - 300;
+ my ($s, $m, $h, $d, $mo, $y) = (localtime($fiveMinutesAgo))[0, 1, 2, 3, 4, 5];
+ printf("%04d%02d%02d%02d%02d%02d", $y+1900, $mo+1, $d, $h, $m, $s);')
+
+echo_i "mock the cache date to $LASTWEEK (serve-stale answers disabled) ($n)"
+ret=0
+sed -E "s/DATE [0-9]{14}/DATE $LASTWEEK/g" ns4/named_dump.db.test$n > ns4/named_dump.db.out || ret=1
+cp ns4/named_dump.db.out ns4/named_dump.db
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+echo_i "start ns4"
+start_server --noclean --restart --port ${PORT} ns4
+
+n=$((n+1))
+echo_i "verify ancient cache statistics (serve-stale answers disabled) ($n)"
+ret=0
+rm -f ns4/named.stats
+$RNDCCMD 10.53.0.4 stats #> /dev/null 2>&1
+[ -f ns4/named.stats ] || ret=1
+cp ns4/named.stats ns4/named.stats.$n
+# Check first 10 lines of Cache DB statistics. After last queries, we expect
+# everything to be removed or scheduled to be removed.
+grep -A 10 "++ Cache DB RRsets ++" ns4/named.stats.$n > ns4/named.stats.$n.cachedb || ret=1
+grep "#TXT" ns4/named.stats.$n.cachedb > /dev/null && ret=1
+grep "#Others" ns4/named.stats.$n.cachedb > /dev/null && ret=1
+grep "#!TXT" ns4/named.stats.$n.cachedb > /dev/null && ret=1
+grep "#NXDOMAIN" ns4/named.stats.$n.cachedb > /dev/null && ret=1
+status=$((status+ret))
+if [ $ret != 0 ]; then echo_i "failed"; fi
+
+#
+# Test the server with stale-cache disabled.
+#
+echo_i "test server with serve-stale cache disabled"
+
+n=$((n+1))
+echo_i "enable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt enable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache longttl.example TXT (serve-stale cache disabled) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.5 longttl.example TXT > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache data.example TXT (serve-stale cache disabled) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.5 data.example TXT > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*2.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache othertype.example CAA (serve-stale cache disabled) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.5 othertype.example CAA > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "othertype\.example\..*2.*IN.*CAA.*0.*issue" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache nodata.example TXT (serve-stale cache disabled) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.5 nodata.example TXT > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+grep "example\..*2.*IN.*SOA" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache nxdomain.example TXT (serve-stale cache disabled) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.5 nxdomain.example TXT > dig.out.test$n
+grep "status: NXDOMAIN" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+grep "example\..*2.*IN.*SOA" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "verify prime cache statistics (serve-stale cache disabled) ($n)"
+ret=0
+rm -f ns5/named.stats
+$RNDCCMD 10.53.0.5 stats > /dev/null 2>&1
+[ -f ns5/named.stats ] || ret=1
+cp ns5/named.stats ns5/named.stats.$n
+# Check first 10 lines of Cache DB statistics. After serve-stale queries,
+# we expect two active TXT RRsets, one active Others, one nxrrset TXT, and
+# one NXDOMAIN.
+grep -A 10 "++ Cache DB RRsets ++" ns5/named.stats.$n > ns5/named.stats.$n.cachedb || ret=1
+grep "2 TXT" ns5/named.stats.$n.cachedb > /dev/null || ret=1
+grep "1 Others" ns5/named.stats.$n.cachedb > /dev/null || ret=1
+grep "1 !TXT" ns5/named.stats.$n.cachedb > /dev/null || ret=1
+status=$((status+ret))
+if [ $ret != 0 ]; then echo_i "failed"; fi
+
+n=$((n+1))
+echo_i "disable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt disable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"0\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check 'rndc serve-stale status' ($n)"
+ret=0
+$RNDCCMD 10.53.0.5 serve-stale status > rndc.out.test$n 2>&1 || ret=1
+grep "_default: stale cache disabled; stale answers unavailable" rndc.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+sleep 2
+
+echo_i "sending queries for tests $((n+1))-$((n+4))..."
+$DIG -p ${PORT} @10.53.0.5 data.example TXT > dig.out.test$((n+1)) &
+$DIG -p ${PORT} @10.53.0.5 othertype.example CAA > dig.out.test$((n+2)) &
+$DIG -p ${PORT} @10.53.0.5 nodata.example TXT > dig.out.test$((n+3)) &
+$DIG -p ${PORT} @10.53.0.5 nxdomain.example TXT > dig.out.test$((n+4)) &
+
+wait
+
+n=$((n+1))
+echo_i "check fail of data.example TXT (serve-stale cache disabled) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+grep "EDE" dig.out.test$n > /dev/null && ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check fail of othertype.example CAA (serve-stale cache disabled) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+grep "EDE" dig.out.test$n > /dev/null && ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check fail of nodata.example TXT (serve-stale cache disabled) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+grep "EDE" dig.out.test$n > /dev/null && ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check fail of nxdomain.example TXT (serve-stale cache disabled) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+grep "EDE" dig.out.test$n > /dev/null && ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "verify stale cache statistics (serve-stale cache disabled) ($n)"
+ret=0
+rm -f ns5/named.stats
+$RNDCCMD 10.53.0.5 stats > /dev/null 2>&1
+[ -f ns5/named.stats ] || ret=1
+cp ns5/named.stats ns5/named.stats.$n
+# Check first 10 lines of Cache DB statistics. After serve-stale queries,
+# we expect one active TXT (longttl) and the rest to be expired from cache,
+# but since we keep everything for 5 minutes (RBTDB_VIRTUAL) in the cache
+# after expiry, they still show up in the stats.
+grep -A 10 "++ Cache DB RRsets ++" ns5/named.stats.$n > ns5/named.stats.$n.cachedb || ret=1
+grep -F "1 Others" ns5/named.stats.$n.cachedb > /dev/null || ret=1
+grep -F "2 TXT" ns5/named.stats.$n.cachedb > /dev/null || ret=1
+grep -F "1 !TXT" ns5/named.stats.$n.cachedb > /dev/null || ret=1
+status=$((status+ret))
+if [ $ret != 0 ]; then echo_i "failed"; fi
+
+# Dump the cache.
+n=$((n+1))
+echo_i "dump the cache (serve-stale cache disabled) ($n)"
+ret=0
+rndc_dumpdb ns5 || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+# Check that expired records are not dumped.
+ret=0
+grep "; expired since .* (awaiting cleanup)" ns5/named_dump.db.test$n && ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Dump the cache including expired entries.
+n=$((n+1))
+echo_i "dump the cache including expired entries (serve-stale cache disabled) ($n)"
+ret=0
+rndc_dumpdb ns5 -expired || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Check that expired records are dumped.
+echo_i "check rndc dump expired data.example ($n)"
+ret=0
+awk '/; expired/ { x=$0; getline; print x, $0}' ns5/named_dump.db.test$n |
+ grep "; expired since .* (awaiting cleanup) data\.example\..*A text record with a 2 second ttl" > /dev/null 2>&1 || ret=1
+awk '/; expired/ { x=$0; getline; print x, $0}' ns5/named_dump.db.test$n |
+ grep "; expired since .* (awaiting cleanup) nodata\.example\." > /dev/null 2>&1 || ret=1
+awk '/; expired/ { x=$0; getline; print x, $0}' ns5/named_dump.db.test$n |
+ grep "; expired since .* (awaiting cleanup) nxdomain\.example\." > /dev/null 2>&1 || ret=1
+awk '/; expired/ { x=$0; getline; print x, $0}' ns5/named_dump.db.test$n |
+ grep "; expired since .* (awaiting cleanup) othertype\.example\." > /dev/null 2>&1 || ret=1
+# Also make sure the not expired data does not have an expired comment.
+awk '/; authanswer/ { x=$0; getline; print x, $0}' ns5/named_dump.db.test$n |
+ grep "; authanswer longttl\.example.*A text record with a 600 second ttl" > /dev/null 2>&1 || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+echo_i "stop ns5"
+stop_server --use-rndc --port ${CONTROLPORT} ns5
+
+# Load the cache as if it was five minutes (RBTDB_VIRTUAL) older.
+cp ns5/named_dump.db.test$n ns5/named_dump.db
+FIVEMINUTESAGO=$(TZ=UTC perl -e 'my $now = time();
+ my $fiveMinutesAgo = 300;
+ my ($s, $m, $h, $d, $mo, $y) = (localtime($fiveMinutesAgo))[0, 1, 2, 3, 4, 5];
+ printf("%04d%02d%02d%02d%02d%02d", $y+1900, $mo+1, $d, $h, $m, $s);')
+
+n=$((n+1))
+echo_i "mock the cache date to $FIVEMINUTESAGO (serve-stale cache disabled) ($n)"
+ret=0
+sed -E "s/DATE [0-9]{14}/DATE $FIVEMINUTESAGO/g" ns5/named_dump.db > ns5/named_dump.db.out || ret=1
+cp ns5/named_dump.db.out ns5/named_dump.db
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+echo_i "start ns5"
+start_server --noclean --restart --port ${PORT} ns5
+
+n=$((n+1))
+echo_i "verify ancient cache statistics (serve-stale cache disabled) ($n)"
+ret=0
+rm -f ns5/named.stats
+$RNDCCMD 10.53.0.5 stats #> /dev/null 2>&1
+[ -f ns5/named.stats ] || ret=1
+cp ns5/named.stats ns5/named.stats.$n
+# Check first 10 lines of Cache DB statistics. After last queries, we expect
+# everything to be removed or scheduled to be removed.
+grep -A 10 "++ Cache DB RRsets ++" ns5/named.stats.$n > ns5/named.stats.$n.cachedb || ret=1
+grep -F "#TXT" ns5/named.stats.$n.cachedb > /dev/null && ret=1
+grep -F "#Others" ns5/named.stats.$n.cachedb > /dev/null && ret=1
+grep -F "#!TXT" ns5/named.stats.$n.cachedb > /dev/null && ret=1
+status=$((status+ret))
+if [ $ret != 0 ]; then echo_i "failed"; fi
+
+################################################
+# Test for stale-answer-client-timeout (1.8s). #
+################################################
+echo_i "test stale-answer-client-timeout (1.8)"
+
+n=$((n+1))
+echo_i "updating ns3/named.conf ($n)"
+ret=0
+copy_setports ns3/named2.conf.in ns3/named.conf
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+echo_i "restart ns3"
+stop_server --use-rndc --port ${CONTROLPORT} ns3
+start_server --noclean --restart --port ${PORT} ns3
+
+n=$((n+1))
+echo_i "check 'rndc serve-stale status' ($n)"
+ret=0
+$RNDCCMD 10.53.0.3 serve-stale status > rndc.out.test$n 2>&1 || ret=1
+grep '_default: stale cache enabled; stale answers enabled (stale-answer-ttl=3 max-stale-ttl=3600 stale-refresh-time=0)' rndc.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "enable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt enable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache data.example TXT (stale-answer-client-timeout) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache nodata.example TXT (stale-answer-client-timeout) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.3 nodata.example TXT > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "delay responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt slowdown > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache data.slow TXT (stale-answer-client-timeout) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.3 data.slow TXT > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "disable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt disable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"0\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Allow RRset to become stale.
+sleep 2
+
+nextpart ns3/named.run > /dev/null
+
+echo_i "sending queries for tests $((n+1))-$((n+3))..."
+t1=$($PERL -e 'print time()')
+$DIG -p ${PORT} +tries=1 +timeout=11 @10.53.0.3 data.example TXT > dig.out.test$((n+1)) &
+$DIG -p ${PORT} +tries=1 +timeout=11 @10.53.0.3 nodata.example TXT > dig.out.test$((n+2)) &
+$DIG -p ${PORT} +tries=1 +timeout=11 @10.53.0.3 data.slow TXT > dig.out.test$((n+3)) &
+wait
+t2=$($PERL -e 'print time()')
+
+# We configured a long value of 30 seconds for resolver-query-timeout.
+# That should give us enough time to receive an stale answer from cache
+# after stale-answer-client-timeout timer of 1.8 sec triggers.
+n=$((n+1))
+echo_i "check stale data.example TXT comes from cache (stale-answer-client-timeout 1.8) ($n)"
+ret=0
+wait_for_log 5 "data.example client timeout, stale answer used" ns3/named.run || ret=1
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (client timeout)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*3.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+# Configured stale-answer-client-timeout is 1.8s, we allow some extra time
+# just in case other tests are taking too much cpu.
+[ $((t2 - t1)) -le 10 ] || { echo_i "query took $((t2 - t1))s to resolve."; ret=1; }
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check stale nodata.example TXT comes from cache (stale-answer-client-timeout 1.8) ($n)"
+ret=0
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (client timeout)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+grep "example\..*3.*IN.*SOA" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check stale data.slow TXT comes from cache (stale-answer-client-timeout 1.8) ($n)"
+ret=0
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (client timeout)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.slow\..*3.*IN.*TXT.*A slow text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Now query for RRset not in cache. The first query should time out, but once
+# we enable the authoritative server, the second query should be able to get a
+# response.
+
+nextpart ns3/named.run > /dev/null
+
+echo_i "sending queries for tests $((n+2))-$((n+4))..."
+# first dig runs in background for 10 seconds, second in background for 3
+# seconds and the last for 3 seconds in the foreground.
+# the second RRSIG lookup triggers the issue in [GL #3622]
+$DIG -p ${PORT} +tries=1 +timeout=10 @10.53.0.3 longttl.example TXT > dig.out.test$((n+3)) &
+$DIG -p ${PORT} +tries=1 +timeout=3 @10.53.0.3 longttl.example RRSIG > dig.out.test$((n+4)) &
+$DIG -p ${PORT} +tries=1 +timeout=3 @10.53.0.3 longttl.example TXT > dig.out.test$((n+2)) || true
+
+# Enable the authoritative name server after stale-answer-client-timeout.
+n=$((n+1))
+echo_i "enable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt enable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check not in cache longttl.example TXT times out (stale-answer-client-timeout 1.8) ($n)"
+ret=0
+wait_for_log 4 "longttl.example client timeout, stale answer unavailable" ns3/named.run || ret=1
+grep "timed out" dig.out.test$n > /dev/null || ret=1
+grep ";; no servers could be reached" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+wait
+
+n=$((n+1))
+echo_i "check not in cache longttl.example TXT comes from authoritative (stale-answer-client-timeout 1.8) ($n)"
+ret=0
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE" dig.out.test$n > /dev/null && ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check not in cache longttl.example RRSIG times out (stale-answer-client-timeout 1.8) ($n)"
+ret=0
+grep "timed out" dig.out.test$n > /dev/null || ret=1
+grep ";; no servers could be reached" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# CVE-2022-3924, GL #3619
+n=$((n+1))
+echo_i "check that named survives reaching recursive-clients quota (stale-answer-client-timeout 1.8) ($n)"
+ret=0
+num=0
+# Make sure to exceed the configured value of 'recursive-clients 10;' by running
+# 20 parallel queries with simulated network latency.
+while [ $num -lt 20 ]; do
+ $DIG +tries=1 -p ${PORT} @10.53.0.3 "latency${num}.data.example" TXT >/dev/null 2>&1 &
+ num=$((num+1))
+done;
+check_server_responds() {
+ $DIG -p ${PORT} @10.53.0.3 version.bind txt ch >dig.out.test$n || return 1
+ grep "status: NOERROR" dig.out.test$n > /dev/null || return 1
+}
+retry_quiet 5 check_server_responds || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+#############################################
+# Test for stale-answer-client-timeout off. #
+#############################################
+echo_i "test stale-answer-client-timeout (off)"
+
+n=$((n+1))
+echo_i "updating ns3/named.conf ($n)"
+ret=0
+copy_setports ns3/named3.conf.in ns3/named.conf
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "running 'rndc reload' ($n)"
+ret=0
+rndc_reload ns3 10.53.0.3
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Send a query, auth server is disabled, we will enable it after a while in
+# order to receive an answer before resolver-query-timeout expires. Since
+# stale-answer-client-timeout is disabled we must receive an answer from
+# authoritative server.
+echo_i "sending query for test $((n+2))"
+$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$((n+2)) &
+sleep 3
+
+n=$((n+1))
+echo_i "enable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt enable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Wait until dig is done.
+wait
+
+n=$((n+1))
+echo_i "check data.example TXT comes from authoritative server (stale-answer-client-timeout off) ($n)"
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE" dig.out.test$n > /dev/null && ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*[12].*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+##############################################################
+# Test for stale-answer-client-timeout off and CNAME record. #
+##############################################################
+echo_i "test stale-answer-client-timeout (0) and CNAME record"
+
+n=$((n+1))
+echo_i "prime cache shortttl.cname.example (stale-answer-client-timeout off) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.3 shortttl.cname.example A > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 2," dig.out.test$n > /dev/null || ret=1
+grep "shortttl\.cname\.example\..*1.*IN.*CNAME.*longttl\.target\.example\." dig.out.test$n > /dev/null || ret=1
+grep "longttl\.target\.example\..*600.*IN.*A.*10\.53\.0\.2" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Allow RRset to become stale.
+sleep 1
+
+n=$((n+1))
+echo_i "disable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt disable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"0\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+ret=0
+echo_i "check stale shortttl.cname.example comes from cache (stale-answer-client-timeout off) ($n)"
+nextpart ns3/named.run > /dev/null
+$DIG -p ${PORT} @10.53.0.3 shortttl.cname.example A > dig.out.test$n
+wait_for_log 5 "shortttl.cname.example resolver failure, stale answer used" ns3/named.run || ret=1
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (resolver failure)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 2," dig.out.test$n > /dev/null || ret=1
+grep "shortttl\.cname\.example\..*3.*IN.*CNAME.*longttl\.target\.example\." dig.out.test$n > /dev/null || ret=1
+# We can't reliably test the TTL of the longttl.target.example A record.
+grep "longttl\.target\.example\..*IN.*A.*10\.53\.0\.2" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "enable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt enable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check server is alive or restart ($n)"
+ret=0
+$RNDCCMD 10.53.0.3 status > rndc.out.test$n 2>&1 || ret=1
+if [ $ret != 0 ]; then
+ echo_i "failed"
+ echo_i "restart ns3"
+ start_server --noclean --restart --port ${PORT} serve-stale ns3
+fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check server is alive or restart ($n)"
+ret=0
+$RNDCCMD 10.53.0.3 status > rndc.out.test$n 2>&1 || ret=1
+if [ $ret != 0 ]; then
+ echo_i "failed"
+ echo_i "restart ns3"
+ start_server --noclean --restart --port ${PORT} serve-stale ns3
+fi
+status=$((status+ret))
+
+#############################################
+# Test for stale-answer-client-timeout 0. #
+#############################################
+echo_i "test stale-answer-client-timeout (0)"
+
+n=$((n+1))
+echo_i "updating ns3/named.conf ($n)"
+ret=0
+copy_setports ns3/named4.conf.in ns3/named.conf
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+echo_i "restart ns3"
+stop_server --use-rndc --port ${CONTROLPORT} ns3
+start_server --noclean --restart --port ${PORT} ns3
+
+n=$((n+1))
+echo_i "prime cache data.example TXT (stale-answer-client-timeout 0)"
+ret=0
+$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache nodata.example TXT (stale-answer-client-timeout 0)"
+ret=0
+$DIG -p ${PORT} @10.53.0.3 nodata.example TXT > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "disable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt disable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"0\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Allow RRset to become stale.
+sleep 2
+
+n=$((n+1))
+ret=0
+echo_i "check stale nodata.example TXT comes from cache (stale-answer-client-timeout 0) ($n)"
+nextpart ns3/named.run > /dev/null
+$DIG -p ${PORT} @10.53.0.3 nodata.example TXT > dig.out.test$n
+wait_for_log 5 "nodata.example stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (stale data prioritized over lookup)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
+grep "example\..*3.*IN.*SOA" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+ret=0
+echo_i "check stale data.example TXT comes from cache (stale-answer-client-timeout 0) ($n)"
+nextpart ns3/named.run > /dev/null
+$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
+wait_for_log 5 "data.example stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (stale data prioritized over lookup)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*3.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "enable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt enable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+wait_for_rrset_refresh() {
+ $DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
+ grep "status: NOERROR" dig.out.test$n > /dev/null || return 1
+ grep "EDE" dig.out.test$n > /dev/null && return 1
+ grep "ANSWER: 1," dig.out.test$n > /dev/null || return 1
+ grep "data\.example\..*[12].*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || return 1
+}
+
+# This test ensures that after we get stale data due to
+# stale-answer-client-timeout 0, enabling the authoritative server will allow
+# the RRset to be updated.
+n=$((n+1))
+ret=0
+echo_i "check stale data.example TXT was refreshed (stale-answer-client-timeout 0) ($n)"
+retry_quiet 10 wait_for_rrset_refresh || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+wait_for_nodata_refresh() {
+ $DIG -p ${PORT} @10.53.0.3 nodata.example TXT > dig.out.test$n
+ grep "status: NOERROR" dig.out.test$n > /dev/null || return 1
+ grep "ANSWER: 0," dig.out.test$n > /dev/null || return 1
+ grep "example\..*[12].*IN.*SOA" dig.out.test$n > /dev/null || return 1
+ return 0
+}
+
+n=$((n+1))
+ret=0
+echo_i "check stale nodata.example TXT was refreshed (stale-answer-client-timeout 0) ($n)"
+retry_quiet 10 wait_for_nodata_refresh || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+####################################################################
+# Test for stale-answer-client-timeout 0 and recursive-clients 10. #
+# CVE-2023-2911, GL #4089 #
+# ##################################################################
+echo_i "test stale-answer-client-timeout (0) and recursive-clients 10"
+
+n=$((n+1))
+echo_i "prime cache data.slow TXT (stale-answer-client-timeout 0) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.3 data.slow TXT > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Run the following check twice. Sometimes a priming query interrupts the first
+# attempt to exceed the quota.
+attempt=0
+while [ $ret -eq 0 ] && [ $attempt -lt 2 ]; do
+ n=$((n+1))
+ echo_i "slow down response from authoritative server ($n)"
+ ret=0
+ $DIG -p ${PORT} @10.53.0.2 slowdown TXT > dig.out.test$n
+ grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+ grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1
+ if [ $ret != 0 ]; then echo_i "failed"; fi
+ status=$((status+ret))
+
+ # Let the data.slow TTL expire
+ sleep 2
+
+ n=$((n+1))
+ echo_i "check that named survives reaching recursive-clients quota (stale-answer-client-timeout 0) ($n)"
+ ret=0
+ num=0
+ # Attempt to exceed the configured value of 'recursive-clients 10;' by running
+ # 20 parallel queries for the stale domain which has slow auth.
+ while [ $num -lt 20 ]; do
+ $DIG +tries=1 +timeout=10 -p ${PORT} @10.53.0.3 data.slow TXT >/dev/null 2>&1 &
+ num=$((num+1))
+ done;
+ # Let the dig processes finish.
+ wait
+ retry_quiet 5 check_server_responds || ret=1
+ if [ $ret != 0 ]; then echo_i "failed"; fi
+ status=$((status+ret))
+
+ attempt=$((attempt+1))
+done
+
+# Restart ns3 to avoid the exceeded recursive-clients limit from previous check
+# to interfere with subsequent checks.
+echo_i "restart ns3"
+stop_server --use-rndc --port ${CONTROLPORT} ns3
+start_server --noclean --restart --port ${PORT} ns3
+
+############################################################
+# Test for stale-answer-client-timeout 0 and CNAME record. #
+############################################################
+echo_i "test stale-answer-client-timeout (0) and CNAME record"
+
+n=$((n+1))
+echo_i "prime cache cname1.stale.test A (stale-answer-client-timeout 0) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.3 cname1.stale.test A > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 2," dig.out.test$n > /dev/null || ret=1
+grep "cname1\.stale\.test\..*1.*IN.*CNAME.*a1\.stale\.test\." dig.out.test$n > /dev/null || ret=1
+grep "a1\.stale\.test\..*1.*IN.*A.*192\.0\.2\.1" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Allow RRset to become stale.
+sleep 1
+
+n=$((n+1))
+ret=0
+echo_i "check stale cname1.stale.test A comes from cache (stale-answer-client-timeout 0) ($n)"
+nextpart ns3/named.run > /dev/null
+$DIG -p ${PORT} @10.53.0.3 cname1.stale.test A > dig.out.test$n
+wait_for_log 5 "cname1.stale.test stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (stale data prioritized over lookup)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 2," dig.out.test$n > /dev/null || ret=1
+grep "cname1\.stale\.test\..*3.*IN.*CNAME.*a1\.stale\.test\." dig.out.test$n > /dev/null || ret=1
+grep "a1\.stale\.test\..*3.*IN.*A.*192\.0\.2\.1" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check server is alive or restart ($n)"
+ret=0
+$RNDCCMD 10.53.0.3 status > rndc.out.test$n 2>&1 || ret=1
+if [ $ret != 0 ]; then
+ echo_i "failed"
+ echo_i "restart ns3"
+ start_server --noclean --restart --port ${PORT} ns3
+fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache cname2.stale.test A (stale-answer-client-timeout 0) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.3 cname2.stale.test A > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 2," dig.out.test$n > /dev/null || ret=1
+grep "cname2\.stale\.test\..*1.*IN.*CNAME.*a2\.stale\.test\." dig.out.test$n > /dev/null || ret=1
+grep "a2\.stale\.test\..*300.*IN.*A.*192\.0\.2\.2" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Allow CNAME record in the RRSET to become stale.
+sleep 1
+
+n=$((n+1))
+ret=0
+echo_i "check stale cname2.stale.test A comes from cache (stale-answer-client-timeout 0) ($n)"
+nextpart ns3/named.run > /dev/null
+$DIG -p ${PORT} @10.53.0.3 cname2.stale.test A > dig.out.test$n
+wait_for_log 5 "cname2.stale.test stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (stale data prioritized over lookup)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 2," dig.out.test$n > /dev/null || ret=1
+grep "cname2\.stale\.test\..*3.*IN.*CNAME.*a2\.stale\.test\." dig.out.test$n > /dev/null || ret=1
+# We can't reliably test the TTL of the a2.stale.test A record.
+grep "a2\.stale\.test\..*IN.*A.*192\.0\.2\.2" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check server is alive or restart ($n)"
+ret=0
+$RNDCCMD 10.53.0.3 status > rndc.out.test$n 2>&1 || ret=1
+if [ $ret != 0 ]; then
+ echo_i "failed"
+ echo_i "restart ns3"
+ start_server --noclean --restart --port ${PORT} ns3
+fi
+status=$((status+ret))
+
+####################################################################
+# Test for stale-answer-client-timeout 0 and stale-refresh-time 4. #
+####################################################################
+echo_i "test stale-answer-client-timeout (0) and stale-refresh-time (4)"
+
+n=$((n+1))
+echo_i "updating ns3/named.conf ($n)"
+ret=0
+copy_setports ns3/named5.conf.in ns3/named.conf
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "running 'rndc reload' ($n)"
+ret=0
+rndc_reload ns3 10.53.0.3
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "flush cache, enable responses from authoritative server ($n)"
+ret=0
+$RNDCCMD 10.53.0.3 flushtree example > rndc.out.test$n.1 2>&1 || ret=1
+$DIG -p ${PORT} @10.53.0.2 txt enable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "prime cache data.example TXT (stale-answer-client-timeout 0, stale-refresh-time 4) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*2.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Allow RRset to become stale.
+sleep 2
+
+n=$((n+1))
+echo_i "disable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt disable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"0\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+ret=0
+echo_i "check stale data.example TXT comes from cache (stale-answer-client-timeout 0 stale-refresh-time 4) ($n)"
+nextpart ns3/named.run > /dev/null
+$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
+wait_for_log 5 "data.example stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (stale data prioritized over lookup)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*3.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "enable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt enable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# This test ensures that after we get stale data due to
+# stale-answer-client-timeout 0, enabling the authoritative server will allow
+# the RRset to be updated.
+n=$((n+1))
+ret=0
+echo_i "check stale data.example TXT was refreshed (stale-answer-client-timeout 0 stale-refresh-time 4) ($n)"
+retry_quiet 10 wait_for_rrset_refresh || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Allow RRset to become stale.
+sleep 2
+
+n=$((n+1))
+echo_i "disable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt disable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"0\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+ret=0
+echo_i "check stale data.example TXT comes from cache (stale-answer-client-timeout 0 stale-refresh-time 4) ($n)"
+nextpart ns3/named.run > /dev/null
+$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
+wait_for_log 5 "data.example stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (stale data prioritized over lookup)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*3.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Allow stale-refresh-time to be activated.
+n=$((n+1))
+ret=0
+echo_i "wait until resolver query times out, activating stale-refresh-time"
+wait_for_log 15 "data.example resolver failure, stale answer used" ns3/named.run || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+ret=0
+echo_i "check stale data.example TXT comes from cache within stale-refresh-time (stale-answer-client-timeout 0 stale-refresh-time 4) ($n)"
+nextpart ns3/named.run > /dev/null
+$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
+wait_for_log 5 "data.example query within stale refresh time" ns3/named.run || ret=1
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (query within stale refresh time window)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*3.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "enable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt enable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# We give BIND some time to ensure that after we enable authoritative server,
+# this RRset is still not refreshed because it was hit during
+# stale-refresh-time window.
+sleep 1
+
+n=$((n+1))
+ret=0
+echo_i "check stale data.example TXT was not refreshed (stale-answer-client-timeout 0 stale-refresh-time 4) ($n)"
+nextpart ns3/named.run > /dev/null
+$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
+wait_for_log 5 "data.example query within stale refresh time" ns3/named.run || ret=1
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (query within stale refresh time window)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*3.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# After the refresh-time-window, the RRset will be refreshed.
+sleep 4
+
+n=$((n+1))
+ret=0
+echo_i "check stale data.example TXT comes from cache (stale-answer-client-timeout 0 stale-refresh-time 4) ($n)"
+$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
+wait_for_log 5 "data.example stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (stale data prioritized over lookup)" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*3.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+ret=0
+echo_i "check stale data.example TXT was refreshed (stale-answer-client-timeout 0 stale-refresh-time 4) ($n)"
+$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE" dig.out.test$n > /dev/null && ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*[12].*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+####################################################################
+# Test serve-stale's interaction with fetch limits (cache only) #
+#################################################################
+echo_i "test serve-stale's interaction with fetch-limits (cache only)"
+
+# We update the named configuration to enable fetch-limits. The fetch-limits
+# are set to 1, which is ridiciously low, but that is because for this test we
+# want to reach the fetch-limits.
+n=$((n+1))
+echo_i "updating ns3/named.conf ($n)"
+ret=0
+copy_setports ns3/named6.conf.in ns3/named.conf
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "running 'rndc reload' ($n)"
+ret=0
+rndc_reload ns3 10.53.0.3
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Disable responses from authoritative server. If we can't resolve the example
+# zone, fetch limits will be reached.
+n=$((n+1))
+echo_i "disable responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt disable > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"0\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Allow RRset to become stale.
+sleep 2
+
+# Turn on serve-stale.
+n=$((n+1))
+echo_i "running 'rndc serve-stale on' ($n)"
+ret=0
+$RNDCCMD 10.53.0.3 serve-stale on || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check 'rndc serve-stale status' ($n)"
+ret=0
+$RNDCCMD 10.53.0.3 serve-stale status > rndc.out.test$n 2>&1 || ret=1
+grep '_default: stale cache enabled; stale answers enabled (stale-answer-ttl=3 max-stale-ttl=3600 stale-refresh-time=4)' rndc.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Hit the fetch-limits. We burst the name server with a small batch of queries.
+# Only 2 queries are required to hit the fetch-limits. The first query will
+# start to resolve, the second one hit the fetch-limits.
+burst() {
+ num=${1}
+ rm -f burst.input.$$
+ while [ $num -gt 0 ]; do
+ num=$((num - 1))
+ echo "fetch${num}.example A" >> burst.input.$$
+ done
+ $PERL ../ditch.pl -p ${PORT} -s 10.53.0.3 burst.input.$$
+ rm -f burst.input.$$
+}
+
+wait_for_fetchlimits() {
+ burst 2
+ # We expect a query for nx.example to fail because fetch-limits for
+ # the domain 'example.' (and everything below) has been reached.
+ $DIG -p ${PORT} +tries=1 +timeout=1 @10.53.0.3 nx.example > dig.out.test$n
+ grep "status: SERVFAIL" dig.out.test$n > /dev/null || return 1
+}
+
+n=$((n+1))
+echo_i "hit fetch limits ($n)"
+ret=0
+retry_quiet 10 wait_for_fetchlimits || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Expect stale data now (because fetch-limits for the domain 'example.' (and
+# everything below) has been reached. But we have a stale RRset for
+# 'data.example/TXT' that can be used.
+n=$((n+1))
+ret=0
+echo_i "check stale data.example TXT comes from cache (fetch-limits) ($n)"
+nextpart ns3/named.run > /dev/null
+$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
+wait_for_log 5 "data.example resolver failure, stale answer used" ns3/named.run || ret=1
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (resolver failure" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*3.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# The previous query should not have started the stale-refresh-time window.
+n=$((n+1))
+ret=0
+echo_i "check stale data.example TXT comes from cache again (fetch-limits) ($n)"
+nextpart ns3/named.run > /dev/null
+$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
+wait_for_log 5 "data.example resolver failure, stale answer used" ns3/named.run || ret=1
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "EDE: 3 (Stale Answer): (resolver failure" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "data\.example\..*3.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+########################################################################
+# Test serve-stale's interaction with fetch limits (dual-mode) #
+########################################################################
+echo_i "test serve-stale's interaction with fetch limits (dual-mode)"
+
+# Update named configuration so that ns3 becomes a recursive resolver which is
+# also a secondary server for the root zone.
+n=$((n+1))
+echo_i "updating ns3/named.conf ($n)"
+ret=0
+copy_setports ns3/named7.conf.in ns3/named.conf
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "running 'rndc reload' ($n)"
+ret=0
+rndc_reload ns3 10.53.0.3
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check 'rndc serve-stale status' ($n)"
+ret=0
+$RNDCCMD 10.53.0.3 serve-stale status > rndc.out.test$n 2>&1 || ret=1
+grep '_default: stale cache enabled; stale answers enabled (stale-answer-ttl=3 max-stale-ttl=3600 stale-refresh-time=4)' rndc.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Flush the cache to ensure the example/NS RRset cached during previous tests
+# does not override the authoritative delegation found in the root zone.
+n=$((n+1))
+echo_i "flush cache ($n)"
+ret=0
+$RNDCCMD 10.53.0.3 flush > rndc.out.test$n 2>&1 || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Test that after flush, serve-stale configuration is not reset.
+n=$((n+1))
+echo_i "check serve-stale configuration is not reset after flush ($n)"
+ret=0
+$RNDCCMD 10.53.0.3 serve-stale status > rndc.out.test$n 2>&1 || ret=1
+grep '_default: stale cache enabled; stale answers enabled (stale-answer-ttl=3 max-stale-ttl=3600 stale-refresh-time=4)' rndc.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# Query name server with low fetch limits. The authoritative server (ans2) is
+# not responding. Sending queries for multiple names in the 'example' zone
+# in parallel causes the fetch limit for that zone (set to 1) to be
+# reached. This should not trigger a crash.
+echo_i "sending queries for tests $((n+1))-$((n+4))..."
+$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$((n+1)) &
+$DIG -p ${PORT} @10.53.0.3 othertype.example CAA > dig.out.test$((n+2)) &
+$DIG -p ${PORT} @10.53.0.3 nodata.example TXT > dig.out.test$((n+3)) &
+$DIG -p ${PORT} @10.53.0.3 nxdomain.example TXT > dig.out.test$((n+4)) &
+
+wait
+
+# Expect SERVFAIL for the entries not in cache.
+n=$((n+1))
+echo_i "check stale data.example TXT (fetch-limits dual-mode) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check stale othertype.example CAA (fetch-limits dual-mode) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check stale nodata.example TXT (fetch-limits dual-mode) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check stale nxdomain.example TXT (fetch-limits dual-mode) ($n)"
+ret=0
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+n=$((n+1))
+echo_i "check DNS64 processing of a stale negative answer ($n)"
+ret=0
+# configure ns3 with dns64
+copy_setports ns3/named8.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
+$RNDCCMD 10.53.0.3 serve-stale on > rndc.out.test$n.2 2>&1 || ret=1
+# prime the cache with an AAAA NXRRSET response
+$DIG -p ${PORT} @10.53.0.3 a-only.example AAAA > dig.out.1.test$n
+grep "status: NOERROR" dig.out.1.test$n > /dev/null || ret=1
+grep "2001:aaaa" 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
+# wait two seconds for the previous answer to become stale
+sleep 2
+# resend the query and wait in the background; we should get a stale answer
+$DIG -p ${PORT} @10.53.0.3 a-only.example AAAA > dig.out.2.test$n &
+# re-enable queries after a pause, so the server gets a real answer too
+sleep 2
+$DIG -p ${PORT} @10.53.0.2 txt enable > /dev/null
+wait
+grep "status: NOERROR" dig.out.2.test$n > /dev/null || ret=1
+grep "2001:aaaa" dig.out.2.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+###########################################################
+# Test serve-stale's interaction with prefetch processing #
+###########################################################
+echo_i "test serve-stale's interaction with prefetch processing"
+
+# Test case for #2733, ensuring that prefetch queries do not trigger
+# a lookup due to stale-answer-client-timeout.
+#
+# 1. Cache the following records:
+# cname.example 7 IN CNAME target.example.
+# target.example 9 IN A <addr>.
+# 2. Let the CNAME RRset expire.
+# 3. Query for 'cname.example/A'.
+#
+# This starts recursion because cname.example/CNAME is expired.
+# The authoritative server is up so likely it will respond before
+# stale-answer-client-timeout is triggered.
+# The 'target.example/A' RRset is found in cache with a positive value
+# and is eligble for prefetching.
+# A prefetch is done for 'target.example/A', our ans2 server will
+# delay the request.
+# The 'prefetch_done()' callback should have the right event type
+# (DNS_EVENT_FETCHDONE).
+
+# flush cache
+n=$((n+1))
+echo_i "flush cache ($n)"
+ret=0
+$RNDCCMD 10.53.0.3 flushtree example > rndc.out.test$n.1 2>&1 || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# prime the cache with CNAME and A; CNAME expires sooner
+n=$((n+1))
+echo_i "prime cache cname.example A (stale-answer-client-timeout 1.8) ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.3 cname.example A > dig.out.test$n
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 2," dig.out.test$n > /dev/null || ret=1
+grep "cname\.example\..*7.*IN.*CNAME.*target\.example\." dig.out.test$n > /dev/null || ret=1
+grep "target\.example\..*9.*IN.*A" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# wait for the CNAME to be stale; A will still be valid and in prefetch window.
+# (the longer TTL is needed, otherwise data won't be prefetch-eligible.)
+sleep 7
+
+# re-enable auth responses, but with a delay answering the A
+n=$((n+1))
+echo_i "delay responses from authoritative server ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.2 txt slowdown > dig.out.test$n
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
+grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status+ret))
+
+# resend the query and wait in the background; we should get a stale answer
+n=$((n+1))
+echo_i "check prefetch processing of a stale CNAME target ($n)"
+ret=0
+$DIG -p ${PORT} @10.53.0.3 cname.example A > dig.out.test$n &
+sleep 2
+wait
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
+grep "ANSWER: 2," dig.out.test$n > /dev/null || ret=1
+grep "cname\.example\..*7.*IN.*CNAME.*target\.example\." dig.out.test$n > /dev/null || ret=1
+grep "target\.example\..*[1-2].*IN.*A" dig.out.test$n > /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/bin/tests/system/serve-stale/tests_sh_serve_stale.py b/bin/tests/system/serve-stale/tests_sh_serve_stale.py
new file mode 100644
index 0000000..3e6d3a1
--- /dev/null
+++ b/bin/tests/system/serve-stale/tests_sh_serve_stale.py
@@ -0,0 +1,14 @@
+# 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.
+
+
+def test_serve_stale(run_tests_sh):
+ run_tests_sh()