From df50751585b64f72d93ad665abf0f485c8941a3b Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Wed, 6 Sep 2017 09:58:29 +1000 Subject: [PATCH] 4700. [func] Serving of stale answers is now supported. This allows named to provide stale cached answers when the authoritative server is under attack. See max-stale-ttl, stale-answer-enable, stale-answer-ttl. [RT #44790] --- CHANGES | 6 + bin/named/config.c | 168 +++---- bin/named/control.c | 2 + bin/named/include/named/control.h | 1 + bin/named/include/named/server.h | 13 +- bin/named/query.c | 92 +++- bin/named/server.c | 176 ++++++- bin/named/statschannel.c | 6 + bin/rndc/rndc.c | 2 + bin/rndc/rndc.docbook | 19 + bin/tests/system/conf.sh.in | 8 +- bin/tests/system/dyndb/driver/db.c | 2 + bin/tests/system/serve-stale/ans2/ans.pl | 138 +++++ bin/tests/system/serve-stale/clean.sh | 4 + bin/tests/system/serve-stale/ns1/named1.conf | 35 ++ bin/tests/system/serve-stale/ns1/named2.conf | 35 ++ bin/tests/system/serve-stale/ns1/root.db | 5 + bin/tests/system/serve-stale/ns3/named.conf | 35 ++ bin/tests/system/serve-stale/setup.sh | 1 + bin/tests/system/serve-stale/tests.sh | 498 +++++++++++++++++++ doc/arm/Bv9ARM-book.xml | 69 ++- doc/misc/options | 10 + lib/bind9/check.c | 148 +++--- lib/dns/cache.c | 37 +- lib/dns/db.c | 22 + lib/dns/ecdb.c | 4 +- lib/dns/include/dns/cache.h | 21 + lib/dns/include/dns/db.h | 35 ++ lib/dns/include/dns/rdataset.h | 1 + lib/dns/include/dns/resolver.h | 43 +- lib/dns/include/dns/types.h | 6 + lib/dns/include/dns/view.h | 3 + lib/dns/master.c | 14 +- lib/dns/masterdump.c | 16 + lib/dns/rbtdb.c | 203 ++++++-- lib/dns/resolver.c | 78 ++- lib/dns/sdb.c | 4 +- lib/dns/sdlz.c | 4 +- lib/dns/tests/db_test.c | 214 +++++++- lib/dns/view.c | 3 + lib/isccfg/namedconf.c | 5 + 41 files changed, 1964 insertions(+), 222 deletions(-) create mode 100644 bin/tests/system/serve-stale/ans2/ans.pl create mode 100644 bin/tests/system/serve-stale/clean.sh create mode 100644 bin/tests/system/serve-stale/ns1/named1.conf create mode 100644 bin/tests/system/serve-stale/ns1/named2.conf create mode 100644 bin/tests/system/serve-stale/ns1/root.db create mode 100644 bin/tests/system/serve-stale/ns3/named.conf create mode 100644 bin/tests/system/serve-stale/setup.sh create mode 100755 bin/tests/system/serve-stale/tests.sh diff --git a/CHANGES b/CHANGES index 3d04d415f8..fd5169de25 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +4700. [func] Serving of stale answers is now supported. This + allows named to provide stale cached answers when + the authoritative server is under attack. + See max-stale-ttl, stale-answer-enable, + stale-answer-ttl. [RT #44790] + 4699. [func] Multiple cookie-secret clauses can now be specified. The first one specified is used to generate new server cookies. [RT #45672] diff --git a/bin/named/config.c b/bin/named/config.c index 606e083134..fcb88c06ad 100644 --- a/bin/named/config.c +++ b/bin/named/config.c @@ -53,17 +53,18 @@ options {\n\ #endif #ifndef WIN32 " coresize default;\n\ - datasize default;\n\ - files unlimited;\n\ - stacksize default;\n" + datasize default;\n" #endif -"# session-keyfile \"" NS_LOCALSTATEDIR "/run/named/session.key\";\n\ - session-keyname local-ddns;\n\ - session-keyalg hmac-sha256;\n\ +"\ # deallocate-on-exit ;\n\ # directory \n\ dump-file \"named_dump.db\";\n\ -# fake-iquery ;\n\ + edns-udp-size 4096;\n\ +# fake-iquery ;\n" +#ifndef WIN32 +" files unlimited;\n" +#endif +"\ # has-old-clients ;\n\ heartbeat-interval 60;\n\ # host-statistics ;\n\ @@ -71,33 +72,40 @@ options {\n\ # keep-response-order {none;};\n\ listen-on {any;};\n\ listen-on-v6 {any;};\n\ +# lock-file \"" NS_LOCALSTATEDIR "/run/named/named.lock\";\n\ match-mapped-addresses no;\n\ max-rsa-exponent-size 0; /* no limit */\n\ + max-udp-size 4096;\n\ memstatistics-file \"named.memstats\";\n\ # multiple-cnames ;\n\ # named-xfer ;\n\ + nocookie-udp-size 4096;\n\ + notify-rate 20;\n\ nta-lifetime 3600;\n\ nta-recheck 300;\n\ - notify-rate 20;\n\ # pid-file \"" NS_LOCALSTATEDIR "/run/named/named.pid\"; /* or /lwresd.pid */\n\ -# lock-file \"" NS_LOCALSTATEDIR "/run/named/named.lock\";\n\ port 53;\n\ - prefetch 2 9;\n\ - recursing-file \"named.recursing\";\n\ - secroots-file \"named.secroots\";\n\ -" + prefetch 2 9;\n" #ifdef PATH_RANDOMDEV -"\ - random-device \"" PATH_RANDOMDEV "\";\n\ -" +" random-device \"" PATH_RANDOMDEV "\";\n" #endif -"\ +" recursing-file \"named.recursing\";\n\ recursive-clients 1000;\n\ + request-nsid false;\n\ + reserved-sockets 512;\n\ resolver-query-timeout 10;\n\ + secroots-file \"named.secroots\";\n\ + send-cookie true;\n\ # serial-queries ;\n\ serial-query-rate 20;\n\ server-id none;\n\ - startup-notify-rate 20;\n\ + session-keyalg hmac-sha256;\n\ +# session-keyfile \"" NS_LOCALSTATEDIR "/run/named/session.key\";\n\ + session-keyname local-ddns;\n" +#ifndef WIN32 +" stacksize default;\n" +#endif +" startup-notify-rate 20;\n\ statistics-file \"named.stats\";\n\ # statistics-interval ;\n\ tcp-advertised-timeout 300;\n\ @@ -107,22 +115,16 @@ options {\n\ tcp-keepalive-timeout 300;\n\ tcp-listen-queue 10;\n\ # tkey-dhkey \n\ -# tkey-gssapi-credential \n\ # tkey-domain \n\ +# tkey-gssapi-credential \n\ transfer-message-size 20480;\n\ - transfers-per-ns 2;\n\ transfers-in 10;\n\ transfers-out 10;\n\ + transfers-per-ns 2;\n\ # treat-cr-as-space ;\n\ trust-anchor-telemetry yes;\n\ # use-id-pool ;\n\ # use-ixfr ;\n\ - edns-udp-size 4096;\n\ - max-udp-size 4096;\n\ - nocookie-udp-size 4096;\n\ - send-cookie true;\n\ - request-nsid false;\n\ - reserved-sockets 512;\n\ \n\ /* DLV */\n\ dnssec-lookaside . trust-anchor dlv.isc.org;\n\ @@ -147,34 +149,35 @@ options {\n\ clients-per-query 10;\n\ dnssec-accept-expired no;\n\ dnssec-enable yes;\n\ - dnssec-validation yes; \n\ + dnssec-validation yes; \n" +#ifdef HAVE_DNSTAP +" dnstap-identity hostname;\n" +#endif +"\ # fetch-glue ;\n\ fetch-quota-params 100 0.1 0.3 0.7;\n\ fetches-per-server 0;\n\ - fetches-per-zone 0;\n\ -" + fetches-per-zone 0;\n" #ifdef ALLOW_FILTER_AAAA " filter-aaaa-on-v4 no;\n\ filter-aaaa-on-v6 no;\n\ - filter-aaaa { any; };\n\ -" + filter-aaaa { any; };\n" #endif -"\ - glue-cache yes;\n\ - lame-ttl 600;\n\ -" +#ifdef HAVE_GEOIP +" geoip-use-ecs yes;\n" +#endif +" glue-cache yes;\n\ + lame-ttl 600;\n" #ifdef HAVE_LMDB -"\ - lmdb-mapsize 32M;\n\ -" +" lmdb-mapsize 32M;\n" #endif -"\ - max-cache-size 90%;\n\ +" max-cache-size 90%;\n\ max-cache-ttl 604800; /* 1 week */\n\ max-clients-per-query 100;\n\ max-ncache-ttl 10800; /* 3 hours */\n\ max-recursion-depth 7;\n\ max-recursion-queries 75;\n\ + max-stale-ttl 604800; /* 1 week */\n\ message-compression yes;\n\ # min-roots ;\n\ minimal-any false;\n\ @@ -189,74 +192,67 @@ options {\n\ request-expire true;\n\ request-ixfr true;\n\ require-server-cookie no;\n\ + resolver-nonbackoff-tries 3;\n\ + resolver-retry-interval 800; /* in milliseconds */\n\ # rfc2308-type1 ;\n\ servfail-ttl 1;\n\ # sortlist \n\ + stale-answer-enable false;\n\ + stale-answer-ttl 1; /* 1 second */\n\ synth-from-dnssec yes;\n\ # topology \n\ transfer-format many-answers;\n\ v6-bias 50;\n\ zero-no-soa-ttl-cache no;\n\ -" -#ifdef HAVE_DNSTAP -"\ - dnstap-identity hostname;\n\ -" -#endif -#ifdef HAVE_GEOIP -"\ - geoip-use-ecs yes;\n\ -" -#endif - -" /* zone */\n\ +\n\ + /* zone */\n\ allow-query {any;};\n\ allow-query-on {any;};\n\ allow-transfer {any;};\n\ - notify yes;\n\ # also-notify \n\ - notify-delay 5;\n\ - notify-to-soa no;\n\ - dialup no;\n\ -# forward \n\ -# forwarders \n\ -# maintain-ixfr-base ;\n\ -# max-ixfr-log-size \n\ - transfer-source *;\n\ - transfer-source-v6 *;\n\ alt-transfer-source *;\n\ alt-transfer-source-v6 *;\n\ - max-transfer-time-in 120;\n\ - max-transfer-time-out 120;\n\ + check-integrity yes;\n\ + check-mx-cname warn;\n\ + check-sibling yes;\n\ + check-srv-cname warn;\n\ + check-wildcard yes;\n\ + dialup no;\n\ + dnssec-dnskey-kskonly no;\n\ + dnssec-loadkeys-interval 60;\n\ + dnssec-secure-to-insecure no;\n\ + dnssec-update-mode maintain;\n\ +# forward \n\ +# forwarders \n\ + inline-signing no;\n\ + ixfr-from-differences false;\n\ +# maintain-ixfr-base ;\n\ +# max-ixfr-log-size \n\ + max-journal-size default;\n\ + max-records 0;\n\ + max-refresh-time 2419200; /* 4 weeks */\n\ + max-retry-time 1209600; /* 2 weeks */\n\ max-transfer-idle-in 60;\n\ max-transfer-idle-out 60;\n\ - max-records 0;\n\ - max-retry-time 1209600; /* 2 weeks */\n\ - min-retry-time 500;\n\ - max-refresh-time 2419200; /* 4 weeks */\n\ + max-transfer-time-in 120;\n\ + max-transfer-time-out 120;\n\ min-refresh-time 300;\n\ + min-retry-time 500;\n\ multi-master no;\n\ - dnssec-secure-to-insecure no;\n\ - sig-validity-interval 30; /* days */\n\ + notify yes;\n\ + notify-delay 5;\n\ + notify-to-soa no;\n\ + serial-update-method increment;\n\ sig-signing-nodes 100;\n\ sig-signing-signatures 10;\n\ sig-signing-type 65534;\n\ - inline-signing no;\n\ - zone-statistics terse;\n\ - max-journal-size default;\n\ - ixfr-from-differences false;\n\ - check-wildcard yes;\n\ - check-sibling yes;\n\ - check-integrity yes;\n\ - check-mx-cname warn;\n\ - check-srv-cname warn;\n\ - zero-no-soa-ttl yes;\n\ - update-check-ksk yes;\n\ - serial-update-method increment;\n\ - dnssec-update-mode maintain;\n\ - dnssec-dnskey-kskonly no;\n\ - dnssec-loadkeys-interval 60;\n\ + sig-validity-interval 30; /* days */\n\ + transfer-source *;\n\ + transfer-source-v6 *;\n\ try-tcp-refresh yes; /* BIND 8 compat */\n\ + update-check-ksk yes;\n\ + zero-no-soa-ttl yes;\n\ + zone-statistics terse;\n\ };\n\ " diff --git a/bin/named/control.c b/bin/named/control.c index bd78931b2b..e02bdfb813 100644 --- a/bin/named/control.c +++ b/bin/named/control.c @@ -277,6 +277,8 @@ ns_control_docommand(isccc_sexpr_t *message, isc_boolean_t readonly, result = ns_server_dnstap(ns_g_server, lex, text); } else if (command_compare(command, NS_COMMAND_TCPTIMEOUTS)) { result = ns_server_tcptimeouts(lex, text); + } else if (command_compare(command, NS_COMMAND_SERVESTALE)) { + result = ns_server_servestale(ns_g_server, lex, text); } else { isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_CONTROL, ISC_LOG_WARNING, diff --git a/bin/named/include/named/control.h b/bin/named/include/named/control.h index e1569990ee..586bec0cde 100644 --- a/bin/named/include/named/control.h +++ b/bin/named/include/named/control.h @@ -66,6 +66,7 @@ #define NS_COMMAND_DNSTAPREOPEN "dnstap-reopen" #define NS_COMMAND_DNSTAP "dnstap" #define NS_COMMAND_TCPTIMEOUTS "tcp-timeouts" +#define NS_COMMAND_SERVESTALE "serve-stale" isc_result_t ns_controls_create(ns_server_t *server, ns_controls_t **ctrlsp); diff --git a/bin/named/include/named/server.h b/bin/named/include/named/server.h index 60d1ce6dd4..ff728c6879 100644 --- a/bin/named/include/named/server.h +++ b/bin/named/include/named/server.h @@ -215,7 +215,10 @@ enum { dns_nsstatscounter_nodatasynth = 59, dns_nsstatscounter_wildcardsynth = 60, - dns_nsstatscounter_max = 61 + dns_nsstatscounter_trystale = 61, + dns_nsstatscounter_usedstale = 62, + + dns_nsstatscounter_max = 63 }; /*% @@ -760,4 +763,12 @@ ns_server_dnstap(ns_server_t *server, isc_lex_t *lex, isc_buffer_t **text); isc_result_t ns_server_tcptimeouts(isc_lex_t *lex, isc_buffer_t **text); +/*% + * Control whether stale answers are served or not when configured in + * named.conf. + */ +isc_result_t +ns_server_servestale(ns_server_t *server, isc_lex_t *lex, + isc_buffer_t **text); + #endif /* NAMED_SERVER_H */ diff --git a/bin/named/query.c b/bin/named/query.c index 8e6db4bf74..3bbaa22511 100644 --- a/bin/named/query.c +++ b/bin/named/query.c @@ -146,10 +146,14 @@ do { \ #define REDIRECT(c) (((c)->query.attributes & \ NS_QUERYATTR_REDIRECT) != 0) -/*% No QNAME Proof? */ +/*% Does the rdataset 'r' have an attached 'No QNAME Proof'? */ #define NOQNAME(r) (((r)->attributes & \ DNS_RDATASETATTR_NOQNAME) != 0) +/*% Does the rdataset 'r' contains a stale answer? */ +#define STALE(r) (((r)->attributes & \ + DNS_RDATASETATTR_STALE) != 0) + #ifdef WANT_QUERYTRACE static inline void client_trace(ns_client_t *client, int level, const char *message) { @@ -326,6 +330,7 @@ typedef struct query_ctx { isc_boolean_t need_wildcardproof; /* wilcard proof needed */ isc_boolean_t nxrewrite; /* negative answer from RPZ */ isc_boolean_t findcoveringnsec; /* lookup covering NSEC */ + isc_boolean_t want_stale; /* want stale records? */ dns_fixedname_t wildcardname; /* name needing wcard proof */ dns_fixedname_t dsname; /* name needing DS */ @@ -4634,6 +4639,7 @@ qctx_init(ns_client_t *client, dns_fetchevent_t *event, qctx->findcoveringnsec = client->view->synthfromdnssec; qctx->is_staticstub_zone = ISC_FALSE; qctx->nxrewrite = ISC_FALSE; + qctx->want_stale = ISC_FALSE; qctx->authoritative = ISC_FALSE; } @@ -5009,6 +5015,35 @@ query_lookup(query_ctx_t *qctx) { dns_cache_updatestats(qctx->client->view->cache, result); } + if (qctx->want_stale) { + char namebuf[DNS_NAME_FORMATSIZE]; + isc_boolean_t success; + + qctx->client->query.dboptions &= ~DNS_DBFIND_STALEOK; + qctx->want_stale = ISC_FALSE; + if (dns_rdataset_isassociated(qctx->rdataset) && + dns_rdataset_count(qctx->rdataset) > 0 && + STALE(qctx->rdataset)) { + qctx->rdataset->ttl = + qctx->client->view->staleanswerttl; + success = ISC_TRUE; + } else { + success = ISC_FALSE; + } + + dns_name_format(qctx->client->query.qname, + namebuf, sizeof(namebuf)); + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_QUERY, ISC_LOG_INFO, + "%s resolver failure, stale answer %s", + namebuf, success ? "used" : "unavailable"); + + if (!success) { + QUERY_ERROR(qctx, DNS_R_SERVFAIL); + return (query_done(qctx)); + } + } + return (query_gotanswer(qctx, result)); } @@ -5126,6 +5161,8 @@ query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname, dns_rdataset_t *rdataset, *sigrdataset; isc_sockaddr_t *peeraddr = NULL; + CTRACE(ISC_LOG_DEBUG(3), "query_recurse"); + if (!resuming) inc_stats(client, dns_nsstatscounter_recursion); @@ -5995,7 +6032,11 @@ query_gotanswer(query_ctx_t *qctx, isc_result_t result) { "query_gotanswer: unexpected error: %s", isc_result_totext(result)); CCTRACE(ISC_LOG_ERROR, errmsg); - QUERY_ERROR(qctx, DNS_R_SERVFAIL); + if (qctx->resuming) { + qctx->want_stale = ISC_TRUE; + } else { + QUERY_ERROR(qctx, DNS_R_SERVFAIL); + } return (query_done(qctx)); } } @@ -6469,7 +6510,7 @@ query_respond(query_ctx_t *qctx) { /* * If we have a zero ttl from the cache, refetch. */ - if (!qctx->is_zone && !qctx->resuming && + if (!qctx->is_zone && !qctx->resuming && !STALE(qctx->rdataset) && qctx->rdataset->ttl == 0 && RECURSIONOK(qctx->client)) { qctx_clean(qctx); @@ -8274,7 +8315,7 @@ query_cname(query_ctx_t *qctx) { /* * If we have a zero ttl from the cache refetch it. */ - if (!qctx->is_zone && !qctx->resuming && + if (!qctx->is_zone && !qctx->resuming && !STALE(qctx->rdataset) && qctx->rdataset->ttl == 0 && RECURSIONOK(qctx->client)) { qctx_clean(qctx); @@ -9561,6 +9602,49 @@ query_done(query_ctx_t *qctx) { return (query_start(qctx)); } + if (qctx->want_stale) { + dns_ttl_t stale_ttl = 0; + isc_result_t result; + isc_boolean_t staleanswersok = ISC_FALSE; + + /* + * Stale answers only make sense if stale_ttl > 0 but + * we want rndc to be able to control returning stale + * answers if they are configured. + */ + dns_db_attach(qctx->client->view->cachedb, &qctx->db); + result = dns_db_getservestalettl(qctx->db, &stale_ttl); + if (result == ISC_R_SUCCESS && stale_ttl > 0) { + switch (qctx->client->view->staleanswersok) { + case dns_stale_answer_yes: + staleanswersok = ISC_TRUE; + break; + case dns_stale_answer_conf: + staleanswersok = + qctx->client->view->staleanswersenable; + break; + case dns_stale_answer_no: + staleanswersok = ISC_FALSE; + break; + } + } else { + staleanswersok = ISC_FALSE; + } + + if (staleanswersok) { + qctx->client->query.dboptions |= DNS_DBFIND_STALEOK; + inc_stats(qctx->client, dns_nsstatscounter_trystale); + if (qctx->client->query.fetch != NULL) + dns_resolver_destroyfetch( + &qctx->client->query.fetch); + return (query_lookup(qctx)); + } + dns_db_detach(&qctx->db); + qctx->want_stale = ISC_FALSE; + QUERY_ERROR(qctx, DNS_R_SERVFAIL); + return (query_done(qctx)); + } + if (qctx->result != ISC_R_SUCCESS && (!PARTIALANSWER(qctx->client) || WANTRECURSION(qctx->client) || qctx->result == DNS_R_DROP)) diff --git a/bin/named/server.c b/bin/named/server.c index 9815331b71..d893224953 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -1737,7 +1737,8 @@ static isc_boolean_t cache_sharable(dns_view_t *originview, dns_view_t *view, isc_boolean_t new_zero_no_soattl, unsigned int new_cleaning_interval, - isc_uint64_t new_max_cache_size) + isc_uint64_t new_max_cache_size, + isc_uint32_t new_stale_ttl) { /* * If the cache cannot even reused for the same view, it cannot be @@ -1752,6 +1753,7 @@ cache_sharable(dns_view_t *originview, dns_view_t *view, */ if (dns_cache_getcleaninginterval(originview->cache) != new_cleaning_interval || + dns_cache_getservestalettl(originview->cache) != new_stale_ttl || dns_cache_getcachesize(originview->cache) != new_max_cache_size) { return (ISC_FALSE); } @@ -3331,6 +3333,7 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, isc_uint32_t max_cache_size_percent = 0; size_t max_adb_size; isc_uint32_t lame_ttl, fail_ttl; + isc_uint32_t max_stale_ttl; dns_tsig_keyring_t *ring = NULL; dns_view_t *pview = NULL; /* Production view */ isc_mem_t *cmctx = NULL, *hmctx = NULL; @@ -3360,6 +3363,7 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, isc_boolean_t old_rpz_ok = ISC_FALSE; isc_dscp_t dscp4 = -1, dscp6 = -1; dns_dyndbctx_t *dctx = NULL; + unsigned int resolver_param; REQUIRE(DNS_VIEW_VALID(view)); @@ -3738,6 +3742,23 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, INSIST(result == ISC_R_SUCCESS); view->synthfromdnssec = cfg_obj_asboolean(obj); + result = ns_config_get(maps, "max-stale-ttl", &obj); + INSIST(result == ISC_R_SUCCESS); + max_stale_ttl = cfg_obj_asuint32(obj); + + obj = NULL; + result = ns_config_get(maps, "stale-answer-enable", &obj); + INSIST(result == ISC_R_SUCCESS); + view->staleanswersenable = cfg_obj_asboolean(obj); + + result = dns_viewlist_find(&ns_g_server->viewlist, view->name, + view->rdclass, &pview); + if (result == ISC_R_SUCCESS) { + view->staleanswersok = pview->staleanswersok; + dns_view_detach(&pview); + } else + view->staleanswersok = dns_stale_answer_conf; + /* * Configure the view's cache. * @@ -3771,7 +3792,8 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, nsc = cachelist_find(cachelist, cachename, view->rdclass); if (nsc != NULL) { if (!cache_sharable(nsc->primaryview, view, zero_no_soattl, - cleaning_interval, max_cache_size)) { + cleaning_interval, max_cache_size, + max_stale_ttl)) { isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR, "views %s and %s can't share the cache " @@ -3870,9 +3892,15 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, dns_cache_setcleaninginterval(cache, cleaning_interval); dns_cache_setcachesize(cache, max_cache_size); + dns_cache_setservestalettl(cache, max_stale_ttl); dns_cache_detach(&cache); + obj = NULL; + result = ns_config_get(maps, "stale-answer-ttl", &obj); + INSIST(result == ISC_R_SUCCESS); + view->staleanswerttl = ISC_MAX(cfg_obj_asuint32(obj), 1); + /* * Resolver. * @@ -4059,6 +4087,21 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, maxbits = 4096; view->maxbits = maxbits; + /* + * Set resolver retry parameters. + */ + obj = NULL; + CHECK(ns_config_get(maps, "resolver-retry-interval", &obj)); + resolver_param = cfg_obj_asuint32(obj); + if (resolver_param > 0) + dns_resolver_setretryinterval(view->resolver, resolver_param); + + obj = NULL; + CHECK(ns_config_get(maps, "resolver-nonbackoff-tries", &obj)); + resolver_param = cfg_obj_asuint32(obj); + if (resolver_param > 0) + dns_resolver_setnonbackofftries(view->resolver, resolver_param); + /* * Set supported DNSSEC algorithms. */ @@ -14090,3 +14133,132 @@ ns_server_tcptimeouts(isc_lex_t *lex, isc_buffer_t **text) { return (result); } + +isc_result_t +ns_server_servestale(ns_server_t *server, isc_lex_t *lex, + isc_buffer_t **text) +{ + char *ptr, *classtxt, *viewtxt = NULL; + char msg[128]; + dns_rdataclass_t rdclass = dns_rdataclass_in; + dns_view_t *view; + isc_boolean_t found = ISC_FALSE; + dns_stale_answer_t staleanswersok; + isc_boolean_t wantstatus = ISC_FALSE; + isc_result_t result = ISC_R_SUCCESS; + + /* Skip the command name. */ + ptr = next_token(lex, text); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + + ptr = next_token(lex, NULL); + if (ptr == NULL) + return (ISC_R_UNEXPECTEDEND); + + if (strcasecmp(ptr, "on") == 0 || strcasecmp(ptr, "yes") == 0) { + staleanswersok = dns_stale_answer_yes; + } else if (strcasecmp(ptr, "off") == 0 || strcasecmp(ptr, "no") == 0) { + staleanswersok = dns_stale_answer_no; + } else if (strcasecmp(ptr, "reset") == 0) { + staleanswersok = dns_stale_answer_conf; + } else if (strcasecmp(ptr, "status") == 0) { + wantstatus = ISC_TRUE; + } else + return (DNS_R_SYNTAX); + + /* Look for the optional class name. */ + classtxt = next_token(lex, text); + if (classtxt != NULL) { + /* Look for the optional view name. */ + viewtxt = next_token(lex, text); + } + + if (classtxt != NULL) { + isc_textregion_t r; + + r.base = classtxt; + r.length = strlen(classtxt); + result = dns_rdataclass_fromtext(&rdclass, &r); + if (result != ISC_R_SUCCESS) { + if (viewtxt == NULL) { + viewtxt = classtxt; + classtxt = NULL; + result = ISC_R_SUCCESS; + } else { + snprintf(msg, sizeof(msg), + "unknown class '%s'", classtxt); + (void) putstr(text, msg); + goto cleanup; + } + } + } + + result = isc_task_beginexclusive(server->task); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + for (view = ISC_LIST_HEAD(server->viewlist); + view != NULL; + view = ISC_LIST_NEXT(view, link)) + { + dns_ttl_t stale_ttl = 0; + dns_db_t *db = NULL; + + if (classtxt != NULL && rdclass != view->rdclass) + continue; + + if (viewtxt != NULL && strcmp(view->name, viewtxt) != 0) + continue; + + if (!wantstatus) { + view->staleanswersok = staleanswersok; + found = ISC_TRUE; + continue; + } + + db = NULL; + dns_db_attach(view->cachedb, &db); + (void)dns_db_getservestalettl(db, &stale_ttl); + dns_db_detach(&db); + if (found) + CHECK(putstr(text, "\n")); + CHECK(putstr(text, view->name)); + CHECK(putstr(text, ": ")); + switch (view->staleanswersok) { + case dns_stale_answer_yes: + if (stale_ttl > 0) + CHECK(putstr(text, "on (rndc)")); + else + CHECK(putstr(text, "off (not-cached)")); + break; + case dns_stale_answer_no: + CHECK(putstr(text, "off (rndc)")); + break; + case dns_stale_answer_conf: + if (view->staleanswersenable && stale_ttl > 0) + CHECK(putstr(text, "on")); + else if (view->staleanswersenable) + CHECK(putstr(text, "off (not-cached)")); + else + CHECK(putstr(text, "off")); + break; + } + if (stale_ttl > 0) { + snprintf(msg, sizeof(msg), + " (stale-answer-ttl=%u max-stale-ttl=%u)", + view->staleanswerttl, stale_ttl); + CHECK(putstr(text, msg)); + } + found = ISC_TRUE; + } + isc_task_endexclusive(ns_g_server->task); + + if (!found) + result = ISC_R_NOTFOUND; + +cleanup: + if (isc_buffer_usedlength(*text) > 0) + (void) putnull(text); + + return (result); +} diff --git a/bin/named/statschannel.c b/bin/named/statschannel.c index 7fb1bbdb03..db2628a814 100644 --- a/bin/named/statschannel.c +++ b/bin/named/statschannel.c @@ -293,6 +293,12 @@ init_desc(void) { SET_NSSTATDESC(nxdomainsynth, "synthesized a NXDOMAIN response", "SynthNXDOMAIN"); SET_NSSTATDESC(nodatasynth, "syththesized a no-data response", "SynthNODATA"); SET_NSSTATDESC(wildcardsynth, "synthesized a wildcard response", "SynthWILDCARD"); + SET_NSSTATDESC(trystale, + "attempts to use stale cache data after lookup failure", + "QryTryStale"); + SET_NSSTATDESC(usedstale, + "successful uses of stale cache data after lookup failure", + "QryUsedStale"); INSIST(i == dns_nsstatscounter_max); /* Initialize resolver statistics */ diff --git a/bin/rndc/rndc.c b/bin/rndc/rndc.c index 8dfabe075c..84f3efbf57 100644 --- a/bin/rndc/rndc.c +++ b/bin/rndc/rndc.c @@ -158,6 +158,8 @@ command is one of the following:\n\ scan Scan available network interfaces for changes.\n\ secroots [view ...]\n\ Write security roots to the secroots file.\n\ + serve-stale ( yes | no | reset ) [class [view]]\n\ + Control whether stale answers are returned\n\ showzone zone [class [view]]\n\ Print a zone's configuration.\n\ sign zone [class [view]]\n\ diff --git a/bin/rndc/rndc.docbook b/bin/rndc/rndc.docbook index 7df47db88d..9598645233 100644 --- a/bin/rndc/rndc.docbook +++ b/bin/rndc/rndc.docbook @@ -702,6 +702,25 @@ + + serve-stale ( on | off | reset | status) class view + + + Enable, disable, or reset the serving of stale answers + as configured in named.conf. Serving of stale answers + will remain disabled across named.conf + reloads if disabled via rndc until it is reset via rndc. + + + Status will report whether serving of stale answers is + currently enabled, disabled or not configured for a + view. If serving of stale records is configured then + the values of stale-answer-ttl and max-stale-ttl are + reported. + + + + secroots - view ... diff --git a/bin/tests/system/conf.sh.in b/bin/tests/system/conf.sh.in index 0bc6c6ee38..3d73bd0e1b 100644 --- a/bin/tests/system/conf.sh.in +++ b/bin/tests/system/conf.sh.in @@ -83,10 +83,10 @@ SUBDIRS="acl additional addzone allow_query autosign builtin names notify nslookup nsupdate nzd2nzf padding pending pipelined @PKCS11_TEST@ reclimit redirect resolver rndc rpz rpzrecurse rrchecker rrl rrsetorder rsabigexponent - runtime sfcache smartsign sortlist spf staticstub statistics - statschannel stub synthfromdnssec tcp tkey tools tsig - tsiggss unknown upforwd verify views wildcard xfer xferquota - zero zonechecks" + runtime serve-stale sfcache smartsign sortlist spf staticstub + statistics statschannel stub synthfromdnssec tcp tkey tools + tsig tsiggss unknown upforwd verify views wildcard xfer + xferquota zero zonechecks" # Things that are different on Windows KILL=kill diff --git a/bin/tests/system/dyndb/driver/db.c b/bin/tests/system/dyndb/driver/db.c index f75aca81a3..d720c3a6dd 100644 --- a/bin/tests/system/dyndb/driver/db.c +++ b/bin/tests/system/dyndb/driver/db.c @@ -607,6 +607,8 @@ static dns_dbmethods_t sampledb_methods = { hashsize, NULL, NULL, + NULL, + NULL, }; /* Auxiliary driver functions. */ diff --git a/bin/tests/system/serve-stale/ans2/ans.pl b/bin/tests/system/serve-stale/ans2/ans.pl new file mode 100644 index 0000000000..0a7c44df18 --- /dev/null +++ b/bin/tests/system/serve-stale/ans2/ans.pl @@ -0,0 +1,138 @@ +#!/usr/bin/env perl +# +# Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use strict; +use warnings; + +use IO::File; +use Getopt::Long; +use Net::DNS::Nameserver; +use Time::HiRes qw(usleep nanosleep); + +my $pidf = new IO::File "ans.pid", "w" or die "cannot open pid file: $!"; +print $pidf "$$\n" or die "cannot write pid file: $!"; +$pidf->close or die "cannot close pid file: $!"; +sub rmpid { unlink "ans.pid"; exit 1; }; + +$SIG{INT} = \&rmpid; +$SIG{TERM} = \&rmpid; + +my $send_response = 1; + +my $localaddr = "10.53.0.2"; +my $localport = 5300; +my $verbose = 0; + +# +# Delegation +# +my $SOA = "example 300 IN SOA . . 0 0 0 0 300"; +my $NS = "example 300 IN NS ns.example"; +my $A = "ns.example 300 IN A $localaddr"; +# +# Records to be TTL stretched +# +my $TXT = "data.example 1 IN TXT \"A text record with a 1 second ttl\""; +my $negSOA = "example 1 IN SOA . . 0 0 0 0 300"; + +sub reply_handler { + my ($qname, $qclass, $qtype, $peerhost, $query, $conn) = @_; + my ($rcode, @ans, @auth, @add); + + print ("request: $qname/$qtype\n"); + STDOUT->flush(); + + # Control whether we send a response or not. + # We always respond to control commands. + if ($qname eq "enable" ) { + if ($qtype eq "TXT") { + $send_response = 1; + my $rr = new Net::DNS::RR("$qname 0 $qclass TXT $send_response"); + push @ans, $rr; + } + $rcode = "NOERROR"; + return ($rcode, \@ans, \@auth, \@add, { aa => 1 }); + } elsif ($qname eq "disable" ) { + if ($qtype eq "TXT") { + $send_response = 0; + my $rr = new Net::DNS::RR("$qname 0 $qclass TXT $send_response"); + push @ans, $rr; + } + $rcode = "NOERROR"; + return ($rcode, \@ans, \@auth, \@add, { aa => 1 }); + } + + # If we are not responding to queries we are done. + return if (!$send_response); + + # Construct the response and send it. + if ($qname eq "ns.example" ) { + if ($qtype eq "A") { + my $rr = new Net::DNS::RR($A); + push @ans, $rr; + } else { + my $rr = new Net::DNS::RR($SOA); + push @auth, $rr; + } + $rcode = "NOERROR"; + } elsif ($qname eq "example") { + if ($qtype eq "NS") { + my $rr = new Net::DNS::RR($NS); + push @auth, $rr; + $rr = new Net::DNS::RR($A); + push @add, $rr; + } elsif ($qtype eq "SOA") { + my $rr = new Net::DNS::RR($SOA); + push @ans, $rr; + } else { + my $rr = new Net::DNS::RR($SOA); + push @auth, $rr; + } + $rcode = "NOERROR"; + } elsif ($qname eq "nodata.example") { + my $rr = new Net::DNS::RR($negSOA); + push @auth, $rr; + $rcode = "NOERROR"; + } elsif ($qname eq "data.example") { + if ($qtype eq "TXT") { + my $rr = new Net::DNS::RR($TXT); + push @ans, $rr; + } else { + my $rr = new Net::DNS::RR($negSOA); + push @auth, $rr; + } + $rcode = "NOERROR"; + } elsif ($qname eq "nxdomain.example") { + my $rr = new Net::DNS::RR($negSOA); + push @auth, $rr; + $rcode = "NXDOMAIN"; + } else { + my $rr = new Net::DNS::RR($SOA); + push @auth, $rr; + $rcode = "NXDOMAIN"; + } + + # mark the answer as authoritive (by setting the 'aa' flag + return ($rcode, \@ans, \@auth, \@add, { aa => 1 }); +} + +GetOptions( + 'port=i' => \$localport, + 'verbose!' => \$verbose, +); + +$A = "ns.example 300 IN A $localaddr"; + +my $ns = Net::DNS::Nameserver->new( + LocalAddr => $localaddr, + LocalPort => $localport, + ReplyHandler => \&reply_handler, + Verbose => $verbose, +); + +$ns->main_loop; diff --git a/bin/tests/system/serve-stale/clean.sh b/bin/tests/system/serve-stale/clean.sh new file mode 100644 index 0000000000..754158e60c --- /dev/null +++ b/bin/tests/system/serve-stale/clean.sh @@ -0,0 +1,4 @@ +rm -f dig.out.test* +rm -f ns1/named.conf +rm -f ns3/root.bk +rm -f rndc.out.test* diff --git a/bin/tests/system/serve-stale/ns1/named1.conf b/bin/tests/system/serve-stale/ns1/named1.conf new file mode 100644 index 0000000000..0e924e64b4 --- /dev/null +++ b/bin/tests/system/serve-stale/ns1/named1.conf @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-sha256; +}; + +controls { + inet 10.53.0.1 port 9953 allow { any; } keys { rndc_key; }; +}; + +options { + query-source address 10.53.0.1; + notify-source 10.53.0.1; + transfer-source 10.53.0.1; + port 5300; + pid-file "named.pid"; + listen-on { 10.53.0.1; }; + listen-on-v6 { none; }; + recursion yes; + max-stale-ttl 3600; + stale-answer-ttl 1; + stale-answer-enable yes; +}; + +zone "." { + type master; + file "root.db"; +}; diff --git a/bin/tests/system/serve-stale/ns1/named2.conf b/bin/tests/system/serve-stale/ns1/named2.conf new file mode 100644 index 0000000000..df6077ff9f --- /dev/null +++ b/bin/tests/system/serve-stale/ns1/named2.conf @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-sha256; +}; + +controls { + inet 10.53.0.1 port 9953 allow { any; } keys { rndc_key; }; +}; + +options { + query-source address 10.53.0.1; + notify-source 10.53.0.1; + transfer-source 10.53.0.1; + port 5300; + pid-file "named.pid"; + listen-on { 10.53.0.1; }; + listen-on-v6 { none; }; + recursion yes; + max-stale-ttl 7200; + stale-answer-ttl 2; + stale-answer-enable yes; +}; + +zone "." { + type master; + file "root.db"; +}; diff --git a/bin/tests/system/serve-stale/ns1/root.db b/bin/tests/system/serve-stale/ns1/root.db new file mode 100644 index 0000000000..eb9ad3ecf1 --- /dev/null +++ b/bin/tests/system/serve-stale/ns1/root.db @@ -0,0 +1,5 @@ +. 300 SOA . . 0 0 0 0 0 +. 300 NS ns.nil. +ns.nil. 300 A 10.53.0.1 +example. 300 NS ns.example. +ns.example. 300 A 10.53.0.2 diff --git a/bin/tests/system/serve-stale/ns3/named.conf b/bin/tests/system/serve-stale/ns3/named.conf new file mode 100644 index 0000000000..361f10aff3 --- /dev/null +++ b/bin/tests/system/serve-stale/ns3/named.conf @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-sha256; +}; + +controls { + inet 10.53.0.3 port 9953 allow { any; } keys { rndc_key; }; +}; + +options { + query-source address 10.53.0.3; + notify-source 10.53.0.3; + transfer-source 10.53.0.3; + port 5300; + pid-file "named.pid"; + listen-on { 10.53.0.3; }; + listen-on-v6 { none; }; + recursion yes; + // max-stale-ttl 3600; + // stale-answer-ttl 3; +}; + +zone "." { + type slave; + masters { 10.53.0.1; }; + file "root.bk"; +}; diff --git a/bin/tests/system/serve-stale/setup.sh b/bin/tests/system/serve-stale/setup.sh new file mode 100644 index 0000000000..1faa70e308 --- /dev/null +++ b/bin/tests/system/serve-stale/setup.sh @@ -0,0 +1 @@ +cp -f ns1/named1.conf ns1/named.conf diff --git a/bin/tests/system/serve-stale/tests.sh b/bin/tests/system/serve-stale/tests.sh new file mode 100755 index 0000000000..e27c3a067f --- /dev/null +++ b/bin/tests/system/serve-stale/tests.sh @@ -0,0 +1,498 @@ +#!/bin/sh +# +# Copyright (C) 2000, 2001, 2004, 2007, 2009-2016 Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SYSTEMTESTTOP=.. +. $SYSTEMTESTTOP/conf.sh + +RNDCCMD="$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p 9953 -s" + +status=0 +n=0 + +#echo "I:check ans.pl server ($n)" +#$DIG -p 5300 @10.53.0.2 example NS +#$DIG -p 5300 @10.53.0.2 example SOA +#$DIG -p 5300 @10.53.0.2 ns.example A +#$DIG -p 5300 @10.53.0.2 ns.example AAAA +#$DIG -p 5300 @10.53.0.2 txt enable +#$DIG -p 5300 @10.53.0.2 txt disable +#$DIG -p 5300 @10.53.0.2 ns.example AAAA +#$DIG -p 5300 @10.53.0.2 txt enable +#$DIG -p 5300 @10.53.0.2 ns.example AAAA +##$DIG -p 5300 @10.53.0.2 data.example TXT +#$DIG -p 5300 @10.53.0.2 nodata.example TXT +#$DIG -p 5300 @10.53.0.2 nxdomain.example TXT + +n=`expr $n + 1` +echo "I:prime cache data.example ($n)" +ret=0 +$DIG -p 5300 @10.53.0.1 data.example TXT > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:prime cache nodata.example ($n)" +ret=0 +$DIG -p 5300 @10.53.0.1 nodata.example TXT > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:prime cache nxdomain.example ($n)" +ret=0 +$DIG -p 5300 @10.53.0.1 nxdomain.example TXT > dig.out.test$n +grep "status: NXDOMAIN" dig.out.test$n > /dev/null || ret=1 +grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:disable responses from authoritative server ($n)" +ret=0 +$DIG -p 5300 @10.53.0.2 txt disable > dig.out.test$n +grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1 +grep "TXT.\"0\"" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +sleep 1 + +n=`expr $n + 1` +echo "I:check 'rndc serve-stale status' ($n)" +ret=0 +$RNDCCMD 10.53.0.1 serve-stale status > rndc.out.test$n 2>&1 || ret=1 +grep '_default: on (stale-answer-ttl=1 max-stale-ttl=3600)' rndc.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check stale data.example ($n)" +ret=0 +$DIG -p 5300 @10.53.0.1 data.example TXT > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check stale nodata.example ($n)" +ret=0 +$DIG -p 5300 @10.53.0.1 nodata.example TXT > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check stale nxdomain.example ($n)" +ret=0 +$DIG -p 5300 @10.53.0.1 nxdomain.example TXT > dig.out.test$n +grep "status: NXDOMAIN" dig.out.test$n > /dev/null || ret=1 +grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:running 'rndc serve-stale off' ($n)" +ret=0 +$RNDCCMD 10.53.0.1 serve-stale off || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check 'rndc serve-stale status' ($n)" +ret=0 +$RNDCCMD 10.53.0.1 serve-stale status > rndc.out.test$n 2>&1 || ret=1 +grep '_default: off (rndc) (stale-answer-ttl=1 max-stale-ttl=3600)' rndc.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check stale data.example (serve-stale off) ($n)" +ret=0 +$DIG -p 5300 @10.53.0.1 data.example TXT > dig.out.test$n +grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check stale nodata.example (serve-stale off) ($n)" +ret=0 +$DIG -p 5300 @10.53.0.1 nodata.example TXT > dig.out.test$n +grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check stale nxdomain.example (serve-stale off) ($n)" +ret=0 +$DIG -p 5300 @10.53.0.1 nxdomain.example TXT > dig.out.test$n +grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:running 'rndc serve-stale on' ($n)" +ret=0 +$RNDCCMD 10.53.0.1 serve-stale on || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check 'rndc serve-stale status' ($n)" +ret=0 +$RNDCCMD 10.53.0.1 serve-stale status > rndc.out.test$n 2>&1 || ret=1 +grep '_default: on (rndc) (stale-answer-ttl=1 max-stale-ttl=3600)' rndc.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check stale data.example (serve-stale on) ($n)" +ret=0 +$DIG -p 5300 @10.53.0.1 data.example TXT > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check stale nodata.example (serve-stale on) ($n)" +ret=0 +$DIG -p 5300 @10.53.0.1 nodata.example TXT > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check stale nxdomain.example (serve-stale on) ($n)" +ret=0 +$DIG -p 5300 @10.53.0.1 nxdomain.example TXT > dig.out.test$n +grep "status: NXDOMAIN" dig.out.test$n > /dev/null || ret=1 +grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:running 'rndc serve-stale no' ($n)" +ret=0 +$RNDCCMD 10.53.0.1 serve-stale no || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check 'rndc serve-stale status' ($n)" +ret=0 +$RNDCCMD 10.53.0.1 serve-stale status > rndc.out.test$n 2>&1 || ret=1 +grep '_default: off (rndc) (stale-answer-ttl=1 max-stale-ttl=3600)' rndc.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check stale data.example (serve-stale no) ($n)" +ret=0 +$DIG -p 5300 @10.53.0.1 data.example TXT > dig.out.test$n +grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check stale nodata.example (serve-stale no) ($n)" +ret=0 +$DIG -p 5300 @10.53.0.1 nodata.example TXT > dig.out.test$n +grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check stale nxdomain.example (serve-stale no) ($n)" +ret=0 +$DIG -p 5300 @10.53.0.1 nxdomain.example TXT > dig.out.test$n +grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:running 'rndc serve-stale yes' ($n)" +ret=0 +$RNDCCMD 10.53.0.1 serve-stale yes || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check 'rndc serve-stale status' ($n)" +ret=0 +$RNDCCMD 10.53.0.1 serve-stale status > rndc.out.test$n 2>&1 || ret=1 +grep '_default: on (rndc) (stale-answer-ttl=1 max-stale-ttl=3600)' rndc.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check stale data.example (serve-stale yes) ($n)" +ret=0 +$DIG -p 5300 @10.53.0.1 data.example TXT > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check stale nodata.example (serve-stale yes) ($n)" +ret=0 +$DIG -p 5300 @10.53.0.1 nodata.example TXT > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check stale nxdomain.example (serve-stale yes) ($n)" +ret=0 +$DIG -p 5300 @10.53.0.1 nxdomain.example TXT > dig.out.test$n +grep "status: NXDOMAIN" dig.out.test$n > /dev/null || ret=1 +grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:running 'rndc serve-stale off' ($n)" +ret=0 +$RNDCCMD 10.53.0.1 serve-stale off || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:running 'rndc serve-stale reset' ($n)" +ret=0 +$RNDCCMD 10.53.0.1 serve-stale reset || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check 'rndc serve-stale status' ($n)" +ret=0 +$RNDCCMD 10.53.0.1 serve-stale status > rndc.out.test$n 2>&1 || ret=1 +grep '_default: on (stale-answer-ttl=1 max-stale-ttl=3600)' rndc.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check stale data.example (serve-stale reset) ($n)" +ret=0 +$DIG -p 5300 @10.53.0.1 data.example TXT > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check stale nodata.example (serve-stale reset) ($n)" +ret=0 +$DIG -p 5300 @10.53.0.1 nodata.example TXT > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check stale nxdomain.example (serve-stale reset) ($n)" +ret=0 +$DIG -p 5300 @10.53.0.1 nxdomain.example TXT > dig.out.test$n +grep "status: NXDOMAIN" dig.out.test$n > /dev/null || ret=1 +grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:running 'rndc serve-stale off' ($n)" +ret=0 +$RNDCCMD 10.53.0.1 serve-stale off || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check 'rndc serve-stale status' ($n)" +ret=0 +$RNDCCMD 10.53.0.1 serve-stale status > rndc.out.test$n 2>&1 || ret=1 +grep '_default: off (rndc) (stale-answer-ttl=1 max-stale-ttl=3600)' rndc.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:updating ns1/named.conf ($n)" +ret=0 +cp -f ns1/named2.conf ns1/named.conf || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:running 'rndc reload' ($n)" +ret=0 +$RNDCCMD 10.53.0.1 reload > rndc.out.test$n 2>&1 || ret=1 +grep "server reload successful" rndc.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check 'rndc serve-stale status' ($n)" +ret=0 +$RNDCCMD 10.53.0.1 serve-stale status > rndc.out.test$n 2>&1 || ret=1 +grep '_default: off (rndc) (stale-answer-ttl=2 max-stale-ttl=7200)' rndc.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check 'rndc serve-stale' ($n)" +ret=0 +$RNDCCMD 10.53.0.1 serve-stale > rndc.out.test$n 2>&1 && ret=1 +grep "unexpected end of input" rndc.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check 'rndc serve-stale unknown' ($n)" +ret=0 +$RNDCCMD 10.53.0.1 serve-stale unknown > rndc.out.test$n 2>&1 && ret=1 +grep "syntax error" rndc.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:enable responses from authoritative server ($n)" +ret=0 +$DIG -p 5300 @10.53.0.2 txt enable > dig.out.test$n +grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1 +grep "TXT.\"1\"" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:prime cache data.example (max-stale-ttl default) ($n)" +ret=0 +$DIG -p 5300 @10.53.0.3 data.example TXT > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:prime cache nodata.example (max-stale-ttl default) ($n)" +ret=0 +$DIG -p 5300 @10.53.0.3 nodata.example TXT > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:prime cache nxdomain.example (max-stale-ttl default) ($n)" +ret=0 +$DIG -p 5300 @10.53.0.3 nxdomain.example TXT > dig.out.test$n +grep "status: NXDOMAIN" dig.out.test$n > /dev/null || ret=1 +grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:disable responses from authoritative server ($n)" +ret=0 +$DIG -p 5300 @10.53.0.2 txt disable > dig.out.test$n +grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1 +grep "TXT.\"0\"" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +sleep 1 + +n=`expr $n + 1` +echo "I:check 'rndc serve-stale status' ($n)" +ret=0 +$RNDCCMD 10.53.0.3 serve-stale status > rndc.out.test$n 2>&1 || ret=1 +grep '_default: off (stale-answer-ttl=1 max-stale-ttl=604800)' rndc.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check fail of data.example (max-stale-ttl default) ($n)" +ret=0 +$DIG -p 5300 @10.53.0.3 data.example TXT > dig.out.test$n +grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1 +grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check fail of nodata.example (max-stale-ttl default) ($n)" +ret=0 +$DIG -p 5300 @10.53.0.3 nodata.example TXT > dig.out.test$n +grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1 +grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check fail of nxdomain.example (max-stale-ttl default) ($n)" +ret=0 +$DIG -p 5300 @10.53.0.3 nxdomain.example TXT > dig.out.test$n +grep "status: SERVFAIL" dig.out.test$n > /dev/null || ret=1 +grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check 'rndc serve-stale on' ($n)" +ret=0 +$RNDCCMD 10.53.0.3 serve-stale on > rndc.out.test$n 2>&1 || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check 'rndc serve-stale status' ($n)" +ret=0 +$RNDCCMD 10.53.0.3 serve-stale status > rndc.out.test$n 2>&1 || ret=1 +grep '_default: on (rndc) (stale-answer-ttl=1 max-stale-ttl=604800)' rndc.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check data.example (max-stale-ttl default) ($n)" +ret=0 +$DIG -p 5300 @10.53.0.3 data.example TXT > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +grep "ANSWER: 1," dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check nodata.example (max-stale-ttl default) ($n)" +ret=0 +$DIG -p 5300 @10.53.0.3 nodata.example TXT > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +n=`expr $n + 1` +echo "I:check nxdomain.example (max-stale-ttl default) ($n)" +ret=0 +$DIG -p 5300 @10.53.0.3 nxdomain.example TXT > dig.out.test$n +grep "status: NXDOMAIN" dig.out.test$n > /dev/null || ret=1 +grep "ANSWER: 0," dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo "I:failed"; fi +status=`expr $status + $ret` + +echo "I:exit status: $status" +[ $status -eq 0 ] || exit 1 diff --git a/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml index eb4b600a7d..2dd26d9671 100644 --- a/doc/arm/Bv9ARM-book.xml +++ b/doc/arm/Bv9ARM-book.xml @@ -4599,7 +4599,10 @@ badresp:1,adberr:0,findfail:0,valfail:0] [ lame-ttl number ; ] [ max-ncache-ttl number ; ] [ max-cache-ttl number ; ] + [ max-stale-ttl number ; ] [ max-zone-ttl ( | number ) ; ] + [ stale-answer-enable yes_or_no ; ] + [ stale-answer-ttl number ; ] [ serial-update-method ( | | ) ; ] [ servfail-ttl number ; ] [ sig-validity-interval number [number] ; ] @@ -4802,6 +4805,7 @@ badresp:1,adberr:0,findfail:0,valfail:0] dnssec-validation, max-cache-ttl, max-ncache-ttl, + max-stale-ttl, max-cache-size, and zero-no-soa-ttl. @@ -5826,7 +5830,6 @@ options { - max-zone-ttl @@ -5862,6 +5865,21 @@ options { + + stale-answer-ttl + + + Specifies the TTL to be returned on stale answers. + The default is 1 second. The minimal allowed is + also 1 second; a value of 0 will be updated silently + to 1 second. For stale answers to be returned + must be set to a + non zero value and they must not have been disabled + by rndc. + + + + serial-update-method @@ -6591,6 +6609,22 @@ options { + + serve-stale-enable + + + Enable the returning of stale answers when the + nameservers for the zone are not answering. This + is off by default but can be enabled/disabled via + rndc server-stale on and + rndc server-stale off which + override the named.conf setting. rndc + server-stale reset will restore control + via named.conf. + + + + nocookie-udp-size @@ -7751,14 +7785,20 @@ options { resolver-query-timeout - The amount of time in seconds that the resolver + The amount of time in milliseconds that the resolver will spend attempting to resolve a recursive query before failing. The default and minimum - is 10 and the maximum is - 30. Setting it to + is 10000 and the maximum is + 30000. Setting it to 0 will result in the default being used. + + This value was originally specified in seconds. + Values less than or equal to 300 will be be treated + as seconds and converted to milliseconds before + applying the above limits. + @@ -9397,6 +9437,27 @@ avoid-v6-udp-ports { 40000; range 50000 60000; }; + + max-stale-ttl + + + Sets the maximum time for which the server will + retain records past their normal expiry to + return them as stale records when the servers + for those records are not reachable. The default + is to not retain the record. + + + rndc serve-stale can be used + to disable and re-enable the serving of stale + records at runtime. Reloading or reconfiguring + named will not re-enable serving + of stale records if they have been disabled via + rndc. + + + + min-roots diff --git a/doc/misc/options b/doc/misc/options index 1c06be4153..087b54c01f 100644 --- a/doc/misc/options +++ b/doc/misc/options @@ -228,6 +228,7 @@ options { max-refresh-time ; max-retry-time ; max-rsa-exponent-size ; + max-stale-ttl ; max-transfer-idle-in ; max-transfer-idle-out ; max-transfer-time-in ; @@ -302,7 +303,9 @@ options { request-sit ; // obsolete require-server-cookie ; reserved-sockets ; + resolver-nonbackoff-tries ; resolver-query-timeout ; + resolver-retry-interval ; response-padding { ; ... } block-size ; response-policy { zone [ log ] [ @@ -334,6 +337,8 @@ options { sit-secret ; // obsolete sortlist { ; ... }; stacksize ( default | unlimited | ); + stale-answer-enable ; + stale-answer-ttl ; startup-notify-rate ; statistics-file ; statistics-interval ; // not yet implemented @@ -553,6 +558,7 @@ view [ ] { max-recursion-queries ; max-refresh-time ; max-retry-time ; + max-stale-ttl ; max-transfer-idle-in ; max-transfer-idle-out ; max-transfer-time-in ; @@ -615,7 +621,9 @@ view [ ] { request-nsid ; request-sit ; // obsolete require-server-cookie ; + resolver-nonbackoff-tries ; resolver-query-timeout ; + resolver-retry-interval ; response-padding { ; ... } block-size ; response-policy { zone [ log ] [ @@ -674,6 +682,8 @@ view [ ] { sig-signing-type ; sig-validity-interval [ ]; sortlist { ; ... }; + stale-answer-enable ; + stale-answer-ttl ; suppress-initial-notify ; // not yet implemented synth-from-dnssec ; topology { ; ... }; // not implemented diff --git a/lib/bind9/check.c b/lib/bind9/check.c index b4dbdbde27..a196c922bd 100644 --- a/lib/bind9/check.c +++ b/lib/bind9/check.c @@ -83,7 +83,8 @@ check_orderent(const cfg_obj_t *ent, isc_log_t *logctx) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "rrset-order: invalid class '%s'", r.base); - result = ISC_R_FAILURE; + if (result == ISC_R_SUCCESS) + result = ISC_R_FAILURE; } } @@ -96,7 +97,8 @@ check_orderent(const cfg_obj_t *ent, isc_log_t *logctx) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "rrset-order: invalid type '%s'", r.base); - result = ISC_R_FAILURE; + if (result == ISC_R_SUCCESS) + result = ISC_R_FAILURE; } } @@ -110,7 +112,8 @@ check_orderent(const cfg_obj_t *ent, isc_log_t *logctx) { if (tresult != ISC_R_SUCCESS) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "rrset-order: invalid name '%s'", str); - result = ISC_R_FAILURE; + if (result == ISC_R_SUCCESS) + result = ISC_R_FAILURE; } } @@ -119,14 +122,16 @@ check_orderent(const cfg_obj_t *ent, isc_log_t *logctx) { strcasecmp("order", cfg_obj_asstring(obj)) != 0) { cfg_obj_log(ent, logctx, ISC_LOG_ERROR, "rrset-order: keyword 'order' missing"); - result = ISC_R_FAILURE; + if (result == ISC_R_SUCCESS) + result = ISC_R_FAILURE; } obj = cfg_tuple_get(ent, "ordering"); if (!cfg_obj_isstring(obj)) { cfg_obj_log(ent, logctx, ISC_LOG_ERROR, "rrset-order: missing ordering"); - result = ISC_R_FAILURE; + if (result == ISC_R_SUCCESS) + result = ISC_R_FAILURE; } else if (strcasecmp(cfg_obj_asstring(obj), "fixed") == 0) { #if !DNS_RDATASET_FIXED cfg_obj_log(obj, logctx, ISC_LOG_WARNING, @@ -139,7 +144,8 @@ check_orderent(const cfg_obj_t *ent, isc_log_t *logctx) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "rrset-order: invalid order '%s'", cfg_obj_asstring(obj)); - result = ISC_R_FAILURE; + if (result == ISC_R_SUCCESS) + result = ISC_R_FAILURE; } return (result); } @@ -159,7 +165,7 @@ check_order(const cfg_obj_t *options, isc_log_t *logctx) { element = cfg_list_next(element)) { tresult = check_orderent(cfg_listelt_value(element), logctx); - if (tresult != ISC_R_SUCCESS) + if (result == ISC_R_SUCCESS && tresult != ISC_R_SUCCESS) result = tresult; } return (result); @@ -189,7 +195,8 @@ check_dual_stack(const cfg_obj_t *options, isc_log_t *logctx) { if (val > ISC_UINT16_MAX) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "port '%u' out of range", val); - result = ISC_R_FAILURE; + if (result == ISC_R_SUCCESS) + result = ISC_R_RANGE; } } obj = cfg_tuple_get(alternates, "addresses"); @@ -210,7 +217,8 @@ check_dual_stack(const cfg_obj_t *options, isc_log_t *logctx) { if (tresult != ISC_R_SUCCESS) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "bad name '%s'", str); - result = ISC_R_FAILURE; + if (result == ISC_R_SUCCESS) + result = tresult; } obj = cfg_tuple_get(value, "port"); if (cfg_obj_isuint32(obj)) { @@ -218,7 +226,8 @@ check_dual_stack(const cfg_obj_t *options, isc_log_t *logctx) { if (val > ISC_UINT16_MAX) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "port '%u' out of range", val); - result = ISC_R_FAILURE; + if (result == ISC_R_SUCCESS) + result = ISC_R_RANGE; } } } @@ -1235,7 +1244,8 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "auto-dnssec may only be activated at the " "zone level"); - result = ISC_R_FAILURE; + if (result == ISC_R_SUCCESS) + result = ISC_R_FAILURE; } } @@ -1255,7 +1265,7 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, { obj = cfg_listelt_value(element); tresult = mustbesecure(obj, symtab, logctx, mctx); - if (tresult != ISC_R_SUCCESS) + if (result == ISC_R_SUCCESS && tresult != ISC_R_SUCCESS) result = tresult; } if (symtab != NULL) @@ -1274,7 +1284,8 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "%s: invalid name '%s'", server_contact[i], str); - result = ISC_R_FAILURE; + if (result == ISC_R_SUCCESS) + result = ISC_R_FAILURE; } } } @@ -1294,7 +1305,8 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "disable-empty-zone: invalid name '%s'", str); - result = ISC_R_FAILURE; + if (result == ISC_R_SUCCESS) + result = ISC_R_FAILURE; } } @@ -1308,11 +1320,12 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, strlen(cfg_obj_asstring(obj)) > 1024U) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'server-id' too big (>1024 bytes)"); - result = ISC_R_FAILURE; + if (result == ISC_R_SUCCESS) + result = ISC_R_FAILURE; } tresult = check_dscp(options, logctx); - if (tresult != ISC_R_SUCCESS) + if (result == ISC_R_SUCCESS && tresult != ISC_R_SUCCESS) result = tresult; obj = NULL; @@ -1322,11 +1335,13 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, if (lifetime > 604800) { /* 7 days */ cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'nta-lifetime' cannot exceed one week"); - result = ISC_R_RANGE; + if (result == ISC_R_SUCCESS) + result = ISC_R_RANGE; } else if (lifetime == 0) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'nta-lifetime' may not be zero"); - result = ISC_R_RANGE; + if (result == ISC_R_SUCCESS) + result = ISC_R_RANGE; } } @@ -1337,7 +1352,8 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, if (recheck > 604800) { /* 7 days */ cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'nta-recheck' cannot exceed one week"); - result = ISC_R_RANGE; + if (result == ISC_R_SUCCESS) + result = ISC_R_RANGE; } if (recheck > lifetime) @@ -1355,7 +1371,8 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, if (strcasecmp(ccalg, "aes") == 0) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "cookie-algorithm: '%s' not supported", ccalg); - result = ISC_R_NOTIMPLEMENTED; + if (result == ISC_R_SUCCESS) + result = ISC_R_NOTIMPLEMENTED; } #endif @@ -1438,7 +1455,8 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "%s out of range (%u < %u)", fstrm[i].name, value, fstrm[i].min); - result = ISC_R_RANGE; + if (result == ISC_R_SUCCESS) + result = ISC_R_RANGE; } if (strcmp(fstrm[i].name, "fstrm-set-input-queue-size") == 0) { @@ -1452,7 +1470,8 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, "%s '%u' not a power-of-2", fstrm[i].name, cfg_obj_asuint32(obj)); - result = ISC_R_RANGE; + if (result == ISC_R_SUCCESS) + result = ISC_R_RANGE; } } } @@ -1468,42 +1487,46 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, if (obj2 == NULL) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "dnstap-output mode not found"); - return (ISC_R_FAILURE); - } + if (result == ISC_R_SUCCESS) + result = ISC_R_FAILURE; + } else { + if (strcasecmp(cfg_obj_asstring(obj2), "file") == 0) + dmode = dns_dtmode_file; + else + dmode = dns_dtmode_unix; - if (strcasecmp(cfg_obj_asstring(obj2), "file") == 0) - dmode = dns_dtmode_file; - else - dmode = dns_dtmode_unix; + obj2 = cfg_tuple_get(obj, "size"); + if (obj2 != NULL && !cfg_obj_isvoid(obj2) && + dmode == dns_dtmode_unix) + { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "dnstap-output size " + "cannot be set with mode unix"); + if (result == ISC_R_SUCCESS) + result = ISC_R_FAILURE; + } - obj2 = cfg_tuple_get(obj, "size"); - if (obj2 != NULL && !cfg_obj_isvoid(obj2) && - dmode == dns_dtmode_unix) - { - cfg_obj_log(obj, logctx, ISC_LOG_ERROR, - "dnstap-output size " - "cannot be set with mode unix"); - return (ISC_R_FAILURE); - } + obj2 = cfg_tuple_get(obj, "versions"); + if (obj2 != NULL && !cfg_obj_isvoid(obj2) && + dmode == dns_dtmode_unix) + { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "dnstap-output versions " + "cannot be set with mode unix"); + if (result == ISC_R_SUCCESS) + result = ISC_R_FAILURE; + } - obj2 = cfg_tuple_get(obj, "versions"); - if (obj2 != NULL && !cfg_obj_isvoid(obj2) && - dmode == dns_dtmode_unix) - { - cfg_obj_log(obj, logctx, ISC_LOG_ERROR, - "dnstap-output versions " - "cannot be set with mode unix"); - return (ISC_R_FAILURE); - } - - obj2 = cfg_tuple_get(obj, "suffix"); - if (obj2 != NULL && !cfg_obj_isvoid(obj2) && - dmode == dns_dtmode_unix) - { - cfg_obj_log(obj, logctx, ISC_LOG_ERROR, - "dnstap-output suffix " - "cannot be set with mode unix"); - return (ISC_R_FAILURE); + obj2 = cfg_tuple_get(obj, "suffix"); + if (obj2 != NULL && !cfg_obj_isvoid(obj2) && + dmode == dns_dtmode_unix) + { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "dnstap-output suffix " + "cannot be set with mode unix"); + if (result == ISC_R_SUCCESS) + result = ISC_R_FAILURE; + } } } #endif @@ -1520,7 +1543,8 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, "%" ISC_PRINT_QUADFORMAT "d' " "is too small", mapsize); - return (ISC_R_RANGE); + if (result == ISC_R_SUCCESS) + result = ISC_R_RANGE; } else if (mapsize > (1ULL << 40)) { /* 1 terabyte */ cfg_obj_log(obj, logctx, ISC_LOG_ERROR, @@ -1528,10 +1552,20 @@ check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx, "%" ISC_PRINT_QUADFORMAT "d' " "is too large", mapsize); - return (ISC_R_RANGE); + if (result == ISC_R_SUCCESS) + result = ISC_R_RANGE; } } + obj = NULL; + (void)cfg_map_get(options, "resolver-nonbackoff-tries", &obj); + if (obj != NULL && cfg_obj_asuint32(obj) == 0U) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "'resolver-nonbackoff-tries' must be >= 1"); + if (result == ISC_R_SUCCESS) + result = ISC_R_RANGE; + } + return (result); } diff --git a/lib/dns/cache.c b/lib/dns/cache.c index 45b8b785d8..10e2331520 100644 --- a/lib/dns/cache.c +++ b/lib/dns/cache.c @@ -133,6 +133,7 @@ struct dns_cache { int db_argc; char **db_argv; size_t size; + dns_ttl_t serve_stale_ttl; isc_stats_t *stats; /* Locked by 'filelock'. */ @@ -162,9 +163,13 @@ overmem_cleaning_action(isc_task_t *task, isc_event_t *event); static inline isc_result_t cache_create_db(dns_cache_t *cache, dns_db_t **db) { - return (dns_db_create(cache->mctx, cache->db_type, dns_rootname, - dns_dbtype_cache, cache->rdclass, - cache->db_argc, cache->db_argv, db)); + isc_result_t result; + result = dns_db_create(cache->mctx, cache->db_type, dns_rootname, + dns_dbtype_cache, cache->rdclass, + cache->db_argc, cache->db_argv, db); + if (result == ISC_R_SUCCESS) + dns_db_setservestalettl(*db, cache->serve_stale_ttl); + return (result); } isc_result_t @@ -1087,6 +1092,32 @@ dns_cache_getcachesize(dns_cache_t *cache) { return (size); } +void +dns_cache_setservestalettl(dns_cache_t *cache, dns_ttl_t ttl) { + REQUIRE(VALID_CACHE(cache)); + + LOCK(&cache->lock); + cache->serve_stale_ttl = ttl; + UNLOCK(&cache->lock); + + (void)dns_db_setservestalettl(cache->db, ttl); +} + +dns_ttl_t +dns_cache_getservestalettl(dns_cache_t *cache) { + dns_ttl_t ttl; + isc_result_t result; + + REQUIRE(VALID_CACHE(cache)); + + /* + * Could get it straight from the dns_cache_t, but use db + * to confirm the value that the db is really using. + */ + result = dns_db_getservestalettl(cache->db, &ttl); + return result == ISC_R_SUCCESS ? ttl : 0; +} + /* * The cleaner task is shutting down; do the necessary cleanup. */ diff --git a/lib/dns/db.c b/lib/dns/db.c index cbe431ad47..5924934c16 100644 --- a/lib/dns/db.c +++ b/lib/dns/db.c @@ -1125,3 +1125,25 @@ dns_db_nodefullname(dns_db_t *db, dns_dbnode_t *node, dns_name_t *name) { return (ISC_R_NOTIMPLEMENTED); return ((db->methods->nodefullname)(db, node, name)); } + +isc_result_t +dns_db_setservestalettl(dns_db_t *db, dns_ttl_t ttl) +{ + REQUIRE(DNS_DB_VALID(db)); + REQUIRE((db->attributes & DNS_DBATTR_CACHE) != 0); + + if (db->methods->setservestalettl != NULL) + return ((db->methods->setservestalettl)(db, ttl)); + return (ISC_R_NOTIMPLEMENTED); +} + +isc_result_t +dns_db_getservestalettl(dns_db_t *db, dns_ttl_t *ttl) +{ + REQUIRE(DNS_DB_VALID(db)); + REQUIRE((db->attributes & DNS_DBATTR_CACHE) != 0); + + if (db->methods->getservestalettl != NULL) + return ((db->methods->getservestalettl)(db, ttl)); + return (ISC_R_NOTIMPLEMENTED); +} diff --git a/lib/dns/ecdb.c b/lib/dns/ecdb.c index dd1ce7ed2e..e39be8fa72 100644 --- a/lib/dns/ecdb.c +++ b/lib/dns/ecdb.c @@ -581,7 +581,9 @@ static dns_dbmethods_t ecdb_methods = { NULL, /* setcachestats */ NULL, /* hashsize */ NULL, /* nodefullname */ - NULL /* getsize */ + NULL, /* getsize */ + NULL, /* setservestalettl */ + NULL /* getservestalettl */ }; static isc_result_t diff --git a/lib/dns/include/dns/cache.h b/lib/dns/include/dns/cache.h index bb0557707d..52e2a848e4 100644 --- a/lib/dns/include/dns/cache.h +++ b/lib/dns/include/dns/cache.h @@ -256,6 +256,27 @@ dns_cache_getcachesize(dns_cache_t *cache); * Get the maximum cache size. */ +void +dns_cache_setservestalettl(dns_cache_t *cache, dns_ttl_t ttl); +/*%< + * Sets the maximum length of time that cached answers may be retained + * past their normal TTL. Default value for the library is 0, disabling + * the use of stale data. + * + * Requires: + *\li 'cache' to be valid. + */ + +dns_ttl_t +dns_cache_getservestalettl(dns_cache_t *cache); +/*%< + * Gets the maximum length of time that cached answers may be kept past + * normal expiry. + * + * Requires: + *\li 'cache' to be valid. + */ + isc_result_t dns_cache_flush(dns_cache_t *cache); /*%< diff --git a/lib/dns/include/dns/db.h b/lib/dns/include/dns/db.h index d89c4d7d75..8ef076b58b 100644 --- a/lib/dns/include/dns/db.h +++ b/lib/dns/include/dns/db.h @@ -189,6 +189,8 @@ typedef struct dns_dbmethods { dns_name_t *name); isc_result_t (*getsize)(dns_db_t *db, dns_dbversion_t *version, isc_uint64_t *records, isc_uint64_t *bytes); + isc_result_t (*setservestalettl)(dns_db_t *db, dns_ttl_t ttl); + isc_result_t (*getservestalettl)(dns_db_t *db, dns_ttl_t *ttl); } dns_dbmethods_t; typedef isc_result_t @@ -247,6 +249,7 @@ struct dns_dbonupdatelistener { #define DNS_DBFIND_FORCENSEC3 0x0080 #define DNS_DBFIND_ADDITIONALOK 0x0100 #define DNS_DBFIND_NOZONECUT 0x0200 +#define DNS_DBFIND_STALEOK 0x0400 /*@}*/ /*@{*/ @@ -1687,6 +1690,38 @@ dns_db_nodefullname(dns_db_t *db, dns_dbnode_t *node, dns_name_t *name); * \li 'db' is a valid database * \li 'node' and 'name' are not NULL */ + +isc_result_t +dns_db_setservestalettl(dns_db_t *db, dns_ttl_t ttl); +/*%< + * Sets the maximum length of time that cached answers may be retained + * past their normal TTL. Default value for the library is 0, disabling + * the use of stale data. + * + * Requires: + * \li 'db' is a valid cache database. + * \li 'ttl' is the number of seconds to retain data past its normal expiry. + * + * Returns: + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOTIMPLEMENTED - Not supported by this DB implementation. + */ + +isc_result_t +dns_db_getservestalettl(dns_db_t *db, dns_ttl_t *ttl); +/*%< + * Gets maximum length of time that cached answers may be kept past + * normal TTL expiration. + * + * Requires: + * \li 'db' is a valid cache database. + * \li 'ttl' is the number of seconds to retain data past its normal expiry. + * + * Returns: + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOTIMPLEMENTED - Not supported by this DB implementation. + */ + ISC_LANG_ENDDECLS #endif /* DNS_DB_H */ diff --git a/lib/dns/include/dns/rdataset.h b/lib/dns/include/dns/rdataset.h index c450292294..de3ea37775 100644 --- a/lib/dns/include/dns/rdataset.h +++ b/lib/dns/include/dns/rdataset.h @@ -183,6 +183,7 @@ struct dns_rdataset { #define DNS_RDATASETATTR_NEGATIVE 0x00200000 #define DNS_RDATASETATTR_PREFETCH 0x00400000 #define DNS_RDATASETATTR_CYCLIC 0x00800000 /*%< Cyclic ordering. */ +#define DNS_RDATASETATTR_STALE 0x01000000 /*% * _OMITDNSSEC: diff --git a/lib/dns/include/dns/resolver.h b/lib/dns/include/dns/resolver.h index 45be77e3d2..0473d51430 100644 --- a/lib/dns/include/dns/resolver.h +++ b/lib/dns/include/dns/resolver.h @@ -535,9 +535,12 @@ dns_resolver_getmustbesecure(dns_resolver_t *resolver, const dns_name_t *name); void -dns_resolver_settimeout(dns_resolver_t *resolver, unsigned int seconds); +dns_resolver_settimeout(dns_resolver_t *resolver, unsigned int timeout); /*%< - * Set the length of time the resolver will work on a query, in seconds. + * Set the length of time the resolver will work on a query, in milliseconds. + * + * 'timeout' was originally defined in seconds, and later redefined to be in + * milliseconds. Values less than or equal to 300 are treated as seconds. * * If timeout is 0, the default timeout will be applied. * @@ -548,7 +551,8 @@ dns_resolver_settimeout(dns_resolver_t *resolver, unsigned int seconds); unsigned int dns_resolver_gettimeout(dns_resolver_t *resolver); /*%< - * Get the current length of time the resolver will work on a query, in seconds. + * Get the current length of time the resolver will work on a query, + * in milliseconds. * * Requires: * \li resolver to be valid. @@ -570,6 +574,39 @@ dns_resolver_getzeronosoattl(dns_resolver_t *resolver); void dns_resolver_setzeronosoattl(dns_resolver_t *resolver, isc_boolean_t state); +unsigned int +dns_resolver_getretryinterval(dns_resolver_t *resolver); + +void +dns_resolver_setretryinterval(dns_resolver_t *resolver, unsigned int interval); +/*%< + * Sets the amount of time, in millseconds, that is waited for a reply + * to a server before another server is tried. Interacts with the + * value of dns_resolver_getnonbackofftries() by trying that number of times + * at this interval, before doing exponential backoff and doubling the interval + * on each subsequent try, to a maximum of 10 seconds. Defaults to 800 ms; + * silently capped at 2000 ms. + * + * Requires: + * \li resolver to be valid. + * \li interval > 0. + */ + +unsigned int +dns_resolver_getnonbackofftries(dns_resolver_t *resolver); + +void +dns_resolver_setnonbackofftries(dns_resolver_t *resolver, unsigned int tries); +/*%< + * Sets the number of failures of getting a reply from remote servers for + * a query before backing off by doubling the retry interval for each + * subsequent request sent. Defaults to 3. + * + * Requires: + * \li resolver to be valid. + * \li tries > 0. + */ + unsigned int dns_resolver_getoptions(dns_resolver_t *resolver); diff --git a/lib/dns/include/dns/types.h b/lib/dns/include/dns/types.h index 3e79760c55..e5e0db5ce2 100644 --- a/lib/dns/include/dns/types.h +++ b/lib/dns/include/dns/types.h @@ -377,6 +377,12 @@ typedef enum { dns_updatemethod_date } dns_updatemethod_t; +typedef enum { + dns_stale_answer_no, + dns_stale_answer_yes, + dns_stale_answer_conf +} dns_stale_answer_t; + /* * Functions. */ diff --git a/lib/dns/include/dns/view.h b/lib/dns/include/dns/view.h index 908ed24b81..7d696abdc4 100644 --- a/lib/dns/include/dns/view.h +++ b/lib/dns/include/dns/view.h @@ -162,6 +162,9 @@ struct dns_view { dns_name_t * dlv; dns_fixedname_t dlv_fixed; isc_uint16_t maxudp; + dns_ttl_t staleanswerttl; + dns_stale_answer_t staleanswersok; /* rndc setting */ + isc_boolean_t staleanswersenable; /* named.conf setting */ isc_uint16_t nocookieudp; isc_uint16_t padding; dns_acl_t * pad_acl; diff --git a/lib/dns/master.c b/lib/dns/master.c index 876405e731..f1ee6503a0 100644 --- a/lib/dns/master.c +++ b/lib/dns/master.c @@ -1906,12 +1906,18 @@ load_text(dns_loadctx_t *lctx) { if ((lctx->options & DNS_MASTER_AGETTL) != 0) { /* - * Adjust the TTL for $DATE. If the RR has already - * expired, ignore it. + * Adjust the TTL for $DATE. If the RR has + * already expired, set its TTL to 0. This + * should be okay even if the TTL stretching + * feature is not in effect, because it will + * just be quickly expired by the cache, and the + * way this was written before the patch it + * could potentially add 0 TTLs anyway. */ if (lctx->ttl < ttl_offset) - continue; - lctx->ttl -= ttl_offset; + lctx->ttl = 0; + else + lctx->ttl -= ttl_offset; } /* diff --git a/lib/dns/masterdump.c b/lib/dns/masterdump.c index 89ad70dac2..082b61cdd9 100644 --- a/lib/dns/masterdump.c +++ b/lib/dns/masterdump.c @@ -89,6 +89,7 @@ typedef struct dns_totext_ctx { dns_fixedname_t origin_fixname; isc_uint32_t current_ttl; isc_boolean_t current_ttl_valid; + dns_ttl_t serve_stale_ttl; } dns_totext_ctx_t; LIBDNS_EXTERNAL_DATA const dns_master_style_t @@ -382,6 +383,7 @@ totext_ctx_init(const dns_master_style_t *style, dns_totext_ctx_t *ctx) { ctx->neworigin = NULL; ctx->current_ttl = 0; ctx->current_ttl_valid = ISC_FALSE; + ctx->serve_stale_ttl = 0; return (ISC_R_SUCCESS); } @@ -1032,6 +1034,8 @@ dump_rdatasets_text(isc_mem_t *mctx, const dns_name_t *name, (ctx->style.flags & DNS_STYLEFLAG_NCACHE) == 0) { /* Omit negative cache entries */ } else { + if (rds->ttl < ctx->serve_stale_ttl) + fprintf(f, "; stale\n"); isc_result_t result = dump_rdataset(mctx, name, rds, ctx, buffer, f); @@ -1494,6 +1498,15 @@ dumpctx_create(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *version, dns_db_attach(db, &dctx->db); dctx->do_date = dns_db_iscache(dctx->db); + if (dctx->do_date) { + /* + * Adjust the date backwards by the serve-stale TTL, if any. + * This is so the TTL will be loaded correctly when next started. + */ + (void)dns_db_getservestalettl(dctx->db, + &dctx->tctx.serve_stale_ttl); + dctx->now -= dctx->tctx.serve_stale_ttl; + } if (dctx->format == dns_masterformat_text && (dctx->tctx.style.flags & DNS_STYLEFLAG_REL_OWNER) != 0) { @@ -1553,6 +1566,9 @@ writeheader(dns_dumpctx_t *dctx) { * it in the zone case. */ if (dctx->do_date) { + fprintf(dctx->f, + "; using a %d second stale ttl\n", + dctx->tctx.serve_stale_ttl); result = dns_time32_totext(dctx->now, &buffer); RUNTIME_CHECK(result == ISC_R_SUCCESS); isc_buffer_usedregion(&buffer, &r); diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c index 8242fa7063..a61101afcf 100644 --- a/lib/dns/rbtdb.c +++ b/lib/dns/rbtdb.c @@ -456,6 +456,7 @@ typedef ISC_LIST(rdatasetheader_t) rdatasetheaderlist_t; typedef ISC_LIST(dns_rbtnode_t) rbtnodelist_t; #define RDATASET_ATTR_NONEXISTENT 0x0001 +/*%< May be potentially served as stale data. */ #define RDATASET_ATTR_STALE 0x0002 #define RDATASET_ATTR_IGNORE 0x0004 #define RDATASET_ATTR_RETAIN 0x0008 @@ -468,6 +469,8 @@ typedef ISC_LIST(dns_rbtnode_t) rbtnodelist_t; #define RDATASET_ATTR_CASESET 0x0400 #define RDATASET_ATTR_ZEROTTL 0x0800 #define RDATASET_ATTR_CASEFULLYLOWER 0x1000 +/*%< Ancient - awaiting cleanup. */ +#define RDATASET_ATTR_ANCIENT 0x2000 /* * XXX @@ -505,6 +508,8 @@ typedef ISC_LIST(dns_rbtnode_t) rbtnodelist_t; (((header)->attributes & RDATASET_ATTR_ZEROTTL) != 0) #define CASEFULLYLOWER(header) \ (((header)->attributes & RDATASET_ATTR_CASEFULLYLOWER) != 0) +#define ANCIENT(header) \ + (((header)->attributes & RDATASET_ATTR_ANCIENT) != 0) #define ACTIVE(header, now) \ (((header)->rdh_ttl > (now)) || \ @@ -572,6 +577,12 @@ typedef struct rbtdb_glue_table_node { rbtdb_glue_t *glue_list; } rbtdb_glue_table_node_t; +typedef enum { + rdataset_ttl_fresh, + rdataset_ttl_stale, + rdataset_ttl_ancient +} rdataset_ttl_t; + typedef struct rbtdb_version { /* Not locked */ rbtdb_serial_t serial; @@ -643,6 +654,12 @@ struct dns_rbtdb { dns_dbnode_t *soanode; dns_dbnode_t *nsnode; + /* + * Maximum length of time to keep using a stale answer past its + * normal TTL expiry. + */ + dns_ttl_t serve_stale_ttl; + /* * This is a linked list used to implement the LRU cache. There will * be node_lock_count linked lists here. Nodes in bucket 1 will be @@ -683,6 +700,8 @@ struct dns_rbtdb { #define RBTDB_ATTR_LOADED 0x01 #define RBTDB_ATTR_LOADING 0x02 +#define KEEPSTALE(rbtdb) ((rbtdb)->serve_stale_ttl > 0) + /*% * Search Context */ @@ -1694,15 +1713,15 @@ rollback_node(dns_rbtnode_t *node, rbtdb_serial_t serial) { } static inline void -mark_stale_header(dns_rbtdb_t *rbtdb, rdatasetheader_t *header) { +mark_header_ancient(dns_rbtdb_t *rbtdb, rdatasetheader_t *header) { /* - * If we are already stale there is nothing to do. + * If we are already ancient there is nothing to do. */ - if ((header->attributes & RDATASET_ATTR_STALE) != 0) + if (ANCIENT(header)) return; - header->attributes |= RDATASET_ATTR_STALE; + header->attributes |= RDATASET_ATTR_ANCIENT; header->node->dirty = 1; /* @@ -1743,8 +1762,8 @@ clean_cache_node(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node) { /* * If current is nonexistent or stale, we can clean it up. */ - if ((current->attributes & - (RDATASET_ATTR_NONEXISTENT|RDATASET_ATTR_STALE)) != 0) { + if (NONEXISTENT(current) || ANCIENT(current) || + (STALE(current) && ! KEEPSTALE(rbtdb))) { if (top_prev != NULL) top_prev->next = current->next; else @@ -1967,6 +1986,80 @@ delete_node(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node) { } } +#if 0 +static void +clean_now_or_later(dns_rbtnode_t *node, dns_rbtdb_t *rbtdb, + rdatasetheader_t *header, rdatasetheader_t **header_prevp) +{ + if (dns_rbtnode_refcurrent(node) == 0) { + isc_mem_t *mctx; + + /* + * header->down can be non-NULL if the refcount has just + * decremented to 0 but decrement_reference() has not performed + * clean_cache_node(), in which case we need to purge the stale + * headers first. + */ + mctx = rbtdb->common.mctx; + clean_stale_headers(rbtdb, mctx, header); + if (*header_prevp != NULL) + (*header_prevp)->next = header->next; + else + node->data = header->next; + free_rdataset(rbtdb, mctx, header); + } else { + header->attributes |= RDATASET_ATTR_STALE | + RDATASET_ATTR_ANCIENT; + node->dirty = 1; + *header_prevp = header; + } +} + +static rdataset_ttl_t +check_ttl(dns_rbtnode_t *node, rbtdb_search_t *search, + rdatasetheader_t *header, rdatasetheader_t **header_prevp, + nodelock_t *lock, isc_rwlocktype_t *locktype) +{ + dns_rbtdb_t *rbtdb = search->rbtdb; + + if (header->rdh_ttl > search->now) + return rdataset_ttl_fresh; + + /* + * This rdataset is stale, but perhaps still usable. + */ + if (KEEPSTALE(rbtdb) && + header->rdh_ttl + rbtdb->serve_stale_ttl > search->now) { + header->attributes |= RDATASET_ATTR_STALE; + /* Doesn't set dirty because it doesn't need removal. */ + return rdataset_ttl_stale; + } + + /* + * This rdataset is so stale it is no longer usable, even with + * KEEPSTALE. If no one else is using the node, we can clean it up + * right now, otherwise we mark it as ancient, and the node as dirty, + * so it will get cleaned up later. + */ + if ((header->rdh_ttl <= search->now - RBTDB_VIRTUAL) && + (*locktype == isc_rwlocktype_write || + NODE_TRYUPGRADE(lock) == ISC_R_SUCCESS)) { + /* + * We update the node's status only when we can get write + * access; otherwise, we leave others to this work. Periodical + * cleaning will eventually take the job as the last resort. + * We won't downgrade the lock, since other rdatasets are + * probably stale, too. + */ + *locktype = isc_rwlocktype_write; + clean_now_or_later(node, rbtdb, header, header_prevp); + } else + *header_prevp = header; + + return rdataset_ttl_ancient; +} +#endif + /* * Caller must be holding the node lock. */ @@ -3176,6 +3269,10 @@ bind_rdataset(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node, rdataset->attributes |= DNS_RDATASETATTR_OPTOUT; if (PREFETCH(header)) rdataset->attributes |= DNS_RDATASETATTR_PREFETCH; + if (STALE(header)) { + rdataset->attributes |= DNS_RDATASETATTR_STALE; + rdataset->ttl = 0; + } rdataset->private1 = rbtdb; rdataset->private2 = node; raw = (unsigned char *)header + sizeof(*header); @@ -4542,6 +4639,18 @@ check_stale_header(dns_rbtnode_t *node, rdatasetheader_t *header, #endif if (!ACTIVE(header, search->now)) { + dns_ttl_t stale = header->rdh_ttl + + search->rbtdb->serve_stale_ttl; + /* + * If this data is in the stale window keep it and if + * DNS_DBFIND_STALEOK is not set we tell the caller to + * skip this record. + */ + if (KEEPSTALE(search->rbtdb) && stale > search->now) { + header->attributes |= RDATASET_ATTR_STALE; + return ((search->options & DNS_DBFIND_STALEOK) == 0); + } + /* * This rdataset is stale. If no one else is using the * node, we can clean it up right now, otherwise we mark @@ -4581,7 +4690,7 @@ check_stale_header(dns_rbtnode_t *node, rdatasetheader_t *header, node->data = header->next; free_rdataset(search->rbtdb, mctx, header); } else { - mark_stale_header(search->rbtdb, header); + mark_header_ancient(search->rbtdb, header); *header_prev = header; } } else @@ -4990,7 +5099,7 @@ cache_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version, &locktype, lock, &search, &header_prev)) { /* Do nothing. */ - } else if (EXISTS(header) && (!STALE(header))) { + } else if (EXISTS(header) && !ANCIENT(header)) { /* * We now know that there is at least one active * non-stale rdataset at this node. @@ -5468,7 +5577,7 @@ expirenode(dns_db_t *db, dns_dbnode_t *node, isc_stdtime_t now) { * refcurrent(rbtnode) must be non-zero. This is so * because 'node' is an argument to the function. */ - mark_stale_header(rbtdb, header); + mark_header_ancient(rbtdb, header); if (log) isc_log_write(dns_lctx, category, module, level, "overmem cache: stale %s", @@ -5476,7 +5585,7 @@ expirenode(dns_db_t *db, dns_dbnode_t *node, isc_stdtime_t now) { } else if (force_expire) { if (! RETAIN(header)) { set_ttl(rbtdb, header, 0); - mark_stale_header(rbtdb, header); + mark_header_ancient(rbtdb, header); } else if (log) { isc_log_write(dns_lctx, category, module, level, "overmem cache: " @@ -5733,9 +5842,9 @@ cache_findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, * non-zero. This is so because 'node' is an * argument to the function. */ - mark_stale_header(rbtdb, header); + mark_header_ancient(rbtdb, header); } - } else if (EXISTS(header) && (!STALE(header))) { + } else if (EXISTS(header) && !ANCIENT(header)) { if (header->type == matchtype) found = header; else if (header->type == RBTDB_RDATATYPE_NCACHEANY || @@ -6009,7 +6118,7 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, rbtdb_version_t *rbtversion, topheader = topheader->next) { set_ttl(rbtdb, topheader, 0); - mark_stale_header(rbtdb, topheader); + mark_header_ancient(rbtdb, topheader); } goto find_header; } @@ -6067,7 +6176,7 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, rbtdb_version_t *rbtversion, * ncache entry. */ set_ttl(rbtdb, topheader, 0); - mark_stale_header(rbtdb, topheader); + mark_header_ancient(rbtdb, topheader); topheader = NULL; goto find_header; } @@ -6105,8 +6214,11 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, rbtdb_version_t *rbtversion, } /* - * Trying to add an rdataset with lower trust to a cache DB - * has no effect, provided that the cache data isn't stale. + * Trying to add an rdataset with lower trust to a cache + * DB has no effect, provided that the cache data isn't + * stale. If the cache data is stale, new lower trust + * data will supersede it below. Unclear what the best + * policy is here. */ if (rbtversion == NULL && trust < header->trust && (ACTIVE(header, now) || header_nx)) { @@ -6135,6 +6247,10 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, rbtdb_version_t *rbtversion, if ((options & DNS_DBADD_EXACT) != 0) flags |= DNS_RDATASLAB_EXACT; + /* + * TTL use here is irrelevant to the cache; + * merge is only done with zonedbs. + */ if ((options & DNS_DBADD_EXACTTTL) != 0 && newheader->rdh_ttl != header->rdh_ttl) result = DNS_R_NOTEXACT; @@ -6178,11 +6294,12 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, rbtdb_version_t *rbtversion, } } /* - * Don't replace existing NS, A and AAAA RRsets - * in the cache if they are already exist. This - * prevents named being locked to old servers. - * Don't lower trust of existing record if the - * update is forced. + * Don't replace existing NS, A and AAAA RRsets in the + * cache if they are already exist. This prevents named + * being locked to old servers. Don't lower trust of + * existing record if the update is forced. Nothing + * special to be done w.r.t stale data; it gets replaced + * normally further down. */ if (IS_CACHE(rbtdb) && ACTIVE(header, now) && header->type == dns_rdatatype_ns && @@ -6322,10 +6439,10 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, rbtdb_version_t *rbtversion, changed->dirty = ISC_TRUE; if (rbtversion == NULL) { set_ttl(rbtdb, header, 0); - mark_stale_header(rbtdb, header); + mark_header_ancient(rbtdb, header); if (sigheader != NULL) { set_ttl(rbtdb, sigheader, 0); - mark_stale_header(rbtdb, sigheader); + mark_header_ancient(rbtdb, sigheader); } } idx = newheader->node->locknum; @@ -8039,6 +8156,30 @@ nodefullname(dns_db_t *db, dns_dbnode_t *node, dns_name_t *name) { return (result); } +static isc_result_t +setservestalettl(dns_db_t *db, dns_ttl_t ttl) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(IS_CACHE(rbtdb)); + + /* currently no bounds checking. 0 means disable. */ + rbtdb->serve_stale_ttl = ttl; + return ISC_R_SUCCESS; +} + +static isc_result_t +getservestalettl(dns_db_t *db, dns_ttl_t *ttl) { + dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db; + + REQUIRE(VALID_RBTDB(rbtdb)); + REQUIRE(IS_CACHE(rbtdb)); + + *ttl = rbtdb->serve_stale_ttl; + return ISC_R_SUCCESS; +} + + static dns_dbmethods_t zone_methods = { attach, detach, @@ -8084,7 +8225,9 @@ static dns_dbmethods_t zone_methods = { NULL, hashsize, nodefullname, - getsize + getsize, + NULL, + NULL }; static dns_dbmethods_t cache_methods = { @@ -8132,7 +8275,9 @@ static dns_dbmethods_t cache_methods = { setcachestats, hashsize, nodefullname, - NULL + NULL, + setservestalettl, + getservestalettl }; isc_result_t @@ -8400,6 +8545,7 @@ dns_rbtdb_create } rbtdb->attributes = 0; rbtdb->task = NULL; + rbtdb->serve_stale_ttl = 0; /* * Version Initialization. @@ -8828,7 +8974,8 @@ rdatasetiter_first(dns_rdatasetiter_t *iterator) { * rdatasets to work. */ if (NONEXISTENT(header) || - (now != 0 && now > header->rdh_ttl)) + (now != 0 && now > header->rdh_ttl + + rbtdb->serve_stale_ttl)) header = NULL; break; } else @@ -10183,7 +10330,7 @@ static inline isc_boolean_t need_headerupdate(rdatasetheader_t *header, isc_stdtime_t now) { if ((header->attributes & (RDATASET_ATTR_NONEXISTENT | - RDATASET_ATTR_STALE | + RDATASET_ATTR_ANCIENT | RDATASET_ATTR_ZEROTTL)) != 0) return (ISC_FALSE); @@ -10289,7 +10436,7 @@ expire_header(dns_rbtdb_t *rbtdb, rdatasetheader_t *header, isc_boolean_t tree_locked, expire_t reason) { set_ttl(rbtdb, header, 0); - mark_stale_header(rbtdb, header); + mark_header_ancient(rbtdb, header); /* * Caller must hold the node (write) lock. diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index 6c1931f342..7d00a332d3 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -140,16 +140,17 @@ #endif /* WANT_QUERYTRACE */ #define US_PER_SEC 1000000U +#define US_PER_MSEC 1000U /* * The maximum time we will wait for a single query. */ -#define MAX_SINGLE_QUERY_TIMEOUT 9U -#define MAX_SINGLE_QUERY_TIMEOUT_US (MAX_SINGLE_QUERY_TIMEOUT*US_PER_SEC) +#define MAX_SINGLE_QUERY_TIMEOUT 9000U +#define MAX_SINGLE_QUERY_TIMEOUT_US (MAX_SINGLE_QUERY_TIMEOUT*US_PER_MSEC) /* * We need to allow a individual query time to complete / timeout. */ -#define MINIMUM_QUERY_TIMEOUT (MAX_SINGLE_QUERY_TIMEOUT + 1U) +#define MINIMUM_QUERY_TIMEOUT (MAX_SINGLE_QUERY_TIMEOUT + 1000U) /* The default time in seconds for the whole query to live. */ #ifndef DEFAULT_QUERY_TIMEOUT @@ -158,7 +159,7 @@ /* The maximum time in seconds for the whole query to live. */ #ifndef MAXIMUM_QUERY_TIMEOUT -#define MAXIMUM_QUERY_TIMEOUT 30 +#define MAXIMUM_QUERY_TIMEOUT 30000 #endif /* The default maximum number of recursions to follow before giving up. */ @@ -494,6 +495,10 @@ struct dns_resolver { unsigned int maxqueries; isc_result_t quotaresp[2]; + /* Additions for serve-stale feature. */ + unsigned int retryinterval; /* in milliseconds */ + unsigned int nonbackofftries; + /* Locked by lock. */ unsigned int references; isc_boolean_t exiting; @@ -1793,14 +1798,12 @@ fctx_setretryinterval(fetchctx_t *fctx, unsigned int rtt) { unsigned int seconds; unsigned int us; + us = fctx->res->retryinterval * 1000; /* - * We retry every .8 seconds the first two times through the address - * list, and then we do exponential back-off. + * Exponential backoff after the first few tries. */ - if (fctx->restarts < 3) - us = 800000; - else - us = (800000 << (fctx->restarts - 2)); + if (fctx->restarts >= fctx->res->nonbackofftries) + us <<= (fctx->restarts - fctx->res->nonbackofftries - 1); /* * Add a fudge factor to the expected rtt based on the current @@ -4671,7 +4674,8 @@ fctx_create(dns_resolver_t *res, const dns_name_t *name, dns_rdatatype_t type, /* * Compute an expiration time for the entire fetch. */ - isc_interval_set(&interval, res->query_timeout, 0); + isc_interval_set(&interval, res->query_timeout / 1000, + res->query_timeout % 1000 * 1000000); iresult = isc_time_nowplusinterval(&fctx->expires, &interval); if (iresult != ISC_R_SUCCESS) { UNEXPECTED_ERROR(__FILE__, __LINE__, @@ -9623,6 +9627,8 @@ dns_resolver_create(dns_view_t *view, res->spillattimer = NULL; res->zspill = 0; res->zero_no_soa_ttl = ISC_FALSE; + res->retryinterval = 30000; + res->nonbackofftries = 3; res->query_timeout = DEFAULT_QUERY_TIMEOUT; res->maxdepth = DEFAULT_RECURSION_DEPTH; res->maxqueries = DEFAULT_MAX_QUERIES; @@ -10943,17 +10949,20 @@ dns_resolver_gettimeout(dns_resolver_t *resolver) { } void -dns_resolver_settimeout(dns_resolver_t *resolver, unsigned int seconds) { +dns_resolver_settimeout(dns_resolver_t *resolver, unsigned int timeout) { REQUIRE(VALID_RESOLVER(resolver)); - if (seconds == 0) - seconds = DEFAULT_QUERY_TIMEOUT; - if (seconds > MAXIMUM_QUERY_TIMEOUT) - seconds = MAXIMUM_QUERY_TIMEOUT; - if (seconds < MINIMUM_QUERY_TIMEOUT) - seconds = MINIMUM_QUERY_TIMEOUT; + if (timeout <= 300) + timeout *= 1000; - resolver->query_timeout = seconds; + if (timeout == 0) + timeout = DEFAULT_QUERY_TIMEOUT; + if (timeout > MAXIMUM_QUERY_TIMEOUT) + timeout = MAXIMUM_QUERY_TIMEOUT; + if (timeout < MINIMUM_QUERY_TIMEOUT) + timeout = MINIMUM_QUERY_TIMEOUT; + + resolver->query_timeout = timeout; } void @@ -11050,3 +11059,34 @@ dns_resolver_getquotaresponse(dns_resolver_t *resolver, dns_quotatype_t which) return (resolver->quotaresp[which]); } + +unsigned int +dns_resolver_getretryinterval(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + + return (resolver->retryinterval); +} + +void +dns_resolver_setretryinterval(dns_resolver_t *resolver, unsigned int interval) +{ + REQUIRE(VALID_RESOLVER(resolver)); + REQUIRE(interval > 0); + + resolver->retryinterval = ISC_MIN(interval, 2000); +} + +unsigned int +dns_resolver_getnonbackofftries(dns_resolver_t *resolver) { + REQUIRE(VALID_RESOLVER(resolver)); + + return (resolver->nonbackofftries); +} + +void +dns_resolver_setnonbackofftries(dns_resolver_t *resolver, unsigned int tries) { + REQUIRE(VALID_RESOLVER(resolver)); + REQUIRE(tries > 0); + + resolver->nonbackofftries = tries; +} diff --git a/lib/dns/sdb.c b/lib/dns/sdb.c index 2d6aab1b8d..44d010750e 100644 --- a/lib/dns/sdb.c +++ b/lib/dns/sdb.c @@ -1292,7 +1292,9 @@ static dns_dbmethods_t sdb_methods = { NULL, /* setcachestats */ NULL, /* hashsize */ NULL, /* nodefullname */ - NULL /* getsize */ + NULL, /* getsize */ + NULL, /* setservestalettl */ + NULL /* getservestalettl */ }; static isc_result_t diff --git a/lib/dns/sdlz.c b/lib/dns/sdlz.c index 2130711780..568129ce38 100644 --- a/lib/dns/sdlz.c +++ b/lib/dns/sdlz.c @@ -1328,7 +1328,9 @@ static dns_dbmethods_t sdlzdb_methods = { NULL, /* setcachestats */ NULL, /* hashsize */ NULL, /* nodefullname */ - NULL /* getsize */ + NULL, /* getsize */ + NULL, /* setservestalettl */ + NULL /* getservestalettl */ }; /* diff --git a/lib/dns/tests/db_test.c b/lib/dns/tests/db_test.c index d71b748055..d1bbba9d89 100644 --- a/lib/dns/tests/db_test.c +++ b/lib/dns/tests/db_test.c @@ -19,8 +19,9 @@ #include #include -#include #include +#include +#include #include "dnstest.h" @@ -54,25 +55,230 @@ ATF_TC_BODY(getoriginnode, tc) { ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); result = dns_db_create(mymctx, "rbt", dns_rootname, dns_dbtype_zone, - dns_rdataclass_in, 0, NULL, &db); + dns_rdataclass_in, 0, NULL, &db); ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); result = dns_db_getoriginnode(db, &node); - ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); dns_db_detachnode(db, &node); result = dns_db_getoriginnode(db, &node); - ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); dns_db_detachnode(db, &node); dns_db_detach(&db); isc_mem_detach(&mymctx); } +ATF_TC(getsetservestalettl); +ATF_TC_HEAD(getsetservestalettl, tc) { + atf_tc_set_md_var(tc, "descr", + "test getservestalettl and setservestalettl"); +} +ATF_TC_BODY(getsetservestalettl, tc) { + dns_db_t *db = NULL; + isc_mem_t *mymctx = NULL; + isc_result_t result; + dns_ttl_t ttl; + + result = isc_mem_create(0, 0, &mymctx); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = isc_hash_create(mymctx, NULL, 256); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_db_create(mymctx, "rbt", dns_rootname, dns_dbtype_cache, + dns_rdataclass_in, 0, NULL, &db); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + ttl = 5000; + result = dns_db_getservestalettl(db, &ttl); + ATF_CHECK_EQ_MSG(result, ISC_R_SUCCESS, "dns_db_getservestalettl"); + ATF_CHECK_EQ_MSG(ttl, 0, "dns_db_getservestalettl initial value"); + + ttl = 6 * 3600; + result = dns_db_setservestalettl(db, ttl); + ATF_CHECK_EQ_MSG(result, ISC_R_SUCCESS, "dns_db_setservestalettl"); + + ttl = 5000; + result = dns_db_getservestalettl(db, &ttl); + ATF_CHECK_EQ_MSG(result, ISC_R_SUCCESS, "dns_db_getservestalettl"); + ATF_CHECK_EQ_MSG(ttl, 6 * 3600, "dns_db_getservestalettl update value"); + + dns_db_detach(&db); + isc_mem_detach(&mymctx); +} + +ATF_TC(dns_dbfind_staleok); +ATF_TC_HEAD(dns_dbfind_staleok, tc) { + atf_tc_set_md_var(tc, "descr", + "check DNS_DBFIND_STALEOK works"); +} +ATF_TC_BODY(dns_dbfind_staleok, tc) { + dns_db_t *db = NULL; + dns_dbnode_t *node = NULL; + dns_fixedname_t example_fixed; + dns_fixedname_t found_fixed; + dns_name_t *example; + dns_name_t *found; + dns_rdatalist_t rdatalist; + dns_rdataset_t rdataset; + int count; + int pass; + isc_mem_t *mymctx = NULL; + isc_result_t result; + unsigned char data[] = { 0x0a, 0x00, 0x00, 0x01 }; + + result = isc_mem_create(0, 0, &mymctx); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = isc_hash_create(mymctx, NULL, 256); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_db_create(mymctx, "rbt", dns_rootname, dns_dbtype_cache, + dns_rdataclass_in, 0, NULL, &db); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + dns_fixedname_init(&example_fixed); + example = dns_fixedname_name(&example_fixed); + + dns_fixedname_init(&found_fixed); + found = dns_fixedname_name(&found_fixed); + + result = dns_name_fromstring(example, "example", 0, NULL); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* + * Pass 0: default; no stale processing permitted. + * Pass 1: stale processing for 1 second. + * Pass 2: stale turned off after being on. + */ + for (pass = 0; pass < 3; pass++) { + dns_rdata_t rdata = DNS_RDATA_INIT; + + /* 10.0.0.1 */ + rdata.data = data; + rdata.length = 4; + rdata.rdclass = dns_rdataclass_in; + rdata.type = dns_rdatatype_a; + + dns_rdatalist_init(&rdatalist); + rdatalist.ttl = 2; + rdatalist.type = dns_rdatatype_a; + rdatalist.rdclass = dns_rdataclass_in; + ISC_LIST_APPEND(rdatalist.rdata, &rdata, link); + + switch (pass) { + case 0: + /* default: stale processing off */ + break; + case 1: + /* turn on stale processing */ + result = dns_db_setservestalettl(db, 1); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + break; + case 2: + /* turn off stale processing */ + result = dns_db_setservestalettl(db, 0); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + break; + } + + dns_rdataset_init(&rdataset); + result = dns_rdatalist_tordataset(&rdatalist, &rdataset); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_db_findnode(db, example, ISC_TRUE, &node); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + result = dns_db_addrdataset(db, node, NULL, 0, &rdataset, 0, + NULL); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + dns_db_detachnode(db, &node); + dns_rdataset_disassociate(&rdataset); + + result = dns_db_find(db, example, NULL, dns_rdatatype_a, + 0, 0, &node, found, &rdataset, NULL); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* + * May loop for up to 2 seconds performing non stale lookups. + */ + count = 0; + do { + count++; + ATF_REQUIRE(count < 21); /* loop sanity */ + ATF_CHECK_EQ(rdataset.attributes & + DNS_RDATASETATTR_STALE, 0); + ATF_CHECK(rdataset.ttl > 0); + dns_db_detachnode(db, &node); + dns_rdataset_disassociate(&rdataset); + + usleep(100000); /* 100 ms */ + + result = dns_db_find(db, example, NULL, + dns_rdatatype_a, 0, 0, + &node, found, &rdataset, NULL); + } while (result == ISC_R_SUCCESS); + + ATF_CHECK_EQ(result, ISC_R_NOTFOUND); + + /* + * Check whether we can get stale data. + */ + result = dns_db_find(db, example, NULL, dns_rdatatype_a, + DNS_DBFIND_STALEOK, 0, + &node, found, &rdataset, NULL); + switch (pass) { + case 0: + ATF_CHECK_EQ(result, ISC_R_NOTFOUND); + break; + case 1: + /* + * Should loop for 1 second with stale lookups then + * stop. + */ + count = 0; + do { + count++; + ATF_REQUIRE(count < 50); /* loop sanity */ + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK_EQ(rdataset.ttl, 0); + ATF_CHECK_EQ(rdataset.attributes & + DNS_RDATASETATTR_STALE, + DNS_RDATASETATTR_STALE); + dns_db_detachnode(db, &node); + dns_rdataset_disassociate(&rdataset); + + usleep(100000); /* 100 ms */ + + result = dns_db_find(db, example, NULL, + dns_rdatatype_a, + DNS_DBFIND_STALEOK, + 0, &node, found, + &rdataset, NULL); + } while (result == ISC_R_SUCCESS); + ATF_CHECK(count > 1); + ATF_CHECK(count < 11); + ATF_CHECK_EQ(result, ISC_R_NOTFOUND); + break; + case 2: + ATF_CHECK_EQ(result, ISC_R_NOTFOUND); + break; + } + } + + dns_db_detach(&db); + isc_mem_detach(&mymctx); +} + /* * Main */ ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, getoriginnode); + ATF_TP_ADD_TC(tp, getsetservestalettl); + ATF_TP_ADD_TC(tp, dns_dbfind_staleok); return (atf_no_error()); } diff --git a/lib/dns/view.c b/lib/dns/view.c index 40bb475b3d..8a7a116a8c 100644 --- a/lib/dns/view.c +++ b/lib/dns/view.c @@ -215,6 +215,9 @@ dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, view->flush = ISC_FALSE; view->dlv = NULL; view->maxudp = 0; + view->staleanswerttl = 1; + view->staleanswersok = dns_stale_answer_conf; + view->staleanswersenable = ISC_FALSE; view->nocookieudp = 0; view->padding = 0; view->pad_acl = NULL; diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 8813c4a070..0c67071f04 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -1899,6 +1899,7 @@ view_clauses[] = { { "max-ncache-ttl", &cfg_type_uint32, 0 }, { "max-recursion-depth", &cfg_type_uint32, 0 }, { "max-recursion-queries", &cfg_type_uint32, 0 }, + { "max-stale-ttl", &cfg_type_ttlval, 0 }, { "max-udp-size", &cfg_type_uint32, 0 }, { "message-compression", &cfg_type_boolean, 0 }, { "min-roots", &cfg_type_uint32, CFG_CLAUSEFLAG_NOTIMP }, @@ -1928,7 +1929,9 @@ view_clauses[] = { { "request-nsid", &cfg_type_boolean, 0 }, { "request-sit", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE }, { "require-server-cookie", &cfg_type_boolean, 0 }, + { "resolver-nonbackoff-tries", &cfg_type_uint32, 0 }, { "resolver-query-timeout", &cfg_type_uint32, 0 }, + { "resolver-retry-interval", &cfg_type_uint32, 0 }, { "response-padding", &cfg_type_resppadding, 0 }, { "response-policy", &cfg_type_rpz, 0 }, { "rfc2308-type1", &cfg_type_boolean, CFG_CLAUSEFLAG_NYI }, @@ -1937,6 +1940,8 @@ view_clauses[] = { { "send-cookie", &cfg_type_boolean, 0 }, { "servfail-ttl", &cfg_type_ttlval, 0 }, { "sortlist", &cfg_type_bracketed_aml, 0 }, + { "stale-answer-enable", &cfg_type_boolean, 0 }, + { "stale-answer-ttl", &cfg_type_ttlval, 0 }, { "suppress-initial-notify", &cfg_type_boolean, CFG_CLAUSEFLAG_NYI }, { "synth-from-dnssec", &cfg_type_boolean, 0 }, { "topology", &cfg_type_bracketed_aml, CFG_CLAUSEFLAG_NOTIMP },