mirror of
https://gitlab.isc.org/isc-projects/bind9
synced 2025-09-03 08:05:21 +00:00
Fall back to normal recursion when mirror zone data is unavailable
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.
This commit is contained in:
@@ -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
|
||||
|
14
bin/tests/system/mirror/ns2/initially-unavailable.db.in
Normal file
14
bin/tests/system/mirror/ns2/initially-unavailable.db.in
Normal file
@@ -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.
|
@@ -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";
|
||||
|
@@ -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`
|
||||
|
@@ -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; };
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
/*%<
|
||||
|
@@ -20,6 +20,7 @@
|
||||
#include <dns/types.h>
|
||||
|
||||
#define DNS_ZTFIND_NOEXACT 0x01
|
||||
#define DNS_ZTFIND_MIRROR 0x02
|
||||
|
||||
ISC_LANG_BEGINDECLS
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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));
|
||||
|
16
lib/dns/zt.c
16
lib/dns/zt.c
@@ -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)
|
||||
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);
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -1600,6 +1600,7 @@
|
||||
./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
|
||||
|
Reference in New Issue
Block a user