Merge branch '2247-add-serve-stale-option-to-set-client-timeout' into 'main'

Resolve "Add serve-stale option to set client timeout"

Closes #2247

See merge request isc-projects/bind9!4514
This commit is contained in:
Diego dos Santos Fronza 2021-01-25 14:25:32 +00:00
commit e96bf1f3dc
33 changed files with 1511 additions and 149 deletions

View file

@ -1,3 +1,8 @@
5566. [func] Add "stale-answer-client-timeout" option, which
is the amount of time a recursive resolver waits before
attempting to answer the query using stale data from cache.
[GL #2247]
5565. [func] The SONAMEs for BIND 9 libraries now include the current
BIND 9 version number, in an effort to tightly couple
internal libraries with a specific release. [GL #2387]

View file

@ -194,9 +194,10 @@ options {\n\
servfail-ttl 1;\n\
# sortlist <none>\n\
stale-answer-enable false;\n\
stale-refresh-time 30; /* 30 seconds */\n\
stale-answer-client-timeout 1800; /* in milliseconds */\n\
stale-answer-ttl 30; /* 30 seconds */\n\
stale-cache-enable false;\n\
stale-refresh-time 30; /* 30 seconds */\n\
synth-from-dnssec no;\n\
# topology <none>\n\
transfer-format many-answers;\n\

View file

@ -403,6 +403,7 @@ OPTIONS
sig-validity-interval integer [ integer ];
sortlist { address_match_element; ... };
stacksize ( default | unlimited | sizeval );
stale-answer-client-timeout ( disabled | off | integer );
stale-answer-enable boolean;
stale-answer-ttl duration;
stale-cache-enable boolean;
@ -806,6 +807,7 @@ VIEW
sig-signing-type integer;
sig-validity-interval integer [ integer ];
sortlist { address_match_element; ... };
stale-answer-client-timeout ( disabled | off | integer );
stale-answer-enable boolean;
stale-answer-ttl duration;
stale-cache-enable boolean;

View file

@ -4485,6 +4485,23 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
view->staleanswersok = dns_stale_answer_conf;
}
obj = NULL;
result = named_config_get(maps, "stale-answer-client-timeout", &obj);
INSIST(result == ISC_R_SUCCESS);
if (cfg_obj_isstring(obj)) {
/*
* The only string values available for this option
* are "disabled" and "off".
* We use (uint32_t) -1 to represent disabled since
* a value of zero means that stale data can be used
* to promptly answer the query, while an attempt to
* refresh the RRset will still be made in background.
*/
view->staleanswerclienttimeout = (uint32_t)-1;
} else {
view->staleanswerclienttimeout = cfg_obj_asuint32(obj);
}
obj = NULL;
result = named_config_get(maps, "stale-refresh-time", &obj);
INSIST(result == ISC_R_SUCCESS);
@ -4774,6 +4791,27 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
query_timeout = cfg_obj_asuint32(obj);
dns_resolver_settimeout(view->resolver, query_timeout);
/*
* Adjust stale-answer-client-timeout upper bound
* to be resolver-query-timeout - 1s.
* This assignment is safe as dns_resolver_settimeout()
* ensures that resolver->querytimeout value will be in the
* [MINIMUM_QUERY_TIMEOUT, MAXIMUM_QUERY_TIMEOUT] range and
* MINIMUM_QUERY_TIMEOUT is > 1000 (in ms).
*/
if (view->staleanswerclienttimeout != (uint32_t)-1 &&
view->staleanswerclienttimeout >
(dns_resolver_gettimeout(view->resolver) - 1000))
{
view->staleanswerclienttimeout =
dns_resolver_gettimeout(view->resolver) - 1000;
isc_log_write(
named_g_lctx, NAMED_LOGCATEGORY_GENERAL,
NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING,
"stale-answer-client-timeout adjusted to %" PRIu32,
view->staleanswerclienttimeout);
}
/* Specify whether to use 0-TTL for negative response for SOA query */
dns_resolver_setzeronosoattl(view->resolver, zero_no_soattl);

View file

@ -15,3 +15,4 @@ rm -f */named.run */named.memstats
rm -f ns*/managed-keys.bind*
rm -f ns*/named_dump*
rm -f ns*/named.stats*
rm -f ns*/named.run.prev

View file

@ -30,6 +30,7 @@ options {
max-stale-ttl 3600;
stale-answer-ttl 4;
stale-answer-enable yes;
stale-answer-client-timeout disabled;
stale-cache-enable yes;
stale-refresh-time 30;
servfail-ttl 0;

View file

@ -30,6 +30,7 @@ options {
max-stale-ttl 20;
stale-answer-ttl 3;
stale-answer-enable yes;
stale-answer-client-timeout disabled;
stale-cache-enable yes;
servfail-ttl 0;
};

View file

@ -30,6 +30,7 @@ options {
max-stale-ttl 20;
stale-answer-ttl 3;
stale-answer-enable yes;
stale-answer-client-timeout disabled;
stale-cache-enable yes;
stale-refresh-time 0;
servfail-ttl 0;

View file

@ -0,0 +1,48 @@
/*
* 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.
*/
/*
* Test default stale-answer-client-timeout value
*/
key rndc_key {
secret "1234abcd8765";
algorithm hmac-sha256;
};
controls {
inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};
options {
query-source address 10.53.0.3;
notify-source 10.53.0.3;
transfer-source 10.53.0.3;
port @PORT@;
pid-file "named.pid";
listen-on { 10.53.0.3; };
listen-on-v6 { none; };
dnssec-validation no;
recursion yes;
stale-answer-enable yes;
stale-cache-enable yes;
stale-answer-ttl 3;
stale-refresh-time 0;
max-stale-ttl 3600;
resolver-query-timeout 10;
};
zone "." {
type secondary;
primaries { 10.53.0.1; };
file "root.bk";
};

View file

@ -0,0 +1,48 @@
/*
* 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.
*/
/*
* Test disable of stale-answer-client-timeout.
*/
key rndc_key {
secret "1234abcd8765";
algorithm hmac-sha256;
};
controls {
inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};
options {
query-source address 10.53.0.3;
notify-source 10.53.0.3;
transfer-source 10.53.0.3;
port @PORT@;
pid-file "named.pid";
listen-on { 10.53.0.3; };
listen-on-v6 { none; };
dnssec-validation no;
recursion yes;
stale-answer-enable yes;
stale-cache-enable yes;
stale-answer-ttl 3;
stale-answer-client-timeout off;
stale-refresh-time 0;
max-stale-ttl 3600;
resolver-query-timeout 10;
};
zone "." {
type hint;
file "root.db";
};

View file

@ -0,0 +1,48 @@
/*
* 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.
*/
/*
* Test stale-answer-client-timeout 0.
*/
key rndc_key {
secret "1234abcd8765";
algorithm hmac-sha256;
};
controls {
inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};
options {
query-source address 10.53.0.3;
notify-source 10.53.0.3;
transfer-source 10.53.0.3;
port @PORT@;
pid-file "named.pid";
listen-on { 10.53.0.3; };
listen-on-v6 { none; };
dnssec-validation no;
recursion yes;
stale-answer-enable yes;
stale-cache-enable yes;
stale-answer-ttl 3;
stale-answer-client-timeout 0;
stale-refresh-time 0;
resolver-query-timeout 10;
max-stale-ttl 3600;
};
zone "." {
type hint;
file "root.db";
};

View file

@ -0,0 +1,48 @@
/*
* 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.
*/
/*
* Test stale-answer-client-timeout 0.
*/
key rndc_key {
secret "1234abcd8765";
algorithm hmac-sha256;
};
controls {
inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};
options {
query-source address 10.53.0.3;
notify-source 10.53.0.3;
transfer-source 10.53.0.3;
port @PORT@;
pid-file "named.pid";
listen-on { 10.53.0.3; };
listen-on-v6 { none; };
dnssec-validation no;
recursion yes;
stale-answer-enable yes;
stale-cache-enable yes;
stale-answer-ttl 3;
stale-answer-client-timeout 0;
stale-refresh-time 4;
resolver-query-timeout 10;
max-stale-ttl 3600;
};
zone "." {
type hint;
file "root.db";
};

View file

@ -0,0 +1,11 @@
; 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.
. 300 NS ns.nil.
ns.nil. 300 A 10.53.0.1

View file

@ -19,7 +19,6 @@ stale_answer_ttl=$(sed -ne 's,^[[:space:]]*stale-answer-ttl \([[:digit:]]*\).*,\
status=0
n=0
#
# First test server with serve-stale options set.
#
@ -188,7 +187,7 @@ if [ $ret != 0 ]; then echo_i "failed"; fi
# 2. Disable responses from authoritative server.
# 3. Sleep for TTL duration so rrset TTL expires (2 sec)
# 4. Query data.example
# 5. Check if response come from stale rrset (3 sec TTL)
# 5. Check if response come from stale rrset (4 sec TTL)
# 6. Enable responses from authoritative server.
# 7. Query data.example
# 8. Check if response come from stale rrset, since the query
@ -548,8 +547,8 @@ grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
# keep track of time so we can access these rrset later,
# when we expect them to become ancient.
# Keep track of time so we can access these RRset later, when we expect them
# to become ancient.
t1=`$PERL -e 'print time()'`
n=$((n+1))
@ -644,16 +643,16 @@ grep "1 #NXDOMAIN" ns1/named.stats.$n.cachedb > /dev/null || ret=1
status=$((status+ret))
if [ $ret != 0 ]; then echo_i "failed"; fi
# retrieve max-stale-ttl value,
# Retrieve max-stale-ttl value.
interval_to_ancient=`grep 'max-stale-ttl' ns1/named2.conf.in | awk '{ print $2 }' | tr -d ';'`
# we add 2 seconds to it since this is the ttl value of the records being tested.
# We add 2 seconds to it since this is the ttl value of the records being
# tested.
interval_to_ancient=$((interval_to_ancient + 2))
t2=`$PERL -e 'print time()'`
elapsed=$((t2 - t1))
# if elapsed time so far is less than max-stale-ttl + 2 seconds,
# then we sleep enough to ensure that we'll ask for ancient rrsets
# in the next queries.
# If elapsed time so far is less than max-stale-ttl + 2 seconds, then we sleep
# enough to ensure that we'll ask for ancient RRsets in the next queries.
if [ $elapsed -lt $interval_to_ancient ]; then
sleep $((interval_to_ancient - elapsed))
fi
@ -885,7 +884,7 @@ $DIG -p ${PORT} @10.53.0.1 data.example TXT > dig.out.test$((n+1))
# Step 8.
n=$((n+1))
echo_i "check stale data.example comes from authoritative (stale-refresh-time disabled) ($n)"
echo_i "check data.example comes from authoritative (stale-refresh-time disabled) ($n)"
ret=0
grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
@ -1040,8 +1039,8 @@ $RNDCCMD 10.53.0.3 stats > /dev/null 2>&1
[ -f ns3/named.stats ] || ret=1
cp ns3/named.stats ns3/named.stats.$n
# Check first 10 lines of Cache DB statistics. After last queries, we expect
# one active TXT RRset, one stale TXT, one stale nxrrset TXT, and one
# stale NXDOMAIN.
# one active TXT RRset, one stale TXT, one stale nxrrset TXT, and one stale
# NXDOMAIN.
grep -A 10 "++ Cache DB RRsets ++" ns3/named.stats.$n > ns3/named.stats.$n.cachedb || ret=1
grep "1 TXT" ns3/named.stats.$n.cachedb > /dev/null || ret=1
grep "1 #TXT" ns3/named.stats.$n.cachedb > /dev/null || ret=1
@ -1260,8 +1259,8 @@ $RNDCCMD 10.53.0.4 stats > /dev/null 2>&1
[ -f ns4/named.stats ] || ret=1
cp ns4/named.stats ns4/named.stats.$n
# Check first 10 lines of Cache DB statistics. After last queries, we expect
# one active TXT RRset, one stale TXT, one stale nxrrset TXT, and one
# stale NXDOMAIN.
# one active TXT RRset, one stale TXT, one stale nxrrset TXT, and one stale
# NXDOMAIN.
grep -A 10 "++ Cache DB RRsets ++" ns4/named.stats.$n > ns4/named.stats.$n.cachedb || ret=1
grep "1 TXT" ns4/named.stats.$n.cachedb > /dev/null || ret=1
grep "1 #TXT" ns4/named.stats.$n.cachedb > /dev/null || ret=1
@ -1282,9 +1281,9 @@ status=$((status+ret))
echo_i "stop ns4"
$PERL ../stop.pl --use-rndc --port ${CONTROLPORT} serve-stale ns4
# Load the cache as if it was five minutes (RBTDB_VIRTUAL) older.
# Since max-stale-ttl defaults to a week, we need to adjust the date by
# one week and five minutes.
# Load the cache as if it was five minutes (RBTDB_VIRTUAL) older. Since
# max-stale-ttl defaults to a week, we need to adjust the date by one week and
# five minutes.
LASTWEEK=`TZ=UTC perl -e 'my $now = time();
my $oneWeekAgo = $now - 604800;
my $fiveMinutesAgo = $oneWeekAgo - 300;
@ -1553,5 +1552,463 @@ grep -F "#NXDOMAIN" ns5/named.stats.$n.cachedb > /dev/null && ret=1
status=$((status+ret))
if [ $ret != 0 ]; then echo_i "failed"; fi
########################################################
# Test for stale-answer-client-timeout (default 1.8s). #
########################################################
echo_i "test stale-answer-client-timeout (default 1.8)"
n=$((n+1))
echo_i "updating ns3/named.conf ($n)"
ret=0
copy_setports ns3/named2.conf.in ns3/named.conf
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
echo_i "restart ns3"
$PERL ../stop.pl --use-rndc --port ${CONTROLPORT} serve-stale ns3
start_server --noclean --restart --port ${PORT} serve-stale ns3
n=$((n+1))
echo_i "check 'rndc serve-stale status' ($n)"
ret=0
$RNDCCMD 10.53.0.3 serve-stale status > rndc.out.test$n 2>&1 || ret=1
grep '_default: on (stale-answer-ttl=3 max-stale-ttl=3600 stale-refresh-time=0)' rndc.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
n=$((n+1))
echo_i "enable responses from authoritative server ($n)"
ret=0
$DIG -p ${PORT} @10.53.0.2 txt enable > dig.out.test$n
grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
n=$((n+1))
echo_i "prime cache data.example (stale-answer-client-timeout)"
ret=0
$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
n=$((n+1))
echo_i "prime cache nodata.example (stale-answer-client-timeout)"
ret=0
$DIG -p ${PORT} @10.53.0.3 nodata.example TXT > dig.out.test$n
grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
n=$((n+1))
echo_i "disable responses from authoritative server ($n)"
ret=0
$DIG -p ${PORT} @10.53.0.2 txt disable > dig.out.test$n
grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
grep "TXT.\"0\"" dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
# Allow RRset to become stale.
sleep 2
# We configured a long value of 30 seconds for resolver-query-timeout.
# That should give us enough time to receive an stale answer from cache
# after stale-answer-client-timeout timer of 1.8 sec triggers.
n=$((n+1))
echo_i "check stale data.example comes from cache (default stale-answer-client-timeout) ($n)"
nextpart ns3/named.run > /dev/null
t1=`$PERL -e 'print time()'`
$DIG -p ${PORT} +tries=1 +timeout=10 @10.53.0.3 data.example TXT > dig.out.test$n
t2=`$PERL -e 'print time()'`
wait_for_log 5 "data.example client timeout, stale answer used" ns3/named.run || ret=1
ret=0
grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
grep "data\.example\..*3.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
# Default stale-answer-client-timeout is 1.8s, we allow some extra time
# just in case other tests are taking too much cpu.
[ $((t2 - t1)) -le 10 ] || { echo_i "query took $((t2 - t1))s to resolve."; ret=1; }
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
echo_i "sending queries for tests $((n+1))-$((n+2))..."
$DIG -p ${PORT} +tries=1 +timeout=3 @10.53.0.3 nodata.example TXT > dig.out.test$((n+1)) &
$DIG -p ${PORT} +tries=1 +timeout=30 @10.53.0.3 nodata.example TXT > dig.out.test$((n+2))
wait
# Since nodata.example is cached as NXRRSET and marked as stale at this point,
# BIND must not return this RRset when stale-answer-client-timeout triggers,
# instead, it must attempt to refresh the RRset. Since the authoritative
# server is disabled and we are using resolver-query-timeout value of 10
# seconds, we expect this query with a timeout of 3 seconds to time out.
n=$((n+1))
echo_i "check query for nodata.example times out (default stale-answer-client-timeout) ($n)"
grep "connection timed out" dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
# For this query we expect BIND to return stale NXRRSET data for
# nodata.example after resolver-query-timeout expires.
n=$((n+1))
echo_i "check stale nodata.example comes from cache after resolver-query-timeout expires (default stale-answer-client-timeout) ($n)"
grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
grep "example\..*3.*IN.*SOA" dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
#############################################
# Test for stale-answer-client-timeout off. #
#############################################
echo_i "test stale-answer-client-timeout (off)"
n=$((n+1))
echo_i "updating ns3/named.conf ($n)"
ret=0
copy_setports ns3/named3.conf.in ns3/named.conf
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
n=$((n+1))
echo_i "running 'rndc reload' ($n)"
ret=0
rndc_reload ns3 10.53.0.3
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
# Send a query, auth server is disabled, we will enable it after a while in
# order to receive an answer before resolver-query-timeout expires. Since
# stale-answer-client-timeout is disabled we must receive an answer from
# authoritative server.
echo_i "sending query for test $((n+2))"
$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$((n+2)) &
sleep 3
n=$((n+1))
echo_i "enable responses from authoritative server ($n)"
ret=0
$DIG -p ${PORT} @10.53.0.2 txt enable > dig.out.test$n
grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
# Wait until dig is done.
wait
n=$((n+1))
echo_i "check data.example comes from authoritative server (stale-answer-client-timeout off) ($n)"
grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
grep "data\.example\..*[12].*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
#############################################
# Test for stale-answer-client-timeout 0. #
#############################################
echo_i "test stale-answer-client-timeout (0)"
n=$((n+1))
echo_i "updating ns3/named.conf ($n)"
ret=0
copy_setports ns3/named4.conf.in ns3/named.conf
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
echo_i "restart ns3"
$PERL ../stop.pl --use-rndc --port ${CONTROLPORT} serve-stale ns3
start_server --noclean --restart --port ${PORT} serve-stale ns3
n=$((n+1))
echo_i "prime cache data.example (stale-answer-client-timeout 0)"
ret=0
$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
n=$((n+1))
echo_i "prime cache nodata.example (stale-answer-client-timeout 0)"
ret=0
$DIG -p ${PORT} @10.53.0.3 nodata.example TXT > dig.out.test$n
grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
n=$((n+1))
echo_i "disable responses from authoritative server ($n)"
ret=0
$DIG -p ${PORT} @10.53.0.2 txt disable > dig.out.test$n
grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
grep "TXT.\"0\"" dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
# Allow RRset to become stale.
sleep 2
n=$((n+1))
ret=0
echo_i "check stale data.example comes from cache (stale-answer-client-timeout 0) ($n)"
nextpart ns3/named.run > /dev/null
$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
wait_for_log 5 "data.example stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1
grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
grep "data\.example\..*3.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
n=$((n+1))
echo_i "enable responses from authoritative server ($n)"
ret=0
$DIG -p ${PORT} @10.53.0.2 txt enable > dig.out.test$n
grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
wait_for_rrset_refresh() {
nextpart ns3/named.run | grep 'data.example.*2.*TXT.*"A text record with a 2 second ttl"' > /dev/null && return 0
return 1
}
# This test ensures that after we get stale data due to
# stale-answer-client-timeout 0, enabling the authoritative server will allow
# the RRset to be updated.
n=$((n+1))
ret=0
echo_i "check stale data.example was refreshed (stale-answer-client-timeout 0) ($n)"
retry_quiet 10 wait_for_rrset_refresh || ret=1
$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
grep "data\.example\..*[12].*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
n=$((n+1))
echo_i "disable responses from authoritative server ($n)"
ret=0
$DIG -p ${PORT} @10.53.0.2 txt disable > dig.out.test$n
grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
grep "TXT.\"0\"" dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
echo_i "sending queries for tests $((n+1))-$((n+2))..."
$DIG -p ${PORT} +tries=1 +timeout=3 @10.53.0.3 nodata.example TXT > dig.out.test$((n+1)) &
$DIG -p ${PORT} +tries=1 +timeout=30 @10.53.0.3 nodata.example TXT > dig.out.test$((n+2))
wait
# Since nodata.example is cached as NXRRSET and marked as stale at this point,
# BIND must not prompty return this RRset due to
# stale-answer-client-timeout == 0, instead, it must attempt to refresh the
# RRset. Since the authoritative server is disabled and we are using
# resolver-query-timeout value of 10 seconds, we expect this query with a
# timeout of 3 seconds to time out.
n=$((n+1))
echo_i "check query for nodata.example times out (stale-answer-client-timeout 0) ($n)"
grep "connection timed out" dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
# For this query we expect BIND to return stale NXRRSET data for
# nodata.example after resolver-query-timeout expires.
n=$((n+1))
echo_i "check stale nodata.example comes from cache after resolver-query-timeout expires (stale-answer-client-timeout 0) ($n)"
grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
grep "example\..*3.*IN.*SOA" dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
####################################################################
# Test for stale-answer-client-timeout 0 and stale-refresh-time 4. #
####################################################################
echo_i "test stale-answer-client-timeout (0) and stale-refresh-time (4)"
n=$((n+1))
echo_i "updating ns3/named.conf ($n)"
ret=0
copy_setports ns3/named5.conf.in ns3/named.conf
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
n=$((n+1))
echo_i "running 'rndc reload' ($n)"
ret=0
rndc_reload ns3 10.53.0.3
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
n=$((n+1))
echo_i "flush cache, enable responses from authoritative server ($n)"
ret=0
$RNDCCMD 10.53.0.3 flushtree example > rndc.out.test$n.1 2>&1 || ret=1
$DIG -p ${PORT} @10.53.0.2 txt enable > dig.out.test$n
grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
n=$((n+1))
echo_i "prime cache data.example (stale-answer-client-timeout 0, stale-refresh-time 4) ($n)"
ret=0
$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
grep "data\.example\..*2.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
# Allow RRset to become stale.
sleep 2
n=$((n+1))
echo_i "disable responses from authoritative server ($n)"
ret=0
$DIG -p ${PORT} @10.53.0.2 txt disable > dig.out.test$n
grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
grep "TXT.\"0\"" dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
n=$((n+1))
ret=0
echo_i "check stale data.example comes from cache (stale-answer-client-timeout 0 stale-refresh-time 4) ($n)"
nextpart ns3/named.run > /dev/null
$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
wait_for_log 5 "data.example stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1
grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
grep "data\.example\..*3.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
n=$((n+1))
echo_i "enable responses from authoritative server ($n)"
ret=0
$DIG -p ${PORT} @10.53.0.2 txt enable > dig.out.test$n
grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
# This test ensures that after we get stale data due to
# stale-answer-client-timeout 0, enabling the authoritative server will allow
# the RRset to be updated.
n=$((n+1))
ret=0
echo_i "check stale data.example was refreshed (stale-answer-client-timeout 0 stale-refresh-time 4) ($n)"
retry_quiet 10 wait_for_rrset_refresh || ret=1
$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
grep "data\.example\..*[12].*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
# Allow RRset to become stale.
sleep 2
n=$((n+1))
echo_i "disable responses from authoritative server ($n)"
ret=0
$DIG -p ${PORT} @10.53.0.2 txt disable > dig.out.test$n
grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
grep "TXT.\"0\"" dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
n=$((n+1))
ret=0
echo_i "check stale data.example comes from cache (stale-answer-client-timeout 0 stale-refresh-time 4) ($n)"
nextpart ns3/named.run > /dev/null
$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
wait_for_log 5 "data.example stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1
grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
grep "data\.example\..*3.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
# Allow stale-refresh-time to be activated.
n=$((n+1))
ret=0
echo_i "wait until resolver query times out, activating stale-refresh-time"
wait_for_log 15 "data.example resolver failure, stale answer used" ns3/named.run || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
n=$((n+1))
ret=0
echo_i "check stale data.example comes from cache within stale-refresh-time (stale-answer-client-timeout 0 stale-refresh-time 4) ($n)"
nextpart ns3/named.run > /dev/null
$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
wait_for_log 5 "data.example query within stale refresh time" ns3/named.run || ret=1
grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
grep "data\.example\..*3.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
n=$((n+1))
echo_i "enable responses from authoritative server ($n)"
ret=0
$DIG -p ${PORT} @10.53.0.2 txt enable > dig.out.test$n
grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
# We give BIND some time to ensure that after we enable authoritative server,
# this RRset is still not refreshed because it was hit during
# stale-refresh-time window.
sleep 1
n=$((n+1))
ret=0
echo_i "check stale data.example was not refreshed (stale-answer-client-timeout 0 stale-refresh-time 4) ($n)"
nextpart ns3/named.run > /dev/null
$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
wait_for_log 5 "data.example query within stale refresh time" ns3/named.run || ret=1
grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
grep "data\.example\..*3.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
# After the refresh-time-window, the RRset will be refreshed.
sleep 4
n=$((n+1))
ret=0
echo_i "check stale data.example comes from cache (stale-answer-client-timeout 0 stale-refresh-time 4) ($n)"
$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
wait_for_log 5 "data.example stale answer used, an attempt to refresh the RRset" ns3/named.run || ret=1
grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
grep "data\.example\..*3.*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
n=$((n+1))
ret=0
echo_i "check stale data.example was refreshed (stale-answer-client-timeout 0 stale-refresh-time 4) ($n)"
$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
grep "data\.example\..*[12].*IN.*TXT.*A text record with a 2 second ttl" dig.out.test$n > /dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status+ret))
echo_i "exit status: $status"
[ $status -eq 0 ] || exit 1

View file

@ -1832,6 +1832,20 @@ Boolean Options
Information about stale answers is logged under the ``serve-stale``
log category.
``stale-answer-client-timeout``
This option defines the amount of time ``named`` waits before attempting to
answer the query with a stale RRset from cache. If a stale answer is found,
``named`` continues the ongoing fetches, attempting to refresh the RRset in
cache until the ``resolver-query-timeout`` interval is reached.
The default value is ``1800`` (in milliseconds) and the maximum value is
bounded to ``resolver-query-timeout`` minus one second. A value of ``0``
immediately returns a cached RRset if available, and still attempts a refresh
of the data in cache.
The option can be disabled by setting the value to ``off`` or ``disabled``.
It also has no effect if ``stale-answer-enable`` is disabled.
``stale-cache-enable``
If ``yes``, enable the retaining of "stale" cached answers. Default ``no``.

View file

@ -466,6 +466,7 @@ options {
sig\-validity\-interval integer [ integer ];
sortlist { address_match_element; ... };
stacksize ( default | unlimited | sizeval );
stale\-answer\-client\-timeout ( disabled | off | integer );
stale\-answer\-enable boolean;
stale\-answer\-ttl duration;
stale\-cache\-enable boolean;
@ -901,6 +902,7 @@ view string [ class ] {
sig\-signing\-type integer;
sig\-validity\-interval integer [ integer ];
sortlist { address_match_element; ... };
stale\-answer\-client\-timeout ( disabled | off | integer );
stale\-answer\-enable boolean;
stale\-answer\-ttl duration;
stale\-cache\-enable boolean;

View file

@ -333,6 +333,7 @@ options {
sig-validity-interval <integer> [ <integer> ];
sortlist { <address_match_element>; ... };
stacksize ( default | unlimited | <sizeval> );
stale-answer-client-timeout ( disabled | off | <integer> );
stale-answer-enable <boolean>;
stale-answer-ttl <duration>;
stale-cache-enable <boolean>;
@ -696,6 +697,7 @@ view <string> [ <class> ] {
sig-signing-type <integer>;
sig-validity-interval <integer> [ <integer> ];
sortlist { <address_match_element>; ... };
stale-answer-client-timeout ( disabled | off | <integer> );
stale-answer-enable <boolean>;
stale-answer-ttl <duration>;
stale-cache-enable <boolean>;

View file

@ -331,6 +331,7 @@ options {
sig-validity-interval <integer> [ <integer> ];
sortlist { <address_match_element>; ... };
stacksize ( default | unlimited | <sizeval> );
stale-answer-client-timeout ( disabled | off | <integer> );
stale-answer-enable <boolean>;
stale-answer-ttl <duration>;
stale-cache-enable <boolean>;
@ -692,6 +693,7 @@ view <string> [ <class> ] {
sig-signing-type <integer>;
sig-validity-interval <integer> [ <integer> ];
sortlist { <address_match_element>; ... };
stale-answer-client-timeout ( disabled | off | <integer> );
stale-answer-enable <boolean>;
stale-answer-ttl <duration>;
stale-cache-enable <boolean>;

View file

@ -259,6 +259,7 @@
sig-validity-interval <integer> [ <integer> ];
sortlist { <address_match_element>; ... };
stacksize ( default | unlimited | <sizeval> );
stale-answer-client-timeout ( disabled | off | <integer> );
stale-answer-enable <boolean>;
stale-answer-ttl <duration>;
stale-cache-enable <boolean>;

View file

@ -26,6 +26,23 @@ New Features
- None.
- A new option, ```stale-answer-client-timeout``, has been added to
improve ``named``'s behavior with respect to serving stale data. The option
defines the amount of time ``named`` waits before attempting
to answer the query with a stale RRset from cache. If a stale answer
is found, ``named`` continues the ongoing fetches, attempting to
refresh the RRset in cache until the ``resolver-query-timeout`` interval is
reached.
The default value is ``1800`` (in milliseconds) and the maximum value is
bounded to ``resolver-query-timeout`` minus one second. A value of
``0`` immediately returns a cached RRset if available, and still
attempts a refresh of the data in cache.
The option can be disabled by setting the value to ``off`` or
``disabled``. It also has no effect if ``stale-answer-enable`` is
disabled.
Removed Features
~~~~~~~~~~~~~~~~

View file

@ -239,8 +239,35 @@ struct dns_dbonupdatelistener {
#define DNS_DBFIND_FORCENSEC3 0x0080
#define DNS_DBFIND_ADDITIONALOK 0x0100
#define DNS_DBFIND_NOZONECUT 0x0200
#define DNS_DBFIND_STALEOK 0x0400
/*
* DNS_DBFIND_STALEOK: This flag is set when BIND fails to refresh a
* RRset due to timeout (resolver-query-timeout), its intent is to
* try to look for stale data in cache as a fallback, but only if
* stale answers are enabled in configuration.
*
* This flag is also used to activate stale-refresh-time window, since it
* is the only way the database knows that a resolution has failed.
*/
#define DNS_DBFIND_STALEOK 0x0400
/*
* DNS_DBFIND_STALEENABLED: This flag is used as a hint to the database
* that it may use stale data. It is always set during query lookup if
* stale answers are enabled, but only effectively used during
* stale-refresh-time window. Also during this window, the resolver will
* not try to resolve the query, in other words no attempt to refresh the
* data in cache is made when the stale-refresh-time window is active.
*/
#define DNS_DBFIND_STALEENABLED 0x0800
/*
* DNS_DBFIND_STALEONLY: This new introduced flag is used when we want
* stale data from the database, but not due to a failure in resolution,
* it also doesn't require stale-refresh-time window timer to be active.
* As long as there is a stale RRset available, it should be returned.
*/
#define DNS_DBFIND_STALEONLY 0x1000
/*@}*/
/*@{*/

View file

@ -78,6 +78,7 @@
#define DNS_EVENT_CATZDELZONE (ISC_EVENTCLASS_DNS + 56)
#define DNS_EVENT_RPZUPDATED (ISC_EVENTCLASS_DNS + 57)
#define DNS_EVENT_STARTUPDATE (ISC_EVENTCLASS_DNS + 58)
#define DNS_EVENT_TRYSTALE (ISC_EVENTCLASS_DNS + 59)
#define DNS_EVENT_FIRSTEVENT (ISC_EVENTCLASS_DNS + 0)
#define DNS_EVENT_LASTEVENT (ISC_EVENTCLASS_DNS + 65535)

View file

@ -189,6 +189,7 @@ struct dns_rdataset {
#define DNS_RDATASETATTR_CYCLIC 0x00800000 /*%< Cyclic ordering. */
#define DNS_RDATASETATTR_STALE 0x01000000
#define DNS_RDATASETATTR_ANCIENT 0x02000000
#define DNS_RDATASETATTR_STALE_WINDOW 0x04000000
/*%
* _OMITDNSSEC:

View file

@ -129,9 +129,10 @@ typedef enum { dns_quotatype_zone = 0, dns_quotatype_server } dns_quotatype_t;
* if possible. */
/* Reserved in use by adb.c 0x00400000 */
#define DNS_FETCHOPT_EDNSVERSIONSET 0x00800000
#define DNS_FETCHOPT_EDNSVERSIONMASK 0xff000000
#define DNS_FETCHOPT_EDNSVERSIONSHIFT 24
#define DNS_FETCHOPT_EDNSVERSIONSET 0x00800000
#define DNS_FETCHOPT_EDNSVERSIONMASK 0xff000000
#define DNS_FETCHOPT_EDNSVERSIONSHIFT 24
#define DNS_FETCHOPT_TRYSTALE_ONTIMEOUT 0x01000000
/*
* Upper bounds of class of query RTT (ms). Corresponds to

View file

@ -173,6 +173,7 @@ struct dns_view {
dns_stale_answer_t staleanswersok; /* rndc setting */
bool staleanswersenable; /* named.conf setting
* */
uint32_t staleanswerclienttimeout;
uint16_t nocookieudp;
uint16_t padding;
dns_acl_t * pad_acl;
@ -1343,6 +1344,15 @@ dns_view_setviewrevert(dns_view_t *view);
*\li 'view' to be valid.
*/
bool
dns_view_staleanswerenabled(dns_view_t *view);
/*%<
* Check if stale answers are enabled for this view.
*
* Requires:
*\li 'view' to be valid.
*/
ISC_LANG_ENDDECLS
#endif /* DNS_VIEW_H */

View file

@ -273,7 +273,8 @@ typedef ISC_LIST(dns_rbtnode_t) rbtnodelist_t;
#define RDATASET_ATTR_ZEROTTL 0x0800
#define RDATASET_ATTR_CASEFULLYLOWER 0x1000
/*%< Ancient - awaiting cleanup. */
#define RDATASET_ATTR_ANCIENT 0x2000
#define RDATASET_ATTR_ANCIENT 0x2000
#define RDATASET_ATTR_STALE_WINDOW 0x4000
/*
* XXX
@ -303,6 +304,9 @@ typedef ISC_LIST(dns_rbtnode_t) rbtnodelist_t;
#define STALE(header) \
((atomic_load_acquire(&(header)->attributes) & RDATASET_ATTR_STALE) != \
0)
#define STALE_WINDOW(header) \
((atomic_load_acquire(&(header)->attributes) & \
RDATASET_ATTR_STALE_WINDOW) != 0)
#define RESIGN(header) \
((atomic_load_acquire(&(header)->attributes) & \
RDATASET_ATTR_RESIGN) != 0)
@ -3148,6 +3152,9 @@ bind_rdataset(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node, rdatasetheader_t *header,
rdataset->attributes |= DNS_RDATASETATTR_PREFETCH;
}
if (STALE(header)) {
if (STALE_WINDOW(header)) {
rdataset->attributes |= DNS_RDATASETATTR_STALE_WINDOW;
}
rdataset->attributes |= DNS_RDATASETATTR_STALE;
rdataset->stale_ttl =
(rbtdb->serve_stale_ttl + header->rdh_ttl) - now;
@ -4551,6 +4558,8 @@ check_stale_header(dns_rbtnode_t *node, rdatasetheader_t *header,
* skip this record. We skip the records with ZEROTTL
* (these records should not be cached anyway).
*/
RDATASET_ATTR_CLR(header, RDATASET_ATTR_STALE_WINDOW);
if (!ZEROTTL(header) && KEEPSTALE(search->rbtdb) &&
stale > search->now) {
mark_header_stale(search->rbtdb, header);
@ -4574,6 +4583,15 @@ check_stale_header(dns_rbtnode_t *node, rdatasetheader_t *header,
* then don't skip this stale entry but use it
* instead.
*/
RDATASET_ATTR_SET(header,
RDATASET_ATTR_STALE_WINDOW);
return (false);
} else if ((search->options & DNS_DBFIND_STALEONLY) !=
0) {
/*
* We want stale RRset only, so we don't skip
* it.
*/
return (false);
}
return ((search->options & DNS_DBFIND_STALEOK) == 0);

View file

@ -307,7 +307,9 @@ struct fetchctx {
dns_rdataset_t nameservers;
atomic_uint_fast32_t attributes;
isc_timer_t *timer;
isc_timer_t *timer_try_stale;
isc_time_t expires;
isc_time_t expires_try_stale;
isc_interval_t interval;
dns_message_t *qmessage;
ISC_LIST(resquery_t) queries;
@ -1135,6 +1137,16 @@ fctx_starttimer(fetchctx_t *fctx) {
NULL, true));
}
static inline isc_result_t
fctx_starttimer_trystale(fetchctx_t *fctx) {
/*
* Start the stale-answer-client-timeout timer for fctx.
*/
return (isc_timer_reset(fctx->timer_try_stale, isc_timertype_once,
&fctx->expires_try_stale, NULL, true));
}
static inline void
fctx_stoptimer(fetchctx_t *fctx) {
isc_result_t result;
@ -1153,6 +1165,22 @@ fctx_stoptimer(fetchctx_t *fctx) {
}
}
static inline void
fctx_stoptimer_trystale(fetchctx_t *fctx) {
isc_result_t result;
if (fctx->timer_try_stale != NULL) {
result = isc_timer_reset(fctx->timer_try_stale,
isc_timertype_inactive, NULL, NULL,
true);
if (result != ISC_R_SUCCESS) {
UNEXPECTED_ERROR(__FILE__, __LINE__,
"isc_timer_reset(): %s",
isc_result_totext(result));
}
}
}
static inline isc_result_t
fctx_startidletimer(fetchctx_t *fctx, isc_interval_t *interval) {
/*
@ -1537,6 +1565,7 @@ fctx_stopqueries(fetchctx_t *fctx, bool no_response, bool age_untried) {
FCTXTRACE("stopqueries");
fctx_cancelqueries(fctx, no_response, age_untried);
fctx_stoptimer(fctx);
fctx_stoptimer_trystale(fctx);
}
static inline void
@ -1697,6 +1726,16 @@ fctx_sendevents(fetchctx_t *fctx, isc_result_t result, int line) {
event = next_event) {
next_event = ISC_LIST_NEXT(event, ev_link);
ISC_LIST_UNLINK(fctx->events, event, ev_link);
if (event->ev_type == DNS_EVENT_TRYSTALE) {
/*
* Not applicable to TRY STALE events, this function is
* called when the fetch has either completed or timed
* out due to resolver-query-timeout being reached.
*/
isc_task_detach((isc_task_t **)&event->ev_sender);
isc_event_free((isc_event_t **)&event);
continue;
}
task = event->ev_sender;
event->ev_sender = fctx;
event->vresult = fctx->vresult;
@ -4182,7 +4221,13 @@ fctx_try(fetchctx_t *fctx, bool retrying, bool badcache) {
*/
if (fctx->minimized && !fctx->forwarding) {
unsigned int options = fctx->options;
options &= ~DNS_FETCHOPT_QMINIMIZE;
/*
* Also clear DNS_FETCHOPT_TRYSTALE_ONTIMEOUT here, otherwise
* every query minimization step will activate the try-stale
* timer again.
*/
options &= ~(DNS_FETCHOPT_QMINIMIZE |
DNS_FETCHOPT_TRYSTALE_ONTIMEOUT);
/*
* Is another QNAME minimization fetch still running?
@ -4222,6 +4267,7 @@ fctx_try(fetchctx_t *fctx, bool retrying, bool badcache) {
fctx_increference(fctx);
task = res->buckets[bucketnum].task;
fctx_stoptimer(fctx);
fctx_stoptimer_trystale(fctx);
result = dns_resolver_createfetch(
fctx->res, &fctx->qminname, fctx->qmintype,
&fctx->domain, &fctx->nameservers, NULL, NULL, 0,
@ -4247,6 +4293,7 @@ fctx_try(fetchctx_t *fctx, bool retrying, bool badcache) {
}
fctx_increference(fctx);
result = fctx_query(fctx, addrinfo, fctx->options);
if (result != ISC_R_SUCCESS) {
fctx_done(fctx, result, __LINE__);
@ -4504,6 +4551,9 @@ fctx_destroy(fetchctx_t *fctx) {
isc_counter_detach(&fctx->qc);
fcount_decr(fctx);
isc_timer_detach(&fctx->timer);
if (fctx->timer_try_stale != NULL) {
isc_timer_detach(&fctx->timer_try_stale);
}
dns_message_detach(&fctx->qmessage);
if (dns_name_countlabels(&fctx->domain) > 0) {
dns_name_free(&fctx->domain, fctx->mctx);
@ -4579,6 +4629,57 @@ fctx_timeout(isc_task_t *task, isc_event_t *event) {
isc_event_free(&event);
}
/*
* Fetch event handlers called if stale answers are enabled
* (stale-answer-enabled) and the fetch took more than
* stale-answer-client-timeout to complete.
*/
static void
fctx_timeout_try_stale(isc_task_t *task, isc_event_t *event) {
fetchctx_t *fctx = event->ev_arg;
dns_fetchevent_t *dns_event, *next_event;
isc_task_t *sender_task;
unsigned int count = 0;
REQUIRE(VALID_FCTX(fctx));
UNUSED(task);
FCTXTRACE("timeout_try_stale");
if (event->ev_type != ISC_TIMEREVENT_LIFE) {
return;
}
LOCK(&fctx->res->buckets[fctx->bucketnum].lock);
/*
* Trigger events of type DNS_EVENT_TRYSTALE.
*/
for (dns_event = ISC_LIST_HEAD(fctx->events); dns_event != NULL;
dns_event = next_event)
{
next_event = ISC_LIST_NEXT(dns_event, ev_link);
if (dns_event->ev_type != DNS_EVENT_TRYSTALE) {
continue;
}
ISC_LIST_UNLINK(fctx->events, dns_event, ev_link);
sender_task = dns_event->ev_sender;
dns_event->ev_sender = fctx;
dns_event->vresult = ISC_R_TIMEDOUT;
dns_event->result = ISC_R_TIMEDOUT;
isc_task_sendanddetach(&sender_task, ISC_EVENT_PTR(&dns_event));
count++;
}
UNLOCK(&fctx->res->buckets[fctx->bucketnum].lock);
isc_event_free(&event);
}
static void
fctx_shutdown(fetchctx_t *fctx) {
isc_event_t *cevent;
@ -4760,6 +4861,9 @@ fctx_start(isc_task_t *task, isc_event_t *event) {
* All is well. Start working on the fetch.
*/
result = fctx_starttimer(fctx);
if (result == ISC_R_SUCCESS && fctx->timer_try_stale != NULL) {
result = fctx_starttimer_trystale(fctx);
}
if (result != ISC_R_SUCCESS) {
fctx_done(fctx, result, __LINE__);
} else {
@ -4826,6 +4930,34 @@ fctx_join(fetchctx_t *fctx, isc_task_t *task, const isc_sockaddr_t *client,
return (ISC_R_SUCCESS);
}
static inline void
fctx_add_event(fetchctx_t *fctx, isc_task_t *task, const isc_sockaddr_t *client,
dns_messageid_t id, isc_taskaction_t action, void *arg,
dns_fetch_t *fetch, isc_eventtype_t event_type) {
isc_task_t *tclone;
dns_fetchevent_t *event;
/*
* We store the task we're going to send this event to in the
* sender field. We'll make the fetch the sender when we actually
* send the event.
*/
tclone = NULL;
isc_task_attach(task, &tclone);
event = (dns_fetchevent_t *)isc_event_allocate(fctx->res->mctx, tclone,
event_type, action, arg,
sizeof(*event));
event->result = DNS_R_SERVFAIL;
event->qtype = fctx->type;
event->db = NULL;
event->node = NULL;
event->rdataset = NULL;
event->sigrdataset = NULL;
event->fetch = fetch;
event->client = client;
event->id = id;
ISC_LIST_APPEND(fctx->events, event, ev_link);
}
static inline void
log_ns_ttl(fetchctx_t *fctx, const char *where) {
char namebuf[DNS_NAME_FORMATSIZE];
@ -4850,9 +4982,11 @@ fctx_create(dns_resolver_t *res, const dns_name_t *name, dns_rdatatype_t type,
isc_result_t iresult;
isc_interval_t interval;
unsigned int findoptions = 0;
char buf[DNS_NAME_FORMATSIZE + DNS_RDATATYPE_FORMATSIZE];
char buf[DNS_NAME_FORMATSIZE + DNS_RDATATYPE_FORMATSIZE + 1];
char typebuf[DNS_RDATATYPE_FORMATSIZE];
isc_mem_t *mctx;
size_t p;
bool try_stale;
/*
* Caller must be holding the lock for bucket number 'bucketnum'.
@ -4879,8 +5013,8 @@ fctx_create(dns_resolver_t *res, const dns_name_t *name, dns_rdatatype_t type,
*/
dns_name_format(name, buf, sizeof(buf));
dns_rdatatype_format(type, typebuf, sizeof(typebuf));
strlcat(buf, "/", sizeof(buf));
strlcat(buf, typebuf, sizeof(buf));
p = strlcat(buf, "/", sizeof(buf));
strlcat(buf + p, typebuf, sizeof(buf));
fctx->info = isc_mem_strdup(mctx, buf);
FCTXTRACE("create");
@ -5077,6 +5211,29 @@ fctx_create(dns_resolver_t *res, const dns_name_t *name, dns_rdatatype_t type,
goto cleanup_qmessage;
}
try_stale = ((options & DNS_FETCHOPT_TRYSTALE_ONTIMEOUT) != 0);
if (try_stale) {
INSIST(res->view->staleanswerclienttimeout <=
(res->query_timeout - 1000));
/*
* Compute an expiration time after which stale data will
* attempted to be served, if stale answers are enabled and
* target RRset is available in cache.
*/
isc_interval_set(
&interval, res->view->staleanswerclienttimeout / 1000,
res->view->staleanswerclienttimeout % 1000 * 1000000);
iresult = isc_time_nowplusinterval(&fctx->expires_try_stale,
&interval);
if (iresult != ISC_R_SUCCESS) {
UNEXPECTED_ERROR(__FILE__, __LINE__,
"isc_time_nowplusinterval: %s",
isc_result_totext(iresult));
result = ISC_R_UNEXPECTED;
goto cleanup_qmessage;
}
}
/*
* Default retry interval initialization. We set the interval now
* mostly so it won't be uninitialized. It will be set to the
@ -5085,10 +5242,11 @@ fctx_create(dns_resolver_t *res, const dns_name_t *name, dns_rdatatype_t type,
isc_interval_set(&fctx->interval, 2, 0);
/*
* Create an inactive timer. It will be made active when the fetch
* is actually started.
* Create an inactive timer for resolver-query-timeout. It
* will be made active when the fetch is actually started.
*/
fctx->timer = NULL;
iresult = isc_timer_create(res->timermgr, isc_timertype_inactive, NULL,
NULL, res->buckets[bucketnum].task,
fctx_timeout, fctx, &fctx->timer);
@ -5099,6 +5257,26 @@ fctx_create(dns_resolver_t *res, const dns_name_t *name, dns_rdatatype_t type,
goto cleanup_qmessage;
}
/*
* If stale answers are enabled, then create an inactive timer
* for stale-answer-client-timeout. It will be made active when
* the fetch is actually started.
*/
fctx->timer_try_stale = NULL;
if (try_stale) {
iresult = isc_timer_create(
res->timermgr, isc_timertype_inactive, NULL, NULL,
res->buckets[bucketnum].task, fctx_timeout_try_stale,
fctx, &fctx->timer_try_stale);
if (iresult != ISC_R_SUCCESS) {
UNEXPECTED_ERROR(__FILE__, __LINE__,
"isc_timer_create: %s",
isc_result_totext(iresult));
result = ISC_R_UNEXPECTED;
goto cleanup_qmessage;
}
}
/*
* Attach to the view's cache and adb.
*/
@ -5143,6 +5321,7 @@ cleanup_mctx:
dns_adb_detach(&fctx->adb);
dns_db_detach(&fctx->cache);
isc_timer_detach(&fctx->timer);
isc_timer_detach(&fctx->timer_try_stale);
cleanup_qmessage:
dns_message_detach(&fctx->qmessage);
@ -5356,6 +5535,23 @@ clone_results(fetchctx_t *fctx) {
for (event = ISC_LIST_NEXT(hevent, ev_link); event != NULL;
event = ISC_LIST_NEXT(event, ev_link))
{
if (event->ev_type == DNS_EVENT_TRYSTALE) {
/*
* We don't need to clone resulting data to this
* type of event, as its associated callback is only
* called when stale-answer-client-timeout triggers,
* and the logic in there doesn't expect any result
* as input, as it will itself lookup for stale data
* in cache to use as result, if any is available.
*
* Also, if we reached this point, then the whole fetch
* context is done, it will cancel timers, process
* associated callbacks of type DNS_EVENT_FETCHDONE, and
* silently remove/free events of type
* DNS_EVENT_TRYSTALE.
*/
continue;
}
name = dns_fixedname_name(&event->foundname);
dns_name_copynf(hname, name);
event->result = hevent->result;
@ -6125,6 +6321,7 @@ cache_name(fetchctx_t *fctx, dns_name_t *name, dns_message_t *message,
(!need_validation)) {
have_answer = true;
event = ISC_LIST_HEAD(fctx->events);
if (event != NULL) {
adbp = &event->db;
aname = dns_fixedname_name(&event->foundname);
@ -10814,6 +11011,14 @@ dns_resolver_createfetch(dns_resolver_t *res, const dns_name_t *name,
result = fctx_join(fctx, task, client, id, action, arg, rdataset,
sigrdataset, fetch);
if (result == ISC_R_SUCCESS &&
((options & DNS_FETCHOPT_TRYSTALE_ONTIMEOUT) != 0))
{
fctx_add_event(fctx, task, client, id, action, arg, fetch,
DNS_EVENT_TRYSTALE);
}
if (new_fctx) {
if (result == ISC_R_SUCCESS) {
/*

View file

@ -2518,3 +2518,25 @@ dns_view_setviewrevert(dns_view_t *view) {
dns_zt_setviewrevert(zonetable);
}
}
bool
dns_view_staleanswerenabled(dns_view_t *view) {
uint32_t stale_ttl = 0;
bool result = false;
REQUIRE(DNS_VIEW_VALID(view));
if (dns_db_getservestalettl(view->cachedb, &stale_ttl) != ISC_R_SUCCESS)
{
return (false);
}
if (stale_ttl > 0) {
if (view->staleanswersok == dns_stale_answer_yes) {
result = true;
} else if (view->staleanswersok == dns_stale_answer_conf) {
result = view->staleanswersenable;
}
}
return (result);
}

View file

@ -1147,6 +1147,7 @@ dns_view_setrootdelonly
dns_view_setviewcommit
dns_view_setviewrevert
dns_view_simplefind
dns_view_staleanswerenabled
dns_view_thaw
dns_view_untrust
dns_view_weakattach

View file

@ -1899,6 +1899,28 @@ static cfg_type_t cfg_type_dns64 = { "dns64", cfg_parse_netprefix_map,
cfg_print_map, cfg_doc_map,
&cfg_rep_map, dns64_clausesets };
static const char *staleanswerclienttimeout_enums[] = { "disabled", "off",
NULL };
static isc_result_t
parse_staleanswerclienttimeout(cfg_parser_t *pctx, const cfg_type_t *type,
cfg_obj_t **ret) {
return (cfg_parse_enum_or_other(pctx, type, &cfg_type_uint32, ret));
}
static void
doc_staleanswerclienttimeout(cfg_printer_t *pctx, const cfg_type_t *type) {
cfg_doc_enum_or_other(pctx, type, &cfg_type_uint32);
}
static cfg_type_t cfg_type_staleanswerclienttimeout = {
"staleanswerclienttimeout",
parse_staleanswerclienttimeout,
cfg_print_ustring,
doc_staleanswerclienttimeout,
&cfg_rep_string,
staleanswerclienttimeout_enums
};
/*%
* Clauses that can be found within the 'view' statement,
* with defaults in the 'options' statement.
@ -2027,6 +2049,8 @@ static cfg_clausedef_t view_clauses[] = {
{ "servfail-ttl", &cfg_type_duration, 0 },
{ "sortlist", &cfg_type_bracketed_aml, 0 },
{ "stale-answer-enable", &cfg_type_boolean, 0 },
{ "stale-answer-client-timeout", &cfg_type_staleanswerclienttimeout,
0 },
{ "stale-answer-ttl", &cfg_type_duration, 0 },
{ "stale-cache-enable", &cfg_type_boolean, 0 },
{ "stale-refresh-time", &cfg_type_duration, 0 },

View file

@ -421,6 +421,10 @@ ns_client_send(ns_client_t *client) {
REQUIRE(NS_CLIENT_VALID(client));
if ((client->query.attributes & NS_QUERYATTR_ANSWERED) != 0) {
return;
}
/*
* XXXWPK TODO
* Delay the response according to the -T delay option
@ -670,6 +674,8 @@ renderend:
ns_statscounter_truncatedresp);
}
client->query.attributes |= NS_QUERYATTR_ANSWERED;
return;
cleanup:
@ -2326,6 +2332,7 @@ ns__client_setup(ns_client_t *client, ns_clientmgr_t *mgr, bool new) {
.query = query };
}
client->query.attributes &= ~NS_QUERYATTR_ANSWERED;
client->state = NS_CLIENTSTATE_INACTIVE;
client->udpsize = 512;
client->ednsversion = -1;

View file

@ -117,6 +117,8 @@ struct ns_query {
#define NS_QUERYATTR_DNS64EXCLUDE 0x08000
#define NS_QUERYATTR_RRL_CHECKED 0x10000
#define NS_QUERYATTR_REDIRECT 0x20000
#define NS_QUERYATTR_ANSWERED 0x40000
#define NS_QUERYATTR_STALEOK 0x80000
typedef struct query_ctx query_ctx_t;

View file

@ -86,11 +86,11 @@
*/
#define MAX_RESTARTS 16
#define QUERY_ERROR(qctx, r) \
do { \
qctx->result = r; \
qctx->want_restart = false; \
qctx->line = __LINE__; \
#define QUERY_ERROR(qctx, r) \
do { \
(qctx)->result = r; \
(qctx)->want_restart = false; \
(qctx)->line = __LINE__; \
} while (0)
/*% Partial answer? */
@ -133,12 +133,21 @@
#define REDIRECT(c) (((c)->query.attributes & NS_QUERYATTR_REDIRECT) != 0)
/*% Was the query already answered due to stale-answer-client-timeout? */
#define QUERY_ANSWERED(q) (((q)->attributes & NS_QUERYATTR_ANSWERED) != 0)
/*% Does the query only wants to check for stale RRset? */
#define QUERY_STALEONLY(q) (((q)->dboptions & DNS_DBFIND_STALEONLY) != 0)
/*% Does the rdataset 'r' have an attached 'No QNAME Proof'? */
#define NOQNAME(r) (((r)->attributes & DNS_RDATASETATTR_NOQNAME) != 0)
/*% Does the rdataset 'r' contain a stale answer? */
#define STALE(r) (((r)->attributes & DNS_RDATASETATTR_STALE) != 0)
/*% Does the rdataset 'r' is stale and within stale-refresh-time? */
#define STALE_WINDOW(r) (((r)->attributes & DNS_RDATASETATTR_STALE_WINDOW) != 0)
#ifdef WANT_QUERYTRACE
static inline void
client_trace(ns_client_t *client, int level, const char *message) {
@ -173,10 +182,11 @@ client_trace(ns_client_t *client, int level, const char *message) {
#define CCTRACE(l, m) ((void)m)
#endif /* WANT_QUERYTRACE */
#define DNS_GETDB_NOEXACT 0x01U
#define DNS_GETDB_NOLOG 0x02U
#define DNS_GETDB_PARTIAL 0x04U
#define DNS_GETDB_IGNOREACL 0x08U
#define DNS_GETDB_NOEXACT 0x01U
#define DNS_GETDB_NOLOG 0x02U
#define DNS_GETDB_PARTIAL 0x04U
#define DNS_GETDB_IGNOREACL 0x08U
#define DNS_GETDB_STALEFIRST 0X0CU
#define PENDINGOK(x) (((x)&DNS_DBFIND_PENDINGOK) != 0)
@ -558,7 +568,10 @@ query_send(ns_client_t *client) {
inc_stats(client, counter);
ns_client_send(client);
isc_nmhandle_detach(&client->reqhandle);
if (!QUERY_STALEONLY(&client->query)) {
isc_nmhandle_detach(&client->reqhandle);
}
}
static void
@ -585,7 +598,10 @@ query_error(ns_client_t *client, isc_result_t result, int line) {
log_queryerror(client, result, line, loglevel);
ns_client_error(client, result);
isc_nmhandle_detach(&client->reqhandle);
if (!QUERY_STALEONLY(&client->query)) {
isc_nmhandle_detach(&client->reqhandle);
}
}
static void
@ -598,7 +614,10 @@ query_next(ns_client_t *client, isc_result_t result) {
inc_stats(client, ns_statscounter_failure);
}
ns_client_drop(client, result);
isc_nmhandle_detach(&client->reqhandle);
if (!QUERY_STALEONLY(&client->query)) {
isc_nmhandle_detach(&client->reqhandle);
}
}
static inline void
@ -5106,6 +5125,26 @@ qctx_init(ns_client_t *client, dns_fetchevent_t **eventp, dns_rdatatype_t qtype,
CALL_HOOK_NORETURN(NS_QUERY_QCTX_INITIALIZED, qctx);
}
/*
* Make 'dst' and exact copy of 'src', with exception of the
* option field, which is reset to zero.
* This function also attaches dst's view and db to the src's
* view and cachedb.
*/
static void
qctx_copy(const query_ctx_t *qctx, query_ctx_t *dst) {
REQUIRE(qctx != NULL);
REQUIRE(dst != NULL);
memmove(dst, qctx, sizeof(*dst));
dst->view = NULL;
dst->db = NULL;
dst->options = 0;
dns_view_attach(qctx->view, &dst->view);
dns_db_attach(qctx->view->cachedb, &dst->db);
CCTRACE(ISC_LOG_DEBUG(3), "qctx_copy");
}
/*%
* Clean up and disassociate the rdataset and node pointers in qctx.
*/
@ -5158,7 +5197,7 @@ qctx_freedata(query_ctx_t *qctx) {
dns_db_detach(&qctx->zdb);
}
if (qctx->event != NULL) {
if (qctx->event != NULL && !QUERY_STALEONLY(&qctx->client->query)) {
free_devent(qctx->client, ISC_EVENT_PTR(&qctx->event),
&qctx->event);
}
@ -5562,12 +5601,138 @@ ns__query_start(query_ctx_t *qctx) {
}
}
return (query_lookup(qctx));
if (!qctx->is_zone && (qctx->view->staleanswerclienttimeout == 0) &&
dns_view_staleanswerenabled(qctx->view))
{
/*
* If stale answers are enabled and
* stale-answer-client-timeout is zero, then we can promptly
* answer with a stale RRset if one is available in cache.
*/
qctx->options |= DNS_GETDB_STALEFIRST;
}
result = query_lookup(qctx);
/*
* Clear "look-also-for-stale-data" flag.
* If a fetch is created to resolve this query, then,
* when it completes, this option is not expected to be set.
*/
qctx->options &= ~DNS_GETDB_STALEFIRST;
cleanup:
return (result);
}
/*
* Allocate buffers in 'qctx' used to store query results.
*
* 'buffer' must be a pointer to an object whose lifetime
* doesn't expire while 'qctx' is in use.
*/
static isc_result_t
qctx_prepare_buffers(query_ctx_t *qctx, isc_buffer_t *buffer) {
REQUIRE(qctx != NULL);
REQUIRE(qctx->client != NULL);
REQUIRE(buffer != NULL);
qctx->dbuf = ns_client_getnamebuf(qctx->client);
if (ISC_UNLIKELY(qctx->dbuf == NULL)) {
CCTRACE(ISC_LOG_ERROR,
"qctx_prepare_buffers: ns_client_getnamebuf "
"failed");
return (ISC_R_NOMEMORY);
}
qctx->fname = ns_client_newname(qctx->client, qctx->dbuf, buffer);
if (ISC_UNLIKELY(qctx->fname == NULL)) {
CCTRACE(ISC_LOG_ERROR,
"qctx_prepare_buffers: ns_client_newname failed");
return (ISC_R_NOMEMORY);
}
qctx->rdataset = ns_client_newrdataset(qctx->client);
if (ISC_UNLIKELY(qctx->rdataset == NULL)) {
CCTRACE(ISC_LOG_ERROR,
"qctx_prepare_buffers: ns_client_newrdataset failed");
goto error;
}
if ((WANTDNSSEC(qctx->client) || qctx->findcoveringnsec) &&
(!qctx->is_zone || dns_db_issecure(qctx->db)))
{
qctx->sigrdataset = ns_client_newrdataset(qctx->client);
if (qctx->sigrdataset == NULL) {
CCTRACE(ISC_LOG_ERROR,
"qctx_prepare_buffers: "
"ns_client_newrdataset failed (2)");
goto error;
}
}
return (ISC_R_SUCCESS);
error:
if (qctx->fname != NULL) {
ns_client_releasename(qctx->client, &qctx->fname);
}
if (qctx->rdataset != NULL) {
ns_client_putrdataset(qctx->client, &qctx->rdataset);
}
return (ISC_R_NOMEMORY);
}
/*
* Setup a new query context for resolving a query.
*
* This function is only called if both these conditions are met:
* 1. BIND is configured with stale-answer-client-timeout 0.
* 2. A stale RRset is found in cache during initial query
* database lookup.
*
* We continue with this function for refreshing/resolving an RRset
* after answering a client with stale data.
*/
static void
query_refresh_rrset(query_ctx_t *orig_qctx) {
isc_buffer_t buffer;
query_ctx_t qctx;
REQUIRE(orig_qctx != NULL);
REQUIRE(orig_qctx->client != NULL);
qctx_copy(orig_qctx, &qctx);
qctx.client->query.dboptions &= ~(DNS_DBFIND_STALEONLY |
DNS_DBFIND_STALEOK |
DNS_DBFIND_STALEENABLED);
/*
* We'll need some resources...
*/
if (qctx_prepare_buffers(&qctx, &buffer) != ISC_R_SUCCESS) {
dns_db_detach(&qctx.db);
qctx_destroy(&qctx);
return;
}
/*
* Pretend we didn't find anything in cache.
*/
(void)query_gotanswer(&qctx, ISC_R_NOTFOUND);
if (qctx.fname != NULL) {
ns_client_releasename(qctx.client, &qctx.fname);
}
if (qctx.rdataset != NULL) {
ns_client_putrdataset(qctx.client, &qctx.rdataset);
}
qctx_destroy(&qctx);
}
/*%
* Perform a local database lookup, in either an authoritative or
* cache database. If unable to answer, call ns_query_done(); otherwise
@ -5575,15 +5740,18 @@ cleanup:
*/
static isc_result_t
query_lookup(query_ctx_t *qctx) {
isc_buffer_t b;
isc_buffer_t buffer;
isc_result_t result = ISC_R_UNSET;
dns_clientinfomethods_t cm;
dns_clientinfo_t ci;
dns_name_t *rpzqname = NULL;
unsigned int dboptions;
dns_ttl_t stale_ttl = 0;
dns_ttl_t stale_refresh = 0;
bool dbfind_stale = false;
bool stale_only = false;
bool stale_found = false;
bool refresh_rrset = false;
bool stale_refresh_window = false;
CCTRACE(ISC_LOG_DEBUG(3), "query_lookup");
@ -5595,37 +5763,12 @@ query_lookup(query_ctx_t *qctx) {
/*
* We'll need some resources...
*/
qctx->dbuf = ns_client_getnamebuf(qctx->client);
if (ISC_UNLIKELY(qctx->dbuf == NULL)) {
CCTRACE(ISC_LOG_ERROR, "query_lookup: ns_client_getnamebuf "
"failed (2)");
QUERY_ERROR(qctx, ISC_R_NOMEMORY);
result = qctx_prepare_buffers(qctx, &buffer);
if (result != ISC_R_SUCCESS) {
QUERY_ERROR(qctx, result);
return (ns_query_done(qctx));
}
qctx->fname = ns_client_newname(qctx->client, qctx->dbuf, &b);
qctx->rdataset = ns_client_newrdataset(qctx->client);
if (ISC_UNLIKELY(qctx->fname == NULL || qctx->rdataset == NULL)) {
CCTRACE(ISC_LOG_ERROR, "query_lookup: ns_client_newname failed "
"(2)");
QUERY_ERROR(qctx, ISC_R_NOMEMORY);
return (ns_query_done(qctx));
}
if ((WANTDNSSEC(qctx->client) || qctx->findcoveringnsec) &&
(!qctx->is_zone || dns_db_issecure(qctx->db)))
{
qctx->sigrdataset = ns_client_newrdataset(qctx->client);
if (qctx->sigrdataset == NULL) {
CCTRACE(ISC_LOG_ERROR, "query_lookup: "
"ns_client_newrdataset failed "
"(2)");
QUERY_ERROR(qctx, ISC_R_NOMEMORY);
return (ns_query_done(qctx));
}
}
/*
* Now look for an answer in the database.
*/
@ -5635,6 +5778,16 @@ query_lookup(query_ctx_t *qctx) {
rpzqname = qctx->client->query.qname;
}
if ((qctx->options & DNS_GETDB_STALEFIRST) != 0) {
/*
* If DNS_GETDB_STALEFIRST is set, it means that a stale
* RRset may be returned as part of this lookup. An attempt
* to refresh the RRset will still take place if an
* active RRset is not available.
*/
qctx->client->query.dboptions |= DNS_DBFIND_STALEONLY;
}
dboptions = qctx->client->query.dboptions;
if (!qctx->is_zone && qctx->findcoveringnsec &&
(qctx->type != dns_rdatatype_null || !dns_name_istat(rpzqname)))
@ -5644,18 +5797,9 @@ query_lookup(query_ctx_t *qctx) {
(void)dns_db_getservestalerefresh(qctx->client->view->cachedb,
&stale_refresh);
(void)dns_db_getservestalettl(qctx->client->view->cachedb, &stale_ttl);
if (stale_refresh > 0) {
if (qctx->client->view->staleanswersok == dns_stale_answer_yes)
{
dboptions |= DNS_DBFIND_STALEENABLED;
} else if (qctx->client->view->staleanswersok ==
dns_stale_answer_conf) {
if (qctx->client->view->staleanswersenable &&
stale_ttl > 0) {
dboptions |= DNS_DBFIND_STALEENABLED;
}
}
if (stale_refresh > 0 &&
dns_view_staleanswerenabled(qctx->client->view)) {
dboptions |= DNS_DBFIND_STALEENABLED;
}
result = dns_db_findext(qctx->db, rpzqname, qctx->version, qctx->type,
@ -5681,69 +5825,214 @@ query_lookup(query_ctx_t *qctx) {
/*
* If DNS_DBFIND_STALEOK is set this means we are dealing with a
* lookup following a failed lookup and it is okay to serve a stale
* answer. This will start a time window in rbtdb, tracking the last
* time the RRset lookup failed.
*
* A stale answer may also be served if this is a normal lookup,
* the view has enabled serve-stale (DNS_DBFIND_STALE_ENABLED is set),
* and the request is within the stale-refresh-time window. If this
* is the case we have to make sure that the lookup found a stale
* answer, otherwise "fresh" answers are also treated as stale.
* answer. This will (re)start the 'stale-refresh-time' window in
* rbtdb, tracking the last time the RRset lookup failed.
*/
dbfind_stale = ((dboptions & DNS_DBFIND_STALEOK) != 0);
if (dbfind_stale != 0 ||
(((dboptions & DNS_DBFIND_STALEENABLED) != 0) &&
STALE(qctx->rdataset)))
/*
* If DNS_DBFIND_STALEENABLED is set, this may be a normal lookup, but
* we are allowed to immediately respond with a stale answer if the
* request is within the 'stale-refresh-time' window.
*/
stale_refresh_window = (STALE_WINDOW(qctx->rdataset) &&
(dboptions & DNS_DBFIND_STALEENABLED) != 0);
/*
* If DNS_DBFIND_STALEONLY is set, a stale positive answer is requested.
* This can happen if 'stale-answer-client-timeout' is enabled.
*
* If 'stale-answer-client-timeout' is set to 0, and a stale positive
* answer is found, send it to the client, and try to refresh the
* RRset. If a stale negative answer is found, continue with recursion
* (perhaps the query will be resolved eventually and the answer from
* the authoritative is returned to the client, or the query will
* timeout, in that case DNS_DBFIND_STALEOK may be set, and a stale
* negative answer is returned (or SERVFAIL).
*
* If 'stale-answer-client-timeout' is non-zero, and a stale positive
* answer is found, send it to the client. Don't try to refresh the
* RRset because a fetch is already in progress. If a stale negative
* answer is found, then abort the lookup and the client has to wait
* until recursion is finished.
*/
stale_only = ((dboptions & DNS_DBFIND_STALEONLY) != 0);
if (dbfind_stale ||
(STALE(qctx->rdataset) && (stale_only || stale_refresh_window)))
{
char namebuf[DNS_NAME_FORMATSIZE];
bool success;
inc_stats(qctx->client, ns_statscounter_trystale);
qctx->client->query.dboptions &= ~DNS_DBFIND_STALEOK;
if (dns_rdataset_isassociated(qctx->rdataset) &&
dns_rdataset_count(qctx->rdataset) > 0 &&
STALE(qctx->rdataset))
{
qctx->rdataset->ttl = qctx->view->staleanswerttl;
success = true;
stale_found = true;
} else {
success = false;
stale_found = false;
}
dns_name_format(qctx->client->query.qname, namebuf,
sizeof(namebuf));
if (dbfind_stale) {
isc_log_write(ns_lctx, NS_LOGCATEGORY_SERVE_STALE,
NS_LOGMODULE_QUERY, ISC_LOG_INFO,
"%s resolver failure, stale answer %s",
namebuf,
success ? "used" : "unavailable");
} else {
stale_found ? "used" : "unavailable");
if (!stale_found) {
/*
* Resolver failure, no stale data, nothing
* more we can do, return SERVFAIL.
*/
QUERY_ERROR(qctx, DNS_R_SERVFAIL);
return (ns_query_done(qctx));
}
stale_only = false;
} else if (stale_refresh_window) {
/*
* A recent lookup failed, so during this time window
* we are allowed to return stale data immediately.
*/
isc_log_write(ns_lctx, NS_LOGCATEGORY_SERVE_STALE,
NS_LOGMODULE_QUERY, ISC_LOG_INFO,
"%s query within stale refresh time, "
"stale answer %s",
namebuf,
success ? "used" : "unavailable");
}
stale_found ? "used" : "unavailable");
if (!success) {
QUERY_ERROR(qctx, DNS_R_SERVFAIL);
return (ns_query_done(qctx));
if (!stale_found) {
/*
* During the stale refresh window explicitly
* do not try to refresh the data, because a
* recent lookup failed.
*/
QUERY_ERROR(qctx, DNS_R_SERVFAIL);
return (ns_query_done(qctx));
}
stale_only = false;
} else if ((dboptions & DNS_DBFIND_STALEONLY) != 0) {
if ((qctx->options & DNS_GETDB_STALEFIRST) != 0) {
if (stale_found && result == ISC_R_SUCCESS) {
/*
* Immediately return the stale answer,
* start a resolver fetch to refresh
* the data in cache.
*/
isc_log_write(
ns_lctx,
NS_LOGCATEGORY_SERVE_STALE,
NS_LOGMODULE_QUERY,
ISC_LOG_INFO,
"%s stale answer used, an "
"attempt to refresh the RRset "
"will still be made",
namebuf);
refresh_rrset = true;
} else {
/*
* We have nothing useful in cache to
* return immediately.
*/
qctx_clean(qctx);
qctx_freedata(qctx);
dns_db_attach(
qctx->client->view->cachedb,
&qctx->db);
qctx->client->query.dboptions &=
~DNS_DBFIND_STALEONLY;
qctx->options &= ~DNS_GETDB_STALEFIRST;
if (qctx->client->query.fetch != NULL) {
dns_resolver_destroyfetch(
&qctx->client->query
.fetch);
}
return query_lookup(qctx);
}
} else {
/*
* The 'stale-answer-client-timeout' triggered,
* return the stale answer if available,
* otherwise wait until the resolver finishes.
*/
isc_log_write(
ns_lctx, NS_LOGCATEGORY_SERVE_STALE,
NS_LOGMODULE_QUERY, ISC_LOG_INFO,
"%s client timeout, stale answer %s",
namebuf,
stale_found ? "used" : "unavailable");
if (!stale_found || result != ISC_R_SUCCESS) {
return (result);
}
}
}
} else {
stale_only = false;
}
if (!stale_only) {
/*
* The DNS_DBFIND_STALEONLY option may be set, but if we
* didn't have stale data in cache, or we responded with
* a stale answer because of 'stale-refresh-time', this is
* actually not a 'stale-only' lookup. Clear the flag to
* allow the client to be detach the handle.
*/
qctx->client->query.dboptions &= ~DNS_DBFIND_STALEONLY;
}
result = query_gotanswer(qctx, result);
if (refresh_rrset) {
/*
* If we reached this point then it means that we have found a
* stale RRset entry in cache and BIND is configured to allow
* queries to be answered with stale data if no active RRset
* is available, i.e. "stale-anwer-client-timeout 0". But, we
* still need to refresh the RRset.
*/
query_refresh_rrset(qctx);
}
return (query_gotanswer(qctx, result));
cleanup:
return (result);
}
/*
* Event handler to resume processing a query after recursion.
* If the query has timed out or been canceled or the system
* is shutting down, clean up and exit; otherwise, call
* query_resume() to continue the ongoing work.
* Create a new query context with the sole intent
* of looking up for a stale RRset in cache.
* If an entry is found, we mark the original query as
* answered, in order to avoid answering the query twice,
* when the original fetch finishes.
*/
static inline void
query_lookup_staleonly(ns_client_t *client) {
query_ctx_t qctx;
qctx_init(client, NULL, client->query.qtype, &qctx);
dns_db_attach(client->view->cachedb, &qctx.db);
client->query.dboptions |= DNS_DBFIND_STALEONLY;
(void)query_lookup(&qctx);
if (qctx.node != NULL) {
dns_db_detachnode(qctx.db, &qctx.node);
}
qctx_freedata(&qctx);
client->query.dboptions &= ~DNS_DBFIND_STALEONLY;
qctx_destroy(&qctx);
}
/*
* Event handler to resume processing a query after recursion, or when a
* client timeout is triggered. If the query has timed out or been cancelled
* or the system is shutting down, clean up and exit. If a client timeout is
* triggered, see if we can respond with a stale answer from cache. Otherwise,
* call query_resume() to continue the ongoing work.
*/
static void
fetch_callback(isc_task_t *task, isc_event_t *event) {
@ -5758,7 +6047,8 @@ fetch_callback(isc_task_t *task, isc_event_t *event) {
UNUSED(task);
REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE);
REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE ||
event->ev_type == DNS_EVENT_TRYSTALE);
client = devent->ev_arg;
REQUIRE(NS_CLIENT_VALID(client));
REQUIRE(task == client->task);
@ -5766,6 +6056,14 @@ fetch_callback(isc_task_t *task, isc_event_t *event) {
CTRACE(ISC_LOG_DEBUG(3), "fetch_callback");
if (event->ev_type == DNS_EVENT_TRYSTALE) {
query_lookup_staleonly(client);
isc_event_free(ISC_EVENT_PTR(&event));
return;
} else {
client->query.dboptions &= ~DNS_DBFIND_STALEONLY;
}
LOCK(&client->query.fetchlock);
if (client->query.fetch != NULL) {
/*
@ -6086,6 +6384,13 @@ ns_query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname,
peeraddr = &client->peeraddr;
}
if (client->view->staleanswerclienttimeout > 0 &&
client->view->staleanswerclienttimeout != (uint32_t)-1 &&
dns_view_staleanswerenabled(client->view))
{
client->query.fetchoptions |= DNS_FETCHOPT_TRYSTALE_ONTIMEOUT;
}
isc_nmhandle_attach(client->handle, &client->fetchhandle);
result = dns_resolver_createfetch(
client->view->resolver, qname, qtype, qdomain, nameservers,
@ -7136,45 +7441,20 @@ root_key_sentinel_return_servfail(query_ctx_t *qctx, isc_result_t result) {
*/
static bool
query_usestale(query_ctx_t *qctx) {
bool staleanswersok = false;
dns_ttl_t stale_ttl = 0;
isc_result_t result;
qctx_clean(qctx);
qctx_freedata(qctx);
/*
* Stale answers only make sense if stale_ttl > 0 but we want rndc to
* be able to control returning stale answers if they are configured.
*/
dns_db_attach(qctx->client->view->cachedb, &qctx->db);
result = dns_db_getservestalettl(qctx->db, &stale_ttl);
if (result == ISC_R_SUCCESS && stale_ttl > 0) {
switch (qctx->client->view->staleanswersok) {
case dns_stale_answer_yes:
staleanswersok = true;
break;
case dns_stale_answer_conf:
staleanswersok = qctx->client->view->staleanswersenable;
break;
case dns_stale_answer_no:
staleanswersok = false;
break;
}
} else {
staleanswersok = false;
}
if (staleanswersok) {
if (dns_view_staleanswerenabled(qctx->client->view)) {
dns_db_attach(qctx->client->view->cachedb, &qctx->db);
qctx->client->query.dboptions |= DNS_DBFIND_STALEOK;
if (qctx->client->query.fetch != NULL) {
dns_resolver_destroyfetch(&qctx->client->query.fetch);
}
} else {
dns_db_detach(&qctx->db);
return (true);
}
return (staleanswersok);
return (false);
}
/*%
@ -7688,7 +7968,9 @@ query_addanswer(query_ctx_t *qctx) {
query_filter64(qctx);
ns_client_putrdataset(qctx->client, &qctx->rdataset);
} else {
if (!qctx->is_zone && RECURSIONOK(qctx->client)) {
if (!qctx->is_zone && RECURSIONOK(qctx->client) &&
!QUERY_STALEONLY(&qctx->client->query))
{
query_prefetch(qctx->client, qctx->fname,
qctx->rdataset);
}
@ -7796,7 +8078,7 @@ query_respond(query_ctx_t *qctx) {
* We shouldn't ever fail to add 'rdataset'
* because it's already in the answer.
*/
INSIST(qctx->rdataset == NULL);
INSIST(qctx->rdataset == NULL || QUERY_ANSWERED(&qctx->client->query));
query_addauth(qctx);
@ -11213,6 +11495,7 @@ isc_result_t
ns_query_done(query_ctx_t *qctx) {
isc_result_t result = ISC_R_UNSET;
const dns_namelist_t *secs = qctx->client->message->sections;
bool query_stale_only;
CCTRACE(ISC_LOG_DEBUG(3), "ns_query_done");
@ -11281,7 +11564,10 @@ ns_query_done(query_ctx_t *qctx) {
* If we're recursing then just return; the query will
* resume when recursion ends.
*/
if (RECURSING(qctx->client)) {
if (RECURSING(qctx->client) &&
(!QUERY_STALEONLY(&qctx->client->query) ||
((qctx->options & DNS_GETDB_STALEFIRST) != 0)))
{
return (qctx->result);
}
@ -11292,8 +11578,10 @@ ns_query_done(query_ctx_t *qctx) {
* to the AA bit if the auth-nxdomain config option
* says so, then render and send the response.
*/
query_setup_sortlist(qctx);
query_glueanswer(qctx);
if (!QUERY_ANSWERED(qctx->client)) {
query_setup_sortlist(qctx);
query_glueanswer(qctx);
}
if (qctx->client->message->rcode == dns_rcode_nxdomain &&
qctx->view->auth_nxdomain)
@ -11315,9 +11603,16 @@ ns_query_done(query_ctx_t *qctx) {
CALL_HOOK(NS_QUERY_DONE_SEND, qctx);
/*
* Client may have been detached after query_send(), so
* we test and store the flag state here, for safety.
*/
query_stale_only = QUERY_STALEONLY(&qctx->client->query);
query_send(qctx->client);
qctx->detach_client = true;
if (!query_stale_only) {
qctx->detach_client = true;
}
return (qctx->result);
cleanup: