]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Fall back to normal recursion when mirror zone data is unavailable
authorMichał Kępień <michal@isc.org>
Thu, 28 Jun 2018 11:38:39 +0000 (13:38 +0200)
committerMichał Kępień <michal@isc.org>
Thu, 28 Jun 2018 11:38:39 +0000 (13:38 +0200)
If transferring or loading a mirror zone fails, resolution should still
succeed by means of falling back to regular recursive queries.
Currently, though, if a slave zone is present in the zone table and not
loaded, a SERVFAIL response is generated.  Thus, mirror zones need
special handling in this regard.

Add a new dns_zt_find() flag, DNS_ZTFIND_MIRROR, and set it every time a
domain name is looked up rather than a zone itself.  Handle that flag in
dns_zt_find() in such a way that a mirror zone which is expired or not
yet loaded is ignored when looking up domain names, but still possible
to find when the caller wants to know whether the zone is configured.
This causes a fallback to recursion when mirror zone data is unavailable
without making unloaded mirror zones invisible to code checking a zone's
existence.

13 files changed:
bin/tests/system/mirror/ns1/root.db.in
bin/tests/system/mirror/ns2/initially-unavailable.db.in [new file with mode: 0644]
bin/tests/system/mirror/ns2/named.conf.in
bin/tests/system/mirror/ns2/sign.sh
bin/tests/system/mirror/ns3/named.conf.in
bin/tests/system/mirror/tests.sh
lib/dns/include/dns/zone.h
lib/dns/include/dns/zt.h
lib/dns/view.c
lib/dns/zone.c
lib/dns/zt.c
lib/ns/query.c
util/copyrights

index d5728f419b69609151f631a9299d88c08f95501c..65af796574fe012f726d9ae1472a4a2c4d8e1b51 100644 (file)
@@ -13,3 +13,5 @@ $TTL 3600
 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/ns2/initially-unavailable.db.in b/bin/tests/system/mirror/ns2/initially-unavailable.db.in
new file mode 100644 (file)
index 0000000..65d573e
--- /dev/null
@@ -0,0 +1,14 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; 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 http://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 2000010100 3600 1200 604800 3600
+@              NS      ns2
+ns2            A       10.53.0.2
+foo            CNAME   foo.example.
index 8f714fcf083e44fa73dae87e78b89c7d82ea6338..7928dc2f7037e7ec66f717ffa72135859aa1626a 100644 (file)
@@ -39,6 +39,12 @@ zone "sub.example" {
        file "sub.example.db.in";
 };
 
+zone "initially-unavailable" {
+       type master;
+       file "initially-unavailable.db.signed";
+       allow-transfer { 10.53.0.254; };
+};
+
 zone "verify-axfr" {
        type master;
        file "verify-axfr.db.signed";
index 0cc89807947c8c5ead31c5a776115951d536d410..d647c12288b82678b1d64e644dc3c9c877e5b54e 100644 (file)
@@ -14,7 +14,7 @@ SYSTEMTESTTOP=../..
 
 keys_to_trust=""
 
-for zonename in example; do
+for zonename in example initially-unavailable; do
        zone=$zonename
        infile=$zonename.db.in
        zonefile=$zonename.db
@@ -27,6 +27,11 @@ for zonename in example; do
        $SIGNER -P -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"
+
 ORIGINAL_SERIAL=`awk '$2 == "SOA" {print $5}' verify.db.in`
 UPDATED_SERIAL_BAD=`expr ${ORIGINAL_SERIAL} + 1`
 UPDATED_SERIAL_GOOD=`expr ${ORIGINAL_SERIAL} + 2`
index 1873f88e17fd0b3ccbedcf2e5e9e7991655e6081..a66b216eb7cd1e7b71bd6edf7d5d11a571b4d2d3 100644 (file)
@@ -41,6 +41,13 @@ zone "." {
        file "root.db.mirror";
 };
 
+zone "initially-unavailable" {
+       type slave;
+       masters { 10.53.0.2; };
+       mirror yes;
+       file "initially-unavailable.db.mirror";
+};
+
 zone "verify-axfr" {
        type slave;
        masters { 10.53.0.2; };
index e95f97122c10a8b023c0234d890b09a30a237be1..9e79a953d7400e1219b0c9f8f670121076147a93 100644 (file)
@@ -16,11 +16,11 @@ DIGOPTS="-p ${PORT} +dnssec +time=1 +tries=1 +multi"
 RNDCCMD="$RNDC -c $SYSTEMTESTTOP/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 is aborted by a verification failure or a REFUSED response from the master.
 wait_for_transfer() {
        zone=$1
        for i in 1 2 3 4 5 6 7 8 9 10; do
-               nextpartpeek ns3/named.run | egrep "'$zone/IN'.*Transfer status: (success|verify failure)" > /dev/null && return
+               nextpartpeek ns3/named.run | egrep "'$zone/IN'.*Transfer status: (success|verify failure|REFUSED)" > /dev/null && return
                sleep 1
        done
        echo_i "exceeded time limit waiting for proof of '$zone' being transferred to appear in ns3/named.run"
@@ -271,5 +271,63 @@ grep "flags:.* ad" dig.out.ns3.test$n > /dev/null || ret=1
 if [ $ret != 0 ]; then echo_i "failed"; fi
 status=`expr $status + $ret`
 
+n=`expr $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 master.  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 "s/10.53.0.254/10.53.0.3/;" ns2/named.conf > ns2/named.conf.modified
+mv ns2/named.conf.modified ns2/named.conf
+$RNDCCMD 10.53.0.2 reconfig > /dev/null 2>&1
+# 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=`expr $status + $ret`
+
+n=`expr $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 "s/10.53.0.3/10.53.0.254/;" ns2/named.conf > ns2/named.conf.modified
+mv ns2/named.conf.modified ns2/named.conf
+$RNDCCMD 10.53.0.2 reconfig > /dev/null 2>&1
+# Stop ns3, update the timestamp of the zone file to one far in the past, then
+# restart ns3.
+$PERL $SYSTEMTESTTOP/stop.pl --use-rndc --port ${CONTROLPORT} . ns3
+touch -t 200001010000 ns3/initially-unavailable.db.mirror
+nextpart ns3/named.run > /dev/null
+$PERL $SYSTEMTESTTOP/start.pl --noclean --restart --port ${PORT} . ns3
+# Ensure named attempts to retransfer the zone due to its expiry.
+wait_for_transfer initially-unavailable
+nextpart ns3/named.run | grep "initially-unavailable.*expired" > /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=`expr $status + $ret`
+
 echo_i "exit status: $status"
 [ $status -eq 0 ] || exit 1
index c724951d9e352a700e0c7b725bc396b875b51325..fe61e78a8744d180744fd47d8464710ee3d15aa2 100644 (file)
@@ -2480,6 +2480,13 @@ dns_zone_getgluecachestats(dns_zone_t *zone);
  *     otherwise NULL.
  */
 
+isc_boolean_t
+dns_zone_isloaded(const dns_zone_t *zone);
+/*%<
+ * Return ISC_TRUE if 'zone' was loaded and has not expired yet, return
+ * ISC_FALSE otherwise.
+ */
+
 isc_boolean_t
 dns_zone_ismirror(const dns_zone_t *zone);
 /*%<
index 337ff03b3bfdec51acc039372625f92d44e4397a..c79223879ef322ee26aae641531d5631cea77b53 100644 (file)
@@ -20,6 +20,7 @@
 #include <dns/types.h>
 
 #define DNS_ZTFIND_NOEXACT             0x01
+#define DNS_ZTFIND_MIRROR              0x02
 
 ISC_LANG_BEGINDECLS
 
index 5dafdb5a1e24e9b9d3862ee958884f92008386f7..e36576fd40ab384e17036a5121aaa5b51d1ee39a 100644 (file)
@@ -1031,7 +1031,8 @@ dns_view_find(dns_view_t *view, const dns_name_t *name, dns_rdatatype_t type,
        zone = NULL;
        LOCK(&view->lock);
        if (view->zonetable != NULL)
-               result = dns_zt_find(view->zonetable, name, 0, NULL, &zone);
+               result = dns_zt_find(view->zonetable, name, DNS_ZTFIND_MIRROR,
+                                    NULL, &zone);
        else
                result = ISC_R_NOTFOUND;
        UNLOCK(&view->lock);
@@ -1261,7 +1262,7 @@ dns_view_findzonecut(dns_view_t *view, const dns_name_t *name,
        dns_name_t *zfname;
        dns_rdataset_t zrdataset, zsigrdataset;
        dns_fixedname_t zfixedname;
-       unsigned int ztoptions = 0;
+       unsigned int ztoptions = DNS_ZTFIND_MIRROR;
 
        REQUIRE(DNS_VIEW_VALID(view));
        REQUIRE(view->frozen);
index 0acf2e0305cda8e14088a0fb88da2f59a4f6c7b9..7d1daba37f62ea19487fb122c7b3b3d157d0ba1b 100644 (file)
@@ -19331,6 +19331,13 @@ dns_zone_getgluecachestats(dns_zone_t *zone) {
        return (zone->gluecachestats);
 }
 
+isc_boolean_t
+dns_zone_isloaded(const dns_zone_t *zone) {
+       REQUIRE(DNS_ZONE_VALID(zone));
+
+       return (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_LOADED));
+}
+
 isc_boolean_t
 dns_zone_ismirror(const dns_zone_t *zone) {
        REQUIRE(DNS_ZONE_VALID(zone));
index f5d80367c3895f139b6aeff1202c5a688b9351c1..9fec7c49c4e606cda6755662c9064974bf6c6fe1 100644 (file)
@@ -164,8 +164,22 @@ dns_zt_find(dns_zt_t *zt, const dns_name_t *name, unsigned int options,
 
        result = dns_rbt_findname(zt->table, name, rbtoptions, foundname,
                                  (void **) (void*)&dummy);
-       if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH)
-               dns_zone_attach(dummy, zonep);
+       if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+               /*
+                * If DNS_ZTFIND_MIRROR is set and the zone which was
+                * determined to be the deepest match for the supplied name is
+                * a mirror zone which is expired or not yet loaded, treat it
+                * as non-existent.  This will trigger a fallback to recursion
+                * instead of returning a SERVFAIL.
+                */
+               if ((options & DNS_ZTFIND_MIRROR) != 0 &&
+                   dns_zone_ismirror(dummy) && !dns_zone_isloaded(dummy))
+               {
+                       result = ISC_R_NOTFOUND;
+               } else {
+                       dns_zone_attach(dummy, zonep);
+               }
+       }
 
        RWUNLOCK(&zt->rwlock, isc_rwlocktype_read);
 
index 692da1c37a7ab4ae3d7803cf43d5717e882bd24e..dcf0f657563dfd230517efd77a1a58ac01497b9a 100644 (file)
@@ -1176,8 +1176,10 @@ query_getzonedb(ns_client_t *client, const dns_name_t *name,
        /*%
         * Find a zone database to answer the query.
         */
-       ztoptions = ((options & DNS_GETDB_NOEXACT) != 0) ?
-               DNS_ZTFIND_NOEXACT : 0;
+       ztoptions = DNS_ZTFIND_MIRROR;
+       if ((options & DNS_GETDB_NOEXACT) != 0) {
+               ztoptions |= DNS_ZTFIND_NOEXACT;
+       }
 
        result = dns_zt_find(client->view->zonetable, name, ztoptions, NULL,
                             &zone);
index 83b2bd6871957000e7928170fe3bdf33f7132421..990fc8cb53c6168d2216363f0bd32f19108ab3c9 100644 (file)
 ./bin/tests/system/mirror/ns1/root.db.in       ZONE    2018
 ./bin/tests/system/mirror/ns1/sign.sh          SH      2018
 ./bin/tests/system/mirror/ns2/example.db.in    ZONE    2018
+./bin/tests/system/mirror/ns2/initially-unavailable.db.in      ZONE    2018
 ./bin/tests/system/mirror/ns2/named.conf.in    CONF-C  2018
 ./bin/tests/system/mirror/ns2/sign.sh          SH      2018
 ./bin/tests/system/mirror/ns2/sub.example.db.in        ZONE    2018