diff --git a/bin/delv/delv.c b/bin/delv/delv.c index ebce81c5d6..c63c7be5e7 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 b15d607522..db23093737 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 34ea760143..2321ba6250 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 c354c34478..3d34c6b87f 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 9dad7c9d5c..d9dd301b56 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 95977c66d6..8def29ed6b 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 df6026a1ed..574dfe11bf 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 e8c0af78b1..2eadc26898 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 454df5a5a3..3e5018f241 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 3306dbec94..88d853fff4 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 a73c55756c..2dfd15c2a1 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; +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; + } - if (view->hints == 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); return result; } - result = dns_db_find(view->hints, 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) { + 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 b41e8c980d..da187369c6 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) { /*