summaryrefslogtreecommitdiffstats
path: root/bin/tests/system/mirror
diff options
context:
space:
mode:
Diffstat (limited to 'bin/tests/system/mirror')
-rw-r--r--bin/tests/system/mirror/README26
-rw-r--r--bin/tests/system/mirror/clean.sh30
-rw-r--r--bin/tests/system/mirror/ns1/named.conf.in29
-rw-r--r--bin/tests/system/mirror/ns1/root.db.in19
-rw-r--r--bin/tests/system/mirror/ns1/sign.sh37
-rw-r--r--bin/tests/system/mirror/ns2/example.db.in17
-rw-r--r--bin/tests/system/mirror/ns2/initially-unavailable.db.in16
-rw-r--r--bin/tests/system/mirror/ns2/named.conf.in86
-rw-r--r--bin/tests/system/mirror/ns2/sign.sh79
-rw-r--r--bin/tests/system/mirror/ns2/sub.example.db.in15
-rw-r--r--bin/tests/system/mirror/ns2/verify.db.in15
-rw-r--r--bin/tests/system/mirror/ns3/named.args1
-rw-r--r--bin/tests/system/mirror/ns3/named.conf.in102
-rw-r--r--bin/tests/system/mirror/setup.sh25
-rw-r--r--bin/tests/system/mirror/tests.sh558
-rw-r--r--bin/tests/system/mirror/tests_sh_mirror.py14
16 files changed, 1069 insertions, 0 deletions
diff --git a/bin/tests/system/mirror/README b/bin/tests/system/mirror/README
new file mode 100644
index 0000000..f76b41b
--- /dev/null
+++ b/bin/tests/system/mirror/README
@@ -0,0 +1,26 @@
+<!--
+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.
+-->
+
+This test checks whether zones configured with "type mirror;" behave as
+expected.
+
+ns1 is an authoritative-only server. It only serves the root zone, which is
+mirrored by ns3.
+
+ns2 is an authoritative-only server. It serves a number of zones, some of which
+are delegated to it by ns1 and used in recursive resolution tests aimed at ns3
+while others are only served so that ns3 has a primary server to mirror zones
+from during various tests of the mirror zone implementation.
+
+ns3 is a recursive resolver. It has a number of mirror zones configured. This
+is the only server whose behavior is being examined by this system test.
diff --git a/bin/tests/system/mirror/clean.sh b/bin/tests/system/mirror/clean.sh
new file mode 100644
index 0000000..2e02183
--- /dev/null
+++ b/bin/tests/system/mirror/clean.sh
@@ -0,0 +1,30 @@
+#!/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 */*.conf
+rm -f */*.db
+rm -f */*.jnl
+rm -f */*.mirror
+rm -f */*.nzd*
+rm -f */*.prev
+rm -f */*.signed
+rm -f */K*
+rm -f */db-*
+rm -f */dsset-*
+rm -f */jn-*
+rm -f */_default.nzf
+rm -f */managed-keys.bind*
+rm -f */named.memstats
+rm -f */named.run
+rm -f dig.out.*
+rm -f rndc.out.*
diff --git a/bin/tests/system/mirror/ns1/named.conf.in b/bin/tests/system/mirror/ns1/named.conf.in
new file mode 100644
index 0000000..22173f7
--- /dev/null
+++ b/bin/tests/system/mirror/ns1/named.conf.in
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+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 no;
+ dnssec-validation no;
+};
+
+zone "." {
+ type primary;
+ file "root.db.signed";
+};
diff --git a/bin/tests/system/mirror/ns1/root.db.in b/bin/tests/system/mirror/ns1/root.db.in
new file mode 100644
index 0000000..98ecf1f
--- /dev/null
+++ b/bin/tests/system/mirror/ns1/root.db.in
@@ -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.
+
+$TTL 3600
+@ SOA a.root-servers.nil. hostmaster 1 3600 1200 604800 3600
+@ NS a.root-servers.nil.
+a.root-servers.nil. A 10.53.0.1
+example NS ns2.example.
+ns2.example. A 10.53.0.2
+initially-unavailable. NS ns2.initially-unavailable.
+ns2.initially-unavailable. A 10.53.0.2
diff --git a/bin/tests/system/mirror/ns1/sign.sh b/bin/tests/system/mirror/ns1/sign.sh
new file mode 100644
index 0000000..ab613d5
--- /dev/null
+++ b/bin/tests/system/mirror/ns1/sign.sh
@@ -0,0 +1,37 @@
+#!/bin/sh -e
+
+# 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
+
+( cd ../ns2 && $SHELL -e sign.sh )
+
+cp ../ns2/dsset-* .
+
+zone=.
+infile=root.db.in
+zonefile=root.db
+
+keyname1=$($KEYGEN -a ${DEFAULT_ALGORITHM} -f KSK $zone 2> /dev/null)
+keyname2=$($KEYGEN -a ${DEFAULT_ALGORITHM} $zone 2> /dev/null)
+
+cat $infile $keyname1.key $keyname2.key > $zonefile
+
+$SIGNER -P -g -o $zone $zonefile > /dev/null
+
+# Add a trust anchor for a name whose non-existence can be securely proved
+# without recursing when the root zone is mirrored. This will exercise code
+# attempting to send TAT queries for such names (in ns3). Key data is
+# irrelevant here, so just reuse the root zone key generated above.
+sed "s/^\./nonexistent./;" $keyname1.key > $keyname1.modified.key
+
+keyfile_to_static_ds $keyname1 $keyname1.modified > trusted.conf
diff --git a/bin/tests/system/mirror/ns2/example.db.in b/bin/tests/system/mirror/ns2/example.db.in
new file mode 100644
index 0000000..5472399
--- /dev/null
+++ b/bin/tests/system/mirror/ns2/example.db.in
@@ -0,0 +1,17 @@
+; 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.
+
+$TTL 3600
+@ SOA ns2 hostmaster 1 3600 1200 604800 3600
+@ NS ns2
+ns2 A 10.53.0.2
+foo A 127.0.0.1
+sub NS ns2
diff --git a/bin/tests/system/mirror/ns2/initially-unavailable.db.in b/bin/tests/system/mirror/ns2/initially-unavailable.db.in
new file mode 100644
index 0000000..cf809e3
--- /dev/null
+++ b/bin/tests/system/mirror/ns2/initially-unavailable.db.in
@@ -0,0 +1,16 @@
+; 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.
+
+$TTL 3600
+@ SOA a.root-servers.nil. hostmaster 1 3600 1200 604800 3600
+@ NS ns2
+ns2 A 10.53.0.2
+foo CNAME foo.example.
diff --git a/bin/tests/system/mirror/ns2/named.conf.in b/bin/tests/system/mirror/ns2/named.conf.in
new file mode 100644
index 0000000..7eaed5b
--- /dev/null
+++ b/bin/tests/system/mirror/ns2/named.conf.in
@@ -0,0 +1,86 @@
+/*
+ * 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.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+options {
+ query-source address 10.53.0.2;
+ notify-source 10.53.0.2;
+ transfer-source 10.53.0.2;
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { 10.53.0.2; };
+ listen-on-v6 { none; };
+ recursion no;
+ dnssec-validation no;
+};
+
+zone "example" {
+ type primary;
+ file "example.db.signed";
+};
+
+zone "sub.example" {
+ type primary;
+ file "sub.example.db.signed";
+};
+
+zone "initially-unavailable" {
+ type primary;
+ file "initially-unavailable.db.signed";
+ allow-transfer { 10.53.0.254; };
+};
+
+zone "verify-addzone" {
+ type primary;
+ file "verify-addzone.db.original.signed";
+};
+
+zone "verify-axfr" {
+ type primary;
+ file "verify-axfr.db.signed";
+};
+
+zone "verify-csk" {
+ type primary;
+ file "verify-csk.db.signed";
+};
+
+zone "verify-ixfr" {
+ type primary;
+ file "verify-ixfr.db.signed";
+ ixfr-from-differences yes;
+ allow-transfer { 10.53.0.3; };
+};
+
+zone "verify-reconfig" {
+ type primary;
+ file "verify-reconfig.db.signed";
+};
+
+zone "verify-unsigned" {
+ type primary;
+ file "verify.db.in";
+};
+
+zone "verify-untrusted" {
+ type primary;
+ file "verify-untrusted.db.signed";
+};
diff --git a/bin/tests/system/mirror/ns2/sign.sh b/bin/tests/system/mirror/ns2/sign.sh
new file mode 100644
index 0000000..519fd83
--- /dev/null
+++ b/bin/tests/system/mirror/ns2/sign.sh
@@ -0,0 +1,79 @@
+#!/bin/sh -e
+
+# 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
+
+keys_to_trust=""
+
+for zonename in sub.example example initially-unavailable; do
+ zone=$zonename
+ infile=$zonename.db.in
+ zonefile=$zonename.db
+
+ keyname1=$($KEYGEN -a ${DEFAULT_ALGORITHM} -f KSK $zone 2> /dev/null)
+ keyname2=$($KEYGEN -a ${DEFAULT_ALGORITHM} $zone 2> /dev/null)
+
+ cat $infile $keyname1.key $keyname2.key > $zonefile
+
+ $SIGNER -P -g -o $zone $zonefile > /dev/null
+done
+
+# Only add the key for "initially-unavailable" to the list of keys trusted by
+# ns3. "example" is expected to be validated using a chain of trust starting in
+# the "root" zone on ns1.
+keys_to_trust="$keys_to_trust $keyname1"
+
+# Prepare a zone signed using a Combined Signing Key (CSK) without the SEP bit
+# set and add that key to the list of keys to trust.
+zone=verify-csk
+infile=verify.db.in
+zonefile=verify-csk.db
+
+keyname=$($KEYGEN -a ${DEFAULT_ALGORITHM} $zone 2> /dev/null)
+cat $infile $keyname.key > $zonefile
+$SIGNER -P -o $zone $zonefile > /dev/null
+keys_to_trust="$keys_to_trust $keyname"
+
+# Prepare remaining zones used in the test.
+ORIGINAL_SERIAL=$(awk '$2 == "SOA" {print $5}' verify.db.in)
+UPDATED_SERIAL_BAD=$((ORIGINAL_SERIAL + 1))
+UPDATED_SERIAL_GOOD=$((ORIGINAL_SERIAL + 2))
+
+for variant in addzone axfr ixfr load reconfig untrusted; do
+ zone=verify-$variant
+ infile=verify.db.in
+ zonefile=verify-$variant.db
+
+ keyname1=$($KEYGEN -a ${DEFAULT_ALGORITHM} -f KSK $zone 2> /dev/null)
+ keyname2=$($KEYGEN -a ${DEFAULT_ALGORITHM} $zone 2> /dev/null)
+
+ cat $infile $keyname1.key $keyname2.key > $zonefile
+
+ # Prepare a properly signed version of the zone ("*.original.signed").
+ $SIGNER -P -o $zone $zonefile > /dev/null
+ cp $zonefile.signed $zonefile.original.signed
+ # Prepare a version of the zone with a bogus SOA RRSIG ("*.bad.signed").
+ sed "s/${ORIGINAL_SERIAL}/${UPDATED_SERIAL_BAD}/;" $zonefile.signed > $zonefile.bad.signed
+ # Prepare another properly signed version of the zone ("*.good.signed").
+ sed "s/${ORIGINAL_SERIAL}/${UPDATED_SERIAL_GOOD}/;" $zonefile > $zonefile.good
+ $SIGNER -P -o $zone $zonefile.good > /dev/null
+ rm -f $zonefile.good
+
+ # Except for the "verify-untrusted" zone, declare the KSK used for
+ # signing the zone to be a trust anchor for ns3.
+ if [ "$variant" != "untrusted" ]; then
+ keys_to_trust="$keys_to_trust $keyname1"
+ fi
+done
+
+keyfile_to_static_ds $keys_to_trust > trusted-mirror.conf
diff --git a/bin/tests/system/mirror/ns2/sub.example.db.in b/bin/tests/system/mirror/ns2/sub.example.db.in
new file mode 100644
index 0000000..d2c15c7
--- /dev/null
+++ b/bin/tests/system/mirror/ns2/sub.example.db.in
@@ -0,0 +1,15 @@
+; 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.
+
+$TTL 3600
+@ SOA ns2.example. hostmaster 1 3600 1200 604800 3600
+@ NS ns2.example.
+foo A 127.0.0.1
diff --git a/bin/tests/system/mirror/ns2/verify.db.in b/bin/tests/system/mirror/ns2/verify.db.in
new file mode 100644
index 0000000..b3ed22a
--- /dev/null
+++ b/bin/tests/system/mirror/ns2/verify.db.in
@@ -0,0 +1,15 @@
+; 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.
+
+$TTL 3600
+@ SOA ns2 hostmaster 2000010100 3600 1200 604800 3600
+@ NS ns2
+ns2 A 10.53.0.2
diff --git a/bin/tests/system/mirror/ns3/named.args b/bin/tests/system/mirror/ns3/named.args
new file mode 100644
index 0000000..78f3feb
--- /dev/null
+++ b/bin/tests/system/mirror/ns3/named.args
@@ -0,0 +1 @@
+-D mirror-ns3 -X named.lock -m record -c named.conf -d 99 -g -U 4 -T maxcachesize=2097152 -T tat=3
diff --git a/bin/tests/system/mirror/ns3/named.conf.in b/bin/tests/system/mirror/ns3/named.conf.in
new file mode 100644
index 0000000..b644f45
--- /dev/null
+++ b/bin/tests/system/mirror/ns3/named.conf.in
@@ -0,0 +1,102 @@
+/*
+ * 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;
+ allow-query-cache { 10.53.0.1; };
+ trust-anchor-telemetry yes;
+ allow-new-zones yes;
+ dnssec-validation yes;
+};
+
+zone "." {
+ type hint;
+ file "../../common/root.hint";
+};
+
+zone "." {
+ type mirror;
+ primaries { 10.53.0.1; };
+ file "root.db.mirror";
+};
+
+zone "initially-unavailable" {
+ type mirror;
+ primaries { 10.53.0.2; };
+ file "initially-unavailable.db.mirror";
+ use-alt-transfer-source no;
+};
+
+zone "verify-axfr" {
+ type mirror;
+ primaries { 10.53.0.2; };
+ file "verify-axfr.db.mirror";
+};
+
+zone "verify-csk" {
+ type mirror;
+ primaries { 10.53.0.2; };
+ file "verify-csk.db.mirror";
+};
+
+zone "verify-ixfr" {
+ type mirror;
+ primaries { 10.53.0.2; };
+ file "verify-ixfr.db.mirror";
+ masterfile-format text;
+};
+
+zone "verify-load" {
+ type mirror;
+ primaries { 10.53.0.2; };
+ file "verify-load.db.mirror";
+ masterfile-format text;
+};
+
+zone "verify-reconfig" {
+ type mirror;
+ primaries { 10.53.0.2; };
+ file "verify-reconfig.db.mirror";
+ masterfile-format text;
+};
+
+zone "verify-unsigned" {
+ type mirror;
+ primaries { 10.53.0.2; };
+ file "verify-unsigned.db.mirror";
+};
+
+zone "verify-untrusted" {
+ type mirror;
+ primaries { 10.53.0.2; };
+ file "verify-untrusted.db.mirror";
+};
+
+include "../ns1/trusted.conf";
+include "../ns2/trusted-mirror.conf";
diff --git a/bin/tests/system/mirror/setup.sh b/bin/tests/system/mirror/setup.sh
new file mode 100644
index 0000000..b80e797
--- /dev/null
+++ b/bin/tests/system/mirror/setup.sh
@@ -0,0 +1,25 @@
+#!/bin/sh -e
+
+# 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/named.conf.in ns1/named.conf
+copy_setports ns2/named.conf.in ns2/named.conf
+copy_setports ns3/named.conf.in ns3/named.conf
+
+( cd ns1 && $SHELL -e sign.sh )
+
+cat ns2/verify-axfr.db.bad.signed > ns2/verify-axfr.db.signed
+cat ns2/verify-load.db.bad.signed > ns3/verify-load.db.mirror
diff --git a/bin/tests/system/mirror/tests.sh b/bin/tests/system/mirror/tests.sh
new file mode 100644
index 0000000..dc6cffa
--- /dev/null
+++ b/bin/tests/system/mirror/tests.sh
@@ -0,0 +1,558 @@
+#!/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
+
+DIGOPTS="-p ${PORT} -b 10.53.0.1 +dnssec +time=2 +tries=1 +multi"
+RNDCCMD="$RNDC -c ../common/rndc.conf -p ${CONTROLPORT} -s"
+
+# Wait until the transfer of the given zone to ns3 either completes
+# successfully or is aborted by a verification failure or a REFUSED response
+# from the primary. Note that matching on any transfer status is deliberately
+# avoided because some checks performed by this test cause transfer attempts to
+# end with the "IXFR failed" status, which is followed by an AXFR retry and
+# this test needs to check what the result of the latter transfer attempt is.
+wait_for_transfer() {
+ zone=$1
+ for i in 1 2 3 4 5 6 7 8 9 10; do
+ # Wait until a "freeing transfer context" message is logged
+ # after one of the transfer results we are looking for is
+ # logged. This is needed to prevent races when checking for
+ # "mirror zone is now in use" messages.
+ nextpartpeek ns3/named.run | \
+ awk "matched; /'$zone\/IN'.*Transfer status: (success|verify failure|REFUSED)/ {matched=1}" | \
+ grep "'$zone/IN'.*freeing transfer context" > /dev/null && return
+ sleep 1
+ done
+ echo_i "exceeded time limit waiting for proof of '$zone' being transferred to appear in ns3/named.run"
+ ret=1
+}
+
+# Wait until loading the given zone on the given server either completes
+# successfully for the specified serial number or fails.
+wait_for_load() {
+ zone=$1
+ serial=$2
+ log=$3
+ for i in 1 2 3 4 5 6 7 8 9 10; do
+ # Wait until a "zone_postload: (...): done" message is logged
+ # after one of the loading-related messages we are looking for
+ # is logged. This is needed to prevent races when checking for
+ # "mirror zone is now in use" messages.
+ nextpartpeek $log | \
+ awk "matched; /$zone.*(loaded serial $serial|unable to load)/ {matched=1}" | \
+ grep "zone_postload: zone $zone/IN: done" > /dev/null && return
+ sleep 1
+ done
+ echo_i "exceeded time limit waiting for proof of '$zone' being loaded to appear in $log"
+ ret=1
+}
+
+# Trigger a reload of ns2 and wait until loading the given zone completes.
+reload_zone() {
+ zone=$1
+ serial=$2
+ rndc_reload ns2 10.53.0.2
+ wait_for_load $zone $serial ns2/named.run
+}
+
+status=0
+n=0
+
+ORIGINAL_SERIAL=$(awk '$2 == "SOA" {print $5}' ns2/verify.db.in)
+UPDATED_SERIAL_BAD=$((ORIGINAL_SERIAL + 1))
+UPDATED_SERIAL_GOOD=$((ORIGINAL_SERIAL + 2))
+
+n=$((n + 1))
+echo_i "checking that an unsigned mirror zone is rejected ($n)"
+ret=0
+wait_for_transfer verify-unsigned
+$DIG $DIGOPTS @10.53.0.3 +norec verify-unsigned SOA > dig.out.ns3.test$n 2>&1 || ret=1
+grep "ANSWER: 0" dig.out.ns3.test$n > /dev/null || ret=1
+grep "${ORIGINAL_SERIAL}.*; serial" dig.out.ns3.test$n > /dev/null && ret=1
+nextpartpeek ns3/named.run | grep "verify-unsigned.*Zone contains no DNSSEC keys" > /dev/null || ret=1
+nextpartpeek ns3/named.run | grep "verify-unsigned.*mirror zone is now in use" > /dev/null && ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking that a mirror zone signed using an untrusted key is rejected ($n)"
+ret=0
+nextpartreset ns3/named.run
+wait_for_transfer verify-untrusted
+$DIG $DIGOPTS @10.53.0.3 +norec verify-untrusted SOA > dig.out.ns3.test$n 2>&1 || ret=1
+grep "ANSWER: 0" dig.out.ns3.test$n > /dev/null || ret=1
+grep "${ORIGINAL_SERIAL}.*; serial" dig.out.ns3.test$n > /dev/null && ret=1
+nextpartpeek ns3/named.run | grep "verify-untrusted.*No trusted DNSKEY found" > /dev/null || ret=1
+nextpartpeek ns3/named.run | grep "verify-untrusted.*mirror zone is now in use" > /dev/null && ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking that a mirror zone signed using a CSK without the SEP bit set is accepted ($n)"
+ret=0
+nextpartreset ns3/named.run
+wait_for_transfer verify-csk
+$DIG $DIGOPTS @10.53.0.3 +norec verify-csk SOA > dig.out.ns3.test$n 2>&1 || ret=1
+grep "ANSWER: 0" dig.out.ns3.test$n > /dev/null && ret=1
+grep "${ORIGINAL_SERIAL}.*; serial" dig.out.ns3.test$n > /dev/null || ret=1
+nextpartpeek ns3/named.run | grep "verify-csk.*mirror zone is now in use" > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking that an AXFR of an incorrectly signed mirror zone is rejected ($n)"
+ret=0
+nextpartreset ns3/named.run
+wait_for_transfer verify-axfr
+$DIG $DIGOPTS @10.53.0.3 +norec verify-axfr SOA > dig.out.ns3.test$n 2>&1 || ret=1
+grep "ANSWER: 0" dig.out.ns3.test$n > /dev/null || ret=1
+grep "${UPDATED_SERIAL_BAD}.*; serial" dig.out.ns3.test$n > /dev/null && ret=1
+nextpartpeek ns3/named.run | grep "No correct ${DEFAULT_ALGORITHM} signature for verify-axfr SOA" > /dev/null || ret=1
+nextpartpeek ns3/named.run | grep "verify-axfr.*mirror zone is now in use" > /dev/null && ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking that an AXFR of an updated, correctly signed mirror zone is accepted ($n)"
+ret=0
+nextpart ns3/named.run > /dev/null
+cat ns2/verify-axfr.db.good.signed > ns2/verify-axfr.db.signed
+reload_zone verify-axfr ${UPDATED_SERIAL_GOOD}
+$RNDCCMD 10.53.0.3 retransfer verify-axfr > /dev/null 2>&1
+wait_for_transfer verify-axfr
+$DIG $DIGOPTS @10.53.0.3 +norec verify-axfr SOA > dig.out.ns3.test$n 2>&1 || ret=1
+grep "ANSWER: 0" dig.out.ns3.test$n > /dev/null && ret=1
+grep "${UPDATED_SERIAL_GOOD}.*; serial" dig.out.ns3.test$n > /dev/null || ret=1
+nextpartpeek ns3/named.run | grep "verify-axfr.*mirror zone is now in use" > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking that an IXFR of an incorrectly signed mirror zone is rejected ($n)"
+nextpartreset ns3/named.run
+ret=0
+wait_for_transfer verify-ixfr
+# Sanity check: the initial, properly signed version of the zone should have
+# been announced as coming into effect.
+nextpart ns3/named.run | grep "verify-ixfr.*mirror zone is now in use" > /dev/null || ret=1
+# Make a copy of the original zone file for reuse in journal tests below.
+cp ns2/verify-ixfr.db.signed ns3/verify-journal.db.mirror
+# Wait 1 second so that the zone file timestamp changes and the subsequent
+# invocation of "rndc reload" triggers a zone reload.
+sleep 1
+cat ns2/verify-ixfr.db.bad.signed > ns2/verify-ixfr.db.signed
+reload_zone verify-ixfr ${UPDATED_SERIAL_BAD}
+# Make a copy of the bad zone journal for reuse in journal tests below.
+cp ns2/verify-ixfr.db.signed.jnl ns3/verify-journal.db.bad.mirror.jnl
+# Trigger IXFR.
+$RNDCCMD 10.53.0.3 refresh verify-ixfr > /dev/null 2>&1
+wait_for_transfer verify-ixfr
+# Ensure the transfer was incremental as expected.
+if [ $(nextpartpeek ns3/named.run | grep "verify-ixfr.*got incremental response" | wc -l) -eq 0 ]; then
+ echo_i "failed: did not get an incremental response"
+ ret=1
+fi
+# Ensure the new, bad version of the zone was not accepted.
+$DIG $DIGOPTS @10.53.0.3 +norec verify-ixfr SOA > dig.out.ns3.test$n 2>&1 || ret=1
+# A positive answer is expected as the original version of the "verify-ixfr"
+# zone should have been successfully verified.
+grep "ANSWER: 0" dig.out.ns3.test$n > /dev/null && ret=1
+grep "${UPDATED_SERIAL_BAD}.*; serial" dig.out.ns3.test$n > /dev/null && ret=1
+nextpartpeek ns3/named.run | grep "No correct ${DEFAULT_ALGORITHM} signature for verify-ixfr SOA" > /dev/null || ret=1
+# Despite the verification failure for this IXFR, this mirror zone should still
+# be in use as its previous version should have been verified successfully.
+nextpartpeek ns3/named.run | grep "verify-ixfr.*mirror zone is no longer in use" > /dev/null && ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking that an IXFR of an updated, correctly signed mirror zone is accepted after AXFR failover ($n)"
+ret=0
+nextpart ns3/named.run > /dev/null
+# Wait 1 second so that the zone file timestamp changes and the subsequent
+# invocation of "rndc reload" triggers a zone reload.
+sleep 1
+cat ns2/verify-ixfr.db.good.signed > ns2/verify-ixfr.db.signed
+reload_zone verify-ixfr ${UPDATED_SERIAL_GOOD}
+# Make a copy of the good zone journal for reuse in journal tests below.
+cp ns2/verify-ixfr.db.signed.jnl ns3/verify-journal.db.good.mirror.jnl
+# Trigger IXFR.
+$RNDCCMD 10.53.0.3 refresh verify-ixfr > /dev/null 2>&1
+wait_for_transfer verify-ixfr
+# Ensure the new, good version of the zone was accepted.
+$DIG $DIGOPTS @10.53.0.3 +norec verify-ixfr SOA > dig.out.ns3.test$n 2>&1 || ret=1
+grep "ANSWER: 0" dig.out.ns3.test$n > /dev/null && ret=1
+grep "${UPDATED_SERIAL_GOOD}.*; serial" dig.out.ns3.test$n > /dev/null || ret=1
+# The log message announcing the mirror zone coming into effect should not have
+# been logged this time since the mirror zone in question is expected to
+# already be in use before this test case is checked.
+nextpartpeek ns3/named.run | grep "verify-ixfr.*mirror zone is now in use" > /dev/null && ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking that loading an incorrectly signed mirror zone from disk fails ($n)"
+ret=0
+nextpartreset ns3/named.run
+wait_for_load verify-load ${UPDATED_SERIAL_BAD} ns3/named.run
+$DIG $DIGOPTS @10.53.0.3 +norec verify-load SOA > dig.out.ns3.test$n 2>&1 || ret=1
+grep "ANSWER: 0" dig.out.ns3.test$n > /dev/null || ret=1
+grep "${UPDATED_SERIAL_BAD}.*; serial" dig.out.ns3.test$n > /dev/null && ret=1
+nextpartpeek ns3/named.run | grep "No correct ${DEFAULT_ALGORITHM} signature for verify-load SOA" > /dev/null || ret=1
+nextpartpeek ns3/named.run | grep "verify-load.*mirror zone is now in use" > /dev/null && ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "ensuring trust anchor telemetry queries are sent upstream for a mirror zone ($n)"
+ret=0
+# ns3 is started with "-T tat=3", so TAT queries should have already been sent.
+grep "_ta-[-0-9a-f]*/NULL" ns1/named.run > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking that loading a correctly signed mirror zone from disk succeeds ($n)"
+ret=0
+stop_server --use-rndc --port ${CONTROLPORT} ns3
+cat ns2/verify-load.db.good.signed > ns3/verify-load.db.mirror
+nextpart ns3/named.run > /dev/null
+start_server --noclean --restart --port ${PORT} ns3
+wait_for_load verify-load ${UPDATED_SERIAL_GOOD} ns3/named.run
+$DIG $DIGOPTS @10.53.0.3 +norec verify-load SOA > dig.out.ns3.test$n 2>&1 || ret=1
+grep "ANSWER: 0" dig.out.ns3.test$n > /dev/null && ret=1
+grep "${UPDATED_SERIAL_GOOD}.*; serial" dig.out.ns3.test$n > /dev/null || ret=1
+nextpartpeek ns3/named.run | grep "verify-load.*mirror zone is now in use" > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking that loading a journal for an incorrectly signed mirror zone fails ($n)"
+ret=0
+stop_server --use-rndc --port ${CONTROLPORT} ns3
+cp ns3/verify-journal.db.mirror ns3/verify-ixfr.db.mirror
+cp ns3/verify-journal.db.bad.mirror.jnl ns3/verify-ixfr.db.mirror.jnl
+# Temporarily disable transfers of the "verify-ixfr" zone on ns2. This is
+# required to reliably test whether the message announcing the mirror zone
+# coming into effect is not logged after a failed journal verification since
+# otherwise a corrected version of the zone may be transferred after
+# verification fails but before we look for the aforementioned log message.
+# (NOTE: Keep the embedded newline in the sed function list below.)
+sed '/^zone "verify-ixfr" {$/,/^};$/ {
+ s/10.53.0.3/10.53.0.254/
+}' ns2/named.conf > ns2/named.conf.modified
+mv ns2/named.conf.modified ns2/named.conf
+rndc_reconfig ns2 10.53.0.2
+nextpart ns3/named.run > /dev/null
+start_server --noclean --restart --port ${PORT} ns3
+wait_for_load verify-ixfr ${UPDATED_SERIAL_BAD} ns3/named.run
+$DIG $DIGOPTS @10.53.0.3 +norec verify-ixfr SOA > dig.out.ns3.test$n 2>&1 || ret=1
+grep "ANSWER: 0" dig.out.ns3.test$n > /dev/null || ret=1
+grep "${UPDATED_SERIAL_BAD}.*; serial" dig.out.ns3.test$n > /dev/null && ret=1
+nextpartpeek ns3/named.run | grep "No correct ${DEFAULT_ALGORITHM} signature for verify-ixfr SOA" > /dev/null || ret=1
+nextpartpeek ns3/named.run | grep "verify-ixfr.*mirror zone is now in use" > /dev/null && ret=1
+# Restore transfers for the "verify-ixfr" zone on ns2.
+# (NOTE: Keep the embedded newline in the sed function list below.)
+sed '/^zone "verify-ixfr" {$/,/^};$/ {
+ s/10.53.0.254/10.53.0.3/
+}' ns2/named.conf > ns2/named.conf.modified
+mv ns2/named.conf.modified ns2/named.conf
+rndc_reconfig ns2 10.53.0.2
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking that loading a journal for a correctly signed mirror zone succeeds ($n)"
+ret=0
+stop_server --use-rndc --port ${CONTROLPORT} ns3
+cp ns3/verify-journal.db.mirror ns3/verify-ixfr.db.mirror
+cp ns3/verify-journal.db.good.mirror.jnl ns3/verify-ixfr.db.mirror.jnl
+nextpart ns3/named.run > /dev/null
+start_server --noclean --restart --port ${PORT} ns3
+wait_for_load verify-ixfr ${UPDATED_SERIAL_GOOD} ns3/named.run
+$DIG $DIGOPTS @10.53.0.3 +norec verify-ixfr SOA > dig.out.ns3.test$n 2>&1 || ret=1
+grep "ANSWER: 0" dig.out.ns3.test$n > /dev/null && ret=1
+grep "${UPDATED_SERIAL_GOOD}.*; serial" dig.out.ns3.test$n > /dev/null || ret=1
+nextpartpeek ns3/named.run | grep "verify-ixfr.*mirror zone is now in use" > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking delegations sourced from a mirror zone ($n)"
+ret=0
+$DIG $DIGOPTS @10.53.0.3 foo.example A +norec > dig.out.ns3.test$n 2>&1 || ret=1
+# Check response code and flags in the answer.
+grep "NOERROR" dig.out.ns3.test$n > /dev/null || ret=1
+grep "flags:.* ad" dig.out.ns3.test$n > /dev/null && ret=1
+# Check that a delegation containing a DS RRset and glue is present.
+grep "ANSWER: 0" dig.out.ns3.test$n > /dev/null || ret=1
+grep "example.*IN.*NS" dig.out.ns3.test$n > /dev/null || ret=1
+grep "example.*IN.*DS" dig.out.ns3.test$n > /dev/null || ret=1
+grep "ns2.example.*A.*10.53.0.2" dig.out.ns3.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking that resolution involving a mirror zone works as expected ($n)"
+ret=0
+$DIG $DIGOPTS @10.53.0.3 foo.example A > dig.out.ns3.test$n 2>&1 || ret=1
+# Check response code and flags in the answer.
+grep "NOERROR" dig.out.ns3.test$n > /dev/null || ret=1
+grep "flags:.* ad" dig.out.ns3.test$n > /dev/null || ret=1
+# Ensure ns1 was not queried.
+grep "query 'foo.example/A/IN'" ns1/named.run > /dev/null && ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking that non-recursive queries for names below mirror zone get responded from cache ($n)"
+ret=0
+# Issue a non-recursive query for an RRset which is expected to be in cache.
+$DIG $DIGOPTS @10.53.0.3 +norec foo.example. A > dig.out.ns3.test$n 2>&1 || ret=1
+# Check response code and flags in the answer.
+grep "NOERROR" dig.out.ns3.test$n > /dev/null || ret=1
+grep "flags:.* ad" dig.out.ns3.test$n > /dev/null || ret=1
+# Ensure the response is not a delegation.
+grep "ANSWER: 0" dig.out.ns3.test$n > /dev/null && ret=1
+grep "foo.example.*IN.*A.*127.0.0.1" dig.out.ns3.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking that delegations from cache which improve mirror zone delegations are properly handled ($n)"
+ret=0
+# First, issue a recursive query in order to cache an RRset which is not within
+# the mirror zone's bailiwick.
+$DIG $DIGOPTS @10.53.0.3 sub.example. NS > dig.out.ns3.test$n.1 2>&1 || ret=1
+# Ensure the child-side NS RRset is returned.
+grep "NOERROR" dig.out.ns3.test$n.1 > /dev/null || ret=1
+grep "ANSWER: 2" dig.out.ns3.test$n.1 > /dev/null || ret=1
+grep "sub.example.*IN.*NS" dig.out.ns3.test$n.1 > /dev/null || ret=1
+# Issue a non-recursive query for something below the cached zone cut.
+$DIG $DIGOPTS @10.53.0.3 +norec foo.sub.example. A > dig.out.ns3.test$n.2 2>&1 || ret=1
+# Ensure the cached NS RRset is returned in a delegation, along with the
+# parent-side DS RRset.
+grep "NOERROR" dig.out.ns3.test$n.2 > /dev/null || ret=1
+grep "ANSWER: 0" dig.out.ns3.test$n.2 > /dev/null || ret=1
+grep "sub.example.*IN.*NS" dig.out.ns3.test$n.2 > /dev/null || ret=1
+grep "sub.example.*IN.*DS" dig.out.ns3.test$n.2 > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking flags set in a DNSKEY response sourced from a mirror zone ($n)"
+ret=0
+$DIG $DIGOPTS @10.53.0.3 . DNSKEY > dig.out.ns3.test$n 2>&1 || ret=1
+# Check response code and flags in the answer.
+grep "NOERROR" dig.out.ns3.test$n > /dev/null || ret=1
+grep "flags:.* aa" dig.out.ns3.test$n > /dev/null && ret=1
+grep "flags:.* ad" dig.out.ns3.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking flags set in a SOA response sourced from a mirror zone ($n)"
+ret=0
+$DIG $DIGOPTS @10.53.0.3 . SOA > dig.out.ns3.test$n 2>&1 || ret=1
+# Check response code and flags in the answer.
+grep "NOERROR" dig.out.ns3.test$n > /dev/null || ret=1
+grep "flags:.* aa" dig.out.ns3.test$n > /dev/null && ret=1
+grep "flags:.* ad" dig.out.ns3.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking that resolution succeeds with unavailable mirror zone data ($n)"
+ret=0
+wait_for_transfer initially-unavailable
+# Query for a record in a zone that is set up to be mirrored, but
+# untransferrable from the configured primary. Resolution should still succeed.
+$DIG $DIGOPTS @10.53.0.3 foo.initially-unavailable. A > dig.out.ns3.test$n.1 2>&1 || ret=1
+# Check response code and flags in the answer.
+grep "NOERROR" dig.out.ns3.test$n.1 > /dev/null || ret=1
+grep "flags:.* ad" dig.out.ns3.test$n.1 > /dev/null || ret=1
+# Sanity check: the authoritative server should have been queried.
+nextpart ns2/named.run | grep "query 'foo.initially-unavailable/A/IN'" > /dev/null || ret=1
+# Reconfigure ns2 so that the zone can be mirrored on ns3.
+sed '/^zone "initially-unavailable" {$/,/^};$/ {
+ s/10.53.0.254/10.53.0.3/
+}' ns2/named.conf > ns2/named.conf.modified
+mv ns2/named.conf.modified ns2/named.conf
+rndc_reconfig ns2 10.53.0.2
+# Flush the cache on ns3 and retransfer the mirror zone.
+$RNDCCMD 10.53.0.3 flush > /dev/null 2>&1
+nextpart ns3/named.run > /dev/null
+$RNDCCMD 10.53.0.3 retransfer initially-unavailable > /dev/null 2>&1
+wait_for_transfer initially-unavailable
+# Query for the same record again. Resolution should still succeed.
+$DIG $DIGOPTS @10.53.0.3 foo.initially-unavailable. A > dig.out.ns3.test$n.2 2>&1 || ret=1
+# Check response code and flags in the answer.
+grep "NOERROR" dig.out.ns3.test$n.2 > /dev/null || ret=1
+grep "flags:.* ad" dig.out.ns3.test$n.2 > /dev/null || ret=1
+# Ensure the authoritative server was not queried.
+nextpart ns2/named.run | grep "query 'foo.initially-unavailable/A/IN'" > /dev/null && ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking that resolution succeeds with expired mirror zone data ($n)"
+ret=0
+# Reconfigure ns2 so that the zone from the previous test can no longer be
+# mirrored on ns3.
+sed '/^zone "initially-unavailable" {$/,/^};$/ {
+ s/10.53.0.3/10.53.0.254/
+}' ns2/named.conf > ns2/named.conf.modified
+mv ns2/named.conf.modified ns2/named.conf
+rndc_reconfig ns2 10.53.0.2
+# Stop ns3, update the timestamp of the zone file to one far in the past, then
+# restart ns3.
+stop_server --use-rndc --port ${CONTROLPORT} ns3
+touch -t 200001010000 ns3/initially-unavailable.db.mirror
+nextpart ns3/named.run > /dev/null
+start_server --noclean --restart --port ${PORT} ns3
+# Ensure named attempts to retransfer the zone due to its expiry.
+wait_for_transfer initially-unavailable
+# Ensure the expected messages were logged.
+nextpartpeek ns3/named.run | grep "initially-unavailable.*expired" > /dev/null || ret=1
+nextpartpeek ns3/named.run | grep "initially-unavailable.*mirror zone is no longer in use" > /dev/null || ret=1
+# Query for a record in the expired zone. Resolution should still succeed.
+$DIG $DIGOPTS @10.53.0.3 foo.initially-unavailable. A > dig.out.ns3.test$n 2>&1 || ret=1
+# Check response code and flags in the answer.
+grep "NOERROR" dig.out.ns3.test$n > /dev/null || ret=1
+grep "flags:.* ad" dig.out.ns3.test$n > /dev/null || ret=1
+# Sanity check: the authoritative server should have been queried.
+nextpart ns2/named.run | grep "query 'foo.initially-unavailable/A/IN'" > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking that clients without cache access cannot retrieve mirror zone data ($n)"
+ret=0
+$DIG $DIGOPTS @10.53.0.3 -b 10.53.0.3 +norec . SOA > dig.out.ns3.test$n 2>&1 || ret=1
+# Check response code and flags in the answer.
+grep "REFUSED" dig.out.ns3.test$n > /dev/null || ret=1
+grep "flags:.* ad" dig.out.ns3.test$n > /dev/null && ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking that outgoing transfers of mirror zones are disabled by default ($n)"
+ret=0
+$DIG $DIGOPTS @10.53.0.3 . AXFR > dig.out.ns3.test$n 2>&1 || ret=1
+grep "; Transfer failed" dig.out.ns3.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking that notifies are disabled by default for mirror zones ($n)"
+ret=0
+grep "initially-unavailable.*sending notifies" ns3/named.run > /dev/null && ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking output of \"rndc zonestatus\" for a mirror zone ($n)"
+ret=0
+$RNDCCMD 10.53.0.3 zonestatus . > rndc.out.ns3.test$n 2>&1
+grep "type: mirror" rndc.out.ns3.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking that \"rndc reconfig\" properly handles a mirror -> secondary zone type change ($n)"
+ret=0
+# Sanity check before we start.
+$DIG $DIGOPTS @10.53.0.3 +norec verify-reconfig SOA > dig.out.ns3.test$n.1 2>&1 || ret=1
+grep "NOERROR" dig.out.ns3.test$n.1 > /dev/null || ret=1
+grep "flags:.* aa" dig.out.ns3.test$n.1 > /dev/null && ret=1
+grep "flags:.* ad" dig.out.ns3.test$n.1 > /dev/null || ret=1
+# Reconfigure the zone so that it is no longer a mirror zone.
+# (NOTE: Keep the embedded newline in the sed function list below.)
+sed '/^zone "verify-reconfig" {$/,/^};$/ {
+ s/type mirror;/type secondary;/
+}' ns3/named.conf > ns3/named.conf.modified
+mv ns3/named.conf.modified ns3/named.conf
+nextpart ns3/named.run > /dev/null
+rndc_reconfig ns3 10.53.0.3
+# Zones whose type was changed should not be reusable, which means the tested
+# zone should have been reloaded from disk.
+wait_for_load verify-reconfig ${ORIGINAL_SERIAL} ns3/named.run
+# Ensure responses sourced from the reconfigured zone have AA=1 and AD=0.
+$DIG $DIGOPTS @10.53.0.3 +norec verify-reconfig SOA > dig.out.ns3.test$n.2 2>&1 || ret=1
+grep "NOERROR" dig.out.ns3.test$n.2 > /dev/null || ret=1
+grep "flags:.* aa" dig.out.ns3.test$n.2 > /dev/null || ret=1
+grep "flags:.* ad" dig.out.ns3.test$n.2 > /dev/null && ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking that \"rndc reconfig\" properly handles a secondary -> mirror zone type change ($n)"
+ret=0
+# Put an incorrectly signed version of the zone in the zone file used by ns3.
+nextpart ns3/named.run > /dev/null
+cat ns2/verify-reconfig.db.bad.signed > ns3/verify-reconfig.db.mirror
+# Reconfigure the zone so that it is a mirror zone again.
+# (NOTE: Keep the embedded newline in the sed function list below.)
+sed '/^zone "verify-reconfig" {$/,/^};$/ {
+ s/type secondary;/type mirror;/
+}' ns3/named.conf > ns3/named.conf.modified
+mv ns3/named.conf.modified ns3/named.conf
+rndc_reconfig ns3 10.53.0.3
+# The reconfigured zone should fail verification.
+wait_for_load verify-reconfig ${UPDATED_SERIAL_BAD} ns3/named.run
+$DIG $DIGOPTS @10.53.0.3 +norec verify-reconfig SOA > dig.out.ns3.test$n 2>&1 || ret=1
+grep "${UPDATED_SERIAL_BAD}.*; serial" dig.out.ns3.test$n > /dev/null && ret=1
+nextpart ns3/named.run | grep "No correct ${DEFAULT_ALGORITHM} signature for verify-reconfig SOA" > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking that a mirror zone can be added using rndc ($n)"
+ret=0
+# Sanity check: the zone should not exist in the root zone.
+$DIG $DIGOPTS @10.53.0.3 +norec verify-addzone SOA > dig.out.ns3.test$n.1 2>&1 || ret=1
+grep "NXDOMAIN" dig.out.ns3.test$n.1 > /dev/null || ret=1
+grep "flags:.* aa" dig.out.ns3.test$n.1 > /dev/null && ret=1
+grep "flags:.* ad" dig.out.ns3.test$n.1 > /dev/null || ret=1
+# Mirror a zone which does not exist in the root zone.
+nextpart ns3/named.run > /dev/null
+$RNDCCMD 10.53.0.3 addzone verify-addzone '{ type mirror; primaries { 10.53.0.2; }; };' > rndc.out.ns3.test$n 2>&1 || ret=1
+wait_for_transfer verify-addzone
+# Check whether the mirror zone was added and whether it behaves as expected.
+$DIG $DIGOPTS @10.53.0.3 +norec verify-addzone SOA > dig.out.ns3.test$n.2 2>&1 || ret=1
+grep "NOERROR" dig.out.ns3.test$n.2 > /dev/null || ret=1
+grep "flags:.* aa" dig.out.ns3.test$n.2 > /dev/null && ret=1
+grep "flags:.* ad" dig.out.ns3.test$n.2 > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking that a mirror zone can be deleted using rndc ($n)"
+ret=0
+# Remove the mirror zone added in the previous test.
+nextpart ns3/named.run > /dev/null
+$RNDCCMD 10.53.0.3 delzone verify-addzone > rndc.out.ns3.test$n 2>&1 || ret=1
+wait_for_log 20 "zone verify-addzone/IN: mirror zone is no longer in use; reverting to normal recursion" ns3/named.run || ret=1
+# Check whether the mirror zone was removed.
+$DIG $DIGOPTS @10.53.0.3 +norec verify-addzone SOA > dig.out.ns3.test$n 2>&1 || ret=1
+grep "NXDOMAIN" dig.out.ns3.test$n > /dev/null || ret=1
+grep "flags:.* aa" dig.out.ns3.test$n > /dev/null && ret=1
+grep "flags:.* ad" dig.out.ns3.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/mirror/tests_sh_mirror.py b/bin/tests/system/mirror/tests_sh_mirror.py
new file mode 100644
index 0000000..f8d6b9d
--- /dev/null
+++ b/bin/tests/system/mirror/tests_sh_mirror.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_mirror(run_tests_sh):
+ run_tests_sh()