From: Matthijs Mekking Date: Thu, 19 Feb 2026 11:06:14 +0000 (+0100) Subject: Test serve-stale with upstream zones and CNAMEs X-Git-Tag: v9.20.20~6^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c3b320f12aae56bc745309d39091dae9dae31972;p=thirdparty%2Fbind9.git Test serve-stale with upstream zones and CNAMEs Three variants of YWH-PGM40640-56: Stale/Wrong DNS Data Served via CNAME Flag Leak (DNS_DBFIND_STALEOK persistence) are presented in GitLab issue #5751. All these variants have been converted to system tests. Variant 1 forwards source.stale to another server, that provides a CNAME record, while the resolver is authoritative for target.stale. The CNAME points to a non-existing name. A stale CNAME record should result in a stale NXDOMAIN (instead of SERVFAIL). Variant 2 forwards both source.stale and target.stale to other servers. This time the CNAME points to an A RRset. If the source.stale server is not available (and stale-answer-client-timeout is off), the cached CNAME should be followed and pick up the fresh RRset (instead of the stale A RRset). Variant 3 is similar to variant 2, but this time the CNAME points to a non-existing name again. After flushing the target, BIND should return a stale NXDOMAIN (instead of SERVFAIL). (cherry picked from commit c32de7df957ea638f80bd12927029c68906c944b) --- diff --git a/bin/tests/system/serve-stale/ans2/ans.pl b/bin/tests/system/serve-stale/ans2/ans.pl index 7c231e2a808..b437f732e1f 100644 --- a/bin/tests/system/serve-stale/ans2/ans.pl +++ b/bin/tests/system/serve-stale/ans2/ans.pl @@ -75,6 +75,15 @@ 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"; +# +# YWH records +# +my $ywhSOA = "source.stale 300 IN SOA . . 0 0 0 0 300"; +my $ywhNS = "source.stale 300 IN NS ns.source.stale"; +my $ywhA = "ns.source.stale 300 IN A $localaddr"; +my $ywhCNAME = "alias.source.stale 2 IN CNAME www.target.stale"; +my $ywhCNAMENX = "aliasnx.source.stale 2 IN CNAME nonexist.target.stale"; + sub reply_handler { my ($qname, $qclass, $qtype) = @_; my ($rcode, @ans, @auth, @add); @@ -306,6 +315,34 @@ sub reply_handler { push @auth, $rr; } $rcode = "NOERROR"; + } elsif ($qname eq "source.stale") { + if ($qtype eq "SOA") { + my $rr = new Net::DNS::RR($ywhSOA); + push @ans, $rr; + } elsif ($qtype eq "NS") { + my $rr = new Net::DNS::RR($ywhNS); + push @ans, $rr; + $rr = new Net::DNS::RR($ywhA); + push @add, $rr; + } + $rcode = "NOERROR"; + } elsif ($qname eq "ns.source.stale") { + if ($qtype eq "A") { + my $rr = new Net::DNS::RR($ywhA); + push @ans, $rr; + } else { + my $rr = new Net::DNS::RR($ywhSOA); + push @auth, $rr; + } + $rcode = "NOERROR"; + } elsif ($qname eq "alias.source.stale") { + my $rr = new Net::DNS::RR($ywhCNAME); + push @ans, $rr; + $rcode = "NOERROR"; + } elsif ($qname eq "aliasnx.source.stale") { + my $rr = new Net::DNS::RR($ywhCNAMENX); + push @ans, $rr; + $rcode = "NOERROR"; } else { my $rr = new Net::DNS::RR($SOA); push @auth, $rr; diff --git a/bin/tests/system/serve-stale/ans8/ans.pl b/bin/tests/system/serve-stale/ans8/ans.pl new file mode 100644 index 00000000000..af3f8f035dc --- /dev/null +++ b/bin/tests/system/serve-stale/ans8/ans.pl @@ -0,0 +1,164 @@ +#!/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; + +my $localaddr = "10.53.0.8"; + +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 "$!"; + +# +# YWH records +# +my $ywhSOA = "target.stale 300 IN SOA . . 0 0 0 0 300"; +my $ywhNS = "target.stale 300 IN NS ns.target.stale"; +my $ywhA = "ns.target.stale 300 IN A $localaddr"; +my $ywhWWW = "www.target.stale 2 IN A 10.0.0.1"; + +sub reply_handler { + my ($qname, $qclass, $qtype) = @_; + my ($rcode, @ans, @auth, @add); + + print ("request: $qname/$qtype\n"); + STDOUT->flush(); + + # Control what response we send. + if ($qname eq "update" ) { + if ($qtype eq "TXT") { + $ywhWWW = "www.target.stale 2 IN A 10.0.0.2"; + my $rr = new Net::DNS::RR("$qname 0 $qclass TXT \"update\""); + push @ans, $rr; + } + $rcode = "NOERROR"; + return ($rcode, \@ans, \@auth, \@add, { aa => 1 }); + } elsif ($qname eq "restore" ) { + if ($qtype eq "TXT") { + $ywhWWW = "www.target.stale 2 IN A 10.0.0.1"; + my $rr = new Net::DNS::RR("$qname 0 $qclass TXT \"restore\""); + push @ans, $rr; + } + $rcode = "NOERROR"; + return ($rcode, \@ans, \@auth, \@add, { aa => 1 }); + } + + if ($qname eq "target.stale") { + if ($qtype eq "SOA") { + my $rr = new Net::DNS::RR($ywhSOA); + push @ans, $rr; + } elsif ($qtype eq "NS") { + my $rr = new Net::DNS::RR($ywhNS); + push @ans, $rr; + $rr = new Net::DNS::RR($ywhA); + push @add, $rr; + } + $rcode = "NOERROR"; + } elsif ($qname eq "ns.target.stale") { + if ($qtype eq "A") { + my $rr = new Net::DNS::RR($ywhA); + push @ans, $rr; + } else { + my $rr = new Net::DNS::RR($ywhSOA); + push @auth, $rr; + } + $rcode = "NOERROR"; + } elsif ($qname eq "www.target.stale") { + if ($qtype eq "A") { + my $rr = new Net::DNS::RR($ywhWWW); + push @ans, $rr; + } else { + my $rr = new Net::DNS::RR($ywhSOA); + push @auth, $rr; + } + $rcode = "NOERROR"; + } else { + my $rr = new Net::DNS::RR($ywhSOA); + 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/ns6/stale.db b/bin/tests/system/serve-stale/ns6/stale.db index 4ae006802ba..8cb4224ef33 100644 --- a/bin/tests/system/serve-stale/ns6/stale.db +++ b/bin/tests/system/serve-stale/ns6/stale.db @@ -9,9 +9,12 @@ ; See the COPYRIGHT file distributed with this work for additional ; information regarding copyright ownership. -stale. IN SOA ns.stale. matthijs.isc.org. 1 0 0 0 0 -stale. IN NS ns.stale. -ns.stale. IN A 10.53.0.6 +stale. IN SOA ns.stale. matthijs.isc.org. 1 0 0 0 0 +stale. IN NS ns.stale. +ns.stale. IN A 10.53.0.6 -serve.stale. IN NS ns.serve.stale. -ns.serve.stale. IN A 10.53.0.6 +serve.stale. IN NS ns.serve.stale. +ns.serve.stale. IN A 10.53.0.6 + +target.stale. IN NS ns.target.stale. +ns.target.stale. IN A 10.53.0.7 diff --git a/bin/tests/system/serve-stale/ns7/named.conf.j2 b/bin/tests/system/serve-stale/ns7/named.conf.j2 new file mode 100644 index 00000000000..c1600fe86a0 --- /dev/null +++ b/bin/tests/system/serve-stale/ns7/named.conf.j2 @@ -0,0 +1,62 @@ +/* + * 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.7 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +options { + query-source address 10.53.0.7; + notify-source 10.53.0.7; + transfer-source 10.53.0.7; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.7; }; + listen-on-v6 { none; }; + recursion yes; + dnssec-validation no; + qname-minimization off; + + stale-answer-enable yes; + stale-cache-enable yes; + max-stale-ttl 3600; + + stale-answer-client-timeout off; + stale-refresh-time 30; + + max-cache-ttl 300; + max-ncache-ttl 300; +}; + +zone "." { + type hint; + file "root.db"; +}; + +// Authoritative zone: nonexist.target.stale -> NXDOMAIN +zone "target.stale" { + type primary; + file "target.stale.db"; +}; + +// Forward source.stale queries to ans2 +zone "source.stale" { + type forward; + forward only; + forwarders { 10.53.0.2 port @PORT@; }; +}; diff --git a/bin/tests/system/serve-stale/ns7/named1.conf.j2 b/bin/tests/system/serve-stale/ns7/named1.conf.j2 new file mode 100644 index 00000000000..bc1992d7f5a --- /dev/null +++ b/bin/tests/system/serve-stale/ns7/named1.conf.j2 @@ -0,0 +1,63 @@ +/* + * 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.7 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +options { + query-source address 10.53.0.7; + notify-source 10.53.0.7; + transfer-source 10.53.0.7; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.7; }; + listen-on-v6 { none; }; + recursion yes; + dnssec-validation no; + qname-minimization off; + + stale-answer-enable yes; + stale-cache-enable yes; + max-stale-ttl 3600; + + stale-answer-client-timeout off; + stale-refresh-time 30; + + max-cache-ttl 300; + max-ncache-ttl 300; +}; + +zone "." { + type hint; + file "root.db"; +}; + +// Forward source.stale queries to ans2 +zone "source.stale" { + type forward; + forward only; + forwarders { 10.53.0.2 port @PORT@; }; +}; + +// Forward target.stale queries to ans8 +zone "target.stale" { + type forward; + forward only; + forwarders { 10.53.0.8 port @PORT@; }; +}; diff --git a/bin/tests/system/serve-stale/ns7/root.db b/bin/tests/system/serve-stale/ns7/root.db new file mode 120000 index 00000000000..c3546947a1a --- /dev/null +++ b/bin/tests/system/serve-stale/ns7/root.db @@ -0,0 +1 @@ +../ns1/root.db \ No newline at end of file diff --git a/bin/tests/system/serve-stale/ns7/target.stale.db b/bin/tests/system/serve-stale/ns7/target.stale.db new file mode 100644 index 00000000000..a39edf282f8 --- /dev/null +++ b/bin/tests/system/serve-stale/ns7/target.stale.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. + +target.stale. IN SOA ns.target.stale. ywh. 1 0 0 0 0 +target.stale. IN NS ns.target.stale. +ns.target.stale. IN A 10.53.0.6 + +; NOTE: "nonexist.target.stale." is NOT defined here. +; Queries for it will return authoritative NXDOMAIN. +; This is the CNAME target from alias.source.stale. diff --git a/bin/tests/system/serve-stale/tests.sh b/bin/tests/system/serve-stale/tests.sh index 807700f816e..3a7be4ae0fd 100755 --- a/bin/tests/system/serve-stale/tests.sh +++ b/bin/tests/system/serve-stale/tests.sh @@ -24,6 +24,212 @@ stale_answer_ttl=$(sed -ne 's,^[[:space:]]*stale-answer-ttl \([[:digit:]]*\).*,\ status=0 n=0 +# +# YWH-PGM40640-56: +# Stale/Wrong DNS Data Served via CNAME Flag Leak. +# +echo_i "test server with serve-stale options set" + +# +# Variant 1: local authoritative zone +# + +# Initial query — populates cache, gets correct NXDOMAIN +n=$((n + 1)) +echo_i "prime cache aliasnx.source.stale A ($n)" +ret=0 +$DIG -p ${PORT} @10.53.0.7 aliasnx.source.stale A >dig.out.test$n || ret=1 +grep "status: NXDOMAIN" 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)) +# Wait for CNAME TTL to expire +sleep 3 +# Kill auth server — source.test becomes unreachable +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 || ret=1 +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)) +# Query via stale CNAME — triggers the bug +n=$((n + 1)) +echo_i "check stale aliasnx.source.stale A ($n)" +ret=0 +$DIG -p ${PORT} @10.53.0.7 aliasnx.source.stale A >dig.out.test$n || ret=1 +grep "status: NXDOMAIN" 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)) +# Restore auth server +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 || ret=1 +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)) + +# +# Variant 2: stale/wrong data served +# +n=$((n + 1)) +echo_i "updating ns7/named.conf ($n)" +ret=0 +cp ns7/named1.conf ns7/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 ns7 10.53.0.7 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) +# Initial query — caches both CNAME and A record +n=$((n + 1)) +echo_i "prime cache alias.source.stale A ($n)" +ret=0 +$DIG -p ${PORT} @10.53.0.7 alias.source.stale A >dig.out.test$n || ret=1 +grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 +grep "ANSWER: 2," dig.out.test$n >/dev/null || ret=1 +grep "alias.source.stale.*2.*IN.*CNAME.*www.target.stale." dig.out.test$n >/dev/null || ret=1 +grep "www.target.stale.*2.*IN.*A.*10.0.0.1" dig.out.test$n >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) +# Wait for both TTLs to expire +sleep 3 +# Kill source.test auth (CNAME becomes stale) +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 || ret=1 +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)) +# Kill target auth, restart with NEW IP (10.0.0.2) +n=$((n + 1)) +echo_i "update target authoritative server ($n)" +ret=0 +$DIG -p ${PORT} @10.53.0.8 txt update >dig.out.test$n || ret=1 +grep "ANSWER: 1," dig.out.test$n >/dev/null || ret=1 +grep "TXT.\"update\"" dig.out.test$n >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) +# Query via stale CNAME — triggers the bug +n=$((n + 1)) +echo_i "check stale alias.source.stale A ($n)" +ret=0 +$DIG -p ${PORT} @10.53.0.7 alias.source.stale A >dig.out.test$n || ret=1 +grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 +grep "ANSWER: 2," dig.out.test$n >/dev/null || ret=1 +grep "alias.source.stale.*30.*IN.*CNAME.*www.target.stale." dig.out.test$n >/dev/null || ret=1 +grep "www.target.stale.*2.*IN.*A.*10.0.0.2" dig.out.test$n >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) +# Control: direct query for same name (no stale CNAME involved) +n=$((n + 1)) +echo_i "check target www.target.stale A ($n)" +ret=0 +$DIG -p ${PORT} @10.53.0.7 www.target.stale A >dig.out.test$n || ret=1 +grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 +grep "ANSWER: 1," dig.out.test$n >/dev/null || ret=1 +grep "www.target.stale.*IN.*A.*10.0.0.2" dig.out.test$n >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) +# Restore auth servers +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 || ret=1 +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 "update target authoritative server ($n)" +ret=0 +$DIG -p ${PORT} @10.53.0.8 txt restore >dig.out.test$n || ret=1 +grep "ANSWER: 1," dig.out.test$n >/dev/null || ret=1 +grep "TXT.\"restore\"" dig.out.test$n >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +# +# Variant 3: recursion blocked, servfail +# + +# Flush stale data +n=$((n + 1)) +echo_i "flush stale data ($n)" +ret=0 +$RNDCCMD 10.53.0.7 flushtree stale >/dev/null 2>&1 || ret=1 +sleep 1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) +# Initial query — NXDOMAIN via CNAME chain through BOTH forwarders +n=$((n + 1)) +echo_i "prime cache aliasnx.source.stale A ($n)" +ret=0 +$DIG -p ${PORT} @10.53.0.7 aliasnx.source.stale A >dig.out.test$n || ret=1 +grep "status: NXDOMAIN" dig.out.test$n >/dev/null || ret=1 +grep "ANSWER: 1," dig.out.test$n >/dev/null || ret=1 +grep "aliasnx.source.stale.*2.*IN.*CNAME.*nonexist.target.stale." dig.out.test$n >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) +# Wait for CNAME TTL to expire +sleep 3 +# Kill source.test auth ONLY (target.test auth stays alive!) +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 || ret=1 +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)) +# Flush target's negative cache entry (simulates cache eviction/pressure) +n=$((n + 1)) +echo_i "flush name nonexist.target.stale ($n)" +ret=0 +$RNDCCMD 10.53.0.7 flushname nonexist.target.stale >/dev/null 2>&1 || ret=1 +sleep 1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) +# Verify target auth is STILL ALIVE and returns correct NXDOMAIN +n=$((n + 1)) +echo_i "verify nonexist.target.stale A ($n)" +ret=0 +$DIG -p ${PORT} @10.53.0.8 nonexist.target.stale A >dig.out.test$n || ret=1 +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)) +# Query via stale CNAME — triggers the bug +n=$((n + 1)) +echo_i "check stale aliasnx.source.stale A ($n)" +ret=0 +$DIG -p ${PORT} @10.53.0.7 aliasnx.source.stale A >dig.out.test$n || ret=1 +grep "status: NXDOMAIN" 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)) +grep "aliasnx.source.stale.*30.*IN.*CNAME.*nonexist.target.stale." dig.out.test$n >/dev/null || ret=1 +# Restore auth server +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 || ret=1 +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)) + # # First test server with serve-stale options set. #