From: Ondřej Surý Date: Wed, 15 Apr 2026 06:11:17 +0000 (+0200) Subject: Rename view->hints to view->rootdb and rearm priming X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0c007d8659be521640a1be8fd45a975cb0e0d9d8;p=thirdparty%2Fbind9.git Rename view->hints to view->rootdb and rearm priming With the parent-centric resolver, dns_view_bestzonecut() consults the delegation DB (view->deleg) rather than the main cache for the closest zonecut. Root is never the target of a referral, so it never lands in delegdb; bestzonecut therefore falls through to the hints lookup on every query whose closest ancestor is root. prime_done() only called dns_root_checkhints(), which logs discrepancies but does not update any store bestzonecut looks at, so the fresh root NS records obtained by priming were never used and priming kept re-firing. Rename view->hints to view->rootdb and refresh it when a priming fetch completes: the '.' NS rdataset is replaced with the fetched one, and for each listed nameserver the matching A/AAAA glue is copied from the response's ADDITIONAL section. Only glue for names that actually appear as NS targets is accepted, so a hostile response cannot inject unrelated records. Glue the response did not carry is left untouched, so the hints-file records loaded at startup remain as a fallback. Each view gets its own rootdb: the previous shared named_g_server->in_roothints is gone, and configure_view() calls dns_rootns_create() per view when the class-IN defaults are needed. That keeps the priming writer one-per-DB, so concurrent priming in different views cannot race on the same zone-DB version. The rootdb refresh runs synchronously from the resolver response path, so records go straight from the wire into rootdb with no cache round trip and no dependency on DNSSEC validation state. A new DNS_FETCHOPT_PRIMING option marks the priming fetch; prime_done() itself is now pure cleanup. Track the rootdb freshness window in view->rootdb_expires and trigger re-priming lazily from dns_view_find() and bestzonecut_rootdb() only when the window has elapsed. Stale records are still served while the fresh priming fetch is in flight. Drop dns_root_checkhints() and its helpers; the rootdb is now the authoritative source the resolver consults. --- diff --git a/bin/delv/delv.c b/bin/delv/delv.c index ebce81c5d66..c63c7be5e72 100644 --- a/bin/delv/delv.c +++ b/bin/delv/delv.c @@ -2165,7 +2165,7 @@ run_server(void *arg) { CHECK(dns_rootns_create(isc_g_mctx, dns_rdataclass_in, hintfile, &roothints)); - dns_view_sethints(view, roothints); + dns_view_setrootdb(view, roothints); dns_db_detach(&roothints); view->qminimization = qmin; diff --git a/bin/named/include/named/server.h b/bin/named/include/named/server.h index b15d6075221..db230937375 100644 --- a/bin/named/include/named/server.h +++ b/bin/named/include/named/server.h @@ -67,7 +67,6 @@ struct named_server { dns_kasplist_t kasplist; dns_keystorelist_t keystorelist; ns_interfacemgr_t *interfacemgr; - dns_db_t *in_roothints; isc_timer_t *interface_timer; isc_timer_t *heartbeat_timer; diff --git a/bin/named/server.c b/bin/named/server.c index 34ea760143d..2321ba6250d 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -453,6 +453,9 @@ static isc_result_t configure_alternates(const cfg_obj_t *config, dns_view_t *view, const cfg_obj_t *alternates); +static isc_result_t +configure_rootdb(dns_view_t *view, const char *filename); + static isc_result_t configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, const cfg_obj_t *vconfig, dns_view_t *view, @@ -4538,20 +4541,22 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config, } /* - * We have default hints for class IN if we need them. + * We have default root hints for class IN if we need them. + * Each view gets its own rootdb so a priming response only + * writes into that view's copy. */ - if (view->rdclass == dns_rdataclass_in && view->hints == NULL) { - dns_view_sethints(view, named_g_server->in_roothints); + if (view->rdclass == dns_rdataclass_in && view->rootdb == NULL) { + CHECK(configure_rootdb(view, NULL)); } /* - * If we still have no hints, this is a non-IN view with no + * If we still have no root hints, this is a non-IN view with no * "hints zone" configured. Issue a warning, except if this * is a root server. Root servers never need to consult * their hints, so it's no point requiring users to configure * them. */ - if (view->hints == NULL) { + if (view->rootdb == NULL) { dns_zone_t *rootzone = NULL; (void)dns_view_findzone(view, dns_rootname, DNS_ZTFIND_EXACT, &rootzone); @@ -5543,14 +5548,14 @@ cleanup: } static isc_result_t -configure_hints(dns_view_t *view, const char *filename) { +configure_rootdb(dns_view_t *view, const char *filename) { isc_result_t result; dns_db_t *db; db = NULL; result = dns_rootns_create(view->mctx, view->rdclass, filename, &db); if (result == ISC_R_SUCCESS) { - dns_view_sethints(view, db); + dns_view_setrootdb(view, db); dns_db_detach(&db); } @@ -6071,7 +6076,7 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, if (dns_name_equal(origin, dns_rootname)) { const char *hintsfile = cfg_obj_asstring(fileobj); - CHECK(configure_hints(view, hintsfile)); + CHECK(configure_rootdb(view, hintsfile)); } else { isc_log_write(NAMED_LOGCATEGORY_GENERAL, NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, @@ -9207,8 +9212,6 @@ shutdown_server(void *arg) { named_geoip_shutdown(); #endif /* HAVE_GEOIP2 */ - dns_db_detach(&server->in_roothints); - isc_loopmgr_resume(); } @@ -9457,10 +9460,6 @@ named_server_create(isc_mem_t *mctx, named_server_t **serverp) { ISC_LIST_INIT(server->keystorelist); ISC_LIST_INIT(server->viewlist); - CHECKFATAL(dns_rootns_create(mctx, dns_rdataclass_in, NULL, - &server->in_roothints), - "setting up root hints"); - atomic_init(&server->reload_status, NAMED_RELOAD_IN_PROGRESS); ns_server_create(mctx, get_matching_view, &server->sctx); diff --git a/bin/tests/system/dnstap/tests.sh b/bin/tests/system/dnstap/tests.sh index c354c344789..3d34c6b87f1 100644 --- a/bin/tests/system/dnstap/tests.sh +++ b/bin/tests/system/dnstap/tests.sh @@ -363,8 +363,8 @@ echo_i "checking UDP message counts" ret=0 check_count ns1 $udp1 0 check_count ns2 $udp2 2 -check_count ns3 $udp3 2 -check_count ns5 $udp5 4 +check_count ns3 $udp3 4 +check_count ns5 $udp5 6 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) @@ -399,7 +399,7 @@ echo_i "checking CLIENT_QUERY message counts" ret=0 check_count ns1 $cq1 0 check_count ns2 $cq2 0 -check_count ns3 $cq3 1 +check_count ns3 $cq3 2 check_count ns5 $cq5 1 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) @@ -408,7 +408,7 @@ echo_i "checking CLIENT_RESPONSE message counts" ret=0 check_count ns1 $cr1 0 check_count ns2 $cr2 0 -check_count ns3 $cr3 1 +check_count ns3 $cr3 2 check_count ns5 $cr5 1 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) @@ -436,7 +436,7 @@ ret=0 check_count ns1 $fq1 0 check_count ns2 $fq2 0 check_count ns3 $fq3 0 -check_count ns5 $fq5 1 +check_count ns5 $fq5 2 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) @@ -445,7 +445,7 @@ ret=0 check_count ns1 $fr1 0 check_count ns2 $fr2 0 check_count ns3 $fr3 0 -check_count ns5 $fr5 1 +check_count ns5 $fr5 2 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) diff --git a/bin/tests/system/minimalresponses/common.py b/bin/tests/system/minimalresponses/common.py index 9dad7c9d5c9..d9dd301b560 100644 --- a/bin/tests/system/minimalresponses/common.py +++ b/bin/tests/system/minimalresponses/common.py @@ -25,7 +25,7 @@ EXAMPLE4_NS = "example4. 300 IN NS ns.example4." AEXAMPLE4_A = "a.example4. 300 IN A 10.53.0.40" NSEXAMPLE4_A = "ns.example4. 300 IN A 10.53.0.4" -AROOTSERVER_NS = ". 999999 IN NS a.root-servers.nil." +AROOTSERVER_NS = ". 300 IN NS a.root-servers.nil." INPUTPARAMS = "ns, qname, qtype, rd, cached, rcode, answer, authority, additional" diff --git a/lib/dns/include/dns/resolver.h b/lib/dns/include/dns/resolver.h index 95977c66d63..8def29ed6b4 100644 --- a/lib/dns/include/dns/resolver.h +++ b/lib/dns/include/dns/resolver.h @@ -138,6 +138,11 @@ enum { * CD=0, but retry with CD=1 * if it returns SERVFAIL. */ + DNS_FETCHOPT_PRIMING = 1 << 19, /*%< Root priming fetch. + * Copies the '.' NS answer + * and root-server glue from + * the response into + * view->rootdb. */ /*% EDNS version bits: */ DNS_FETCHOPT_EDNSVERSIONSET = 1 << 23, diff --git a/lib/dns/include/dns/rootns.h b/lib/dns/include/dns/rootns.h index df6026a1edd..574dfe11bf3 100644 --- a/lib/dns/include/dns/rootns.h +++ b/lib/dns/include/dns/rootns.h @@ -20,11 +20,3 @@ isc_result_t dns_rootns_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, const char *filename, dns_db_t **target); - -void -dns_root_checkhints(dns_view_t *view, dns_db_t *hints, dns_db_t *db); -/* - * Reports differences between hints and the real roots. - * - * Requires view, hints and (cache) db to be valid. - */ diff --git a/lib/dns/include/dns/view.h b/lib/dns/include/dns/view.h index e8c0af78b1c..2eadc268987 100644 --- a/lib/dns/include/dns/view.h +++ b/lib/dns/include/dns/view.h @@ -57,6 +57,7 @@ #include #include +#include #include #include #include @@ -87,19 +88,20 @@ typedef struct MDB_env MDB_env; struct dns_view { /* Unlocked. */ - unsigned int magic; - isc_mem_t *mctx; - dns_rdataclass_t rdclass; - char *name; - dns_zt_t *zonetable; - dns_resolver_t *resolver; - dns_adb_t *adb; - dns_requestmgr_t *requestmgr; - dns_dispatchmgr_t *dispatchmgr; - dns_cache_t *cache; - dns_db_t *cachedb; - dns_db_t *hints; - dns_delegdb_t *deleg; + unsigned int magic; + isc_mem_t *mctx; + dns_rdataclass_t rdclass; + char *name; + dns_zt_t *zonetable; + dns_resolver_t *resolver; + dns_adb_t *adb; + dns_requestmgr_t *requestmgr; + dns_dispatchmgr_t *dispatchmgr; + dns_cache_t *cache; + dns_db_t *cachedb; + dns_db_t *rootdb; + atomic_uint_fast32_t rootdb_expires; + dns_delegdb_t *deleg; /* * security roots and negative trust anchors. @@ -418,20 +420,25 @@ dns_view_setcache(dns_view_t *view, dns_cache_t *cache, bool shared); */ void -dns_view_sethints(dns_view_t *view, dns_db_t *hints); +dns_view_setrootdb(dns_view_t *view, dns_db_t *rootdb); /*%< - * Set the view's hints database. + * Set the view's root delegation database. + * + * This seeds the rootdb at cold start from a root hints file; it is then + * overwritten by the resolver when priming succeeds (see + * dns_resolver_prime()). Callers consult the rootdb via dns_view_find() + * and bestzonecut to obtain the root NS set regardless of TTL. * * Requires: * - *\li 'view' is a valid, unfrozen view, whose hints database has not been + *\li 'view' is a valid, unfrozen view, whose root database has not been * set. * - *\li 'hints' is a valid zone database. + *\li 'rootdb' is a valid zone database. * * Ensures: * - * \li The hints database of 'view' is 'hints'. + * \li The root database of 'view' is 'rootdb'. */ void diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index 454df5a5a3f..3e5018f241f 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -1035,6 +1035,9 @@ rctx_chaseds(respctx_t *rctx, dns_message_t *message, static void rctx_done(respctx_t *rctx, isc_result_t result); +static void +update_rootdb(dns_view_t *view, dns_message_t *message); + static void rctx_logpacket(respctx_t *rctx); @@ -7886,6 +7889,16 @@ resquery_response_continue(void *arg, isc_result_t result) { rctx_done(rctx, tresult); goto cleanup; } + + /* + * For a priming response, copy the '.' NS answer and + * root-server glue straight from the wire into + * view->rootdb so bestzonecut and ADB see the refreshed + * set without a cache round trip. + */ + if ((fctx->options & DNS_FETCHOPT_PRIMING) != 0) { + update_rootdb(fctx->res->view, query->rmessage); + } } /* @@ -10005,12 +10018,128 @@ dns_resolver_create(dns_view_t *view, unsigned int options, return ISC_R_SUCCESS; } +/* + * Copy the A or AAAA rdataset at 'target' out of 'message's ADDITIONAL + * section into 'rootdb' under 'ver', replacing any existing record of + * that type. Returns ISC_R_SUCCESS if the glue was stored or if the + * response did not carry glue for this target (both benign); any other + * result indicates a zone-DB failure that the caller should roll back + * on. Shrinks '*minttlp' to the TTL of the stored rdataset. + */ +static isc_result_t +update_rootdb_glue(dns_db_t *rootdb, dns_dbversion_t *ver, + dns_message_t *message, const dns_name_t *target, + dns_rdatatype_t type, isc_stdtime_t now, + dns_ttl_t *minttlp) { + dns_name_t *name = NULL; + dns_rdataset_t *rdataset = NULL; + dns_dbnode_t *node = NULL; + isc_result_t result; + + result = dns_message_findname(message, DNS_SECTION_ADDITIONAL, target, + type, 0, &name, &rdataset); + if (result != ISC_R_SUCCESS) { + /* No glue for this target in the response. */ + return ISC_R_SUCCESS; + } + + RETERR(dns_db_findnode(rootdb, name, true, &node)); + + (void)dns_db_deleterdataset(rootdb, node, ver, type, 0); + result = dns_db_addrdataset(rootdb, node, ver, now, rdataset, 0, NULL); + dns_db_detachnode(&node); + if (result != ISC_R_SUCCESS && result != DNS_R_UNCHANGED) { + return result; + } + + if (rdataset->ttl < *minttlp) { + *minttlp = rdataset->ttl; + } + return ISC_R_SUCCESS; +} + +/* + * Refresh 'view->rootdb' from a priming response message. The '.' NS + * rdataset is replaced with the fetched one and, for each nameserver + * it lists, the matching A/AAAA glue from the response's ADDITIONAL + * section is copied in. Only glue for names that actually appear as + * NS targets is accepted; arbitrary ADDITIONAL records are ignored so + * a hostile response cannot inject unrelated data into rootdb. Glue + * the response did not carry is left untouched, so the hints-file + * records loaded at startup remain as a fallback. + * + * The version is committed only if every write succeeded; any failure + * rolls the whole update back so rootdb never ends up with a '.' NS + * rdataset that was deleted but not re-added. + * + * Called synchronously from response processing while the message is + * still live, so records go straight from the wire into rootdb. + */ +static void +update_rootdb(dns_view_t *view, dns_message_t *message) { + dns_db_t *rootdb = view->rootdb; + dns_dbversion_t *ver = NULL; + dns_dbnode_t *node = NULL; + dns_rdataset_t *nsset = NULL; + isc_stdtime_t now = isc_stdtime_now(); + dns_ttl_t minttl = UINT32_MAX; + isc_result_t result; + + if (rootdb == NULL) { + return; + } + + result = dns_message_findname(message, DNS_SECTION_ANSWER, dns_rootname, + dns_rdatatype_ns, 0, NULL, &nsset); + if (result != ISC_R_SUCCESS) { + return; + } + + result = dns_db_newversion(rootdb, &ver); + if (result != ISC_R_SUCCESS) { + return; + } + + CHECK(dns_db_findnode(rootdb, dns_rootname, true, &node)); + + (void)dns_db_deleterdataset(rootdb, node, ver, dns_rdatatype_ns, 0); + result = dns_db_addrdataset(rootdb, node, ver, now, nsset, 0, NULL); + dns_db_detachnode(&node); + if (result != ISC_R_SUCCESS && result != DNS_R_UNCHANGED) { + goto cleanup; + } + result = ISC_R_SUCCESS; + minttl = nsset->ttl; + + DNS_RDATASET_FOREACH(nsset) { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_ns_t ns; + + dns_rdataset_current(nsset, &rdata); + if (dns_rdata_tostruct(&rdata, &ns, NULL) != ISC_R_SUCCESS) { + continue; + } + + CHECK(update_rootdb_glue(rootdb, ver, message, &ns.name, + dns_rdatatype_a, now, &minttl)); + CHECK(update_rootdb_glue(rootdb, ver, message, &ns.name, + dns_rdatatype_aaaa, now, &minttl)); + } + + atomic_store_relaxed(&view->rootdb_expires, (uint32_t)(now + minttl)); + +cleanup: + if (node != NULL) { + dns_db_detachnode(&node); + } + dns_db_closeversion(rootdb, &ver, result == ISC_R_SUCCESS); +} + static void prime_done(void *arg) { dns_fetchresponse_t *resp = (dns_fetchresponse_t *)arg; dns_resolver_t *res = resp->arg; dns_fetch_t *fetch = NULL; - dns_db_t *db = NULL; REQUIRE(VALID_RESOLVER(res)); @@ -10027,14 +10156,6 @@ prime_done(void *arg) { atomic_compare_exchange_enforced(&res->priming, &(bool){ true }, false); - if (resp->result == ISC_R_SUCCESS && res->view->cache != NULL && - res->view->hints != NULL) - { - dns_cache_attachdb(res->view->cache, &db); - dns_root_checkhints(res->view, res->view->hints, db); - dns_db_detach(&db); - } - if (resp->node != NULL) { dns_db_detachnode(&resp->node); } @@ -10083,9 +10204,9 @@ dns_resolver_prime(dns_resolver_t *res) { LOCK(&res->primelock); result = dns_resolver_createfetch( res, dns_rootname, dns_rdatatype_ns, NULL, NULL, NULL, - NULL, 0, DNS_FETCHOPT_NOFORWARD, 0, NULL, NULL, NULL, - isc_loop(), prime_done, res, NULL, rdataset, NULL, - &res->primefetch); + NULL, 0, DNS_FETCHOPT_NOFORWARD | DNS_FETCHOPT_PRIMING, + 0, NULL, NULL, NULL, isc_loop(), prime_done, res, NULL, + rdataset, NULL, &res->primefetch); UNLOCK(&res->primelock); if (result != ISC_R_SUCCESS) { diff --git a/lib/dns/rootns.c b/lib/dns/rootns.c index 3306dbec944..88d853fff45 100644 --- a/lib/dns/rootns.c +++ b/lib/dns/rootns.c @@ -34,9 +34,6 @@ #include #include -/* - * Also update 'upcoming' when updating 'root_ns'. - */ static char root_ns[] = ";\n" "; Internet Root Nameservers\n" @@ -82,23 +79,6 @@ static char root_ns[] = "M.ROOT-SERVERS.NET. 3600000 IN A 202.12.27.33\n" "M.ROOT-SERVERS.NET. 3600000 IN AAAA 2001:DC3::35\n"; -static unsigned char b_data[] = "\001b\014root-servers\003net"; - -static struct upcoming { - const dns_name_t name; - dns_rdatatype_t type; - isc_stdtime_t time; -} upcoming[] = { { - .name = DNS_NAME_INITABSOLUTE(b_data), - .type = dns_rdatatype_a, - .time = 1701086400 /* November 27 2023, 12:00 UTC */ - }, - { - .name = DNS_NAME_INITABSOLUTE(b_data), - .type = dns_rdatatype_aaaa, - .time = 1701086400 /* November 27 2023, 12:00 UTC */ - } }; - static isc_result_t in_rootns(dns_rdataset_t *rootns, dns_name_t *name) { dns_rdata_ns_t ns; @@ -250,259 +230,3 @@ cleanup: return result; } - -static void -report(dns_view_t *view, dns_name_t *name, bool missing, dns_rdata_t *rdata) { - const char *viewname = "", *sep = ""; - char namebuf[DNS_NAME_FORMATSIZE]; - char typebuf[DNS_RDATATYPE_FORMATSIZE]; - char databuf[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:123.123.123.123")]; - isc_buffer_t buffer; - isc_result_t result; - - if (strcmp(view->name, "_bind") != 0 && - strcmp(view->name, "_default") != 0) - { - viewname = view->name; - sep = ": view "; - } - - dns_name_format(name, namebuf, sizeof(namebuf)); - dns_rdatatype_format(rdata->type, typebuf, sizeof(typebuf)); - isc_buffer_init(&buffer, databuf, sizeof(databuf) - 1); - result = dns_rdata_totext(rdata, NULL, &buffer); - RUNTIME_CHECK(result == ISC_R_SUCCESS); - databuf[isc_buffer_usedlength(&buffer)] = '\0'; - - if (missing) { - isc_log_write(DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_HINTS, - ISC_LOG_WARNING, - "checkhints%s%s: %s/%s (%s) missing from hints", - sep, viewname, namebuf, typebuf, databuf); - } else { - isc_log_write(DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_HINTS, - ISC_LOG_WARNING, - "checkhints%s%s: %s/%s (%s) extra record " - "in hints", - sep, viewname, namebuf, typebuf, databuf); - } -} - -static bool -inrrset(dns_rdataset_t *rrset, dns_rdata_t *rdata) { - DNS_RDATASET_FOREACH(rrset) { - dns_rdata_t current = DNS_RDATA_INIT; - dns_rdataset_current(rrset, ¤t); - if (dns_rdata_compare(rdata, ¤t) == 0) { - return true; - } - } - return false; -} - -static bool -changing(const dns_name_t *name, dns_rdatatype_t type, isc_stdtime_t now) { - for (size_t i = 0; i < ARRAY_SIZE(upcoming); i++) { - if (upcoming[i].time > now && upcoming[i].type == type && - dns_name_equal(&upcoming[i].name, name)) - { - return true; - } - } - return false; -} - -/* - * Check that the address RRsets match. - * - * Note we don't complain about missing glue records. - */ - -static void -check_address_records(dns_view_t *view, dns_db_t *hints, dns_db_t *db, - dns_name_t *name, isc_stdtime_t now) { - isc_result_t hresult, rresult; - dns_rdataset_t hintrrset, rootrrset; - dns_name_t *foundname; - dns_fixedname_t fixed; - - dns_rdataset_init(&hintrrset); - dns_rdataset_init(&rootrrset); - foundname = dns_fixedname_initname(&fixed); - - hresult = dns_db_find(hints, name, NULL, dns_rdatatype_a, 0, now, NULL, - foundname, &hintrrset, NULL); - rresult = dns_db_find(db, name, NULL, dns_rdatatype_a, - DNS_DBFIND_GLUEOK, now, NULL, foundname, - &rootrrset, NULL); - if (hresult == ISC_R_SUCCESS && - (rresult == ISC_R_SUCCESS || rresult == DNS_R_GLUE)) - { - DNS_RDATASET_FOREACH(&rootrrset) { - dns_rdata_t rdata = DNS_RDATA_INIT; - dns_rdataset_current(&rootrrset, &rdata); - if (!inrrset(&hintrrset, &rdata) && - !changing(name, dns_rdatatype_a, now)) - { - report(view, name, true, &rdata); - } - } - DNS_RDATASET_FOREACH(&hintrrset) { - dns_rdata_t rdata = DNS_RDATA_INIT; - dns_rdataset_current(&hintrrset, &rdata); - if (!inrrset(&rootrrset, &rdata) && - !changing(name, dns_rdatatype_a, now)) - { - report(view, name, false, &rdata); - } - } - } - if (hresult == ISC_R_NOTFOUND && - (rresult == ISC_R_SUCCESS || rresult == DNS_R_GLUE)) - { - DNS_RDATASET_FOREACH(&rootrrset) { - dns_rdata_t rdata = DNS_RDATA_INIT; - dns_rdataset_current(&rootrrset, &rdata); - report(view, name, true, &rdata); - } - } - dns_rdataset_cleanup(&rootrrset); - dns_rdataset_cleanup(&hintrrset); - - /* - * Check AAAA records. - */ - hresult = dns_db_find(hints, name, NULL, dns_rdatatype_aaaa, 0, now, - NULL, foundname, &hintrrset, NULL); - rresult = dns_db_find(db, name, NULL, dns_rdatatype_aaaa, - DNS_DBFIND_GLUEOK, now, NULL, foundname, - &rootrrset, NULL); - if (hresult == ISC_R_SUCCESS && - (rresult == ISC_R_SUCCESS || rresult == DNS_R_GLUE)) - { - DNS_RDATASET_FOREACH(&rootrrset) { - dns_rdata_t rdata = DNS_RDATA_INIT; - dns_rdataset_current(&rootrrset, &rdata); - if (!inrrset(&hintrrset, &rdata) && - !changing(name, dns_rdatatype_aaaa, now)) - { - report(view, name, true, &rdata); - } - } - DNS_RDATASET_FOREACH(&hintrrset) { - dns_rdata_t rdata = DNS_RDATA_INIT; - dns_rdataset_current(&hintrrset, &rdata); - if (!inrrset(&rootrrset, &rdata) && - !changing(name, dns_rdatatype_aaaa, now)) - { - report(view, name, false, &rdata); - } - } - } - if (hresult == ISC_R_NOTFOUND && - (rresult == ISC_R_SUCCESS || rresult == DNS_R_GLUE)) - { - DNS_RDATASET_FOREACH(&rootrrset) { - dns_rdata_t rdata = DNS_RDATA_INIT; - dns_rdataset_current(&rootrrset, &rdata); - report(view, name, true, &rdata); - } - } - dns_rdataset_cleanup(&rootrrset); - dns_rdataset_cleanup(&hintrrset); -} - -void -dns_root_checkhints(dns_view_t *view, dns_db_t *hints, dns_db_t *db) { - isc_result_t result; - dns_rdata_ns_t ns; - dns_rdataset_t hintns, rootns; - const char *viewname = "", *sep = ""; - isc_stdtime_t now = isc_stdtime_now(); - dns_name_t *name; - dns_fixedname_t fixed; - - REQUIRE(hints != NULL); - REQUIRE(db != NULL); - REQUIRE(view != NULL); - - if (strcmp(view->name, "_bind") != 0 && - strcmp(view->name, "_default") != 0) - { - viewname = view->name; - sep = ": view "; - } - - dns_rdataset_init(&hintns); - dns_rdataset_init(&rootns); - name = dns_fixedname_initname(&fixed); - - result = dns_db_find(hints, dns_rootname, NULL, dns_rdatatype_ns, 0, - now, NULL, name, &hintns, NULL); - if (result != ISC_R_SUCCESS) { - isc_log_write(DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_HINTS, - ISC_LOG_WARNING, - "checkhints%s%s: unable to get root NS rrset " - "from hints: %s", - sep, viewname, isc_result_totext(result)); - goto cleanup; - } - - result = dns_db_find(db, dns_rootname, NULL, dns_rdatatype_ns, 0, now, - NULL, name, &rootns, NULL); - if (result != ISC_R_SUCCESS) { - isc_log_write(DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_HINTS, - ISC_LOG_WARNING, - "checkhints%s%s: unable to get root NS rrset " - "from cache: %s", - sep, viewname, isc_result_totext(result)); - goto cleanup; - } - - /* - * Look for missing root NS names. - */ - DNS_RDATASET_FOREACH(&rootns) { - dns_rdata_t rdata = DNS_RDATA_INIT; - dns_rdataset_current(&rootns, &rdata); - result = dns_rdata_tostruct(&rdata, &ns, NULL); - RUNTIME_CHECK(result == ISC_R_SUCCESS); - result = in_rootns(&hintns, &ns.name); - if (result != ISC_R_SUCCESS) { - char namebuf[DNS_NAME_FORMATSIZE]; - /* missing from hints */ - dns_name_format(&ns.name, namebuf, sizeof(namebuf)); - isc_log_write(DNS_LOGCATEGORY_GENERAL, - DNS_LOGMODULE_HINTS, ISC_LOG_WARNING, - "checkhints%s%s: unable to find root " - "NS '%s' in hints", - sep, viewname, namebuf); - } else { - check_address_records(view, hints, db, &ns.name, now); - } - } - - /* - * Look for extra root NS names. - */ - DNS_RDATASET_FOREACH(&hintns) { - dns_rdata_t rdata = DNS_RDATA_INIT; - dns_rdataset_current(&hintns, &rdata); - result = dns_rdata_tostruct(&rdata, &ns, NULL); - RUNTIME_CHECK(result == ISC_R_SUCCESS); - result = in_rootns(&rootns, &ns.name); - if (result != ISC_R_SUCCESS) { - char namebuf[DNS_NAME_FORMATSIZE]; - /* extra entry in hints */ - dns_name_format(&ns.name, namebuf, sizeof(namebuf)); - isc_log_write(DNS_LOGCATEGORY_GENERAL, - DNS_LOGMODULE_HINTS, ISC_LOG_WARNING, - "checkhints%s%s: extra NS '%s' in hints", - sep, viewname, namebuf); - } - } - -cleanup: - dns_rdataset_cleanup(&rootns); - dns_rdataset_cleanup(&hintns); -} diff --git a/lib/dns/view.c b/lib/dns/view.c index a73c55756c6..2dfd15c2a16 100644 --- a/lib/dns/view.c +++ b/lib/dns/view.c @@ -82,6 +82,31 @@ #define UNREACH_HOLD_TIME_MAX_SEC (UNREACH_HOLD_TIME_INITIAL_SEC << 6) #define UNREACH_BACKOFF_ELIGIBLE_SEC ((uint16_t)120) +/* + * True when rootdb has never been primed, or when its stored TTL has + * elapsed. A stale rootdb still serves records; this is just the + * signal that the resolver should kick off a fresh priming fetch. + */ +static inline bool +rootdb_stale(dns_view_t *view) { + uint32_t exp = atomic_load_relaxed(&view->rootdb_expires); + return exp == 0 || exp <= (uint32_t)isc_stdtime_now(); +} + +static inline void +maybe_prime(dns_view_t *view) { + dns_resolver_t *res = NULL; + + if (!rootdb_stale(view)) { + return; + } + if (dns_view_getresolver(view, &res) != ISC_R_SUCCESS) { + return; + } + dns_resolver_prime(res); + dns_resolver_detach(&res); +} + void dns_view_create(isc_mem_t *mctx, dns_dispatchmgr_t *dispatchmgr, dns_rdataclass_t rdclass, const char *name, @@ -246,8 +271,8 @@ destroy(dns_view_t *view) { ISC_LIST_UNLINK(view->dlz_unsearched, dlzdb, link); dns_dlzdestroy(&dlzdb); } - if (view->hints != NULL) { - dns_db_detach(&view->hints); + if (view->rootdb != NULL) { + dns_db_detach(&view->rootdb); } if (view->cachedb != NULL) { dns_db_detach(&view->cachedb); @@ -593,13 +618,13 @@ dns_view_iscacheshared(dns_view_t *view) { } void -dns_view_sethints(dns_view_t *view, dns_db_t *hints) { +dns_view_setrootdb(dns_view_t *view, dns_db_t *rootdb) { REQUIRE(DNS_VIEW_VALID(view)); REQUIRE(!view->frozen); - REQUIRE(view->hints == NULL); - REQUIRE(dns_db_iszone(hints)); + REQUIRE(view->rootdb == NULL); + REQUIRE(dns_db_iszone(rootdb)); - dns_db_attach(hints, &view->hints); + dns_db_attach(rootdb, &view->rootdb); } void @@ -878,7 +903,7 @@ db_find: } if (result == ISC_R_NOTFOUND && !is_staticstub_zone && use_hints && - view->hints != NULL) + view->rootdb != NULL) { dns_rdataset_cleanup(rdataset); dns_rdataset_cleanup(sigrdataset); @@ -888,31 +913,29 @@ db_find: } dns_db_detach(&db); } - result = dns_db_find(view->hints, name, NULL, type, options, + result = dns_db_find(view->rootdb, name, NULL, type, options, now, &node, foundname, rdataset, sigrdataset); if (result == ISC_R_SUCCESS || result == DNS_R_GLUE) { /* - * We just used a hint. Let the resolver know it - * should consider priming. + * Lazily rearm priming if the rootdb's + * stored TTL has elapsed. The stale + * record is still returned; it is better + * than nothing until the fresh priming + * fetch completes. */ - dns_resolver_t *res = NULL; - result = dns_view_getresolver(view, &res); - if (result == ISC_R_SUCCESS) { - dns_resolver_prime(res); - dns_db_attach(view->hints, &db); - dns_resolver_detach(&res); - result = DNS_R_HINT; - } + maybe_prime(view); + dns_db_attach(view->rootdb, &db); + result = DNS_R_HINT; } else if (result == DNS_R_NXRRSET) { - dns_db_attach(view->hints, &db); + dns_db_attach(view->rootdb, &db); result = DNS_R_HINTNXRRSET; } else if (result == DNS_R_NXDOMAIN) { result = ISC_R_NOTFOUND; } /* - * Cleanup if non-standard hints are used. + * Cleanup if the rootdb lookup failed. */ if (db == NULL && node != NULL) { dns_db_detachnode(&node); @@ -1116,21 +1139,29 @@ bestzonecut_zoneorcache(dns_view_t *view, const dns_name_t *name, } static isc_result_t -bestzonecut_hints(dns_view_t *view, dns_name_t *fname, dns_name_t *dcname, - isc_stdtime_t now, dns_rdataset_t *rdataset) { - isc_result_t result = ISC_R_NOTFOUND; - - if (view->hints == NULL) { - return result; +bestzonecut_rootdb(dns_view_t *view, dns_name_t *fname, dns_name_t *dcname, + isc_stdtime_t now, dns_rdataset_t *rdataset) { + if (view->rootdb == NULL) { + return ISC_R_NOTFOUND; } - result = dns_db_find(view->hints, dns_rootname, NULL, dns_rdatatype_ns, - 0, now, NULL, fname, rdataset, NULL); + isc_result_t result = dns_db_find(view->rootdb, dns_rootname, NULL, + dns_rdatatype_ns, 0, now, NULL, fname, + rdataset, NULL); if (result != ISC_R_SUCCESS) { dns_rdataset_cleanup(rdataset); - } else if (dcname != NULL) { + return result; + } + + if (dcname != NULL) { dns_name_copy(fname, dcname); } + /* + * Returned record may be stale by TTL; that's fine — it + * is better than nothing until the next priming fetch. + * Kick off priming if the stored expiry has elapsed. + */ + maybe_prime(view); return result; } @@ -1167,10 +1198,11 @@ dns_view_bestzonecut(dns_view_t *view, const dns_name_t *name, } /* - * No local zone nor cache match. Last attempt with the hints. + * No local zone nor cache match. Last attempt with the rootdb. */ if (result == DNS_R_NXDOMAIN && usehints) { - result = bestzonecut_hints(view, fname, dcname, now, &rdataset); + result = bestzonecut_rootdb(view, fname, dcname, now, + &rdataset); } if (result != ISC_R_SUCCESS) { diff --git a/lib/ns/query.c b/lib/ns/query.c index b41e8c980d8..da187369c68 100644 --- a/lib/ns/query.c +++ b/lib/ns/query.c @@ -8286,7 +8286,7 @@ query_filter64(query_ctx_t *qctx) { */ static isc_result_t query_notfound(query_ctx_t *qctx) { - isc_result_t result = ISC_R_UNSET; + isc_result_t result = ISC_R_FAILURE; CCTRACE(ISC_LOG_DEBUG(3), "query_notfound"); @@ -8299,24 +8299,21 @@ query_notfound(query_ctx_t *qctx) { } /* - * If the cache doesn't even have the root NS, - * try to get that from the hints DB. + * If the cache doesn't even have the root NS, try to get that from + * the rootdb (root hints, refreshed by priming). */ - if (qctx->view->hints != NULL) { + if (qctx->view->rootdb != NULL) { dns_clientinfomethods_t cm; dns_clientinfo_t ci; dns_clientinfomethods_init(&cm, ns_client_sourceip); dns_clientinfo_init(&ci, qctx->client, NULL); - dns_db_attach(qctx->view->hints, &qctx->db); + dns_db_attach(qctx->view->rootdb, &qctx->db); result = dns_db_findext( qctx->db, dns_rootname, NULL, dns_rdatatype_ns, 0, qctx->client->inner.now, &qctx->node, qctx->fname, &cm, &ci, qctx->rdataset, qctx->sigrdataset); - } else { - /* We have no hints. */ - result = ISC_R_FAILURE; } if (result != ISC_R_SUCCESS) { /*