06d839
From d55a57427ee696dec51149950478394e43019607 Mon Sep 17 00:00:00 2001
57726f
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemensik@redhat.com>
57726f
Date: Thu, 7 Nov 2019 14:31:03 +0100
57726f
Subject: [PATCH] Implement serve-stale in 9.11
57726f
MIME-Version: 1.0
57726f
Content-Type: text/plain; charset=UTF-8
57726f
Content-Transfer-Encoding: 8bit
57726f
57726f
Squashed commit of the following:
57726f
57726f
commit 32f47f36e545223b2a4757588d7bd4af8c5f5760
57726f
Author: Petr Menšík <pemensik@redhat.com>
57726f
Date:   Tue Sep 3 18:45:54 2019 +0200
57726f
57726f
    convert serve_stale to db_test
57726f
57726f
    Manual checkout from commit e8f61dd315c5d1c88915bb79361182241e42e47a.
57726f
    Use test modified for cmocka, including serve-stale tests.
57726f
57726f
commit 071eb1fb0786f6d614955813d99c3caabff33383
57726f
Author: Michał Kępień <michal@isc.org>
57726f
Date:   Fri Apr 27 09:13:26 2018 +0200
57726f
57726f
    Detect recursion loops during query processing
57726f
57726f
    Interrupt query processing when query_recurse() attempts to ask the same
57726f
    name servers for the same QNAME/QTYPE tuple for two times in a row as
57726f
    this indicates that query processing may be stuck for an indeterminate
57726f
    period of time, e.g. due to interactions between features able to
57726f
    restart query_lookup().
57726f
57726f
    (cherry picked from commit 46bb4dd124ed031d4c219d1e37a3c6322092e30c)
57726f
57726f
commit c12090bc361c7fa4522ace73899e778e44e9b295
57726f
Author: Petr Menšík <pemensik@redhat.com>
57726f
Date:   Mon Sep 2 11:12:32 2019 +0200
57726f
57726f
    Fix test name used in whole test-suite
57726f
57726f
    Correct name is serve-stale
57726f
57726f
commit ff4d826f295d268a248ca06941d65c903e1b405c
57726f
Author: Petr Menšík <pemensik@redhat.com>
57726f
Date:   Fri Aug 30 17:43:28 2019 +0200
57726f
57726f
    Clean files in more generic rules
57726f
57726f
commit 8d81ed15eda9a2a11e1433d1fdddacfc772708b6
57726f
Author: Petr Menšík <pemensik@redhat.com>
57726f
Date:   Thu Aug 29 21:27:57 2019 +0200
57726f
57726f
    [rt46602] Pass port numbers to tests via environment variables
57726f
57726f
    Manually applied commit f5d8f079008b648d2e343543e66dd728054c6101
57726f
57726f
commit 94fafa477891576286def8c4041ad127734af2d1
57726f
Author: Tony Finch <dot@dotat.at>
57726f
Date:   Tue Apr 10 16:17:57 2018 +0100
57726f
57726f
    Move serve-stale logging to its own category, so that its verbosity can be curtailed.
57726f
57726f
    (cherry picked from commit 4b442c309dfb2c8880b19af4133047655bb734df)
57726f
57726f
commit e0c884bee98c3d2533dfaa667f58c6a80d8a3a00
57726f
Author: Michał Kępień <michal@isc.org>
57726f
Date:   Fri Apr 27 09:13:26 2018 +0200
57726f
57726f
    Prevent check_stale_header() from leaking rdataset headers
57726f
57726f
    check_stale_header() fails to update the pointer to the previous header
57726f
    while processing rdataset headers eligible for serve-stale, thus
57726f
    enabling rdataset headers to be leaked (i.e. disassociated from a node
57726f
    and left on the relevant TTL heap) while iterating through a node.  This
57726f
    can lead to several different assertion failures.  Add the missing
57726f
    pointer update.
57726f
57726f
    (cherry picked from commit 391fac1fc8d2e470287b5cc4344b3adb90c6f54a)
57726f
57726f
commit d724cc1d80ee8d46113eaf82549d49636739b67c
57726f
Author: Matthijs Mekking <matthijs@isc.org>
57726f
Date:   Thu Jan 24 10:24:44 2019 +0100
57726f
57726f
    Print in dump-file stale ttl
57726f
57726f
    This change makes rndc dumpdb correctly print the "; stale" line.
57726f
    It also provides extra information on how long this data may still
57726f
    be served to clients (in other words how long the stale RRset may
57726f
    still be used).
57726f
57726f
    (cherry picked from commit 924ebc605db798e2a383ee5eaaebad739e7c789c)
57726f
57726f
commit 625da4bd4590ac6108bb30eddd23ceffb245ae49
57726f
Author: Michał Kępień <michal@isc.org>
57726f
Date:   Mon Oct 22 15:26:45 2018 +0200
57726f
57726f
    Check serve-stale behavior with a cold cache
57726f
57726f
    Ensure that serve-stale works as expected when returning stale answers
57726f
    is enabled, the authoritative server does not respond, and there is no
57726f
    cached answer available.
57726f
57726f
    (cherry picked from commit 27cfe83a388147edfa0451b28c06c746912ea684)
57726f
57726f
commit d67ae10461c409fdafdbbe64f857db2552b71059
57726f
Author: Michał Kępień <michal@isc.org>
57726f
Date:   Mon Oct 22 15:26:45 2018 +0200
57726f
57726f
    Check TTL of stale answers
57726f
57726f
    Make sure that stale answers returned when the serve-stale feature is
57726f
    enabled have a TTL matching the value of the stale-answer-ttl setting.
57726f
57726f
    (cherry picked from commit 893ab37ce78c658215bd3a019f25afe795b37d5a)
57726f
57726f
commit 50459107805e68e4a63a8e497bf58ef3ce013ddb
57726f
Author: Michał Kępień <michal@isc.org>
57726f
Date:   Mon Jul 9 14:35:12 2018 +0200
57726f
57726f
    Do not use Net::DNS::Nameserver in the "serve-stale" system test
57726f
57726f
    Net::DNS versions older than 0.67 respond to queries sent to a
57726f
    Net::DNS::Nameserver even if its ReplyHandler returns undef.  This makes
57726f
    the "serve-stale" system test fail as it takes advantage of the newer
57726f
    behavior.  Since the latest Net::DNS version available with stock
57726f
    RHEL/CentOS 6 packages is 0.65 and we officially support that operating
57726f
    system, bin/tests/system/serve-stale/ans2/ans.pl should behave
57726f
    consistently for various Net::DNS versions.  Ensure that by reworking it
57726f
    so that it does not use Net::DNS::Nameserver.
57726f
57726f
    (cherry picked from commit c4209418a50c09142375f7edadca731c526f3d3a)
57726f
57726f
commit 4b5befc714bb386bd245b1c14ce3bce5ae6fb5fa
57726f
Author: Petr Menšík <pemensik@redhat.com>
57726f
Date:   Tue Jun 5 21:38:29 2018 +0200
57726f
57726f
    Fix server-stale requirement, skip without Time::HiRes
57726f
57726f
    (cherry picked from commit 7a0c7bf9c8e6a724e52635eed213ad25b9504e66)
57726f
57726f
commit 5ce51a3a7e5ef3087c4d022e3fca42fb2fd0c996
57726f
Author: Ondřej Surý <ondrej@sury.org>
57726f
Date:   Wed Oct 18 13:01:14 2017 +0200
57726f
57726f
    [rt46602] Update server-stale test to run on port passed from run.sh script
57726f
57726f
    (cherry picked from commit f83ebd34b9555a5a834c58146035173bcbd01dda)
57726f
57726f
commit 3954a9bf3437f6fab050294a7f2f954a23d161ec
57726f
Author: Ondřej Surý <ondrej@sury.org>
57726f
Date:   Wed Oct 18 14:18:59 2017 +0200
57726f
57726f
    [rt46602] Add serve-stale working files to .gitignore
57726f
57726f
    (cherry picked from commit cba162e70e7fac43435a606106841a69ce468526)
57726f
57726f
commit 112aa21f5fa875494820e4d1eb70e41e10e1aae7
57726f
Author: Mark Andrews <marka@isc.org>
57726f
Date:   Thu Oct 12 15:33:47 2017 +1100
57726f
57726f
    test for Net::DNS::Nameserver
57726f
57726f
    (cherry picked from commit 5b60d0608ac2852753180b762d1917163f9dc315)
57726f
57726f
commit 9d610e46af8a636f44914cee4cf8b2016054db1e
57726f
Author: Mark Andrews <marka@isc.org>
57726f
Date:   Thu Oct 12 15:19:45 2017 +1100
57726f
57726f
    add Net::DNS prerequiste test
57726f
57726f
    (cherry picked from commit fa644181f51559da3e3913acd72dbc3f6d916e71)
57726f
57726f
commit e4ea7ba88d9a9a0c79579400c68a5dabe03e8572
57726f
Author: Mark Andrews <marka@isc.org>
57726f
Date:   Wed Sep 6 19:26:10 2017 +1000
57726f
57726f
    add quotes arount $send_response
57726f
57726f
    (cherry picked from commit 023ab19634b287543169e9b7b5259f3126cd60ff)
57726f
57726f
commit 0af0c5d33c2de34da164571288b650282c6be10a
57726f
Author: Mark Andrews <marka@isc.org>
57726f
Date:   Thu Nov 23 16:11:49 2017 +1100
57726f
57726f
    initalise serve_stale_ttl
57726f
57726f
    (cherry picked from commit 2f4e0e5a81278f59037bf06ae99ff52245cd57e9)
57726f
57726f
commit fbadd90ee81863d617c4c319d5f0079b877fe102
57726f
Author: Evan Hunt <each@isc.org>
57726f
Date:   Thu Sep 14 11:48:21 2017 -0700
57726f
57726f
    [master] add thanks to APNIC and add missing note for serve-stale
57726f
57726f
commit deb8adaa59955970b9d2f2fe58060a3cbf08312b
57726f
Author: Mark Andrews <marka@isc.org>
57726f
Date:   Wed Sep 6 12:16:10 2017 +1000
57726f
57726f
    silence 'staleanswersok' may be used uninitialized in this function warning. [RT #14147
57726f
57726f
commit 0e2d03823768dc545015e6ce309777210f4a9f85
57726f
Author: Petr Menšík <pemensik@redhat.com>
57726f
Date:   Thu Aug 29 19:57:58 2019 +0200
57726f
57726f
    More fixes to merge
57726f
57726f
commit 360e25ffe7623ea0a2eec49395001f4940967776
57726f
Author: Mark Andrews <marka@isc.org>
57726f
Date:   Wed Sep 6 09:58:29 2017 +1000
57726f
57726f
    4700.   [func]          Serving of stale answers is now supported. This
57726f
                            allows named to provide stale cached answers when
57726f
                            the authoritative server is under attack.
57726f
                            See max-stale-ttl, stale-answer-enable,
57726f
                            stale-answer-ttl. [RT #44790]
57726f
57726f
Signed-off-by: Petr Menšík <pemensik@redhat.com>
57726f
---
57726f
 bin/named/config.c                            |   9 +-
57726f
 bin/named/control.c                           |   2 +
57726f
 bin/named/include/named/control.h             |   1 +
57726f
 bin/named/include/named/log.h                 |   1 +
57726f
 bin/named/include/named/query.h               |  15 +
57726f
 bin/named/include/named/server.h              |  13 +-
57726f
 bin/named/log.c                               |   1 +
57726f
 bin/named/query.c                             | 164 +++++-
57726f
 bin/named/server.c                            | 177 +++++-
57726f
 bin/named/statschannel.c                      |   6 +
57726f
 bin/rndc/rndc.c                               |   2 +
57726f
 bin/rndc/rndc.docbook                         |  19 +
57726f
 bin/tests/system/chain/prereq.sh              |   7 +
57726f
 bin/tests/system/conf.sh.in                   |   2 +-
57726f
 bin/tests/system/dyndb/driver/db.c            |   2 +
57726f
 bin/tests/system/serve-stale/.gitignore       |  11 +
57726f
 bin/tests/system/serve-stale/ans2/ans.pl.in   | 178 ++++++
57726f
 bin/tests/system/serve-stale/clean.sh         |  15 +
57726f
 .../system/serve-stale/ns1/named1.conf.in     |  35 ++
57726f
 .../system/serve-stale/ns1/named2.conf.in     |  35 ++
57726f
 bin/tests/system/serve-stale/ns1/root.db      |   5 +
57726f
 .../system/serve-stale/ns3/named.conf.in      |  35 ++
57726f
 bin/tests/system/serve-stale/prereq.sh        |  38 ++
57726f
 bin/tests/system/serve-stale/setup.sh         |  13 +
57726f
 bin/tests/system/serve-stale/tests.sh         | 536 ++++++++++++++++++
e48f32
 doc/arm/Bv9ARM-book.xml                       |  77 ++-
57726f
 doc/arm/logging-categories.xml                |  11 +
57726f
 doc/arm/notes-rh-changes.xml                  |  14 +-
57726f
 doc/misc/options                              |  10 +
57726f
 lib/bind9/check.c                             |  78 ++-
57726f
 lib/dns/cache.c                               |  38 +-
57726f
 lib/dns/db.c                                  |  22 +
57726f
 lib/dns/ecdb.c                                |   4 +-
57726f
 lib/dns/include/dns/cache.h                   |  21 +
57726f
 lib/dns/include/dns/db.h                      |  35 ++
57726f
 lib/dns/include/dns/rdataset.h                |  11 +
57726f
 lib/dns/include/dns/resolver.h                |  43 +-
57726f
 lib/dns/include/dns/types.h                   |   6 +
57726f
 lib/dns/include/dns/view.h                    |   3 +
57726f
 lib/dns/master.c                              |  14 +-
57726f
 lib/dns/masterdump.c                          |  23 +
57726f
 lib/dns/rbtdb.c                               | 207 ++++++-
f79ca9
 lib/dns/resolver.c                            |  79 ++-
57726f
 lib/dns/sdb.c                                 |   4 +-
57726f
 lib/dns/sdlz.c                                |   4 +-
57726f
 lib/dns/tests/db_test.c                       | 198 ++++++-
57726f
 lib/dns/view.c                                |   3 +
57726f
 lib/isccfg/namedconf.c                        |   5 +
e48f32
 48 files changed, 2126 insertions(+), 106 deletions(-)
57726f
 create mode 100644 bin/tests/system/serve-stale/.gitignore
57726f
 create mode 100644 bin/tests/system/serve-stale/ans2/ans.pl.in
57726f
 create mode 100644 bin/tests/system/serve-stale/clean.sh
57726f
 create mode 100644 bin/tests/system/serve-stale/ns1/named1.conf.in
57726f
 create mode 100644 bin/tests/system/serve-stale/ns1/named2.conf.in
57726f
 create mode 100644 bin/tests/system/serve-stale/ns1/root.db
57726f
 create mode 100644 bin/tests/system/serve-stale/ns3/named.conf.in
57726f
 create mode 100644 bin/tests/system/serve-stale/prereq.sh
57726f
 create mode 100644 bin/tests/system/serve-stale/setup.sh
57726f
 create mode 100755 bin/tests/system/serve-stale/tests.sh
57726f
57726f
diff --git a/bin/named/config.c b/bin/named/config.c
06d839
index 9e071bb..d2cd3bc 100644
57726f
--- a/bin/named/config.c
57726f
+++ b/bin/named/config.c
57726f
@@ -182,13 +182,14 @@ options {\n\
57726f
 #ifdef HAVE_LMDB
57726f
 "	lmdb-mapsize 32M;\n"
57726f
 #endif
57726f
-"	max-acache-size 16M;\n\
57726f
-	max-cache-size 90%;\n\
57726f
+"	max-cache-size 90%;\n\
57726f
+	max-acache-size 16M;\n\
57726f
 	max-cache-ttl 604800; /* 1 week */\n\
57726f
 	max-clients-per-query 100;\n\
57726f
 	max-ncache-ttl 10800; /* 3 hours */\n\
57726f
 	max-recursion-depth 7;\n\
06d839
 	max-recursion-queries 100;\n\
57726f
+	max-stale-ttl 604800; /* 1 week */\n\
57726f
 	message-compression yes;\n\
57726f
 #	min-roots <obsolete>;\n\
57726f
 	minimal-any false;\n\
57726f
@@ -203,10 +204,14 @@ options {\n\
57726f
 	request-expire true;\n\
57726f
 	request-ixfr true;\n\
57726f
 	require-server-cookie no;\n\
57726f
+	resolver-nonbackoff-tries 3;\n\
57726f
+	resolver-retry-interval 800; /* in milliseconds */\n\
57726f
 #	rfc2308-type1 <obsolete>;\n\
57726f
 	root-key-sentinel yes;\n\
57726f
 	servfail-ttl 1;\n\
57726f
 #	sortlist <none>\n\
57726f
+	stale-answer-enable false;\n\
57726f
+	stale-answer-ttl 1; /* 1 second */\n\
57726f
 #	topology <none>\n\
57726f
 	transfer-format many-answers;\n\
57726f
 	v6-bias 50;\n\
57726f
diff --git a/bin/named/control.c b/bin/named/control.c
e48f32
index 23620b4..0756c73 100644
57726f
--- a/bin/named/control.c
57726f
+++ b/bin/named/control.c
57726f
@@ -282,6 +282,8 @@ ns_control_docommand(isccc_sexpr_t *message, bool readonly,
57726f
 		result = ns_server_validation(ns_g_server, lex, text);
57726f
 	} else if (command_compare(command, NS_COMMAND_ZONESTATUS)) {
57726f
 		result = ns_server_zonestatus(ns_g_server, lex, text);
57726f
+	} else if (command_compare(command, NS_COMMAND_SERVESTALE)) {
57726f
+		result = ns_server_servestale(ns_g_server, lex, text);
57726f
 	} else {
57726f
 		isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL,
57726f
 			      NS_LOGMODULE_CONTROL, ISC_LOG_WARNING,
57726f
diff --git a/bin/named/include/named/control.h b/bin/named/include/named/control.h
e48f32
index 56bad8d..37403f1 100644
57726f
--- a/bin/named/include/named/control.h
57726f
+++ b/bin/named/include/named/control.h
e48f32
@@ -67,6 +67,7 @@
57726f
 #define NS_COMMAND_MKEYS	"managed-keys"
57726f
 #define NS_COMMAND_DNSTAPREOPEN	"dnstap-reopen"
57726f
 #define NS_COMMAND_DNSTAP	"dnstap"
57726f
+#define NS_COMMAND_SERVESTALE	"serve-stale"
57726f
 
57726f
 isc_result_t
57726f
 ns_controls_create(ns_server_t *server, ns_controls_t **ctrlsp);
57726f
diff --git a/bin/named/include/named/log.h b/bin/named/include/named/log.h
e48f32
index 76e3a51..0d1d985 100644
57726f
--- a/bin/named/include/named/log.h
57726f
+++ b/bin/named/include/named/log.h
e48f32
@@ -30,6 +30,7 @@
57726f
 #define NS_LOGCATEGORY_UPDATE_SECURITY	(&ns_g_categories[6])
57726f
 #define NS_LOGCATEGORY_QUERY_ERRORS	(&ns_g_categories[7])
57726f
 #define NS_LOGCATEGORY_TAT		(&ns_g_categories[8])
57726f
+#define NS_LOGCATEGORY_SERVE_STALE	(&ns_g_categories[9])
57726f
 
57726f
 /*
57726f
  * Backwards compatibility.
57726f
diff --git a/bin/named/include/named/query.h b/bin/named/include/named/query.h
e48f32
index ef1b172..53c052b 100644
57726f
--- a/bin/named/include/named/query.h
57726f
+++ b/bin/named/include/named/query.h
57726f
@@ -35,6 +35,18 @@ typedef struct ns_dbversion {
57726f
 	ISC_LINK(struct ns_dbversion)	link;
57726f
 } ns_dbversion_t;
57726f
 
57726f
+/*%
57726f
+ * nameserver recursion parameters, to uniquely identify a recursion
57726f
+ * query; this is used to detect a recursion loop
57726f
+ */
57726f
+typedef struct ns_query_recparam {
57726f
+	dns_rdatatype_t			qtype;
57726f
+	dns_name_t *			qname;
57726f
+	dns_fixedname_t			fqname;
57726f
+	dns_name_t *			qdomain;
57726f
+	dns_fixedname_t			fqdomain;
57726f
+} ns_query_recparam_t;
57726f
+
57726f
 /*% nameserver query structure */
57726f
 struct ns_query {
57726f
 	unsigned int			attributes;
57726f
@@ -63,6 +75,7 @@ struct ns_query {
57726f
 	unsigned int			dns64_aaaaoklen;
57726f
 	unsigned int			dns64_options;
57726f
 	unsigned int			dns64_ttl;
57726f
+
57726f
 	struct {
57726f
 		dns_db_t *      	db;
57726f
 		dns_zone_t *      	zone;
57726f
@@ -76,6 +89,8 @@ struct ns_query {
57726f
 		bool		authoritative;
57726f
 		bool		is_zone;
57726f
 	} redirect;
57726f
+
57726f
+	ns_query_recparam_t		recparam;
57726f
 	dns_keytag_t root_key_sentinel_keyid;
57726f
 	bool root_key_sentinel_is_ta;
57726f
 	bool root_key_sentinel_not_ta;
57726f
diff --git a/bin/named/include/named/server.h b/bin/named/include/named/server.h
e48f32
index 0ba2627..08a02dc 100644
57726f
--- a/bin/named/include/named/server.h
57726f
+++ b/bin/named/include/named/server.h
e48f32
@@ -227,7 +227,10 @@ enum {
57726f
 
f79ca9
 	dns_nsstatscounter_reclimitdropped = 58,
57726f
 
f79ca9
-	dns_nsstatscounter_max = 59
f79ca9
+	dns_nsstatscounter_trystale = 59,
f79ca9
+	dns_nsstatscounter_usedstale = 60,
57726f
+
f79ca9
+	dns_nsstatscounter_max = 61
57726f
 };
57726f
 
57726f
 /*%
e48f32
@@ -766,4 +769,12 @@ ns_server_mkeys(ns_server_t *server, isc_lex_t *lex, isc_buffer_t **text);
57726f
 isc_result_t
57726f
 ns_server_dnstap(ns_server_t *server, isc_lex_t *lex, isc_buffer_t **text);
57726f
 
57726f
+
57726f
+/*%
57726f
+ * Control whether stale answers are served or not when configured in
57726f
+ * named.conf.
57726f
+ */
57726f
+isc_result_t
57726f
+ns_server_servestale(ns_server_t *server, isc_lex_t *lex,
57726f
+		     isc_buffer_t **text);
57726f
 #endif /* NAMED_SERVER_H */
57726f
diff --git a/bin/named/log.c b/bin/named/log.c
e48f32
index acfa766..ea6f114 100644
57726f
--- a/bin/named/log.c
57726f
+++ b/bin/named/log.c
57726f
@@ -38,6 +38,7 @@ static isc_logcategory_t categories[] = {
57726f
 	{ "update-security",		0 },
57726f
 	{ "query-errors",		0 },
57726f
 	{ "trust-anchor-telemetry",	0 },
57726f
+	{ "serve-stale",                0 },
57726f
 	{ NULL, 			0 }
57726f
 };
57726f
 
57726f
diff --git a/bin/named/query.c b/bin/named/query.c
06d839
index b14f081..a95f5ad 100644
57726f
--- a/bin/named/query.c
57726f
+++ b/bin/named/query.c
e48f32
@@ -149,10 +149,14 @@ last_cmpxchg(isc_stdtime_t *x, isc_stdtime_t *e, isc_stdtime_t r) {
57726f
 #define REDIRECT(c)		(((c)->query.attributes & \
57726f
 				  NS_QUERYATTR_REDIRECT) != 0)
57726f
 
57726f
-/*% No QNAME Proof? */
57726f
+/*% Does the rdataset 'r' have an attached 'No QNAME Proof'? */
57726f
 #define NOQNAME(r)		(((r)->attributes & \
57726f
 				  DNS_RDATASETATTR_NOQNAME) != 0)
57726f
 
57726f
+/*% Does the rdataset 'r' contain a stale answer? */
57726f
+#define STALE(r)		(((r)->attributes & \
57726f
+				  DNS_RDATASETATTR_STALE) != 0)
57726f
+
57726f
 #ifdef WANT_QUERYTRACE
57726f
 static inline void
57726f
 client_trace(ns_client_t *client, int level, const char *message) {
e48f32
@@ -241,6 +245,10 @@ static bool
57726f
 rpz_ck_dnssec(ns_client_t *client, isc_result_t qresult,
57726f
 	      dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset);
57726f
 
57726f
+static void
57726f
+recparam_update(ns_query_recparam_t *param, dns_rdatatype_t qtype,
57726f
+		const dns_name_t *qname, const dns_name_t *qdomain);
57726f
+
57726f
 /*%
57726f
  * Increment query statistics counters.
57726f
  */
e48f32
@@ -494,6 +502,7 @@ query_reset(ns_client_t *client, bool everything) {
57726f
 	client->query.isreferral = false;
57726f
 	client->query.dns64_options = 0;
57726f
 	client->query.dns64_ttl = UINT32_MAX;
57726f
+	recparam_update(&client->query.recparam, 0, NULL, NULL);
57726f
 	client->query.root_key_sentinel_keyid = 0;
57726f
 	client->query.root_key_sentinel_is_ta = false;
57726f
 	client->query.root_key_sentinel_not_ta = false;
e48f32
@@ -4305,6 +4314,54 @@ log_quota(ns_client_t *client, isc_stdtime_t *last, isc_stdtime_t now,
e48f32
 	}
57726f
 }
57726f
 
57726f
+/*%
57726f
+ * Check whether the recursion parameters in 'param' match the current query's
57726f
+ * recursion parameters provided in 'qtype', 'qname', and 'qdomain'.
57726f
+ */
57726f
+static bool
57726f
+recparam_match(const ns_query_recparam_t *param, dns_rdatatype_t qtype,
57726f
+	       const dns_name_t *qname, const dns_name_t *qdomain)
57726f
+{
57726f
+	REQUIRE(param != NULL);
57726f
+
57726f
+	return (param->qtype == qtype &&
57726f
+	        param->qname != NULL && qname != NULL &&
57726f
+	        param->qdomain != NULL && qdomain != NULL &&
57726f
+	        dns_name_equal(param->qname, qname) &&
57726f
+	        dns_name_equal(param->qdomain, qdomain));
57726f
+}
57726f
+
57726f
+/*%
57726f
+ * Update 'param' with current query's recursion parameters provided in
57726f
+ * 'qtype', 'qname', and 'qdomain'.
57726f
+ */
57726f
+static void
57726f
+recparam_update(ns_query_recparam_t *param, dns_rdatatype_t qtype,
57726f
+		const dns_name_t *qname, const dns_name_t *qdomain)
57726f
+{
57726f
+	isc_result_t result;
57726f
+
57726f
+	REQUIRE(param != NULL);
57726f
+
57726f
+	param->qtype = qtype;
57726f
+
57726f
+	if (qname == NULL) {
57726f
+		param->qname = NULL;
57726f
+	} else {
57726f
+		param->qname = dns_fixedname_initname(&param->fqname);
57726f
+		result = dns_name_copy(qname, param->qname, NULL);
57726f
+		RUNTIME_CHECK(result == ISC_R_SUCCESS);
57726f
+	}
57726f
+
57726f
+	if (qdomain == NULL) {
57726f
+		param->qdomain = NULL;
57726f
+	} else {
57726f
+		param->qdomain = dns_fixedname_initname(&param->fqdomain);
57726f
+		result = dns_name_copy(qdomain, param->qdomain, NULL);
57726f
+		RUNTIME_CHECK(result == ISC_R_SUCCESS);
57726f
+	}
57726f
+}
57726f
+
57726f
 static isc_result_t
57726f
 query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname,
57726f
 	      dns_name_t *qdomain, dns_rdataset_t *nameservers,
e48f32
@@ -4314,6 +4371,19 @@ query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname,
57726f
 	dns_rdataset_t *rdataset, *sigrdataset;
57726f
 	isc_sockaddr_t *peeraddr;
57726f
 
57726f
+	/*
57726f
+	 * Check recursion parameters from the previous query to see if they
57726f
+	 * match.  If not, update recursion parameters and proceed.
57726f
+	 */
57726f
+	if (recparam_match(&client->query.recparam, qtype, qname, qdomain)) {
57726f
+		ns_client_log(client, NS_LOGCATEGORY_CLIENT,
57726f
+			      NS_LOGMODULE_QUERY, ISC_LOG_INFO,
57726f
+			      "recursion loop detected");
57726f
+		return (ISC_R_FAILURE);
57726f
+	}
57726f
+
57726f
+	recparam_update(&client->query.recparam, qtype, qname, qdomain);
57726f
+
57726f
 	if (!resuming)
57726f
 		inc_stats(client, dns_nsstatscounter_recursion);
57726f
 
e48f32
@@ -6821,6 +6891,7 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype)
57726f
 	int line = -1;
57726f
 	bool dns64_exclude, dns64, rpz;
57726f
 	bool nxrewrite = false;
57726f
+	bool want_stale = false;
57726f
 	bool redirected = false;
57726f
 	dns_clientinfomethods_t cm;
57726f
 	dns_clientinfo_t ci;
e48f32
@@ -7130,6 +7201,7 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype)
57726f
 		type = qtype;
57726f
 
57726f
  restart:
57726f
+	// query_start
57726f
 	CTRACE(ISC_LOG_DEBUG(3), "query_find: restart");
57726f
 	want_restart = false;
57726f
 	authoritative = false;
e48f32
@@ -7274,6 +7346,7 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype)
57726f
 	}
57726f
 
57726f
  db_find:
57726f
+	// query_lookup
57726f
 	CTRACE(ISC_LOG_DEBUG(3), "query_find: db_find");
57726f
 	/*
57726f
 	 * We'll need some resources...
e48f32
@@ -7331,6 +7404,35 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype)
57726f
 	if (!is_zone)
57726f
 		dns_cache_updatestats(client->view->cache, result);
57726f
 
57726f
+	if (want_stale) {
57726f
+		char namebuf[DNS_NAME_FORMATSIZE];
57726f
+		bool success;
57726f
+
57726f
+		client->query.dboptions &= ~DNS_DBFIND_STALEOK;
57726f
+		want_stale = false;
57726f
+
57726f
+		if (dns_rdataset_isassociated(rdataset) &&
57726f
+		    dns_rdataset_count(rdataset) > 0 &&
57726f
+		    STALE(rdataset)) {
57726f
+			rdataset->ttl = client->view->staleanswerttl;
57726f
+			success = true;
57726f
+		} else {
57726f
+			success = false;
57726f
+		}
57726f
+
57726f
+		dns_name_format(client->query.qname,
57726f
+				namebuf, sizeof(namebuf));
57726f
+		isc_log_write(ns_g_lctx, NS_LOGCATEGORY_SERVE_STALE,
57726f
+			      NS_LOGMODULE_QUERY, ISC_LOG_INFO,
57726f
+			      "%s resolver failure, stale answer %s",
57726f
+			      namebuf, success ? "used" : "unavailable");
57726f
+
57726f
+		if (!success) {
57726f
+			QUERY_ERROR(DNS_R_SERVFAIL);
57726f
+			goto cleanup;
57726f
+		}
57726f
+	}
57726f
+
57726f
  resume:
57726f
 	CTRACE(ISC_LOG_DEBUG(3), "query_find: resume");
57726f
 
e48f32
@@ -7676,6 +7778,7 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype)
57726f
 		 * The cache doesn't even have the root NS.  Get them from
57726f
 		 * the hints DB.
57726f
 		 */
57726f
+		// query_notfound
57726f
 		INSIST(!is_zone);
57726f
 		if (db != NULL)
57726f
 			dns_db_detach(&db);
e48f32
@@ -7738,12 +7841,14 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype)
57726f
 		 */
57726f
 		/* FALLTHROUGH */
57726f
 	case DNS_R_DELEGATION:
57726f
+		// query_delegation
57726f
 		authoritative = false;
57726f
 		if (is_zone) {
57726f
 			/*
57726f
 			 * Look to see if we are authoritative for the
57726f
 			 * child zone if the query type is DS.
57726f
 			 */
57726f
+			// query_zone_delegation
57726f
 			if (!RECURSIONOK(client) &&
57726f
 			    (options & DNS_GETDB_NOEXACT) != 0 &&
57726f
 			    qtype == dns_rdatatype_ds) {
e48f32
@@ -8130,6 +8235,7 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype)
57726f
 						       false, true);
57726f
 			}
57726f
 		}
57726f
+		// query_nxdomain
57726f
 		if (dns_rdataset_isassociated(rdataset)) {
57726f
 			/*
57726f
 			 * If we've got a NSEC record, we need to save the
e48f32
@@ -8450,7 +8556,8 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype)
57726f
 		/*
57726f
 		 * If we have a zero ttl from the cache refetch it.
57726f
 		 */
57726f
-		if (!is_zone && !resuming && rdataset->ttl == 0 &&
57726f
+		// query_cname
57726f
+		if (!is_zone && !resuming && !STALE(rdataset) && rdataset->ttl == 0 &&
57726f
 		    RECURSIONOK(client))
57726f
 		{
57726f
 			if (dns_rdataset_isassociated(rdataset))
06d839
@@ -8676,7 +8783,11 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype)
57726f
 			 "query_find: unexpected error after resuming: %s",
57726f
 			 isc_result_totext(result));
57726f
 		CTRACE(ISC_LOG_ERROR, errmsg);
57726f
-		QUERY_ERROR(DNS_R_SERVFAIL);
57726f
+		if (resuming) {
57726f
+			want_stale = true;
57726f
+		} else {
57726f
+			QUERY_ERROR(DNS_R_SERVFAIL);
57726f
+		}
57726f
 		goto cleanup;
57726f
 	}
57726f
 
06d839
@@ -8932,7 +9043,7 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype)
57726f
 		/*
57726f
 		 * If we have a zero ttl from the cache refetch it.
57726f
 		 */
57726f
-		if (!is_zone && !resuming && rdataset->ttl == 0 &&
57726f
+		if (!is_zone && !resuming && !STALE(rdataset) && rdataset->ttl == 0 &&
57726f
 		    RECURSIONOK(client))
57726f
 		{
57726f
 			if (dns_rdataset_isassociated(rdataset))
06d839
@@ -8943,6 +9054,7 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype)
57726f
 			if (node != NULL)
57726f
 				dns_db_detachnode(db, &node);
57726f
 
57726f
+			// query_respond
57726f
 			INSIST(!REDIRECT(client));
57726f
 			result = query_recurse(client, qtype,
57726f
 					       client->query.qname,
06d839
@@ -9223,6 +9335,7 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype)
57726f
 				       dns_fixedname_name(&wildcardname),
57726f
 				       true, false);
57726f
  cleanup:
57726f
+	// query_done
57726f
 	CTRACE(ISC_LOG_DEBUG(3), "query_find: cleanup");
57726f
 	/*
57726f
 	 * General cleanup.
06d839
@@ -9279,6 +9392,49 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype)
57726f
 		goto restart;
57726f
 	}
57726f
 
57726f
+	if (want_stale) {
57726f
+		dns_ttl_t stale_ttl = 0;
57726f
+		isc_result_t result;
57726f
+		bool staleanswersok = false;
57726f
+
57726f
+		/*
57726f
+		 * Stale answers only make sense if stale_ttl > 0 but
57726f
+		 * we want rndc to be able to control returning stale
57726f
+		 * answers if they are configured.
57726f
+		 */
57726f
+		dns_db_attach(client->view->cachedb, &db);
57726f
+		result = dns_db_getservestalettl(db, &stale_ttl);
57726f
+		if (result == ISC_R_SUCCESS && stale_ttl > 0)  {
57726f
+			switch (client->view->staleanswersok) {
57726f
+			case dns_stale_answer_yes:
57726f
+				staleanswersok = true;
57726f
+				break;
57726f
+			case dns_stale_answer_conf:
57726f
+				staleanswersok =
57726f
+					client->view->staleanswersenable;
57726f
+				break;
57726f
+			case dns_stale_answer_no:
57726f
+				staleanswersok = false;
57726f
+				break;
57726f
+			}
57726f
+		} else {
57726f
+			staleanswersok = false;
57726f
+		}
57726f
+
57726f
+		if (staleanswersok) {
57726f
+			client->query.dboptions |= DNS_DBFIND_STALEOK;
57726f
+			inc_stats(client, dns_nsstatscounter_trystale);
57726f
+			if (client->query.fetch != NULL)
57726f
+				dns_resolver_destroyfetch(
57726f
+						   &client->query.fetch);
57726f
+			goto db_find;
57726f
+		}
57726f
+		dns_db_detach(&db);
57726f
+		want_stale = false;
57726f
+		QUERY_ERROR(DNS_R_SERVFAIL);
57726f
+		goto cleanup;
57726f
+	}
57726f
+
57726f
 	if (eresult != ISC_R_SUCCESS &&
57726f
 	    (!PARTIALANSWER(client) || WANTRECURSION(client)
57726f
 	     || eresult == DNS_R_DROP)) {
57726f
diff --git a/bin/named/server.c b/bin/named/server.c
e48f32
index 2bdf690..3a5ba91 100644
57726f
--- a/bin/named/server.c
57726f
+++ b/bin/named/server.c
f79ca9
@@ -1720,7 +1720,8 @@ static bool
57726f
 cache_sharable(dns_view_t *originview, dns_view_t *view,
57726f
 	       bool new_zero_no_soattl,
57726f
 	       unsigned int new_cleaning_interval,
57726f
-	       uint64_t new_max_cache_size)
57726f
+	       uint64_t new_max_cache_size,
57726f
+	       uint32_t new_stale_ttl)
57726f
 {
57726f
 	/*
57726f
 	 * If the cache cannot even reused for the same view, it cannot be
f79ca9
@@ -1735,6 +1736,7 @@ cache_sharable(dns_view_t *originview, dns_view_t *view,
57726f
 	 */
57726f
 	if (dns_cache_getcleaninginterval(originview->cache) !=
57726f
 	    new_cleaning_interval ||
57726f
+	    dns_cache_getservestalettl(originview->cache) != new_stale_ttl ||
57726f
 	    dns_cache_getcachesize(originview->cache) != new_max_cache_size) {
57726f
 		return (false);
57726f
 	}
f79ca9
@@ -3290,6 +3292,7 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist,
57726f
 	size_t max_acache_size;
57726f
 	size_t max_adb_size;
57726f
 	uint32_t lame_ttl, fail_ttl;
57726f
+	uint32_t max_stale_ttl;
57726f
 	dns_tsig_keyring_t *ring = NULL;
57726f
 	dns_view_t *pview = NULL;	/* Production view */
57726f
 	isc_mem_t *cmctx = NULL, *hmctx = NULL;
f79ca9
@@ -3318,6 +3321,7 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist,
57726f
 	bool old_rpz_ok = false;
57726f
 	isc_dscp_t dscp4 = -1, dscp6 = -1;
57726f
 	dns_dyndbctx_t *dctx = NULL;
57726f
+	unsigned int resolver_param;
57726f
 
57726f
 	REQUIRE(DNS_VIEW_VALID(view));
57726f
 
f79ca9
@@ -3732,6 +3736,24 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist,
57726f
 	if (view->maxncachettl > 7 * 24 * 3600)
57726f
 		view->maxncachettl = 7 * 24 * 3600;
57726f
 
57726f
+	obj = NULL;
57726f
+	result = ns_config_get(maps, "max-stale-ttl", &obj);
57726f
+	INSIST(result == ISC_R_SUCCESS);
57726f
+	max_stale_ttl = cfg_obj_asuint32(obj);
57726f
+
57726f
+	obj = NULL;
57726f
+	result = ns_config_get(maps, "stale-answer-enable", &obj);
57726f
+	INSIST(result == ISC_R_SUCCESS);
57726f
+	view->staleanswersenable = cfg_obj_asboolean(obj);
57726f
+
57726f
+	result = dns_viewlist_find(&ns_g_server->viewlist, view->name,
57726f
+				   view->rdclass, &pview);
57726f
+	if (result == ISC_R_SUCCESS) {
57726f
+		view->staleanswersok = pview->staleanswersok;
57726f
+		dns_view_detach(&pview);
57726f
+	} else
57726f
+		view->staleanswersok = dns_stale_answer_conf;
57726f
+
57726f
 	/*
57726f
 	 * Configure the view's cache.
57726f
 	 *
f79ca9
@@ -3765,7 +3787,8 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist,
57726f
 	nsc = cachelist_find(cachelist, cachename, view->rdclass);
57726f
 	if (nsc != NULL) {
57726f
 		if (!cache_sharable(nsc->primaryview, view, zero_no_soattl,
57726f
-				    cleaning_interval, max_cache_size)) {
57726f
+				    cleaning_interval, max_cache_size,
57726f
+				    max_stale_ttl)) {
57726f
 			isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL,
57726f
 				      NS_LOGMODULE_SERVER, ISC_LOG_ERROR,
57726f
 				      "views %s and %s can't share the cache "
f79ca9
@@ -3864,9 +3887,15 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist,
57726f
 
57726f
 	dns_cache_setcleaninginterval(cache, cleaning_interval);
57726f
 	dns_cache_setcachesize(cache, max_cache_size);
57726f
+	dns_cache_setservestalettl(cache, max_stale_ttl);
57726f
 
57726f
 	dns_cache_detach(&cache);
57726f
 
57726f
+	obj = NULL;
57726f
+	result = ns_config_get(maps, "stale-answer-ttl", &obj);
57726f
+	INSIST(result == ISC_R_SUCCESS);
57726f
+	view->staleanswerttl = ISC_MAX(cfg_obj_asuint32(obj), 1);
57726f
+
57726f
 	/*
57726f
 	 * Resolver.
57726f
 	 *
f79ca9
@@ -4055,6 +4084,21 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist,
57726f
 		maxbits = 4096;
57726f
 	view->maxbits = maxbits;
57726f
 
57726f
+	/*
57726f
+	 * Set resolver retry parameters.
57726f
+	 */
57726f
+	obj = NULL;
57726f
+	CHECK(ns_config_get(maps, "resolver-retry-interval", &obj));
57726f
+	resolver_param = cfg_obj_asuint32(obj);
57726f
+	if (resolver_param > 0)
57726f
+		dns_resolver_setretryinterval(view->resolver, resolver_param);
57726f
+
57726f
+	obj = NULL;
57726f
+	CHECK(ns_config_get(maps, "resolver-nonbackoff-tries", &obj));
57726f
+	resolver_param = cfg_obj_asuint32(obj);
57726f
+	if (resolver_param > 0)
57726f
+		dns_resolver_setnonbackofftries(view->resolver, resolver_param);
57726f
+
57726f
 	/*
57726f
 	 * Set supported DNSSEC algorithms.
57726f
 	 */
e48f32
@@ -14559,3 +14603,132 @@ ns_server_dnstap(ns_server_t *server, isc_lex_t *lex, isc_buffer_t **text) {
57726f
 	return (ISC_R_NOTIMPLEMENTED);
57726f
 #endif
57726f
 }
57726f
+
57726f
+isc_result_t
57726f
+ns_server_servestale(ns_server_t *server, isc_lex_t *lex,
57726f
+		     isc_buffer_t **text)
57726f
+{
57726f
+	char *ptr, *classtxt, *viewtxt = NULL;
57726f
+	char msg[128];
57726f
+	dns_rdataclass_t rdclass = dns_rdataclass_in;
57726f
+	dns_view_t *view;
57726f
+	bool found = false;
57726f
+	dns_stale_answer_t staleanswersok = dns_stale_answer_conf;
57726f
+	bool wantstatus = false;
57726f
+	isc_result_t result = ISC_R_SUCCESS;
57726f
+
57726f
+	/* Skip the command name. */
57726f
+	ptr = next_token(lex, text);
57726f
+	if (ptr == NULL)
57726f
+		return (ISC_R_UNEXPECTEDEND);
57726f
+
57726f
+	ptr = next_token(lex, NULL);
57726f
+	if (ptr == NULL)
57726f
+		return (ISC_R_UNEXPECTEDEND);
57726f
+
57726f
+	if (strcasecmp(ptr, "on") == 0 || strcasecmp(ptr, "yes") == 0) {
57726f
+		staleanswersok = dns_stale_answer_yes;
57726f
+	} else if (strcasecmp(ptr, "off") == 0 || strcasecmp(ptr, "no") == 0) {
57726f
+		staleanswersok = dns_stale_answer_no;
57726f
+	} else if (strcasecmp(ptr, "reset") == 0) {
57726f
+		staleanswersok = dns_stale_answer_conf;
57726f
+	} else if (strcasecmp(ptr, "status") == 0) {
57726f
+		wantstatus = true;
57726f
+	} else
57726f
+		return (DNS_R_SYNTAX);
57726f
+
57726f
+	/* Look for the optional class name. */
57726f
+	classtxt = next_token(lex, text);
57726f
+	if (classtxt != NULL) {
57726f
+		/* Look for the optional view name. */
57726f
+		viewtxt = next_token(lex, text);
57726f
+	}
57726f
+
57726f
+	if (classtxt != NULL) {
57726f
+		isc_textregion_t r;
57726f
+
57726f
+		r.base = classtxt;
57726f
+		r.length = strlen(classtxt);
57726f
+		result = dns_rdataclass_fromtext(&rdclass, &r);
57726f
+		if (result != ISC_R_SUCCESS) {
57726f
+			if (viewtxt == NULL) {
57726f
+				viewtxt = classtxt;
57726f
+				classtxt = NULL;
57726f
+				result = ISC_R_SUCCESS;
57726f
+			} else {
57726f
+				snprintf(msg, sizeof(msg),
57726f
+					 "unknown class '%s'", classtxt);
57726f
+				(void) putstr(text, msg);
57726f
+				goto cleanup;
57726f
+			}
57726f
+		}
57726f
+	}
57726f
+
57726f
+	result = isc_task_beginexclusive(server->task);
57726f
+	RUNTIME_CHECK(result == ISC_R_SUCCESS);
57726f
+
57726f
+	for (view = ISC_LIST_HEAD(server->viewlist);
57726f
+	     view != NULL;
57726f
+	     view = ISC_LIST_NEXT(view, link))
57726f
+	{
57726f
+		dns_ttl_t stale_ttl = 0;
57726f
+		dns_db_t *db = NULL;
57726f
+
57726f
+		if (classtxt != NULL && rdclass != view->rdclass)
57726f
+			continue;
57726f
+
57726f
+		if (viewtxt != NULL && strcmp(view->name, viewtxt) != 0)
57726f
+			continue;
57726f
+
57726f
+		if (!wantstatus) {
57726f
+			view->staleanswersok = staleanswersok;
57726f
+			found = true;
57726f
+			continue;
57726f
+		}
57726f
+
57726f
+		db = NULL;
57726f
+		dns_db_attach(view->cachedb, &db);
57726f
+		(void)dns_db_getservestalettl(db, &stale_ttl);
57726f
+		dns_db_detach(&db);
57726f
+		if (found)
57726f
+			CHECK(putstr(text, "\n"));
57726f
+		CHECK(putstr(text, view->name));
57726f
+		CHECK(putstr(text, ": "));
57726f
+		switch (view->staleanswersok) {
57726f
+		case dns_stale_answer_yes:
57726f
+			if (stale_ttl > 0)
57726f
+				CHECK(putstr(text, "on (rndc)"));
57726f
+			else
57726f
+				CHECK(putstr(text, "off (not-cached)"));
57726f
+			break;
57726f
+		case dns_stale_answer_no:
57726f
+			CHECK(putstr(text, "off (rndc)"));
57726f
+			break;
57726f
+		case dns_stale_answer_conf:
57726f
+			if (view->staleanswersenable && stale_ttl > 0)
57726f
+				CHECK(putstr(text, "on"));
57726f
+			else if (view->staleanswersenable)
57726f
+				CHECK(putstr(text, "off (not-cached)"));
57726f
+			else
57726f
+				CHECK(putstr(text, "off"));
57726f
+			break;
57726f
+		}
57726f
+		if (stale_ttl > 0) {
57726f
+			snprintf(msg, sizeof(msg),
57726f
+				 " (stale-answer-ttl=%u max-stale-ttl=%u)",
57726f
+				 view->staleanswerttl, stale_ttl);
57726f
+			CHECK(putstr(text, msg));
57726f
+		}
57726f
+		found = true;
57726f
+	}
57726f
+	isc_task_endexclusive(ns_g_server->task);
57726f
+
57726f
+	if (!found)
57726f
+		result = ISC_R_NOTFOUND;
57726f
+
57726f
+cleanup:
57726f
+	if (isc_buffer_usedlength(*text) > 0)
57726f
+		(void) putnull(text);
57726f
+
57726f
+	return (result);
57726f
+}
57726f
diff --git a/bin/named/statschannel.c b/bin/named/statschannel.c
e48f32
index 12ab048..4938c03 100644
57726f
--- a/bin/named/statschannel.c
57726f
+++ b/bin/named/statschannel.c
f79ca9
@@ -300,6 +300,12 @@ init_desc(void) {
f79ca9
 	SET_NSSTATDESC(reclimitdropped,
f79ca9
 		       "queries dropped due to recursive client limit",
f79ca9
 		       "RecLimitDropped");
57726f
+	SET_NSSTATDESC(trystale,
57726f
+		       "attempts to use stale cache data after lookup failure",
57726f
+		       "QryTryStale");
57726f
+	SET_NSSTATDESC(usedstale,
57726f
+		       "successful uses of stale cache data after lookup failure",
57726f
+		       "QryUsedStale");
57726f
 	INSIST(i == dns_nsstatscounter_max);
57726f
 
57726f
 	/* Initialize resolver statistics */
57726f
diff --git a/bin/rndc/rndc.c b/bin/rndc/rndc.c
e48f32
index 0acfe3a..2c21c1d 100644
57726f
--- a/bin/rndc/rndc.c
57726f
+++ b/bin/rndc/rndc.c
57726f
@@ -160,6 +160,8 @@ command is one of the following:\n\
57726f
   scan		Scan available network interfaces for changes.\n\
57726f
   secroots [view ...]\n\
57726f
 		Write security roots to the secroots file.\n\
57726f
+  serve-stale	( yes | no | reset ) [class [view]]\n\
57726f
+		Control whether stale answers are returned\n\
57726f
   showzone zone [class [view]]\n\
57726f
 		Print a zone's configuration.\n\
57726f
   sign zone [class [view]]\n\
57726f
diff --git a/bin/rndc/rndc.docbook b/bin/rndc/rndc.docbook
06d839
index 159ded9..12a7208 100644
57726f
--- a/bin/rndc/rndc.docbook
57726f
+++ b/bin/rndc/rndc.docbook
f79ca9
@@ -689,6 +689,25 @@
57726f
 	</listitem>
57726f
       </varlistentry>
57726f
 
57726f
+      <varlistentry>
57726f
+	<term><userinput>serve-stale ( on | off | reset | status) <optional><replaceable>class</replaceable> <optional><replaceable>view</replaceable></optional></optional></userinput></term>
57726f
+	<listitem>
57726f
+	  <para>
57726f
+	    Enable, disable, or reset the serving of stale answers
57726f
+	    as configured in named.conf. Serving of stale answers
57726f
+	    will remain disabled across <filename>named.conf</filename>
57726f
+	    reloads if disabled via rndc until it is reset via rndc.
57726f
+	  </para>
57726f
+	  <para>
57726f
+	    Status will report whether serving of stale answers is
57726f
+	    currently enabled, disabled or not configured for a
57726f
+	    view.  If serving of stale records is configured then
57726f
+	    the values of stale-answer-ttl and max-stale-ttl are
57726f
+	    reported.
57726f
+	  </para>
57726f
+	</listitem>
57726f
+      </varlistentry>
57726f
+
57726f
       <varlistentry>
57726f
 	<term><userinput>secroots <optional>-</optional> <optional><replaceable>view ...</replaceable></optional></userinput></term>
57726f
 	<listitem>
57726f
diff --git a/bin/tests/system/chain/prereq.sh b/bin/tests/system/chain/prereq.sh
e48f32
index 23bedcd..43385de 100644
57726f
--- a/bin/tests/system/chain/prereq.sh
57726f
+++ b/bin/tests/system/chain/prereq.sh
57726f
@@ -48,3 +48,10 @@ else
57726f
     echo_i "This test requires the Net::DNS::Nameserver library." >&2
57726f
     exit 1
57726f
 fi
57726f
+if $PERL -e 'use Net::DNS::Nameserver;' 2>/dev/null
57726f
+then
57726f
+	:
57726f
+else
57726f
+    echo "I:This test requires the Net::DNS::Nameserver library." >&2
57726f
+    exit 1
57726f
+fi
57726f
diff --git a/bin/tests/system/conf.sh.in b/bin/tests/system/conf.sh.in
06d839
index f6412f6..26c8901 100644
57726f
--- a/bin/tests/system/conf.sh.in
57726f
+++ b/bin/tests/system/conf.sh.in
f79ca9
@@ -128,7 +128,7 @@ PARALLELDIRS="dnssec rpzrecurse \
57726f
 	reclimit redirect resolver rndc rootkeysentinel rpz \
57726f
 	rrchecker rrl rrsetorder rsabigexponent runtime \
57726f
 	sfcache smartsign sortlist \
57726f
-	spf staticstub statistics statschannel stub \
57726f
+	spf serve-stale staticstub statistics statschannel stub \
57726f
 	tcp tsig tsiggss \
57726f
 	unknown upforwd verify views wildcard \
57726f
 	xfer xferquota zero zonechecks"
57726f
diff --git a/bin/tests/system/dyndb/driver/db.c b/bin/tests/system/dyndb/driver/db.c
57726f
index 02aa6ab..a77c7de 100644
57726f
--- a/bin/tests/system/dyndb/driver/db.c
57726f
+++ b/bin/tests/system/dyndb/driver/db.c
57726f
@@ -629,6 +629,8 @@ static dns_dbmethods_t sampledb_methods = {
57726f
 	hashsize,
57726f
 	NULL,
57726f
 	NULL,
57726f
+	NULL,
57726f
+	NULL,
57726f
 };
57726f
 
57726f
 /* Auxiliary driver functions. */
57726f
diff --git a/bin/tests/system/serve-stale/.gitignore b/bin/tests/system/serve-stale/.gitignore
57726f
new file mode 100644
57726f
index 0000000..2272eef
57726f
--- /dev/null
57726f
+++ b/bin/tests/system/serve-stale/.gitignore
57726f
@@ -0,0 +1,11 @@
57726f
+/ans2/ans.pid
57726f
+/ans2/ans.pl
57726f
+/dig.out*
57726f
+/ns1/named.conf
57726f
+/ns3/named.conf
57726f
+/ns3/root.bk
57726f
+/rndc.out*
57726f
+named.lock
57726f
+named.pid
57726f
+named.port
57726f
+named.run
57726f
diff --git a/bin/tests/system/serve-stale/ans2/ans.pl.in b/bin/tests/system/serve-stale/ans2/ans.pl.in
57726f
new file mode 100644
57726f
index 0000000..2b39eca
57726f
--- /dev/null
57726f
+++ b/bin/tests/system/serve-stale/ans2/ans.pl.in
57726f
@@ -0,0 +1,178 @@
57726f
+#!/usr/bin/env perl
57726f
+#
57726f
+# Copyright (C) 2014-2016  Internet Systems Consortium, Inc. ("ISC")
57726f
+#
57726f
+# This Source Code Form is subject to the terms of the Mozilla Public
57726f
+# License, v. 2.0. If a copy of the MPL was not distributed with this
57726f
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
57726f
+
57726f
+use strict;
57726f
+use warnings;
57726f
+
57726f
+use IO::File;
57726f
+use IO::Socket;
57726f
+use Getopt::Long;
57726f
+use Net::DNS;
57726f
+use Time::HiRes qw(usleep nanosleep);
57726f
+
57726f
+my $pidf = new IO::File "ans.pid", "w" or die "cannot open pid file: $!";
57726f
+print $pidf "$$\n" or die "cannot write pid file: $!";
57726f
+$pidf->close or die "cannot close pid file: $!";
57726f
+sub rmpid { unlink "ans.pid"; exit 1; };
57726f
+
57726f
+$SIG{INT} = \&rmpid;
57726f
+$SIG{TERM} = \&rmpid;
57726f
+
57726f
+my $send_response = 1;
57726f
+
57726f
+my $localaddr = "10.53.0.2";
57726f
+my $localport = @PORT@;
57726f
+my $udpsock = IO::Socket::INET->new(LocalAddr => "$localaddr",
57726f
+   LocalPort => $localport, Proto => "udp", Reuse => 1) or die "$!";
57726f
+
57726f
+#
57726f
+# Delegation
57726f
+#
57726f
+my $SOA = "example 300 IN SOA . . 0 0 0 0 300";
57726f
+my $NS = "example 300 IN NS ns.example";
57726f
+my $A = "ns.example 300 IN A $localaddr";
57726f
+#
57726f
+# Records to be TTL stretched
57726f
+#
57726f
+my $TXT = "data.example 1 IN TXT \"A text record with a 1 second ttl\"";
57726f
+my $negSOA = "example 1 IN SOA . . 0 0 0 0 300";
57726f
+
57726f
+sub reply_handler {
57726f
+    my ($qname, $qclass, $qtype) = @_;
57726f
+    my ($rcode, @ans, @auth, @add);
57726f
+
57726f
+    print ("request: $qname/$qtype\n");
57726f
+    STDOUT->flush();
57726f
+
57726f
+    # Control whether we send a response or not.
57726f
+    # We always respond to control commands.
57726f
+    if ($qname eq "enable" ) {
57726f
+	if ($qtype eq "TXT") {
57726f
+	    $send_response = 1;
57726f
+            my $rr = new Net::DNS::RR("$qname 0 $qclass TXT \"$send_response\"");
57726f
+            push @ans, $rr;
57726f
+	}
57726f
+	$rcode = "NOERROR";
57726f
+        return ($rcode, \@ans, \@auth, \@add, { aa => 1 });
57726f
+    } elsif ($qname eq "disable" ) {
57726f
+	if ($qtype eq "TXT") {
57726f
+	    $send_response = 0;
57726f
+            my $rr = new Net::DNS::RR("$qname 0 $qclass TXT \"$send_response\"");
57726f
+            push @ans, $rr;
57726f
+	}
57726f
+	$rcode = "NOERROR";
57726f
+        return ($rcode, \@ans, \@auth, \@add, { aa => 1 });
57726f
+    }
57726f
+
57726f
+    # If we are not responding to queries we are done.
57726f
+    return if (!$send_response);
57726f
+
57726f
+    # Construct the response and send it.
57726f
+    if ($qname eq "ns.example" ) {
57726f
+	if ($qtype eq "A") {
57726f
+	    my $rr = new Net::DNS::RR($A);
57726f
+	    push @ans, $rr;
57726f
+	} else {
57726f
+	    my $rr = new Net::DNS::RR($SOA);
57726f
+	    push @auth, $rr;
57726f
+	}
57726f
+	$rcode = "NOERROR";
57726f
+    } elsif ($qname eq "example") {
57726f
+	if ($qtype eq "NS") {
57726f
+	    my $rr = new Net::DNS::RR($NS);
57726f
+	    push @auth, $rr;
57726f
+	    $rr = new Net::DNS::RR($A);
57726f
+	    push @add, $rr;
57726f
+	} elsif ($qtype eq "SOA") {
57726f
+	    my $rr = new Net::DNS::RR($SOA);
57726f
+	    push @ans, $rr;
57726f
+	} else {
57726f
+	    my $rr = new Net::DNS::RR($SOA);
57726f
+	    push @auth, $rr;
57726f
+	}
57726f
+	$rcode = "NOERROR";
57726f
+    } elsif ($qname eq "nodata.example") {
57726f
+	my $rr = new Net::DNS::RR($negSOA);
57726f
+	push @auth, $rr;
57726f
+	$rcode = "NOERROR";
57726f
+    } elsif ($qname eq "data.example") {
57726f
+	if ($qtype eq "TXT") {
57726f
+	    my $rr = new Net::DNS::RR($TXT);
57726f
+	    push @ans, $rr;
57726f
+	} else {
57726f
+	    my $rr = new Net::DNS::RR($negSOA);
57726f
+	    push @auth, $rr;
57726f
+	}
57726f
+	$rcode = "NOERROR";
57726f
+    } elsif ($qname eq "nxdomain.example") {
57726f
+	my $rr = new Net::DNS::RR($negSOA);
57726f
+	push @auth, $rr;
57726f
+	$rcode = "NXDOMAIN";
57726f
+    } else {
57726f
+        my $rr = new Net::DNS::RR($SOA);
57726f
+	push @auth, $rr;
57726f
+	$rcode = "NXDOMAIN";
57726f
+    }
57726f
+
57726f
+    # mark the answer as authoritive (by setting the 'aa' flag
57726f
+    return ($rcode, \@ans, \@auth, \@add, { aa => 1 });
57726f
+}
57726f
+
57726f
+GetOptions(
57726f
+    'port=i' => \$localport,
57726f
+);
57726f
+
57726f
+my $rin;
57726f
+my $rout;
57726f
+
57726f
+for (;;) {
57726f
+	$rin = '';
57726f
+	vec($rin, fileno($udpsock), 1) = 1;
57726f
+
57726f
+	select($rout = $rin, undef, undef, undef);
57726f
+
57726f
+	if (vec($rout, fileno($udpsock), 1)) {
57726f
+		my ($buf, $request, $err);
57726f
+		$udpsock->recv($buf, 512);
57726f
+
57726f
+		if ($Net::DNS::VERSION > 0.68) {
57726f
+			$request = new Net::DNS::Packet(\$buf, 0);
57726f
+			$@ and die $@;
57726f
+		} else {
57726f
+			my $err;
57726f
+			($request, $err) = new Net::DNS::Packet(\$buf, 0);
57726f
+			$err and die $err;
57726f
+		}
57726f
+
57726f
+		my @questions = $request->question;
57726f
+		my $qname = $questions[0]->qname;
57726f
+		my $qclass = $questions[0]->qclass;
57726f
+		my $qtype = $questions[0]->qtype;
57726f
+		my $id = $request->header->id;
57726f
+
57726f
+		my ($rcode, $ans, $auth, $add, $headermask) = reply_handler($qname, $qclass, $qtype);
57726f
+
57726f
+		if (!defined($rcode)) {
57726f
+			print "  Silently ignoring query\n";
57726f
+			next;
57726f
+		}
57726f
+
57726f
+		my $reply = Net::DNS::Packet->new();
57726f
+		$reply->header->qr(1);
57726f
+		$reply->header->aa(1) if $headermask->{'aa'};
57726f
+		$reply->header->id($id);
57726f
+		$reply->header->rcode($rcode);
57726f
+		$reply->push("question",   @questions);
57726f
+		$reply->push("answer",     @$ans)  if $ans;
57726f
+		$reply->push("authority",  @$auth) if $auth;
57726f
+		$reply->push("additional", @$add)  if $add;
57726f
+
57726f
+		my $num_chars = $udpsock->send($reply->data);
57726f
+		print "  Sent $num_chars bytes via UDP\n";
57726f
+	}
57726f
+}
57726f
diff --git a/bin/tests/system/serve-stale/clean.sh b/bin/tests/system/serve-stale/clean.sh
57726f
new file mode 100644
57726f
index 0000000..2397326
57726f
--- /dev/null
57726f
+++ b/bin/tests/system/serve-stale/clean.sh
57726f
@@ -0,0 +1,15 @@
57726f
+# Copyright (C) 2017  Internet Systems Consortium, Inc. ("ISC")
57726f
+#
57726f
+# This Source Code Form is subject to the terms of the Mozilla Public
57726f
+# License, v. 2.0. If a copy of the MPL was not distributed with this
57726f
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
57726f
+
57726f
+rm -f test.output
57726f
+rm -f dig.out.test*
57726f
+rm -f ans2/ans.pl
57726f
+rm -f ns3/root.bk
57726f
+rm -f rndc.out.test*
57726f
+rm -f ns*/named.memstats
57726f
+rm -f ns*/managed-keys.bind
57726f
+rm -f ns*/named.conf
57726f
+rm -f ns*/named.run
57726f
diff --git a/bin/tests/system/serve-stale/ns1/named1.conf.in b/bin/tests/system/serve-stale/ns1/named1.conf.in
57726f
new file mode 100644
57726f
index 0000000..8a75a10
57726f
--- /dev/null
57726f
+++ b/bin/tests/system/serve-stale/ns1/named1.conf.in
57726f
@@ -0,0 +1,35 @@
57726f
+/*
57726f
+ * Copyright (C) 2017  Internet Systems Consortium, Inc. ("ISC")
57726f
+ *
57726f
+ * This Source Code Form is subject to the terms of the Mozilla Public
57726f
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
57726f
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
57726f
+ */
57726f
+
57726f
+key rndc_key {
57726f
+        secret "1234abcd8765";
57726f
+        algorithm hmac-sha256;
57726f
+};
57726f
+
57726f
+controls {
57726f
+	inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
57726f
+};
57726f
+
57726f
+options {
57726f
+	query-source address 10.53.0.1;
57726f
+	notify-source 10.53.0.1;
57726f
+	transfer-source 10.53.0.1;
57726f
+	port @PORT@;
57726f
+	pid-file "named.pid";
57726f
+	listen-on { 10.53.0.1; };
57726f
+	listen-on-v6 { none; };
57726f
+	recursion yes;
57726f
+	max-stale-ttl 3600;
57726f
+	stale-answer-ttl 1;
57726f
+	stale-answer-enable yes;
57726f
+};
57726f
+
57726f
+zone "." {
57726f
+	type master;
57726f
+	file "root.db";
57726f
+};
57726f
diff --git a/bin/tests/system/serve-stale/ns1/named2.conf.in b/bin/tests/system/serve-stale/ns1/named2.conf.in
57726f
new file mode 100644
57726f
index 0000000..072e6ec
57726f
--- /dev/null
57726f
+++ b/bin/tests/system/serve-stale/ns1/named2.conf.in
57726f
@@ -0,0 +1,35 @@
57726f
+/*
57726f
+ * Copyright (C) 2017  Internet Systems Consortium, Inc. ("ISC")
57726f
+ *
57726f
+ * This Source Code Form is subject to the terms of the Mozilla Public
57726f
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
57726f
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
57726f
+ */
57726f
+
57726f
+key rndc_key {
57726f
+        secret "1234abcd8765";
57726f
+        algorithm hmac-sha256;
57726f
+};
57726f
+
57726f
+controls {
57726f
+	inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
57726f
+};
57726f
+
57726f
+options {
57726f
+	query-source address 10.53.0.1;
57726f
+	notify-source 10.53.0.1;
57726f
+	transfer-source 10.53.0.1;
57726f
+	port @PORT@;
57726f
+	pid-file "named.pid";
57726f
+	listen-on { 10.53.0.1; };
57726f
+	listen-on-v6 { none; };
57726f
+	recursion yes;
57726f
+	max-stale-ttl 7200;
57726f
+	stale-answer-ttl 2;
57726f
+	stale-answer-enable yes;
57726f
+};
57726f
+
57726f
+zone "." {
57726f
+	type master;
57726f
+	file "root.db";
57726f
+};
57726f
diff --git a/bin/tests/system/serve-stale/ns1/root.db b/bin/tests/system/serve-stale/ns1/root.db
57726f
new file mode 100644
57726f
index 0000000..eb9ad3e
57726f
--- /dev/null
57726f
+++ b/bin/tests/system/serve-stale/ns1/root.db
57726f
@@ -0,0 +1,5 @@
57726f
+.		300	SOA	. . 0 0 0 0 0
57726f
+.		300	NS	ns.nil.
57726f
+ns.nil.		300	A	10.53.0.1
57726f
+example.	300	NS	ns.example.
57726f
+ns.example.	300	A	10.53.0.2
57726f
diff --git a/bin/tests/system/serve-stale/ns3/named.conf.in b/bin/tests/system/serve-stale/ns3/named.conf.in
57726f
new file mode 100644
57726f
index 0000000..24a3293
57726f
--- /dev/null
57726f
+++ b/bin/tests/system/serve-stale/ns3/named.conf.in
57726f
@@ -0,0 +1,35 @@
57726f
+/*
57726f
+ * Copyright (C) 2017  Internet Systems Consortium, Inc. ("ISC")
57726f
+ *
57726f
+ * This Source Code Form is subject to the terms of the Mozilla Public
57726f
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
57726f
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
57726f
+ */
57726f
+
57726f
+key rndc_key {
57726f
+        secret "1234abcd8765";
57726f
+        algorithm hmac-sha256;
57726f
+};
57726f
+
57726f
+controls {
57726f
+	inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
57726f
+};
57726f
+
57726f
+options {
57726f
+	query-source address 10.53.0.3;
57726f
+	notify-source 10.53.0.3;
57726f
+	transfer-source 10.53.0.3;
57726f
+	port @PORT@;
57726f
+	pid-file "named.pid";
57726f
+	listen-on { 10.53.0.3; };
57726f
+	listen-on-v6 { none; };
57726f
+	recursion yes;
57726f
+	// max-stale-ttl 3600;
57726f
+	// stale-answer-ttl 3;
57726f
+};
57726f
+
57726f
+zone "." {
57726f
+	type slave;
57726f
+	masters { 10.53.0.1; };
57726f
+	file "root.bk";
57726f
+};
57726f
diff --git a/bin/tests/system/serve-stale/prereq.sh b/bin/tests/system/serve-stale/prereq.sh
57726f
new file mode 100644
57726f
index 0000000..a3bbef8
57726f
--- /dev/null
57726f
+++ b/bin/tests/system/serve-stale/prereq.sh
57726f
@@ -0,0 +1,38 @@
57726f
+#!/bin/sh
57726f
+#
57726f
+# Copyright (C) 2011, 2012, 2014, 2016  Internet Systems Consortium, Inc. ("ISC")
57726f
+#
57726f
+# This Source Code Form is subject to the terms of the Mozilla Public
57726f
+# License, v. 2.0. If a copy of the MPL was not distributed with this
57726f
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
57726f
+
57726f
+SYSTEMTESTTOP=..
57726f
+. $SYSTEMTESTTOP/conf.sh
57726f
+
57726f
+if $PERL -e 'use Net::DNS;' 2>/dev/null
57726f
+then
57726f
+    if $PERL -e 'use Net::DNS; die if ($Net::DNS::VERSION >= 0.69 && $Net::DNS::VERSION <= 0.74);' 2>/dev/null
57726f
+    then
57726f
+        :
57726f
+    else
57726f
+        echo "I:Net::DNS versions 0.69 to 0.74 have bugs that cause this test to fail: please update." >&2
57726f
+        exit 1
57726f
+    fi
57726f
+else
57726f
+    echo "I:This test requires the Net::DNS library." >&2
57726f
+    exit 1
57726f
+fi
57726f
+if $PERL -e 'use Net::DNS::Nameserver;' 2>/dev/null
57726f
+then
57726f
+	:
57726f
+else
57726f
+	echo "I:This test requires the Net::DNS::Nameserver library." >&2
57726f
+	exit 1
57726f
+fi
57726f
+if $PERL -e 'use Time::HiRes;' 2>/dev/null
57726f
+then
57726f
+	:
57726f
+else
57726f
+	echo "I:This test requires the Time::HiRes library." >&2
57726f
+	exit 1
57726f
+fi
57726f
diff --git a/bin/tests/system/serve-stale/setup.sh b/bin/tests/system/serve-stale/setup.sh
57726f
new file mode 100644
57726f
index 0000000..690f43c
57726f
--- /dev/null
57726f
+++ b/bin/tests/system/serve-stale/setup.sh
57726f
@@ -0,0 +1,13 @@
57726f
+#!/bin/sh
57726f
+# Copyright (C) 2017  Internet Systems Consortium, Inc. ("ISC")
57726f
+#
57726f
+# This Source Code Form is subject to the terms of the Mozilla Public
57726f
+# License, v. 2.0. If a copy of the MPL was not distributed with this
57726f
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
57726f
+
57726f
+SYSTEMTESTTOP=..
57726f
+. $SYSTEMTESTTOP/conf.sh
57726f
+
57726f
+copy_setports ns1/named1.conf.in ns1/named.conf
57726f
+copy_setports ans2/ans.pl.in ans2/ans.pl
57726f
+copy_setports ns3/named.conf.in ns3/named.conf
57726f
diff --git a/bin/tests/system/serve-stale/tests.sh b/bin/tests/system/serve-stale/tests.sh
57726f
new file mode 100755
57726f
index 0000000..201c996
57726f
--- /dev/null
57726f
+++ b/bin/tests/system/serve-stale/tests.sh
57726f
@@ -0,0 +1,536 @@
57726f
+#!/bin/sh
57726f
+#
57726f
+# Copyright (C) 2000, 2001, 2004, 2007, 2009-2016  Internet Systems Consortium, Inc. ("ISC")
57726f
+#
57726f
+# This Source Code Form is subject to the terms of the Mozilla Public
57726f
+# License, v. 2.0. If a copy of the MPL was not distributed with this
57726f
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
57726f
+
57726f
+SYSTEMTESTTOP=..
57726f
+. $SYSTEMTESTTOP/conf.sh
57726f
+
57726f
+while getopts "p:c:" flag; do
57726f
+    case "$flag" in
57726f
+	p) port=$OPTARG ;;
57726f
+	c) controlport=$OPTARG ;;
57726f
+	*) exit 1 ;;
57726f
+    esac
57726f
+done
57726f
+
57726f
+RNDCCMD="$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p ${CONTROLPORT} -s"
57726f
+
57726f
+echo "RNDCCMD: ${RNDCCMD}"
57726f
+
57726f
+status=0
57726f
+n=0
57726f
+
57726f
+#echo "I:check ans.pl server ($n)"
57726f
+#$DIG -p ${PORT} @10.53.0.2 example NS
57726f
+#$DIG -p ${PORT} @10.53.0.2 example SOA
57726f
+#$DIG -p ${PORT} @10.53.0.2 ns.example A
57726f
+#$DIG -p ${PORT} @10.53.0.2 ns.example AAAA
57726f
+#$DIG -p ${PORT} @10.53.0.2 txt enable
57726f
+#$DIG -p ${PORT} @10.53.0.2 txt disable
57726f
+#$DIG -p ${PORT} @10.53.0.2 ns.example AAAA
57726f
+#$DIG -p ${PORT} @10.53.0.2 txt enable
57726f
+#$DIG -p ${PORT} @10.53.0.2 ns.example AAAA
57726f
+##$DIG -p ${PORT} @10.53.0.2 data.example TXT
57726f
+#$DIG -p ${PORT} @10.53.0.2 nodata.example TXT
57726f
+#$DIG -p ${PORT} @10.53.0.2 nxdomain.example TXT
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:prime cache data.example ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.1 data.example TXT > dig.out.test$n
57726f
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
57726f
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:prime cache nodata.example ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.1 nodata.example TXT > dig.out.test$n
57726f
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
57726f
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:prime cache nxdomain.example ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.1 nxdomain.example TXT > dig.out.test$n
57726f
+grep "status: NXDOMAIN" dig.out.test$n > /dev/null || ret=1
57726f
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:disable responses from authoritative server ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.2 txt disable  > dig.out.test$n
57726f
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
57726f
+grep "TXT.\"0\"" dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+sleep 1
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check 'rndc serve-stale status' ($n)"
57726f
+ret=0
57726f
+$RNDCCMD 10.53.0.1 serve-stale status > rndc.out.test$n 2>&1 || ret=1
57726f
+grep '_default: on (stale-answer-ttl=1 max-stale-ttl=3600)' rndc.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check stale data.example ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.1 data.example TXT > dig.out.test$n
57726f
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
57726f
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
57726f
+grep "example.*1.*IN" dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check stale nodata.example ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.1 nodata.example TXT > dig.out.test$n
57726f
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
57726f
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
57726f
+grep "example.*1.*IN" dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check stale nxdomain.example ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.1 nxdomain.example TXT > dig.out.test$n
57726f
+grep "status: NXDOMAIN" dig.out.test$n > /dev/null || ret=1
57726f
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
57726f
+grep "example.*1.*IN" dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:running 'rndc serve-stale off' ($n)"
57726f
+ret=0
57726f
+$RNDCCMD 10.53.0.1 serve-stale off || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check 'rndc serve-stale status' ($n)"
57726f
+ret=0
57726f
+$RNDCCMD 10.53.0.1 serve-stale status > rndc.out.test$n 2>&1 || ret=1
57726f
+grep '_default: off (rndc) (stale-answer-ttl=1 max-stale-ttl=3600)' rndc.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check stale data.example (serve-stale off) ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.1 data.example TXT > dig.out.test$n
57726f
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check stale nodata.example (serve-stale off) ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.1 nodata.example TXT > dig.out.test$n
57726f
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check stale nxdomain.example (serve-stale off) ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.1 nxdomain.example TXT > dig.out.test$n
57726f
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:running 'rndc serve-stale on' ($n)"
57726f
+ret=0
57726f
+$RNDCCMD 10.53.0.1 serve-stale on || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check 'rndc serve-stale status' ($n)"
57726f
+ret=0
57726f
+$RNDCCMD 10.53.0.1 serve-stale status > rndc.out.test$n 2>&1 || ret=1
57726f
+grep '_default: on (rndc) (stale-answer-ttl=1 max-stale-ttl=3600)' rndc.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check stale data.example (serve-stale on) ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.1 data.example TXT > dig.out.test$n
57726f
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
57726f
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
57726f
+grep "example.*1.*IN" dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check stale nodata.example (serve-stale on) ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.1 nodata.example TXT > dig.out.test$n
57726f
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
57726f
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
57726f
+grep "example.*1.*IN" dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check stale nxdomain.example (serve-stale on) ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.1 nxdomain.example TXT > dig.out.test$n
57726f
+grep "status: NXDOMAIN" dig.out.test$n > /dev/null || ret=1
57726f
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
57726f
+grep "example.*1.*IN" dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:running 'rndc serve-stale no' ($n)"
57726f
+ret=0
57726f
+$RNDCCMD 10.53.0.1 serve-stale no || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check 'rndc serve-stale status' ($n)"
57726f
+ret=0
57726f
+$RNDCCMD 10.53.0.1 serve-stale status > rndc.out.test$n 2>&1 || ret=1
57726f
+grep '_default: off (rndc) (stale-answer-ttl=1 max-stale-ttl=3600)' rndc.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check stale data.example (serve-stale no) ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.1 data.example TXT > dig.out.test$n
57726f
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check stale nodata.example (serve-stale no) ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.1 nodata.example TXT > dig.out.test$n
57726f
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check stale nxdomain.example (serve-stale no) ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.1 nxdomain.example TXT > dig.out.test$n
57726f
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:running 'rndc serve-stale yes' ($n)"
57726f
+ret=0
57726f
+$RNDCCMD 10.53.0.1 serve-stale yes || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check 'rndc serve-stale status' ($n)"
57726f
+ret=0
57726f
+$RNDCCMD 10.53.0.1 serve-stale status > rndc.out.test$n 2>&1 || ret=1
57726f
+grep '_default: on (rndc) (stale-answer-ttl=1 max-stale-ttl=3600)' rndc.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check stale data.example (serve-stale yes) ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.1 data.example TXT > dig.out.test$n
57726f
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
57726f
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
57726f
+grep "example.*1.*IN" dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check stale nodata.example (serve-stale yes) ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.1 nodata.example TXT > dig.out.test$n
57726f
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
57726f
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
57726f
+grep "example.*1.*IN" dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check stale nxdomain.example (serve-stale yes) ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.1 nxdomain.example TXT > dig.out.test$n
57726f
+grep "status: NXDOMAIN" dig.out.test$n > /dev/null || ret=1
57726f
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
57726f
+grep "example.*1.*IN" dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:running 'rndc serve-stale off' ($n)"
57726f
+ret=0
57726f
+$RNDCCMD 10.53.0.1 serve-stale off || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:running 'rndc serve-stale reset' ($n)"
57726f
+ret=0
57726f
+$RNDCCMD 10.53.0.1 serve-stale reset || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check 'rndc serve-stale status' ($n)"
57726f
+ret=0
57726f
+$RNDCCMD 10.53.0.1 serve-stale status > rndc.out.test$n 2>&1 || ret=1
57726f
+grep '_default: on (stale-answer-ttl=1 max-stale-ttl=3600)' rndc.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check stale data.example (serve-stale reset) ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.1 data.example TXT > dig.out.test$n
57726f
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
57726f
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
57726f
+grep "example.*1.*IN" dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check stale nodata.example (serve-stale reset) ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.1 nodata.example TXT > dig.out.test$n
57726f
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
57726f
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
57726f
+grep "example.*1.*IN" dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check stale nxdomain.example (serve-stale reset) ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.1 nxdomain.example TXT > dig.out.test$n
57726f
+grep "status: NXDOMAIN" dig.out.test$n > /dev/null || ret=1
57726f
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
57726f
+grep "example.*1.*IN" dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:running 'rndc serve-stale off' ($n)"
57726f
+ret=0
57726f
+$RNDCCMD 10.53.0.1 serve-stale off || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check 'rndc serve-stale status' ($n)"
57726f
+ret=0
57726f
+$RNDCCMD 10.53.0.1 serve-stale status > rndc.out.test$n 2>&1 || ret=1
57726f
+grep '_default: off (rndc) (stale-answer-ttl=1 max-stale-ttl=3600)' rndc.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:updating ns1/named.conf ($n)"
57726f
+ret=0
57726f
+sed -e "s/@PORT@/${PORT}/g;s/@CONTROLPORT@/${CONTROLPORT}/g" < ns1/named2.conf.in > ns1/named.conf
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:running 'rndc reload' ($n)"
57726f
+ret=0
57726f
+$RNDCCMD 10.53.0.1 reload > rndc.out.test$n 2>&1 || ret=1
57726f
+grep "server reload successful" rndc.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check 'rndc serve-stale status' ($n)"
57726f
+ret=0
57726f
+$RNDCCMD 10.53.0.1 serve-stale status > rndc.out.test$n 2>&1 || ret=1
57726f
+grep '_default: off (rndc) (stale-answer-ttl=2 max-stale-ttl=7200)' rndc.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check 'rndc serve-stale' ($n)"
57726f
+ret=0
57726f
+$RNDCCMD 10.53.0.1 serve-stale > rndc.out.test$n 2>&1 && ret=1
57726f
+grep "unexpected end of input" rndc.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check 'rndc serve-stale unknown' ($n)"
57726f
+ret=0
57726f
+$RNDCCMD 10.53.0.1 serve-stale unknown > rndc.out.test$n 2>&1 && ret=1
57726f
+grep "syntax error" rndc.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo_i "flush cache, re-enable serve-stale and query again ($n)"
57726f
+ret=0
57726f
+$RNDCCMD 10.53.0.1 flushtree example > rndc.out.test$n.1 2>&1 || ret=1
57726f
+$RNDCCMD 10.53.0.1 serve-stale on > rndc.out.test$n.2 2>&1 || ret=1
57726f
+$DIG -p ${PORT} @10.53.0.1 data.example TXT > dig.out.test$n
57726f
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
57726f
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo_i "failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.2 txt enable  > dig.out.test$n
57726f
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
57726f
+grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:prime cache data.example (max-stale-ttl default) ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
57726f
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
57726f
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
57726f
+grep "example.*1.*IN" dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:prime cache nodata.example (max-stale-ttl default) ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.3 nodata.example TXT > dig.out.test$n
57726f
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
57726f
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
57726f
+grep "example.*1.*IN" dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:prime cache nxdomain.example (max-stale-ttl default) ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.3 nxdomain.example TXT > dig.out.test$n
57726f
+grep "status: NXDOMAIN" dig.out.test$n > /dev/null || ret=1
57726f
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
57726f
+grep "example.*1.*IN" dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:disable responses from authoritative server ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.2 txt disable  > dig.out.test$n
57726f
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
57726f
+grep "TXT.\"0\"" dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+sleep 1
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check 'rndc serve-stale status' ($n)"
57726f
+ret=0
57726f
+$RNDCCMD 10.53.0.3 serve-stale status > rndc.out.test$n 2>&1 || ret=1
57726f
+grep '_default: off (stale-answer-ttl=1 max-stale-ttl=604800)' rndc.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check fail of data.example (max-stale-ttl default) ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
57726f
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
57726f
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check fail of nodata.example (max-stale-ttl default) ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.3 nodata.example TXT > dig.out.test$n
57726f
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
57726f
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check fail of nxdomain.example (max-stale-ttl default) ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.3 nxdomain.example TXT > dig.out.test$n
57726f
+grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1
57726f
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check 'rndc serve-stale on' ($n)"
57726f
+ret=0
57726f
+$RNDCCMD 10.53.0.3 serve-stale on > rndc.out.test$n 2>&1 || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check 'rndc serve-stale status' ($n)"
57726f
+ret=0
57726f
+$RNDCCMD 10.53.0.3 serve-stale status > rndc.out.test$n 2>&1 || ret=1
57726f
+grep '_default: on (rndc) (stale-answer-ttl=1 max-stale-ttl=604800)' rndc.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check data.example (max-stale-ttl default) ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.3 data.example TXT > dig.out.test$n
57726f
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
57726f
+grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1
57726f
+grep "example.*1.*IN" dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check nodata.example (max-stale-ttl default) ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.3 nodata.example TXT > dig.out.test$n
57726f
+grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1
57726f
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
57726f
+grep "example.*1.*IN" dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+n=`expr $n + 1`
57726f
+echo "I:check nxdomain.example (max-stale-ttl default) ($n)"
57726f
+ret=0
57726f
+$DIG -p ${PORT} @10.53.0.3 nxdomain.example TXT > dig.out.test$n
57726f
+grep "status: NXDOMAIN" dig.out.test$n > /dev/null || ret=1
57726f
+grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1
57726f
+grep "example.*1.*IN" dig.out.test$n > /dev/null || ret=1
57726f
+if [ $ret != 0 ]; then echo "I:failed"; fi
57726f
+status=`expr $status + $ret`
57726f
+
57726f
+echo "I:exit status: $status"
57726f
+[ $status -eq 0 ] || exit 1
57726f
diff --git a/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml
06d839
index 99c8680..5fbabfe 100644
57726f
--- a/doc/arm/Bv9ARM-book.xml
57726f
+++ b/doc/arm/Bv9ARM-book.xml
e48f32
@@ -4336,6 +4336,9 @@ badresp:1,adberr:0,findfail:0,valfail:0]
57726f
 	  statement in the <filename>named.conf</filename> file:
57726f
 	</para>
57726f
 	<xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="options.grammar.xml"/>
57726f
+  [ <command>max-stale-ttl</command> <replaceable>number</replaceable> ; ]
57726f
+  [ <command>stale-answer-enable</command> <replaceable>yes_or_no</replaceable> ; ]
57726f
+  [ <command>stale-answer-ttl</command> <replaceable>number</replaceable> ; ]
57726f
       </section>
57726f
 
57726f
       <section xml:id="options"><info><title><command>options</command> Statement Definition and
e48f32
@@ -4429,6 +4432,7 @@ badresp:1,adberr:0,findfail:0,valfail:0]
57726f
 		  <command>dnssec-validation</command>,
57726f
 		  <command>max-cache-ttl</command>,
57726f
 		  <command>max-ncache-ttl</command>,
57726f
+		  <command>max-stale-ttl</command>,
57726f
 		  <command>max-cache-size</command>, and
57726f
 		  <command>zero-no-soa-ttl</command>.
57726f
 		</para>
e48f32
@@ -5438,7 +5442,6 @@ options {
57726f
 	    </listitem>
57726f
 	  </varlistentry>
57726f
 
57726f
-
57726f
 	  <varlistentry>
57726f
 	    <term><command>max-zone-ttl</command></term>
57726f
 	    <listitem>
e48f32
@@ -5474,6 +5477,21 @@ options {
57726f
 	    </listitem>
57726f
 	  </varlistentry>
57726f
 
57726f
+	  <varlistentry>
57726f
+	    <term><command>stale-answer-ttl</command></term>
57726f
+	    <listitem>
57726f
+	      <para>
57726f
+		Specifies the TTL to be returned on stale answers.
57726f
+		The default is 1 second. The minimal allowed is
57726f
+		also 1 second; a value of 0 will be updated silently
57726f
+		to 1 second.  For stale answers to be returned
57726f
+		<option>max-stale-ttl</option> must be set to a
57726f
+		non zero value and they must not have been disabled
57726f
+		by <command>rndc</command>.
57726f
+	      </para>
57726f
+	    </listitem>
57726f
+	  </varlistentry>
57726f
+
57726f
 	  <varlistentry>
57726f
 	    <term><command>serial-update-method</command></term>
57726f
 	    <listitem>
e48f32
@@ -6227,6 +6245,22 @@ options {
57726f
 	      </listitem>
57726f
 	    </varlistentry>
57726f
 
57726f
+	    <varlistentry>
57726f
+	      <term><command>serve-stale-enable</command></term>
57726f
+	      <listitem>
57726f
+		<para>
57726f
+		  Enable the returning of stale answers when the
57726f
+		  nameservers for the zone are not answering.  This
57726f
+		  is off by default but can be enabled/disabled via
57726f
+		  <command>rndc server-stale on</command> and
57726f
+		  <command>rndc server-stale off</command> which
57726f
+		  override the named.conf setting.  <command>rndc
57726f
+		  server-stale reset</command> will restore control
57726f
+		  via named.conf.
57726f
+		</para>
57726f
+	      </listitem>
57726f
+	    </varlistentry>
57726f
+
57726f
 	    <varlistentry>
57726f
 	      <term><command>nocookie-udp-size</command></term>
57726f
 	      <listitem>
06d839
@@ -7449,13 +7483,19 @@ options {
57726f
 	      <term><command>resolver-query-timeout</command></term>
57726f
 	      <listitem>
57726f
 		<para>
e48f32
-		  This is the amount of time in seconds that the
e48f32
-		  resolver spends attempting to resolve a recursive
e48f32
-		  query before failing.  The default and minimum
57726f
-		  is <literal>10</literal> and the maximum is
57726f
-		  <literal>30</literal>.  Setting it to
e48f32
-		  <literal>0</literal> results in the default
e48f32
-		  being used.
e48f32
+		  The amount of time in milliseconds that the resolver
e48f32
+ 		  will spend attempting to resolve a recursive
e48f32
+ 		  query before failing.  The default and minimum
57726f
+		  is <literal>10000</literal> and the maximum is
57726f
+		  <literal>30000</literal>.  Setting it to
e48f32
+ 		  <literal>0</literal> will result in the default
e48f32
+ 		  being used.
e48f32
+ 		</para>
57726f
+		<para>
57726f
+		  This value was originally specified in seconds.
57726f
+		  Values less than or equal to 300 will be be treated
57726f
+		  as seconds and converted to milliseconds before
57726f
+		  applying the above limits.
e48f32
 		</para>
57726f
 	      </listitem>
57726f
 	    </varlistentry>
06d839
@@ -9016,6 +9056,27 @@ avoid-v6-udp-ports { 40000; range 50000 60000; };
57726f
 	      </listitem>
57726f
 	    </varlistentry>
57726f
 
57726f
+	    <varlistentry>
57726f
+	      <term><command>max-stale-ttl</command></term>
57726f
+	      <listitem>
57726f
+		<para>
57726f
+		  Sets the maximum time for which the server will
57726f
+		  retain records past their normal expiry to
57726f
+		  return them as stale records when the servers
57726f
+		  for those records are not reachable.  The default
57726f
+		  is to not retain the record.
57726f
+		</para>
57726f
+		<para>
57726f
+		  <command>rndc serve-stale</command> can be used
57726f
+		  to disable and re-enable the serving of stale
57726f
+		  records at runtime.  Reloading or reconfiguring
57726f
+		  <command>named</command> will not re-enable serving
57726f
+		  of stale records if they have been disabled via
57726f
+		  <command>rndc</command>.
57726f
+		</para>
57726f
+	      </listitem>
57726f
+	    </varlistentry>
57726f
+
57726f
 	    <varlistentry>
57726f
 	      <term><command>min-roots</command></term>
57726f
 	      <listitem>
57726f
diff --git a/doc/arm/logging-categories.xml b/doc/arm/logging-categories.xml
06d839
index 56d05e8..098342b 100644
57726f
--- a/doc/arm/logging-categories.xml
57726f
+++ b/doc/arm/logging-categories.xml
57726f
@@ -311,6 +311,17 @@
57726f
 	  </para>
57726f
 	</entry>
57726f
       </row>
57726f
+      <row rowsep="0">
57726f
+	<entry colname="1">
57726f
+	  <para><command>serve-stale</command></para>
57726f
+	</entry>
57726f
+	<entry colname="2">
57726f
+	  <para>
57726f
+	    Whether or not a stale answer is used
57726f
+	    following a resolver failure.
57726f
+	  </para>
57726f
+	</entry>
57726f
+      </row>
57726f
       <row rowsep="0">
57726f
 	<entry colname="1">
57726f
 	  <para><command>spill</command></para>
57726f
diff --git a/doc/arm/notes-rh-changes.xml b/doc/arm/notes-rh-changes.xml
f79ca9
index 89a4961..80b7dee 100644
57726f
--- a/doc/arm/notes-rh-changes.xml
57726f
+++ b/doc/arm/notes-rh-changes.xml
f79ca9
@@ -12,6 +12,9 @@
57726f
 <section xml:id="relnotes_rh_changes"><info><title>Red Hat Specific Changes</title></info>
57726f
   <itemizedlist>
57726f
      <listitem>
57726f
+      <para>
57726f
+        This version includes some features not present in releases by ISC.
57726f
+      </para>
57726f
       <para>
57726f
         By default, BIND now uses the random number generation functions
57726f
         in the cryptographic library (i.e., OpenSSL or a PKCS#11
f79ca9
@@ -36,7 +39,16 @@
57726f
         case <filename>/dev/random</filename> will be the default
57726f
         entropy source.  [RT #31459] [RT #46047]
57726f
       </para>
57726f
-    </listitem>
57726f
+      <para>
57726f
+        When acting as a recursive resolver, <command>named</command>
57726f
+        can now continue returning answers whose TTLs have expired
57726f
+        when the authoritative server is under attack and unable to
57726f
+        respond. This is controlled by the
57726f
+        <command>stale-answer-enable</command>,
57726f
+        <command>stale-answer-ttl</command> and
57726f
+        <command>max-stale-ttl</command> options. [RT #44790]
57726f
+      </para>
57726f
+     </listitem>
57726f
   </itemizedlist>
57726f
 </section>
57726f
 
57726f
diff --git a/doc/misc/options b/doc/misc/options
57726f
index e11beed..fde93c7 100644
57726f
--- a/doc/misc/options
57726f
+++ b/doc/misc/options
57726f
@@ -225,6 +225,7 @@ options {
57726f
         max-refresh-time <integer>;
57726f
         max-retry-time <integer>;
57726f
         max-rsa-exponent-size <integer>;
57726f
+        max-stale-ttl <ttlval>;
57726f
         max-transfer-idle-in <integer>;
57726f
         max-transfer-idle-out <integer>;
57726f
         max-transfer-time-in <integer>;
57726f
@@ -298,7 +299,9 @@ options {
57726f
         request-sit <boolean>; // obsolete
57726f
         require-server-cookie <boolean>;
57726f
         reserved-sockets <integer>;
57726f
+        resolver-nonbackoff-tries <integer>;
57726f
         resolver-query-timeout <integer>;
57726f
+        resolver-retry-interval <integer>;
57726f
         response-policy { zone <string> [ log <boolean> ] [ max-policy-ttl
57726f
             <integer> ] [ policy ( cname | disabled | drop | given | no-op
57726f
             | nodata | nxdomain | passthru | tcp-only <quoted_string> ) ] [
57726f
@@ -328,6 +331,8 @@ options {
57726f
         sit-secret <string>; // obsolete
57726f
         sortlist { <address_match_element>; ... };
57726f
         stacksize ( default | unlimited | <sizeval> );
57726f
+        stale-answer-enable <boolean>;
57726f
+        stale-answer-ttl <ttlval>;
57726f
         startup-notify-rate <integer>;
57726f
         statistics-file <quoted_string>;
57726f
         statistics-interval <integer>; // not yet implemented
57726f
@@ -539,6 +544,7 @@ view <string> [ <class> ] {
57726f
         max-recursion-queries <integer>;
57726f
         max-refresh-time <integer>;
57726f
         max-retry-time <integer>;
57726f
+        max-stale-ttl <ttlval>;
57726f
         max-transfer-idle-in <integer>;
57726f
         max-transfer-idle-out <integer>;
57726f
         max-transfer-time-in <integer>;
57726f
@@ -600,7 +606,9 @@ view <string> [ <class> ] {
57726f
         request-nsid <boolean>;
57726f
         request-sit <boolean>; // obsolete
57726f
         require-server-cookie <boolean>;
57726f
+        resolver-nonbackoff-tries <integer>;
57726f
         resolver-query-timeout <integer>;
57726f
+        resolver-retry-interval <integer>;
57726f
         response-policy { zone <string> [ log <boolean> ] [ max-policy-ttl
57726f
             <integer> ] [ policy ( cname | disabled | drop | given | no-op
57726f
             | nodata | nxdomain | passthru | tcp-only <quoted_string> ) ] [
57726f
@@ -655,6 +663,8 @@ view <string> [ <class> ] {
57726f
         sig-signing-type <integer>;
57726f
         sig-validity-interval <integer> [ <integer> ];
57726f
         sortlist { <address_match_element>; ... };
57726f
+        stale-answer-enable <boolean>;
57726f
+        stale-answer-ttl <ttlval>;
57726f
         suppress-initial-notify <boolean>; // not yet implemented
57726f
         topology { <address_match_element>; ... }; // not implemented
57726f
         transfer-format ( many-answers | one-answer );
57726f
diff --git a/lib/bind9/check.c b/lib/bind9/check.c
e48f32
index bf769fe..6c57fa4 100644
57726f
--- a/lib/bind9/check.c
57726f
+++ b/lib/bind9/check.c
57726f
@@ -99,7 +99,8 @@ check_orderent(const cfg_obj_t *ent, isc_log_t *logctx) {
57726f
 			cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
57726f
 				    "rrset-order: invalid class '%s'",
57726f
 				    r.base);
57726f
-			result = ISC_R_FAILURE;
57726f
+			if (result == ISC_R_SUCCESS)
57726f
+				result = ISC_R_FAILURE;
57726f
 		}
57726f
 	}
57726f
 
57726f
@@ -112,7 +113,8 @@ check_orderent(const cfg_obj_t *ent, isc_log_t *logctx) {
57726f
 			cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
57726f
 				    "rrset-order: invalid type '%s'",
57726f
 				    r.base);
57726f
-			result = ISC_R_FAILURE;
57726f
+			if (result == ISC_R_SUCCESS)
57726f
+				result = ISC_R_FAILURE;
57726f
 		}
57726f
 	}
57726f
 
57726f
@@ -126,7 +128,8 @@ check_orderent(const cfg_obj_t *ent, isc_log_t *logctx) {
57726f
 		if (tresult != ISC_R_SUCCESS) {
57726f
 			cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
57726f
 				    "rrset-order: invalid name '%s'", str);
57726f
-			result = ISC_R_FAILURE;
57726f
+			if (result == ISC_R_SUCCESS)
57726f
+				result = ISC_R_FAILURE;
57726f
 		}
57726f
 	}
57726f
 
57726f
@@ -135,14 +138,16 @@ check_orderent(const cfg_obj_t *ent, isc_log_t *logctx) {
57726f
 	    strcasecmp("order", cfg_obj_asstring(obj)) != 0) {
57726f
 		cfg_obj_log(ent, logctx, ISC_LOG_ERROR,
57726f
 			    "rrset-order: keyword 'order' missing");
57726f
-		result = ISC_R_FAILURE;
57726f
+		if (result == ISC_R_SUCCESS)
57726f
+			result = ISC_R_FAILURE;
57726f
 	}
57726f
 
57726f
 	obj = cfg_tuple_get(ent, "ordering");
57726f
 	if (!cfg_obj_isstring(obj)) {
57726f
 	    cfg_obj_log(ent, logctx, ISC_LOG_ERROR,
57726f
 			"rrset-order: missing ordering");
57726f
-		result = ISC_R_FAILURE;
57726f
+		if (result == ISC_R_SUCCESS)
57726f
+			result = ISC_R_FAILURE;
57726f
 	} else if (strcasecmp(cfg_obj_asstring(obj), "fixed") == 0) {
57726f
 #if !DNS_RDATASET_FIXED
57726f
 		cfg_obj_log(obj, logctx, ISC_LOG_WARNING,
57726f
@@ -154,7 +159,8 @@ check_orderent(const cfg_obj_t *ent, isc_log_t *logctx) {
57726f
 		cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
57726f
 			    "rrset-order: invalid order '%s'",
57726f
 			    cfg_obj_asstring(obj));
57726f
-		result = ISC_R_FAILURE;
57726f
+		if (result == ISC_R_SUCCESS)
57726f
+			result = ISC_R_FAILURE;
57726f
 	}
57726f
 	return (result);
57726f
 }
57726f
@@ -174,7 +180,7 @@ check_order(const cfg_obj_t *options, isc_log_t *logctx) {
57726f
 	     element = cfg_list_next(element))
57726f
 	{
57726f
 		tresult = check_orderent(cfg_listelt_value(element), logctx);
57726f
-		if (tresult != ISC_R_SUCCESS)
57726f
+		if (result == ISC_R_SUCCESS && tresult != ISC_R_SUCCESS)
57726f
 			result = tresult;
57726f
 	}
57726f
 	return (result);
57726f
@@ -204,7 +210,8 @@ check_dual_stack(const cfg_obj_t *options, isc_log_t *logctx) {
57726f
 		if (val > UINT16_MAX) {
57726f
 			cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
57726f
 				    "port '%u' out of range", val);
57726f
-			result = ISC_R_FAILURE;
57726f
+			if (result == ISC_R_SUCCESS)
57726f
+				result = ISC_R_RANGE;
57726f
 		}
57726f
 	}
57726f
 	obj = cfg_tuple_get(alternates, "addresses");
57726f
@@ -224,7 +231,8 @@ check_dual_stack(const cfg_obj_t *options, isc_log_t *logctx) {
57726f
 		if (tresult != ISC_R_SUCCESS) {
57726f
 			cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
57726f
 				    "bad name '%s'", str);
57726f
-			result = ISC_R_FAILURE;
57726f
+			if (result == ISC_R_SUCCESS)
57726f
+				result = tresult;
57726f
 		}
57726f
 		obj = cfg_tuple_get(value, "port");
57726f
 		if (cfg_obj_isuint32(obj)) {
57726f
@@ -232,7 +240,8 @@ check_dual_stack(const cfg_obj_t *options, isc_log_t *logctx) {
57726f
 			if (val > UINT16_MAX) {
57726f
 				cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
57726f
 					    "port '%u' out of range", val);
57726f
-				result = ISC_R_FAILURE;
57726f
+				if (result == ISC_R_SUCCESS)
57726f
+					result = ISC_R_RANGE;
57726f
 			}
57726f
 		}
57726f
 	}
f79ca9
@@ -1271,7 +1280,8 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx,
57726f
 			cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
57726f
 				    "auto-dnssec may only be activated at the "
57726f
 				    "zone level");
57726f
-			result = ISC_R_FAILURE;
57726f
+			if (result == ISC_R_SUCCESS)
57726f
+				result = ISC_R_FAILURE;
57726f
 		}
57726f
 	}
57726f
 
f79ca9
@@ -1291,7 +1301,7 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx,
57726f
 		{
57726f
 			obj = cfg_listelt_value(element);
57726f
 			tresult = mustbesecure(obj, symtab, logctx, mctx);
57726f
-			if (tresult != ISC_R_SUCCESS)
57726f
+			if (result == ISC_R_SUCCESS && tresult != ISC_R_SUCCESS)
57726f
 				result = tresult;
57726f
 		}
57726f
 		if (symtab != NULL)
f79ca9
@@ -1310,7 +1320,8 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx,
57726f
 				cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
57726f
 					    "%s: invalid name '%s'",
57726f
 					    server_contact[i], str);
57726f
-				result = ISC_R_FAILURE;
57726f
+				if (result == ISC_R_SUCCESS)
57726f
+					result = ISC_R_FAILURE;
57726f
 			}
57726f
 		}
57726f
 	}
f79ca9
@@ -1330,7 +1341,8 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx,
57726f
 			cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
57726f
 				    "disable-empty-zone: invalid name '%s'",
57726f
 				    str);
57726f
-			result = ISC_R_FAILURE;
57726f
+			if (result == ISC_R_SUCCESS)
57726f
+				result = ISC_R_FAILURE;
57726f
 		}
57726f
 	}
57726f
 
f79ca9
@@ -1344,11 +1356,12 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx,
57726f
 	    strlen(cfg_obj_asstring(obj)) > 1024U) {
57726f
 		cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
57726f
 			    "'server-id' too big (>1024 bytes)");
57726f
-		result = ISC_R_FAILURE;
57726f
+		if (result == ISC_R_SUCCESS)
57726f
+			result = ISC_R_FAILURE;
57726f
 	}
57726f
 
57726f
 	tresult = check_dscp(options, logctx);
57726f
-	if (tresult != ISC_R_SUCCESS)
57726f
+	if (result == ISC_R_SUCCESS && tresult != ISC_R_SUCCESS)
57726f
 		result = tresult;
57726f
 
57726f
 	obj = NULL;
f79ca9
@@ -1358,11 +1371,13 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx,
57726f
 		if (lifetime > 604800) {	/* 7 days */
57726f
 			cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
57726f
 				    "'nta-lifetime' cannot exceed one week");
57726f
-			result = ISC_R_RANGE;
57726f
+			if (result == ISC_R_SUCCESS)
57726f
+				result = ISC_R_RANGE;
57726f
 		} else if (lifetime == 0) {
57726f
 			cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
57726f
 				    "'nta-lifetime' may not be zero");
57726f
-			result = ISC_R_RANGE;
57726f
+			if (result == ISC_R_SUCCESS)
57726f
+				result = ISC_R_RANGE;
57726f
 		}
57726f
 	}
57726f
 
f79ca9
@@ -1373,7 +1388,8 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx,
57726f
 		if (recheck > 604800) {		/* 7 days */
57726f
 			cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
57726f
 				    "'nta-recheck' cannot exceed one week");
57726f
-			result = ISC_R_RANGE;
57726f
+			if (result == ISC_R_SUCCESS)
57726f
+				result = ISC_R_RANGE;
57726f
 		}
57726f
 
57726f
 		if (recheck > lifetime)
f79ca9
@@ -1391,7 +1407,8 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx,
57726f
 	if (strcasecmp(ccalg, "aes") == 0) {
57726f
 		cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
57726f
 			    "cookie-algorithm: '%s' not supported", ccalg);
57726f
-		result = ISC_R_NOTIMPLEMENTED;
57726f
+		if (result == ISC_R_SUCCESS)
57726f
+			result = ISC_R_NOTIMPLEMENTED;
57726f
 	}
57726f
 #endif
57726f
 
f79ca9
@@ -1480,7 +1497,8 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx,
57726f
 				cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
57726f
 					    "%s out of range (%u < %u)",
57726f
 					    fstrm[i].name, value, fstrm[i].min);
57726f
-			result = ISC_R_RANGE;
57726f
+			if (result == ISC_R_SUCCESS)
57726f
+				result = ISC_R_RANGE;
57726f
 		}
57726f
 
57726f
 		if (strcmp(fstrm[i].name, "fstrm-set-input-queue-size") == 0) {
f79ca9
@@ -1494,7 +1512,8 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx,
57726f
 					    "%s '%u' not a power-of-2",
57726f
 					    fstrm[i].name,
57726f
 					    cfg_obj_asuint32(obj));
57726f
-				result = ISC_R_RANGE;
57726f
+				if (result == ISC_R_SUCCESS)
57726f
+					result = ISC_R_RANGE;
57726f
 			}
57726f
 		}
57726f
 	}
f79ca9
@@ -1512,7 +1531,8 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx,
57726f
 				    "%" PRId64 "' "
57726f
 				    "is too small",
57726f
 				    mapsize);
57726f
-			return (ISC_R_RANGE);
57726f
+			if (result == ISC_R_SUCCESS)
57726f
+				result = ISC_R_RANGE;
57726f
 		} else if (mapsize > (1ULL << 40)) { /* 1 terabyte */
57726f
 			cfg_obj_log(obj, logctx,
57726f
 				    ISC_LOG_ERROR,
f79ca9
@@ -1520,10 +1540,20 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx,
57726f
 				    "%" PRId64 "' "
57726f
 				    "is too large",
57726f
 				    mapsize);
57726f
-			return (ISC_R_RANGE);
57726f
+			if (result == ISC_R_SUCCESS)
57726f
+				result = ISC_R_RANGE;
57726f
 		}
57726f
 	}
57726f
 
57726f
+	obj = NULL;
57726f
+	(void)cfg_map_get(options, "resolver-nonbackoff-tries", &obj);
57726f
+	if (obj != NULL && cfg_obj_asuint32(obj) == 0U) {
57726f
+		cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
57726f
+			    "'resolver-nonbackoff-tries' must be >= 1");
57726f
+		if (result == ISC_R_SUCCESS)
57726f
+			result = ISC_R_RANGE;
57726f
+	}
57726f
+
57726f
 	return (result);
57726f
 }
57726f
 
57726f
diff --git a/lib/dns/cache.c b/lib/dns/cache.c
e48f32
index 2965a4f..617737a 100644
57726f
--- a/lib/dns/cache.c
57726f
+++ b/lib/dns/cache.c
57726f
@@ -138,6 +138,7 @@ struct dns_cache {
57726f
 	int			db_argc;
57726f
 	char			**db_argv;
57726f
 	size_t			size;
57726f
+	dns_ttl_t		serve_stale_ttl;
57726f
 	isc_stats_t		*stats;
57726f
 
57726f
 	/* Locked by 'filelock'. */
57726f
@@ -167,9 +168,13 @@ overmem_cleaning_action(isc_task_t *task, isc_event_t *event);
57726f
 
57726f
 static inline isc_result_t
57726f
 cache_create_db(dns_cache_t *cache, dns_db_t **db) {
57726f
-	return (dns_db_create(cache->mctx, cache->db_type, dns_rootname,
57726f
-			      dns_dbtype_cache, cache->rdclass,
57726f
-			      cache->db_argc, cache->db_argv, db));
57726f
+	isc_result_t result;
57726f
+	result = dns_db_create(cache->mctx, cache->db_type, dns_rootname,
57726f
+			       dns_dbtype_cache, cache->rdclass,
57726f
+			       cache->db_argc, cache->db_argv, db);
57726f
+	if (result == ISC_R_SUCCESS)
57726f
+		dns_db_setservestalettl(*db, cache->serve_stale_ttl);
57726f
+	return (result);
57726f
 }
57726f
 
57726f
 isc_result_t
57726f
@@ -238,6 +243,7 @@ dns_cache_create3(isc_mem_t *cmctx, isc_mem_t *hmctx, isc_taskmgr_t *taskmgr,
57726f
 	cache->references = 1;
57726f
 	cache->live_tasks = 0;
57726f
 	cache->rdclass = rdclass;
57726f
+	cache->serve_stale_ttl = 0;
57726f
 
57726f
 	cache->stats = NULL;
57726f
 	result = isc_stats_create(cmctx, &cache->stats,
57726f
@@ -1092,6 +1098,32 @@ dns_cache_getcachesize(dns_cache_t *cache) {
57726f
 	return (size);
57726f
 }
57726f
 
57726f
+void
57726f
+dns_cache_setservestalettl(dns_cache_t *cache, dns_ttl_t ttl) {
57726f
+	REQUIRE(VALID_CACHE(cache));
57726f
+
57726f
+	LOCK(&cache->lock);
57726f
+	cache->serve_stale_ttl = ttl;
57726f
+	UNLOCK(&cache->lock);
57726f
+
57726f
+	(void)dns_db_setservestalettl(cache->db, ttl);
57726f
+}
57726f
+
57726f
+dns_ttl_t
57726f
+dns_cache_getservestalettl(dns_cache_t *cache) {
57726f
+	dns_ttl_t ttl;
57726f
+	isc_result_t result;
57726f
+
57726f
+	REQUIRE(VALID_CACHE(cache));
57726f
+
57726f
+	/*
57726f
+	 * Could get it straight from the dns_cache_t, but use db
57726f
+	 * to confirm the value that the db is really using.
57726f
+	 */
57726f
+	result = dns_db_getservestalettl(cache->db, &ttl);
57726f
+	return result == ISC_R_SUCCESS ? ttl : 0;
57726f
+}
57726f
+
57726f
 /*
57726f
  * The cleaner task is shutting down; do the necessary cleanup.
57726f
  */
57726f
diff --git a/lib/dns/db.c b/lib/dns/db.c
e48f32
index a28a566..c581646 100644
57726f
--- a/lib/dns/db.c
57726f
+++ b/lib/dns/db.c
57726f
@@ -1130,3 +1130,25 @@ dns_db_nodefullname(dns_db_t *db, dns_dbnode_t *node, dns_name_t *name) {
57726f
 		return (ISC_R_NOTIMPLEMENTED);
57726f
 	return ((db->methods->nodefullname)(db, node, name));
57726f
 }
57726f
+
57726f
+isc_result_t
57726f
+dns_db_setservestalettl(dns_db_t *db, dns_ttl_t ttl)
57726f
+{
57726f
+	REQUIRE(DNS_DB_VALID(db));
57726f
+	REQUIRE((db->attributes & DNS_DBATTR_CACHE) != 0);
57726f
+
57726f
+	if (db->methods->setservestalettl != NULL)
57726f
+		return ((db->methods->setservestalettl)(db, ttl));
57726f
+	return (ISC_R_NOTIMPLEMENTED);
57726f
+}
57726f
+
57726f
+isc_result_t
57726f
+dns_db_getservestalettl(dns_db_t *db, dns_ttl_t *ttl)
57726f
+{
57726f
+	REQUIRE(DNS_DB_VALID(db));
57726f
+	REQUIRE((db->attributes & DNS_DBATTR_CACHE) != 0);
57726f
+
57726f
+	if (db->methods->getservestalettl != NULL)
57726f
+		return ((db->methods->getservestalettl)(db, ttl));
57726f
+	return (ISC_R_NOTIMPLEMENTED);
57726f
+}
57726f
diff --git a/lib/dns/ecdb.c b/lib/dns/ecdb.c
e48f32
index fc94ccf..76d0417 100644
57726f
--- a/lib/dns/ecdb.c
57726f
+++ b/lib/dns/ecdb.c
57726f
@@ -588,7 +588,9 @@ static dns_dbmethods_t ecdb_methods = {
57726f
 	NULL,			/* setcachestats */
57726f
 	NULL,			/* hashsize */
57726f
 	NULL,			/* nodefullname */
57726f
-	NULL			/* getsize */
57726f
+	NULL,			/* getsize */
57726f
+	NULL,			/* setservestalettl */
57726f
+	NULL			/* getservestalettl */
57726f
 };
57726f
 
57726f
 static isc_result_t
57726f
diff --git a/lib/dns/include/dns/cache.h b/lib/dns/include/dns/cache.h
e48f32
index ab4b0b5..e158014 100644
57726f
--- a/lib/dns/include/dns/cache.h
57726f
+++ b/lib/dns/include/dns/cache.h
57726f
@@ -260,6 +260,27 @@ dns_cache_getcachesize(dns_cache_t *cache);
57726f
  * Get the maximum cache size.
57726f
  */
57726f
 
57726f
+void
57726f
+dns_cache_setservestalettl(dns_cache_t *cache, dns_ttl_t ttl);
57726f
+/*%<
57726f
+ * Sets the maximum length of time that cached answers may be retained
57726f
+ * past their normal TTL.  Default value for the library is 0, disabling
57726f
+ * the use of stale data.
57726f
+ *
57726f
+ * Requires:
57726f
+ *\li	'cache' to be valid.
57726f
+ */
57726f
+
57726f
+dns_ttl_t
57726f
+dns_cache_getservestalettl(dns_cache_t *cache);
57726f
+/*%<
57726f
+ * Gets the maximum length of time that cached answers may be kept past
57726f
+ * normal expiry.
57726f
+ *
57726f
+ * Requires:
57726f
+ *\li	'cache' to be valid.
57726f
+ */
57726f
+
57726f
 isc_result_t
57726f
 dns_cache_flush(dns_cache_t *cache);
57726f
 /*%<
57726f
diff --git a/lib/dns/include/dns/db.h b/lib/dns/include/dns/db.h
e48f32
index 96f3a8f..452770f 100644
57726f
--- a/lib/dns/include/dns/db.h
57726f
+++ b/lib/dns/include/dns/db.h
f79ca9
@@ -195,6 +195,8 @@ typedef struct dns_dbmethods {
57726f
 					dns_name_t *name);
57726f
 	isc_result_t	(*getsize)(dns_db_t *db, dns_dbversion_t *version,
57726f
 				   uint64_t *records, uint64_t *bytes);
57726f
+	isc_result_t	(*setservestalettl)(dns_db_t *db, dns_ttl_t ttl);
57726f
+	isc_result_t	(*getservestalettl)(dns_db_t *db, dns_ttl_t *ttl);
57726f
 } dns_dbmethods_t;
57726f
 
57726f
 typedef isc_result_t
f79ca9
@@ -253,6 +255,7 @@ struct dns_dbonupdatelistener {
57726f
 #define DNS_DBFIND_FORCENSEC3		0x0080
57726f
 #define DNS_DBFIND_ADDITIONALOK		0x0100
57726f
 #define DNS_DBFIND_NOZONECUT		0x0200
57726f
+#define DNS_DBFIND_STALEOK		0x0400
57726f
 /*@}*/
57726f
 
57726f
 /*@{*/
f79ca9
@@ -1683,6 +1686,38 @@ dns_db_nodefullname(dns_db_t *db, dns_dbnode_t *node, dns_name_t *name);
57726f
  * \li	'db' is a valid database
57726f
  * \li	'node' and 'name' are not NULL
57726f
  */
57726f
+
57726f
+isc_result_t
57726f
+dns_db_setservestalettl(dns_db_t *db, dns_ttl_t ttl);
57726f
+/*%<
57726f
+ * Sets the maximum length of time that cached answers may be retained
57726f
+ * past their normal TTL. Default value for the library is 0, disabling
57726f
+ * the use of stale data.
57726f
+ *
57726f
+ * Requires:
57726f
+ * \li	'db' is a valid cache database.
57726f
+ * \li	'ttl' is the number of seconds to retain data past its normal expiry.
57726f
+ *
57726f
+ * Returns:
57726f
+ * \li	#ISC_R_SUCCESS
57726f
+ * \li	#ISC_R_NOTIMPLEMENTED - Not supported by this DB implementation.
57726f
+ */
57726f
+
57726f
+isc_result_t
57726f
+dns_db_getservestalettl(dns_db_t *db, dns_ttl_t *ttl);
57726f
+/*%<
57726f
+ * Gets maximum length of time that cached answers may be kept past
57726f
+ * normal TTL expiration.
57726f
+ *
57726f
+ * Requires:
57726f
+ * \li	'db' is a valid cache database.
57726f
+ * \li	'ttl' is the number of seconds to retain data past its normal expiry.
57726f
+ *
57726f
+ * Returns:
57726f
+ * \li	#ISC_R_SUCCESS
57726f
+ * \li	#ISC_R_NOTIMPLEMENTED - Not supported by this DB implementation.
57726f
+ */
57726f
+
57726f
 ISC_LANG_ENDDECLS
57726f
 
57726f
 #endif /* DNS_DB_H */
57726f
diff --git a/lib/dns/include/dns/rdataset.h b/lib/dns/include/dns/rdataset.h
e48f32
index ed9119a..710e97c 100644
57726f
--- a/lib/dns/include/dns/rdataset.h
57726f
+++ b/lib/dns/include/dns/rdataset.h
57726f
@@ -128,6 +128,7 @@ struct dns_rdataset {
57726f
 	unsigned int			magic;		/* XXX ? */
57726f
 	dns_rdatasetmethods_t *		methods;
57726f
 	ISC_LINK(dns_rdataset_t)	link;
57726f
+
57726f
 	/*
57726f
 	 * XXX do we need these, or should they be retrieved by methods?
57726f
 	 * Leaning towards the latter, since they are not frequently required
57726f
@@ -136,12 +137,19 @@ struct dns_rdataset {
57726f
 	dns_rdataclass_t		rdclass;
57726f
 	dns_rdatatype_t			type;
57726f
 	dns_ttl_t			ttl;
57726f
+	/*
57726f
+	 * Stale ttl is used to see how long this RRset can still be used
57726f
+	 * to serve to clients, after the TTL has expired.
57726f
+	 */
57726f
+	dns_ttl_t			stale_ttl;
57726f
 	dns_trust_t			trust;
57726f
 	dns_rdatatype_t			covers;
57726f
+
57726f
 	/*
57726f
 	 * attributes
57726f
 	 */
57726f
 	unsigned int			attributes;
57726f
+
57726f
 	/*%
57726f
 	 * the counter provides the starting point in the "cyclic" order.
57726f
 	 * The value UINT32_MAX has a special meaning of "picking up a
57726f
@@ -149,11 +157,13 @@ struct dns_rdataset {
57726f
 	 * increment the counter.
57726f
 	 */
57726f
 	uint32_t			count;
57726f
+
57726f
 	/*
57726f
 	 * This RRSIG RRset should be re-generated around this time.
57726f
 	 * Only valid if DNS_RDATASETATTR_RESIGN is set in attributes.
57726f
 	 */
57726f
 	isc_stdtime_t			resign;
57726f
+
57726f
 	/*@{*/
57726f
 	/*%
57726f
 	 * These are for use by the rdataset implementation, and MUST NOT
57726f
@@ -206,6 +216,7 @@ struct dns_rdataset {
57726f
 #define DNS_RDATASETATTR_OPTOUT		0x00100000	/*%< OPTOUT proof */
57726f
 #define DNS_RDATASETATTR_NEGATIVE	0x00200000
57726f
 #define DNS_RDATASETATTR_PREFETCH	0x00400000
57726f
+#define DNS_RDATASETATTR_STALE		0x01000000
57726f
 
57726f
 /*%
57726f
  * _OMITDNSSEC:
57726f
diff --git a/lib/dns/include/dns/resolver.h b/lib/dns/include/dns/resolver.h
e48f32
index 7b3c047..bd7d225 100644
57726f
--- a/lib/dns/include/dns/resolver.h
57726f
+++ b/lib/dns/include/dns/resolver.h
57726f
@@ -547,9 +547,12 @@ dns_resolver_getmustbesecure(dns_resolver_t *resolver, dns_name_t *name);
57726f
 
57726f
 
57726f
 void
57726f
-dns_resolver_settimeout(dns_resolver_t *resolver, unsigned int seconds);
57726f
+dns_resolver_settimeout(dns_resolver_t *resolver, unsigned int timeout);
57726f
 /*%<
57726f
- * Set the length of time the resolver will work on a query, in seconds.
57726f
+ * Set the length of time the resolver will work on a query, in milliseconds.
57726f
+ *
57726f
+ * 'timeout' was originally defined in seconds, and later redefined to be in
57726f
+ * milliseconds.  Values less than or equal to 300 are treated as seconds.
57726f
  *
57726f
  * If timeout is 0, the default timeout will be applied.
57726f
  *
57726f
@@ -560,7 +563,8 @@ dns_resolver_settimeout(dns_resolver_t *resolver, unsigned int seconds);
57726f
 unsigned int
57726f
 dns_resolver_gettimeout(dns_resolver_t *resolver);
57726f
 /*%<
57726f
- * Get the current length of time the resolver will work on a query, in seconds.
57726f
+ * Get the current length of time the resolver will work on a query,
57726f
+ * in milliseconds.
57726f
  *
57726f
  * Requires:
57726f
  * \li  resolver to be valid.
57726f
@@ -582,6 +586,39 @@ dns_resolver_getzeronosoattl(dns_resolver_t *resolver);
57726f
 void
57726f
 dns_resolver_setzeronosoattl(dns_resolver_t *resolver, bool state);
57726f
 
57726f
+unsigned int
57726f
+dns_resolver_getretryinterval(dns_resolver_t *resolver);
57726f
+
57726f
+void
57726f
+dns_resolver_setretryinterval(dns_resolver_t *resolver, unsigned int interval);
57726f
+/*%<
57726f
+ * Sets the amount of time, in millseconds, that is waited for a reply
57726f
+ * to a server before another server is tried.  Interacts with the
57726f
+ * value of dns_resolver_getnonbackofftries() by trying that number of times
57726f
+ * at this interval, before doing exponential backoff and doubling the interval
57726f
+ * on each subsequent try, to a maximum of 10 seconds.  Defaults to 800 ms;
57726f
+ * silently capped at 2000 ms.
57726f
+ *
57726f
+ * Requires:
57726f
+ * \li	resolver to be valid.
57726f
+ * \li  interval > 0.
57726f
+ */
57726f
+
57726f
+unsigned int
57726f
+dns_resolver_getnonbackofftries(dns_resolver_t *resolver);
57726f
+
57726f
+void
57726f
+dns_resolver_setnonbackofftries(dns_resolver_t *resolver, unsigned int tries);
57726f
+/*%<
57726f
+ * Sets the number of failures of getting a reply from remote servers for
57726f
+ * a query before backing off by doubling the retry interval for each
57726f
+ * subsequent request sent.  Defaults to 3.
57726f
+ *
57726f
+ * Requires:
57726f
+ * \li	resolver to be valid.
57726f
+ * \li  tries > 0.
57726f
+ */
57726f
+
57726f
 unsigned int
57726f
 dns_resolver_getoptions(dns_resolver_t *resolver);
57726f
 
57726f
diff --git a/lib/dns/include/dns/types.h b/lib/dns/include/dns/types.h
e48f32
index 2468e3c..934a641 100644
57726f
--- a/lib/dns/include/dns/types.h
57726f
+++ b/lib/dns/include/dns/types.h
e48f32
@@ -390,6 +390,12 @@ typedef struct {
e48f32
 	size_t      count;
e48f32
 } dns_indent_t;
57726f
 
57726f
+typedef enum {
57726f
+	dns_stale_answer_no,
57726f
+	dns_stale_answer_yes,
57726f
+	dns_stale_answer_conf
57726f
+} dns_stale_answer_t;
57726f
+
57726f
 /*
57726f
  * Functions.
57726f
  */
57726f
diff --git a/lib/dns/include/dns/view.h b/lib/dns/include/dns/view.h
e48f32
index 53f1db1..96148c7 100644
57726f
--- a/lib/dns/include/dns/view.h
57726f
+++ b/lib/dns/include/dns/view.h
57726f
@@ -229,6 +229,9 @@ struct dns_view {
57726f
 	dns_dtenv_t			*dtenv;		/* Dnstap environment */
57726f
 	dns_dtmsgtype_t			dttypes;	/* Dnstap message types
57726f
 							   to log */
57726f
+	dns_ttl_t			staleanswerttl;
57726f
+	dns_stale_answer_t		staleanswersok;		/* rndc setting */
57726f
+	bool				staleanswersenable;	/* named.conf setting */
57726f
 };
57726f
 
57726f
 #define DNS_VIEW_MAGIC			ISC_MAGIC('V','i','e','w')
57726f
diff --git a/lib/dns/master.c b/lib/dns/master.c
e48f32
index 7d26b81..36999b5 100644
57726f
--- a/lib/dns/master.c
57726f
+++ b/lib/dns/master.c
57726f
@@ -1948,12 +1948,18 @@ load_text(dns_loadctx_t *lctx) {
57726f
 
57726f
 		if ((lctx->options & DNS_MASTER_AGETTL) != 0) {
57726f
 			/*
57726f
-			 * Adjust the TTL for $DATE.  If the RR has already
57726f
-			 * expired, ignore it.
57726f
+			 * Adjust the TTL for $DATE. If the RR has
57726f
+			 * already expired, set its TTL to 0. This
57726f
+			 * should be okay even if the TTL stretching
57726f
+			 * feature is not in effect, because it will
57726f
+			 * just be quickly expired by the cache, and the
57726f
+			 * way this was written before the patch it
57726f
+			 * could potentially add 0 TTLs anyway.
57726f
 			 */
57726f
 			if (lctx->ttl < ttl_offset)
57726f
-				continue;
57726f
-			lctx->ttl -= ttl_offset;
57726f
+				lctx->ttl = 0;
57726f
+			else
57726f
+				lctx->ttl -= ttl_offset;
57726f
 		}
57726f
 
57726f
 		/*
57726f
diff --git a/lib/dns/masterdump.c b/lib/dns/masterdump.c
e48f32
index fa839a0..91b3cab 100644
57726f
--- a/lib/dns/masterdump.c
57726f
+++ b/lib/dns/masterdump.c
57726f
@@ -81,6 +81,9 @@ struct dns_master_style {
57726f
  */
57726f
 #define DNS_TOTEXT_LINEBREAK_MAXLEN 100
57726f
 
57726f
+/*% Does the rdataset 'r' contain a stale answer? */
57726f
+#define STALE(r) (((r)->attributes & DNS_RDATASETATTR_STALE) != 0)
57726f
+
57726f
 /*%
57726f
  * Context structure for a masterfile dump in progress.
57726f
  */
57726f
@@ -94,6 +97,7 @@ typedef struct dns_totext_ctx {
57726f
 	dns_fixedname_t		origin_fixname;
57726f
 	uint32_t 		current_ttl;
57726f
 	bool 			current_ttl_valid;
57726f
+	dns_ttl_t		serve_stale_ttl;
57726f
 } dns_totext_ctx_t;
57726f
 
57726f
 LIBDNS_EXTERNAL_DATA const dns_master_style_t
57726f
@@ -382,6 +386,7 @@ totext_ctx_init(const dns_master_style_t *style, dns_totext_ctx_t *ctx) {
57726f
 	ctx->neworigin = NULL;
57726f
 	ctx->current_ttl = 0;
57726f
 	ctx->current_ttl_valid = false;
57726f
+	ctx->serve_stale_ttl = 0;
57726f
 
57726f
 	return (ISC_R_SUCCESS);
57726f
 }
57726f
@@ -1028,6 +1033,11 @@ dump_rdatasets_text(isc_mem_t *mctx, dns_name_t *name,
57726f
 		    (ctx->style.flags & DNS_STYLEFLAG_NCACHE) == 0) {
57726f
 			/* Omit negative cache entries */
57726f
 		} else {
57726f
+			if (STALE(rds)) {
57726f
+				fprintf(f, "; stale (for %u more seconds)\n",
57726f
+					(rds->stale_ttl -
57726f
+					 ctx->serve_stale_ttl));
57726f
+			}
57726f
 			isc_result_t result =
57726f
 				dump_rdataset(mctx, name, rds, ctx,
57726f
 					       buffer, f);
57726f
@@ -1496,6 +1506,16 @@ dumpctx_create(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version,
57726f
 	dns_db_attach(db, &dctx->db);
57726f
 
57726f
 	dctx->do_date = dns_db_iscache(dctx->db);
57726f
+	if (dctx->do_date) {
57726f
+		/*
57726f
+		 * Adjust the date backwards by the serve-stale TTL, if any.
57726f
+		 * This is so the TTL will be loaded correctly when next
57726f
+		 * started.
57726f
+		 */
57726f
+		(void)dns_db_getservestalettl(dctx->db,
57726f
+					      &dctx->tctx.serve_stale_ttl);
57726f
+		dctx->now -= dctx->tctx.serve_stale_ttl;
57726f
+	}
57726f
 
57726f
 	if (dctx->format == dns_masterformat_text &&
57726f
 	    (dctx->tctx.style.flags & DNS_STYLEFLAG_REL_OWNER) != 0) {
57726f
@@ -1555,6 +1575,9 @@ writeheader(dns_dumpctx_t *dctx) {
57726f
 		 * it in the zone case.
57726f
 		 */
57726f
 		if (dctx->do_date) {
57726f
+			fprintf(dctx->f,
57726f
+				"; using a %d second stale ttl\n",
57726f
+				dctx->tctx.serve_stale_ttl);
57726f
 			result = dns_time32_totext(dctx->now, &buffer);
57726f
 			RUNTIME_CHECK(result == ISC_R_SUCCESS);
57726f
 			isc_buffer_usedregion(&buffer, &r);
57726f
diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c
06d839
index 3a60bcf..8ea4d47 100644
57726f
--- a/lib/dns/rbtdb.c
57726f
+++ b/lib/dns/rbtdb.c
e48f32
@@ -511,6 +511,7 @@ typedef ISC_LIST(rdatasetheader_t)      rdatasetheaderlist_t;
57726f
 typedef ISC_LIST(dns_rbtnode_t)         rbtnodelist_t;
57726f
 
57726f
 #define RDATASET_ATTR_NONEXISTENT       0x0001
57726f
+/*%< May be potentially served as stale data. */
57726f
 #define RDATASET_ATTR_STALE             0x0002
57726f
 #define RDATASET_ATTR_IGNORE            0x0004
57726f
 #define RDATASET_ATTR_RETAIN            0x0008
e48f32
@@ -523,6 +524,8 @@ typedef ISC_LIST(dns_rbtnode_t)         rbtnodelist_t;
57726f
 #define RDATASET_ATTR_CASESET           0x0400
57726f
 #define RDATASET_ATTR_ZEROTTL           0x0800
57726f
 #define RDATASET_ATTR_CASEFULLYLOWER    0x1000
57726f
+/*%< Ancient - awaiting cleanup. */
57726f
+#define RDATASET_ATTR_ANCIENT           0x2000
57726f
 
57726f
 typedef struct acache_cbarg {
57726f
 	dns_rdatasetadditional_t        type;
e48f32
@@ -573,6 +576,8 @@ struct acachectl {
57726f
 	(((header)->attributes & RDATASET_ATTR_ZEROTTL) != 0)
57726f
 #define CASEFULLYLOWER(header) \
57726f
 	(((header)->attributes & RDATASET_ATTR_CASEFULLYLOWER) != 0)
57726f
+#define ANCIENT(header) \
57726f
+	(((header)->attributes & RDATASET_ATTR_ANCIENT) != 0)
57726f
 
57726f
 
57726f
 #define ACTIVE(header, now) \
e48f32
@@ -632,6 +637,12 @@ typedef enum {
57726f
 	expire_flush
57726f
 } expire_t;
57726f
 
57726f
+typedef enum {
57726f
+	rdataset_ttl_fresh,
57726f
+	rdataset_ttl_stale,
57726f
+	rdataset_ttl_ancient
57726f
+} rdataset_ttl_t;
57726f
+
57726f
 typedef struct rbtdb_version {
57726f
 	/* Not locked */
57726f
 	rbtdb_serial_t                  serial;
e48f32
@@ -699,6 +710,12 @@ struct dns_rbtdb {
57726f
 	dns_dbnode_t                    *soanode;
57726f
 	dns_dbnode_t                    *nsnode;
57726f
 
57726f
+	/*
57726f
+	 * Maximum length of time to keep using a stale answer past its
57726f
+	 * normal TTL expiry.
57726f
+	*/
57726f
+	dns_ttl_t			serve_stale_ttl;
57726f
+
57726f
 	/*
57726f
 	 * This is a linked list used to implement the LRU cache.  There will
57726f
 	 * be node_lock_count linked lists here.  Nodes in bucket 1 will be
e48f32
@@ -742,6 +759,8 @@ struct dns_rbtdb {
57726f
 #define RBTDB_ATTR_LOADED               0x01
57726f
 #define RBTDB_ATTR_LOADING              0x02
57726f
 
57726f
+#define KEEPSTALE(rbtdb) ((rbtdb)->serve_stale_ttl > 0)
57726f
+
57726f
 /*%
57726f
  * Search Context
57726f
  */
e48f32
@@ -1816,15 +1835,15 @@ rollback_node(dns_rbtnode_t *node, rbtdb_serial_t serial) {
57726f
 }
57726f
 
57726f
 static inline void
57726f
-mark_stale_header(dns_rbtdb_t *rbtdb, rdatasetheader_t *header) {
57726f
+mark_header_ancient(dns_rbtdb_t *rbtdb, rdatasetheader_t *header) {
57726f
 
57726f
 	/*
57726f
-	 * If we are already stale there is nothing to do.
57726f
+	 * If we are already ancient there is nothing to do.
57726f
 	 */
57726f
-	if ((header->attributes & RDATASET_ATTR_STALE) != 0)
57726f
+	if (ANCIENT(header))
57726f
 		return;
57726f
 
57726f
-	header->attributes |= RDATASET_ATTR_STALE;
57726f
+	header->attributes |= RDATASET_ATTR_ANCIENT;
57726f
 	header->node->dirty = 1;
57726f
 
57726f
 	/*
e48f32
@@ -1865,8 +1884,8 @@ clean_cache_node(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node) {
57726f
 		/*
57726f
 		 * If current is nonexistent or stale, we can clean it up.
57726f
 		 */
57726f
-		if ((current->attributes &
57726f
-		     (RDATASET_ATTR_NONEXISTENT|RDATASET_ATTR_STALE)) != 0) {
57726f
+		if (NONEXISTENT(current) || ANCIENT(current) ||
57726f
+		    (STALE(current) && ! KEEPSTALE(rbtdb))) {
57726f
 			if (top_prev != NULL)
57726f
 				top_prev->next = current->next;
57726f
 			else
e48f32
@@ -2111,6 +2130,80 @@ delete_node(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node) {
57726f
 	}
57726f
 }
57726f
 
57726f
+#if 0
57726f
+static void
57726f
+clean_now_or_later(dns_rbtnode_t *node, dns_rbtdb_t *rbtdb,
57726f
+		   rdatasetheader_t *header, rdatasetheader_t **header_prevp)
57726f
+{
57726f
+	if (dns_rbtnode_refcurrent(node) == 0) {
57726f
+		isc_mem_t *mctx;
57726f
+
57726f
+		/*
57726f
+		 * header->down can be non-NULL if the refcount has just
57726f
+		 * decremented to 0 but decrement_reference() has not performed
57726f
+		 * clean_cache_node(), in which case we need to purge the stale
57726f
+		 * headers first.
57726f
+		 */
57726f
+		mctx = rbtdb->common.mctx;
57726f
+		clean_stale_headers(rbtdb, mctx, header);
57726f
+		if (*header_prevp != NULL)
57726f
+			(*header_prevp)->next = header->next;
57726f
+		else
57726f
+			node->data = header->next;
57726f
+		free_rdataset(rbtdb, mctx, header);
57726f
+	} else {
57726f
+		header->attributes |= RDATASET_ATTR_STALE |
57726f
+		                      RDATASET_ATTR_ANCIENT;
57726f
+		node->dirty = 1;
57726f
+		*header_prevp = header;
57726f
+	}
57726f
+}
57726f
+
57726f
+static rdataset_ttl_t
57726f
+check_ttl(dns_rbtnode_t *node, rbtdb_search_t *search,
57726f
+	  rdatasetheader_t *header, rdatasetheader_t **header_prevp,
57726f
+	  nodelock_t *lock, isc_rwlocktype_t *locktype)
57726f
+{
57726f
+	dns_rbtdb_t *rbtdb = search->rbtdb;
57726f
+
57726f
+	if (header->rdh_ttl > search->now)
57726f
+		return rdataset_ttl_fresh;
57726f
+
57726f
+	/*
57726f
+	 * This rdataset is stale, but perhaps still usable.
57726f
+	 */
57726f
+	if (KEEPSTALE(rbtdb) &&
57726f
+	    header->rdh_ttl + rbtdb->serve_stale_ttl > search->now) {
57726f
+		header->attributes |= RDATASET_ATTR_STALE;
57726f
+		/* Doesn't set dirty because it doesn't need removal. */
57726f
+		return rdataset_ttl_stale;
57726f
+	}
57726f
+
57726f
+	/*
57726f
+	 * This rdataset is so stale it is no longer usable, even with
57726f
+	 * KEEPSTALE.  If no one else is using the node, we can clean it up
57726f
+	 * right now, otherwise we mark it as ancient, and the node as dirty,
57726f
+	 * so it will get cleaned up later.
57726f
+	 */
57726f
+	if ((header->rdh_ttl <= search->now - RBTDB_VIRTUAL) &&
57726f
+	    (*locktype == isc_rwlocktype_write ||
57726f
+	     NODE_TRYUPGRADE(lock) == ISC_R_SUCCESS)) {
57726f
+		/*
57726f
+		 * We update the node's status only when we can get write
57726f
+		 * access; otherwise, we leave others to this work.  Periodical
57726f
+		 * cleaning will eventually take the job as the last resort.
57726f
+		 * We won't downgrade the lock, since other rdatasets are
57726f
+		 * probably stale, too.
57726f
+		 */
57726f
+		*locktype = isc_rwlocktype_write;
57726f
+		clean_now_or_later(node, rbtdb, header, header_prevp);
57726f
+	} else
57726f
+		*header_prevp = header;
57726f
+
57726f
+	return rdataset_ttl_ancient;
57726f
+}
57726f
+#endif
57726f
+
57726f
 /*
57726f
  * Caller must be holding the node lock.
57726f
  */
e48f32
@@ -3343,6 +3436,12 @@ bind_rdataset(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node, rdatasetheader_t *header,
57726f
 		rdataset->attributes |= DNS_RDATASETATTR_OPTOUT;
57726f
 	if (PREFETCH(header))
57726f
 		rdataset->attributes |= DNS_RDATASETATTR_PREFETCH;
57726f
+	if (STALE(header)) {
57726f
+		rdataset->attributes |= DNS_RDATASETATTR_STALE;
57726f
+		rdataset->stale_ttl =
57726f
+			(rbtdb->serve_stale_ttl + header->rdh_ttl) - now;
57726f
+		rdataset->ttl = 0;
57726f
+	}
57726f
 	rdataset->private1 = rbtdb;
57726f
 	rdataset->private2 = node;
57726f
 	raw = (unsigned char *)header + sizeof(*header);
06d839
@@ -4698,6 +4797,19 @@ check_stale_header(dns_rbtnode_t *node, rdatasetheader_t *header,
57726f
 #endif
57726f
 
57726f
 	if (!ACTIVE(header, search->now)) {
57726f
+		dns_ttl_t stale = header->rdh_ttl +
57726f
+				  search->rbtdb->serve_stale_ttl;
57726f
+		/*
57726f
+		 * If this data is in the stale window keep it and if
57726f
+		 * DNS_DBFIND_STALEOK is not set we tell the caller to
57726f
+		 * skip this record.
57726f
+		 */
57726f
+		if (KEEPSTALE(search->rbtdb) && stale > search->now) {
57726f
+			header->attributes |= RDATASET_ATTR_STALE;
57726f
+			*header_prev = header;
57726f
+			return ((search->options & DNS_DBFIND_STALEOK) == 0);
57726f
+		}
57726f
+
57726f
 		/*
57726f
 		 * This rdataset is stale.  If no one else is using the
57726f
 		 * node, we can clean it up right now, otherwise we mark
06d839
@@ -4737,7 +4849,7 @@ check_stale_header(dns_rbtnode_t *node, rdatasetheader_t *header,
57726f
 					node->data = header->next;
57726f
 				free_rdataset(search->rbtdb, mctx, header);
57726f
 			} else {
57726f
-				mark_stale_header(search->rbtdb, header);
57726f
+				mark_header_ancient(search->rbtdb, header);
57726f
 				*header_prev = header;
57726f
 			}
57726f
 		} else
06d839
@@ -5178,7 +5290,7 @@ cache_find(dns_db_t *db, dns_name_t *name, dns_dbversion_t *version,
57726f
 				       &locktype, lock, &search,
57726f
 				       &header_prev)) {
57726f
 			/* Do nothing. */
57726f
-		} else if (EXISTS(header) && (!STALE(header))) {
57726f
+		} else if (EXISTS(header) && !ANCIENT(header)) {
57726f
 			/*
57726f
 			 * We now know that there is at least one active
57726f
 			 * non-stale rdataset at this node.
06d839
@@ -5661,7 +5773,7 @@ expirenode(dns_db_t *db, dns_dbnode_t *node, isc_stdtime_t now) {
57726f
 			 * refcurrent(rbtnode) must be non-zero.  This is so
57726f
 			 * because 'node' is an argument to the function.
57726f
 			 */
57726f
-			mark_stale_header(rbtdb, header);
57726f
+			mark_header_ancient(rbtdb, header);
57726f
 			if (log)
57726f
 				isc_log_write(dns_lctx, category, module,
57726f
 					      level, "overmem cache: stale %s",
06d839
@@ -5669,7 +5781,7 @@ expirenode(dns_db_t *db, dns_dbnode_t *node, isc_stdtime_t now) {
57726f
 		} else if (force_expire) {
57726f
 			if (! RETAIN(header)) {
57726f
 				set_ttl(rbtdb, header, 0);
57726f
-				mark_stale_header(rbtdb, header);
57726f
+				mark_header_ancient(rbtdb, header);
57726f
 			} else if (log) {
57726f
 				isc_log_write(dns_lctx, category, module,
57726f
 					      level, "overmem cache: "
06d839
@@ -5928,9 +6040,9 @@ cache_findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
57726f
 				 * non-zero.  This is so because 'node' is an
57726f
 				 * argument to the function.
57726f
 				 */
57726f
-				mark_stale_header(rbtdb, header);
57726f
+				mark_header_ancient(rbtdb, header);
57726f
 			}
57726f
-		} else if (EXISTS(header) && (!STALE(header))) {
57726f
+		} else if (EXISTS(header) && !ANCIENT(header)) {
57726f
 			if (header->type == matchtype)
57726f
 				found = header;
57726f
 			else if (header->type == RBTDB_RDATATYPE_NCACHEANY ||
06d839
@@ -6232,7 +6344,7 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, rbtdb_version_t *rbtversion,
57726f
 				     topheader = topheader->next)
57726f
 				{
57726f
 					set_ttl(rbtdb, topheader, 0);
57726f
-					mark_stale_header(rbtdb, topheader);
57726f
+					mark_header_ancient(rbtdb, topheader);
57726f
 				}
57726f
 				goto find_header;
57726f
 			}
06d839
@@ -6293,7 +6405,7 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, rbtdb_version_t *rbtversion,
57726f
 				 * ncache entry.
57726f
 				 */
57726f
 				set_ttl(rbtdb, topheader, 0);
57726f
-				mark_stale_header(rbtdb, topheader);
57726f
+				mark_header_ancient(rbtdb, topheader);
57726f
 				topheader = NULL;
57726f
 				goto find_header;
57726f
 			}
06d839
@@ -6331,8 +6443,11 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, rbtdb_version_t *rbtversion,
57726f
 		}
57726f
 
57726f
 		/*
57726f
-		 * Trying to add an rdataset with lower trust to a cache DB
57726f
-		 * has no effect, provided that the cache data isn't stale.
57726f
+		 * Trying to add an rdataset with lower trust to a cache
57726f
+		 * DB has no effect, provided that the cache data isn't
57726f
+		 * stale. If the cache data is stale, new lower trust
57726f
+		 * data will supersede it below. Unclear what the best
57726f
+		 * policy is here.
57726f
 		 */
57726f
 		if (rbtversion == NULL && trust < header->trust &&
57726f
 		    (ACTIVE(header, now) || header_nx)) {
06d839
@@ -6362,6 +6477,10 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, rbtdb_version_t *rbtversion,
57726f
 
57726f
 			if ((options & DNS_DBADD_EXACT) != 0)
57726f
 				flags |= DNS_RDATASLAB_EXACT;
57726f
+			/*
57726f
+			 * TTL use here is irrelevant to the cache;
57726f
+			 * merge is only done with zonedbs.
57726f
+			 */
57726f
 			if ((options & DNS_DBADD_EXACTTTL) != 0 &&
57726f
 			     newheader->rdh_ttl != header->rdh_ttl)
57726f
 					result = DNS_R_NOTEXACT;
06d839
@@ -6405,11 +6524,12 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, rbtdb_version_t *rbtversion,
57726f
 			}
57726f
 		}
57726f
 		/*
57726f
-		 * Don't replace existing NS, A and AAAA RRsets
57726f
-		 * in the cache if they are already exist.  This
57726f
-		 * prevents named being locked to old servers.
57726f
-		 * Don't lower trust of existing record if the
57726f
-		 * update is forced.
57726f
+		 * Don't replace existing NS, A and AAAA RRsets in the
57726f
+		 * cache if they are already exist. This prevents named
57726f
+		 * being locked to old servers. Don't lower trust of
57726f
+		 * existing record if the update is forced. Nothing
57726f
+		 * special to be done w.r.t stale data; it gets replaced
57726f
+		 * normally further down.
57726f
 		 */
57726f
 		if (IS_CACHE(rbtdb) && ACTIVE(header, now) &&
57726f
 		    header->type == dns_rdatatype_ns &&
06d839
@@ -6582,10 +6702,10 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, rbtdb_version_t *rbtversion,
57726f
 				changed->dirty = true;
57726f
 			if (rbtversion == NULL) {
57726f
 				set_ttl(rbtdb, header, 0);
57726f
-				mark_stale_header(rbtdb, header);
57726f
+				mark_header_ancient(rbtdb, header);
57726f
 				if (sigheader != NULL) {
57726f
 					set_ttl(rbtdb, sigheader, 0);
57726f
-					mark_stale_header(rbtdb, sigheader);
57726f
+					mark_header_ancient(rbtdb, sigheader);
57726f
 				}
57726f
 			}
57726f
 			if (rbtversion != NULL && !header_nx) {
06d839
@@ -8436,6 +8556,30 @@ nodefullname(dns_db_t *db, dns_dbnode_t *node, dns_name_t *name) {
57726f
 	return (result);
57726f
 }
57726f
 
57726f
+static isc_result_t
57726f
+setservestalettl(dns_db_t *db, dns_ttl_t ttl) {
57726f
+	dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
57726f
+
57726f
+	REQUIRE(VALID_RBTDB(rbtdb));
57726f
+	REQUIRE(IS_CACHE(rbtdb));
57726f
+
57726f
+	/* currently no bounds checking.  0 means disable. */
57726f
+	rbtdb->serve_stale_ttl = ttl;
57726f
+	return ISC_R_SUCCESS;
57726f
+}
57726f
+
57726f
+static isc_result_t
57726f
+getservestalettl(dns_db_t *db, dns_ttl_t *ttl) {
57726f
+	dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
57726f
+
57726f
+	REQUIRE(VALID_RBTDB(rbtdb));
57726f
+	REQUIRE(IS_CACHE(rbtdb));
57726f
+
57726f
+	*ttl = rbtdb->serve_stale_ttl;
57726f
+	return ISC_R_SUCCESS;
57726f
+}
57726f
+
57726f
+
57726f
 static dns_dbmethods_t zone_methods = {
57726f
 	attach,
57726f
 	detach,
06d839
@@ -8481,7 +8625,9 @@ static dns_dbmethods_t zone_methods = {
57726f
 	NULL,
57726f
 	hashsize,
57726f
 	nodefullname,
57726f
-	getsize
57726f
+	getsize,
57726f
+	NULL,
57726f
+	NULL
57726f
 };
57726f
 
57726f
 static dns_dbmethods_t cache_methods = {
06d839
@@ -8529,7 +8675,9 @@ static dns_dbmethods_t cache_methods = {
57726f
 	setcachestats,
57726f
 	hashsize,
57726f
 	nodefullname,
57726f
-	NULL
57726f
+	NULL,
57726f
+	setservestalettl,
57726f
+	getservestalettl
57726f
 };
57726f
 
57726f
 isc_result_t
06d839
@@ -8800,7 +8948,7 @@ dns_rbtdb_create
57726f
 	rbtdb->rpzs = NULL;
57726f
 	rbtdb->load_rpzs = NULL;
57726f
 	rbtdb->rpz_num = DNS_RPZ_INVALID_NUM;
57726f
-
57726f
+	rbtdb->serve_stale_ttl = 0;
57726f
 	/*
57726f
 	 * Version Initialization.
57726f
 	 */
06d839
@@ -9218,7 +9366,8 @@ rdatasetiter_first(dns_rdatasetiter_t *iterator) {
57726f
 				 * rdatasets to work.
57726f
 				 */
57726f
 				if (NONEXISTENT(header) ||
57726f
-				    (now != 0 && now > header->rdh_ttl))
57726f
+				    (now != 0 && now > header->rdh_ttl
57726f
+						     + rbtdb->serve_stale_ttl))
57726f
 					header = NULL;
57726f
 				break;
57726f
 			} else
06d839
@@ -10427,7 +10576,7 @@ static inline bool
57726f
 need_headerupdate(rdatasetheader_t *header, isc_stdtime_t now) {
57726f
 	if ((header->attributes &
57726f
 	     (RDATASET_ATTR_NONEXISTENT |
57726f
-	      RDATASET_ATTR_STALE |
57726f
+	      RDATASET_ATTR_ANCIENT |
57726f
 	      RDATASET_ATTR_ZEROTTL)) != 0)
57726f
 		return (false);
57726f
 
06d839
@@ -10533,7 +10682,7 @@ expire_header(dns_rbtdb_t *rbtdb, rdatasetheader_t *header,
57726f
 	      bool tree_locked, expire_t reason)
57726f
 {
57726f
 	set_ttl(rbtdb, header, 0);
57726f
-	mark_stale_header(rbtdb, header);
57726f
+	mark_header_ancient(rbtdb, header);
57726f
 
57726f
 	/*
57726f
 	 * Caller must hold the node (write) lock.
57726f
diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c
06d839
index 49ec49c..2de70a6 100644
57726f
--- a/lib/dns/resolver.c
57726f
+++ b/lib/dns/resolver.c
57726f
@@ -141,16 +141,17 @@
57726f
 #endif /* WANT_QUERYTRACE */
57726f
 
57726f
 #define US_PER_SEC 1000000U
57726f
+#define US_PER_MSEC 1000U
57726f
 /*
57726f
  * The maximum time we will wait for a single query.
57726f
  */
57726f
-#define MAX_SINGLE_QUERY_TIMEOUT 9U
57726f
-#define MAX_SINGLE_QUERY_TIMEOUT_US (MAX_SINGLE_QUERY_TIMEOUT*US_PER_SEC)
57726f
+#define MAX_SINGLE_QUERY_TIMEOUT 9000U
57726f
+#define MAX_SINGLE_QUERY_TIMEOUT_US (MAX_SINGLE_QUERY_TIMEOUT*US_PER_MSEC)
57726f
 
57726f
 /*
57726f
  * We need to allow a individual query time to complete / timeout.
57726f
  */
57726f
-#define MINIMUM_QUERY_TIMEOUT (MAX_SINGLE_QUERY_TIMEOUT + 1U)
57726f
+#define MINIMUM_QUERY_TIMEOUT (MAX_SINGLE_QUERY_TIMEOUT + 1000U)
57726f
 
57726f
 /* The default time in seconds for the whole query to live. */
57726f
 #ifndef DEFAULT_QUERY_TIMEOUT
57726f
@@ -159,7 +160,7 @@
57726f
 
57726f
 /* The maximum time in seconds for the whole query to live. */
57726f
 #ifndef MAXIMUM_QUERY_TIMEOUT
57726f
-#define MAXIMUM_QUERY_TIMEOUT 30
57726f
+#define MAXIMUM_QUERY_TIMEOUT 30000
57726f
 #endif
57726f
 
57726f
 /* The default maximum number of recursions to follow before giving up. */
e48f32
@@ -529,6 +530,11 @@ struct dns_resolver {
f79ca9
 	dns_fetch_t *			primefetch;
f79ca9
 	/* Locked by nlock. */
f79ca9
 	unsigned int			nfctx;
f79ca9
+
f79ca9
+	/* Unlocked. Additions for serve-stale feature. */
57726f
+	unsigned int			retryinterval; /* in milliseconds */
57726f
+	unsigned int			nonbackofftries;
57726f
+
f79ca9
 };
f79ca9
 
f79ca9
 #define RES_MAGIC			ISC_MAGIC('R', 'e', 's', '!')
e48f32
@@ -1650,14 +1656,12 @@ fctx_setretryinterval(fetchctx_t *fctx, unsigned int rtt) {
57726f
 	unsigned int seconds;
57726f
 	unsigned int us;
57726f
 
57726f
+	us = fctx->res->retryinterval * 1000;
57726f
 	/*
57726f
-	 * We retry every .8 seconds the first two times through the address
57726f
-	 * list, and then we do exponential back-off.
57726f
+	 * Exponential backoff after the first few tries.
57726f
 	 */
57726f
-	if (fctx->restarts < 3)
57726f
-		us = 800000;
57726f
-	else
57726f
-		us = (800000 << (fctx->restarts - 2));
57726f
+	if (fctx->restarts >= fctx->res->nonbackofftries)
57726f
+		us <<= (fctx->restarts - fctx->res->nonbackofftries - 1);
57726f
 
57726f
 	/*
57726f
 	 * Add a fudge factor to the expected rtt based on the current
06d839
@@ -4542,7 +4546,8 @@ fctx_create(dns_resolver_t *res, dns_name_t *name, dns_rdatatype_t type,
57726f
 	/*
57726f
 	 * Compute an expiration time for the entire fetch.
57726f
 	 */
57726f
-	isc_interval_set(&interval, res->query_timeout, 0);
57726f
+	isc_interval_set(&interval, res->query_timeout / 1000,
57726f
+			 res->query_timeout % 1000 * 1000000);
57726f
 	iresult = isc_time_nowplusinterval(&fctx->expires, &interval);
57726f
 	if (iresult != ISC_R_SUCCESS) {
57726f
 		UNEXPECTED_ERROR(__FILE__, __LINE__,
06d839
@@ -9105,6 +9110,8 @@ dns_resolver_create(dns_view_t *view,
57726f
 	res->spillattimer = NULL;
57726f
 	res->zspill = 0;
57726f
 	res->zero_no_soa_ttl = false;
57726f
+	res->retryinterval = 30000;
57726f
+	res->nonbackofftries = 3;
57726f
 	res->query_timeout = DEFAULT_QUERY_TIMEOUT;
57726f
 	res->maxdepth = DEFAULT_RECURSION_DEPTH;
57726f
 	res->maxqueries = DEFAULT_MAX_QUERIES;
06d839
@@ -10439,17 +10446,20 @@ dns_resolver_gettimeout(dns_resolver_t *resolver) {
57726f
 }
57726f
 
57726f
 void
57726f
-dns_resolver_settimeout(dns_resolver_t *resolver, unsigned int seconds) {
57726f
+dns_resolver_settimeout(dns_resolver_t *resolver, unsigned int timeout) {
57726f
 	REQUIRE(VALID_RESOLVER(resolver));
57726f
 
57726f
-	if (seconds == 0)
57726f
-		seconds = DEFAULT_QUERY_TIMEOUT;
57726f
-	if (seconds > MAXIMUM_QUERY_TIMEOUT)
57726f
-		seconds = MAXIMUM_QUERY_TIMEOUT;
57726f
-	if (seconds < MINIMUM_QUERY_TIMEOUT)
57726f
-		seconds =  MINIMUM_QUERY_TIMEOUT;
57726f
+	if (timeout <= 300)
57726f
+		timeout *= 1000;
57726f
+
57726f
+	if (timeout == 0)
57726f
+		timeout = DEFAULT_QUERY_TIMEOUT;
57726f
+	if (timeout > MAXIMUM_QUERY_TIMEOUT)
57726f
+		timeout = MAXIMUM_QUERY_TIMEOUT;
57726f
+	if (timeout < MINIMUM_QUERY_TIMEOUT)
57726f
+		timeout =  MINIMUM_QUERY_TIMEOUT;
57726f
 
57726f
-	resolver->query_timeout = seconds;
57726f
+	resolver->query_timeout = timeout;
57726f
 }
57726f
 
57726f
 void
06d839
@@ -10546,3 +10556,34 @@ dns_resolver_getquotaresponse(dns_resolver_t *resolver, dns_quotatype_t which)
57726f
 
57726f
 	return (resolver->quotaresp[which]);
57726f
 }
57726f
+
57726f
+unsigned int
57726f
+dns_resolver_getretryinterval(dns_resolver_t *resolver) {
57726f
+	REQUIRE(VALID_RESOLVER(resolver));
57726f
+
57726f
+	return (resolver->retryinterval);
57726f
+}
57726f
+
57726f
+void
57726f
+dns_resolver_setretryinterval(dns_resolver_t *resolver, unsigned int interval)
57726f
+{
57726f
+	REQUIRE(VALID_RESOLVER(resolver));
57726f
+	REQUIRE(interval > 0);
57726f
+
57726f
+	resolver->retryinterval = ISC_MIN(interval, 2000);
57726f
+}
57726f
+
57726f
+unsigned int
57726f
+dns_resolver_getnonbackofftries(dns_resolver_t *resolver) {
57726f
+	REQUIRE(VALID_RESOLVER(resolver));
57726f
+
57726f
+	return (resolver->nonbackofftries);
57726f
+}
57726f
+
57726f
+void
57726f
+dns_resolver_setnonbackofftries(dns_resolver_t *resolver, unsigned int tries) {
57726f
+	REQUIRE(VALID_RESOLVER(resolver));
57726f
+	REQUIRE(tries > 0);
57726f
+
57726f
+	resolver->nonbackofftries = tries;
57726f
+}
57726f
diff --git a/lib/dns/sdb.c b/lib/dns/sdb.c
e48f32
index 477bb74..09cf932 100644
57726f
--- a/lib/dns/sdb.c
57726f
+++ b/lib/dns/sdb.c
e48f32
@@ -1370,7 +1370,9 @@ static dns_dbmethods_t sdb_methods = {
57726f
 	NULL,			/* setcachestats */
57726f
 	NULL,			/* hashsize */
57726f
 	NULL,			/* nodefullname */
57726f
-	NULL			/* getsize */
57726f
+	NULL,			/* getsize */
57726f
+	NULL,			/* setservestalettl */
57726f
+	NULL			/* getservestalettl */
57726f
 };
57726f
 
57726f
 static isc_result_t
57726f
diff --git a/lib/dns/sdlz.c b/lib/dns/sdlz.c
e48f32
index 037d74a..9218fed 100644
57726f
--- a/lib/dns/sdlz.c
57726f
+++ b/lib/dns/sdlz.c
57726f
@@ -1336,7 +1336,9 @@ static dns_dbmethods_t sdlzdb_methods = {
57726f
 	NULL,			/* setcachestats */
57726f
 	NULL,			/* hashsize */
57726f
 	NULL,			/* nodefullname */
57726f
-	NULL			/* getsize */
57726f
+	NULL,			/* getsize */
57726f
+	NULL,			/* setservestalettl */
57726f
+	NULL			/* getservestalettl */
57726f
 };
57726f
 
57726f
 /*
57726f
diff --git a/lib/dns/tests/db_test.c b/lib/dns/tests/db_test.c
e48f32
index bc1cc3f..60fdb81 100644
57726f
--- a/lib/dns/tests/db_test.c
57726f
+++ b/lib/dns/tests/db_test.c
57726f
@@ -28,8 +28,9 @@
57726f
 
57726f
 #include <dns/db.h>
57726f
 #include <dns/dbiterator.h>
57726f
-#include <dns/name.h>
57726f
 #include <dns/journal.h>
57726f
+#include <dns/name.h>
57726f
+#include <dns/rdatalist.h>
57726f
 
57726f
 #include "dnstest.h"
57726f
 
57726f
@@ -76,7 +77,7 @@ getoriginnode_test(void **state) {
57726f
 	assert_int_equal(result, ISC_R_SUCCESS);
57726f
 
57726f
 	result = dns_db_create(mymctx, "rbt", dns_rootname, dns_dbtype_zone,
57726f
-			    dns_rdataclass_in, 0, NULL, &db);
57726f
+			       dns_rdataclass_in, 0, NULL, &db);
57726f
 	assert_int_equal(result, ISC_R_SUCCESS);
57726f
 
57726f
 	result = dns_db_getoriginnode(db, &node);
57726f
@@ -91,6 +92,197 @@ getoriginnode_test(void **state) {
57726f
 	isc_mem_detach(&mymctx);
57726f
 }
57726f
 
57726f
+/* test getservestalettl and setservestalettl */
57726f
+static void
57726f
+getsetservestalettl_test(void **state) {
57726f
+	dns_db_t *db = NULL;
57726f
+	isc_mem_t *mymctx = NULL;
57726f
+	isc_result_t result;
57726f
+	dns_ttl_t ttl;
57726f
+
57726f
+	UNUSED(state);
57726f
+
57726f
+	result = isc_mem_create(0, 0, &mymctx);
57726f
+	assert_int_equal(result, ISC_R_SUCCESS);
57726f
+
57726f
+	result = dns_db_create(mymctx, "rbt", dns_rootname, dns_dbtype_cache,
57726f
+			       dns_rdataclass_in, 0, NULL, &db);
57726f
+	assert_int_equal(result, ISC_R_SUCCESS);
57726f
+
57726f
+	ttl = 5000;
57726f
+	result = dns_db_getservestalettl(db, &ttl);
57726f
+	assert_int_equal(result, ISC_R_SUCCESS);
57726f
+	assert_int_equal(ttl, 0);
57726f
+
57726f
+	ttl = 6 * 3600;
57726f
+	result = dns_db_setservestalettl(db, ttl);
57726f
+	assert_int_equal(result, ISC_R_SUCCESS);
57726f
+
57726f
+	ttl = 5000;
57726f
+	result = dns_db_getservestalettl(db, &ttl);
57726f
+	assert_int_equal(result, ISC_R_SUCCESS);
57726f
+	assert_int_equal(ttl, 6 * 3600);
57726f
+
57726f
+	dns_db_detach(&db);
57726f
+	isc_mem_detach(&mymctx);
57726f
+}
57726f
+
57726f
+/* check DNS_DBFIND_STALEOK works */
57726f
+static void
57726f
+dns_dbfind_staleok_test(void **state) {
57726f
+	dns_db_t *db = NULL;
57726f
+	dns_dbnode_t *node = NULL;
57726f
+	dns_fixedname_t example_fixed;
57726f
+	dns_fixedname_t found_fixed;
57726f
+	dns_name_t *example;
57726f
+	dns_name_t *found;
57726f
+	dns_rdatalist_t rdatalist;
57726f
+	dns_rdataset_t rdataset;
57726f
+	int count;
57726f
+	int pass;
57726f
+	isc_mem_t *mymctx = NULL;
57726f
+	isc_result_t result;
57726f
+	unsigned char data[] = { 0x0a, 0x00, 0x00, 0x01 };
57726f
+
57726f
+	UNUSED(state);
57726f
+
57726f
+	result = isc_mem_create(0, 0, &mymctx);
57726f
+	assert_int_equal(result, ISC_R_SUCCESS);
57726f
+
57726f
+	result = dns_db_create(mymctx, "rbt", dns_rootname, dns_dbtype_cache,
57726f
+			       dns_rdataclass_in, 0, NULL, &db);
57726f
+	assert_int_equal(result, ISC_R_SUCCESS);
57726f
+
57726f
+	example = dns_fixedname_initname(&example_fixed);
57726f
+	found = dns_fixedname_initname(&found_fixed);
57726f
+
57726f
+	result = dns_name_fromstring(example, "example", 0, NULL);
57726f
+	assert_int_equal(result, ISC_R_SUCCESS);
57726f
+
57726f
+	/*
57726f
+	 * Pass 0: default; no stale processing permitted.
57726f
+	 * Pass 1: stale processing for 1 second.
57726f
+	 * Pass 2: stale turned off after being on.
57726f
+	 */
57726f
+	for (pass = 0; pass < 3; pass++) {
57726f
+		dns_rdata_t rdata = DNS_RDATA_INIT;
57726f
+
57726f
+		/* 10.0.0.1 */
57726f
+		rdata.data = data;
57726f
+		rdata.length = 4;
57726f
+		rdata.rdclass = dns_rdataclass_in;
57726f
+		rdata.type = dns_rdatatype_a;
57726f
+
57726f
+		dns_rdatalist_init(&rdatalist);
57726f
+		rdatalist.ttl = 2;
57726f
+		rdatalist.type = dns_rdatatype_a;
57726f
+		rdatalist.rdclass = dns_rdataclass_in;
57726f
+		ISC_LIST_APPEND(rdatalist.rdata, &rdata, link);
57726f
+
57726f
+		switch (pass) {
57726f
+		case 0:
57726f
+			/* default: stale processing off */
57726f
+			break;
57726f
+		case 1:
57726f
+			/* turn on stale processing */
57726f
+			result = dns_db_setservestalettl(db, 1);
57726f
+			assert_int_equal(result, ISC_R_SUCCESS);
57726f
+			break;
57726f
+		case 2:
57726f
+			/* turn off stale processing */
57726f
+			result = dns_db_setservestalettl(db, 0);
57726f
+			assert_int_equal(result, ISC_R_SUCCESS);
57726f
+			break;
57726f
+		}
57726f
+
57726f
+		dns_rdataset_init(&rdataset);
57726f
+		result = dns_rdatalist_tordataset(&rdatalist, &rdataset);
57726f
+		assert_int_equal(result, ISC_R_SUCCESS);
57726f
+
57726f
+		result = dns_db_findnode(db, example, true, &node);
57726f
+		assert_int_equal(result, ISC_R_SUCCESS);
57726f
+
57726f
+		result = dns_db_addrdataset(db, node, NULL, 0, &rdataset, 0,
57726f
+					    NULL);
57726f
+		assert_int_equal(result, ISC_R_SUCCESS);
57726f
+
57726f
+		dns_db_detachnode(db, &node);
57726f
+		dns_rdataset_disassociate(&rdataset);
57726f
+
57726f
+		result = dns_db_find(db, example, NULL, dns_rdatatype_a,
57726f
+				     0, 0, &node, found, &rdataset, NULL);
57726f
+		assert_int_equal(result, ISC_R_SUCCESS);
57726f
+
57726f
+		/*
57726f
+		 * May loop for up to 2 seconds performing non stale lookups.
57726f
+		 */
57726f
+		count = 0;
57726f
+		do {
57726f
+			count++;
57726f
+			assert_in_range(count, 0, 20); /* loop sanity */
57726f
+			assert_int_equal(rdataset.attributes &
57726f
+				     DNS_RDATASETATTR_STALE, 0);
57726f
+			assert_true(rdataset.ttl > 0);
57726f
+			dns_db_detachnode(db, &node);
57726f
+			dns_rdataset_disassociate(&rdataset);
57726f
+
57726f
+			usleep(100000);	/* 100 ms */
57726f
+
57726f
+			result = dns_db_find(db, example, NULL,
57726f
+					     dns_rdatatype_a, 0, 0,
57726f
+					     &node, found, &rdataset, NULL);
57726f
+		} while (result == ISC_R_SUCCESS);
57726f
+
57726f
+		assert_int_equal(result, ISC_R_NOTFOUND);
57726f
+
57726f
+		/*
57726f
+		 * Check whether we can get stale data.
57726f
+		 */
57726f
+		result = dns_db_find(db, example, NULL, dns_rdatatype_a,
57726f
+				     DNS_DBFIND_STALEOK, 0,
57726f
+				     &node, found, &rdataset, NULL);
57726f
+		switch (pass) {
57726f
+		case 0:
57726f
+			assert_int_equal(result, ISC_R_NOTFOUND);
57726f
+			break;
57726f
+		case 1:
57726f
+			/*
57726f
+			 * Should loop for 1 second with stale lookups then
57726f
+			 * stop.
57726f
+			 */
57726f
+			count = 0;
57726f
+			do {
57726f
+				count++;
57726f
+				assert_in_range(count, 0, 49); /* loop sanity */
57726f
+				assert_int_equal(result, ISC_R_SUCCESS);
57726f
+				assert_int_equal(rdataset.ttl, 0);
57726f
+				assert_int_equal(rdataset.attributes &
57726f
+					     DNS_RDATASETATTR_STALE,
57726f
+					     DNS_RDATASETATTR_STALE);
57726f
+				dns_db_detachnode(db, &node);
57726f
+				dns_rdataset_disassociate(&rdataset);
57726f
+
57726f
+				usleep(100000);	/* 100 ms */
57726f
+
57726f
+				result = dns_db_find(db, example, NULL,
57726f
+						     dns_rdatatype_a,
57726f
+						     DNS_DBFIND_STALEOK,
57726f
+						     0, &node, found,
57726f
+						     &rdataset, NULL);
57726f
+			} while (result == ISC_R_SUCCESS);
57726f
+			assert_in_range(count, 1, 10);
57726f
+			assert_int_equal(result, ISC_R_NOTFOUND);
57726f
+			break;
57726f
+		case 2:
57726f
+			assert_int_equal(result, ISC_R_NOTFOUND);
57726f
+			break;
57726f
+		}
57726f
+	}
57726f
+
57726f
+	dns_db_detach(&db);
57726f
+	isc_mem_detach(&mymctx);
57726f
+}
57726f
+
57726f
 /* database class */
57726f
 static void
57726f
 class_test(void **state) {
57726f
@@ -213,6 +405,8 @@ int
57726f
 main(void) {
57726f
 	const struct CMUnitTest tests[] = {
57726f
 		cmocka_unit_test(getoriginnode_test),
57726f
+		cmocka_unit_test(getsetservestalettl_test),
57726f
+		cmocka_unit_test(dns_dbfind_staleok_test),
57726f
 		cmocka_unit_test_setup_teardown(class_test,
57726f
 						_setup, _teardown),
57726f
 		cmocka_unit_test_setup_teardown(dbtype_test,
57726f
diff --git a/lib/dns/view.c b/lib/dns/view.c
e48f32
index a7ba613..a644c5f 100644
57726f
--- a/lib/dns/view.c
57726f
+++ b/lib/dns/view.c
57726f
@@ -229,6 +229,9 @@ dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass,
57726f
 	view->flush = false;
57726f
 	view->dlv = NULL;
57726f
 	view->maxudp = 0;
57726f
+	view->staleanswerttl = 1;
57726f
+	view->staleanswersok = dns_stale_answer_conf;
57726f
+	view->staleanswersenable = false;
57726f
 	view->nocookieudp = 0;
57726f
 	view->maxbits = 0;
57726f
 	view->v4_aaaa = dns_aaaa_ok;
57726f
diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c
e48f32
index 212194e..b562f95 100644
57726f
--- a/lib/isccfg/namedconf.c
57726f
+++ b/lib/isccfg/namedconf.c
57726f
@@ -1778,6 +1778,7 @@ view_clauses[] = {
57726f
 	{ "max-ncache-ttl", &cfg_type_uint32, 0 },
57726f
 	{ "max-recursion-depth", &cfg_type_uint32, 0 },
57726f
 	{ "max-recursion-queries", &cfg_type_uint32, 0 },
57726f
+	{ "max-stale-ttl", &cfg_type_ttlval, 0 },
57726f
 	{ "max-udp-size", &cfg_type_uint32, 0 },
57726f
 	{ "message-compression", &cfg_type_boolean, 0 },
57726f
 	{ "min-roots", &cfg_type_uint32, CFG_CLAUSEFLAG_NOTIMP },
57726f
@@ -1806,7 +1807,9 @@ view_clauses[] = {
57726f
 	{ "request-nsid", &cfg_type_boolean, 0 },
57726f
 	{ "request-sit", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE },
57726f
 	{ "require-server-cookie", &cfg_type_boolean, 0 },
57726f
+	{ "resolver-nonbackoff-tries", &cfg_type_uint32, 0 },
57726f
 	{ "resolver-query-timeout", &cfg_type_uint32, 0 },
57726f
+	{ "resolver-retry-interval", &cfg_type_uint32, 0 },
57726f
 	{ "response-policy", &cfg_type_rpz, 0 },
57726f
 	{ "rfc2308-type1", &cfg_type_boolean, CFG_CLAUSEFLAG_NYI },
57726f
 	{ "root-delegation-only",  &cfg_type_optional_exclude, 0 },
57726f
@@ -1815,6 +1818,8 @@ view_clauses[] = {
57726f
 	{ "send-cookie", &cfg_type_boolean, 0 },
57726f
 	{ "servfail-ttl", &cfg_type_ttlval, 0 },
57726f
 	{ "sortlist", &cfg_type_bracketed_aml, 0 },
57726f
+	{ "stale-answer-enable", &cfg_type_boolean, 0 },
57726f
+	{ "stale-answer-ttl", &cfg_type_ttlval, 0 },
57726f
 	{ "suppress-initial-notify", &cfg_type_boolean, CFG_CLAUSEFLAG_NYI },
57726f
 	{ "topology", &cfg_type_bracketed_aml, CFG_CLAUSEFLAG_NOTIMP },
57726f
 	{ "transfer-format", &cfg_type_transferformat, 0 },
57726f
-- 
e48f32
2.26.2
57726f