Populate the delegation DB from referrals answers

The resolver now caches NS records and their A/AAAA glues from referral
answers into the delegation database.

A new `cache_delegns()` function extracts NS names and associated glue
addresses from the authority/additional sections of a referral answer
and use those informations to build a delegation set, which is then
inserted into the delegation database.

The created delegation set contains a delegation per NS RR. If the NS RR
has matching A/AAAA RR, the delegation only store the addresses and not
the name. (Note this is technically possible to group all NS RR which
doesn't have glues into a single delegation, and the implementation can
be changed in that way in the future).

Each view has its own instance of the delegation database (they are
never shared between views), but a server restart/reload preserve the
delegation database state.
This commit is contained in:
Colin Vidal 2026-01-22 11:07:01 +01:00
parent 1b5f757084
commit c7b75f448f
4 changed files with 160 additions and 6 deletions

View file

@ -4128,6 +4128,12 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
INSIST(result == ISC_R_SUCCESS);
stale_refresh_time = cfg_obj_asduration(obj);
result = dns_viewlist_find(&named_g_server->viewlist, view->name,
view->rdclass, &pview);
if (result != ISC_R_NOTFOUND && result != ISC_R_SUCCESS) {
goto cleanup;
}
/*
* Configure the view's cache.
*
@ -4193,11 +4199,6 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
}
}
} else if (strcmp(cachename, view->name) == 0) {
result = dns_viewlist_find(&named_g_server->viewlist, cachename,
view->rdclass, &pview);
if (result != ISC_R_NOTFOUND && result != ISC_R_SUCCESS) {
goto cleanup;
}
if (pview != NULL) {
if (!cache_reusable(pview, view, zero_no_soattl)) {
isc_log_write(NAMED_LOGCATEGORY_GENERAL,
@ -4222,7 +4223,6 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
dns_resolver_getqueryrttstats(pview->resolver,
&resqueryinrttstats,
&resqueryoutrttstats);
dns_view_detach(&pview);
}
}
@ -4278,6 +4278,28 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
CHECK(dns_view_createresolver(view, resopts, tlsctx_client_cache,
dispatch4, dispatch6));
/*
* The deleg DB cache is preserved if reconfiguring/reloading the
* server.
*/
if (pview != NULL) {
dns_delegdb_reuse(pview, view);
} else {
dns_delegdb_create(&view->deleg);
}
/*
* Totally arbitrary decision for now. This might need its own knob.
*/
dns_delegdb_setsize(view->deleg, max_cache_size / 6);
/*
* The previous view isn't needed anymore.
*/
if (pview != NULL) {
dns_view_detach(&pview);
}
if (resstats == NULL) {
isc_stats_create(mctx, &resstats, dns_resstatscounter_max);
}

View file

@ -67,6 +67,7 @@
#include <dns/acl.h>
#include <dns/catz.h>
#include <dns/clientinfo.h>
#include <dns/deleg.h>
#include <dns/dnstap.h>
#include <dns/fixedname.h>
#include <dns/nta.h>
@ -98,6 +99,7 @@ struct dns_view {
dns_cache_t *cache;
dns_db_t *cachedb;
dns_db_t *hints;
dns_delegdb_t *deleg;
/*
* security roots and negative trust anchors.

View file

@ -47,6 +47,7 @@
#include <dns/adb.h>
#include <dns/cache.h>
#include <dns/db.h>
#include <dns/deleg.h>
#include <dns/dispatch.h>
#include <dns/dns64.h>
#include <dns/dnstap.h>
@ -6547,6 +6548,120 @@ name_external(const dns_name_t *name, dns_rdatatype_t type, respctx_t *rctx) {
return false;
}
static void
cache_delegglue(dns_delegset_t *delegset, dns_deleg_t *deleg, dns_ttl_t *ttl,
dns_rdataset_t *rdataset) {
if (rdataset->ttl < *ttl) {
*ttl = rdataset->ttl;
}
DNS_RDATASET_FOREACH(rdataset) {
dns_rdata_t rdata = DNS_RDATA_INIT;
dns_rdata_in_a_t a;
isc_netaddr_t addr = { .family = AF_INET };
dns_rdataset_current(rdataset, &rdata);
dns_rdata_tostruct(&rdata, &a, NULL);
addr.type.in = a.in_addr;
dns_delegset_addaddr(delegset, deleg, &addr);
}
}
static void
cache_delegglue6(dns_delegset_t *delegset, dns_deleg_t *deleg, dns_ttl_t *ttl,
dns_rdataset_t *rdataset) {
if (rdataset->ttl < *ttl) {
*ttl = rdataset->ttl;
}
DNS_RDATASET_FOREACH(rdataset) {
dns_rdata_t rdata = DNS_RDATA_INIT;
dns_rdata_in_aaaa_t aaaa;
isc_netaddr_t addr = { .family = AF_INET6 };
dns_rdataset_current(rdataset, &rdata);
dns_rdata_tostruct(&rdata, &aaaa, NULL);
addr.type.in6 = aaaa.in6_addr;
dns_delegset_addaddr(delegset, deleg, &addr);
}
}
/*
* Cache the parent-side NS RRset in a delegation.
*
* Currently the resolver doesn't support DELEG, but when it does, this
* code will need to bail out if there is already a delegset from DELEG
* RRset in this zonecut. (See DELEG draft 5.1.3.)
*
* Maybe the simplest way to enforce it could be to pass a boolean flag
* `nooverride` to `dns_deleg_writeset()` so it simply detaches the
* `delegset` if there is already a `delegset` at this zonecut in the DB.
* And the flag would be true only from `cache_delegns()`.
*/
static isc_result_t
cache_delegns(respctx_t *rctx) {
fetchctx_t *fctx = rctx->fctx;
dns_delegdb_t *delegdb = fctx->res->view->deleg;
dns_delegset_t *delegset = NULL;
dns_ttl_t ttl = rctx->ns_rdataset->ttl;
isc_result_t result;
FCTXTRACE("cache_delegns");
dns_delegset_allocset(delegdb, &delegset);
DNS_RDATASET_FOREACH(rctx->ns_rdataset) {
dns_rdataset_t *gluerdataset = NULL;
dns_rdata_t rdata = DNS_RDATA_INIT;
dns_rdata_ns_t ns;
dns_deleg_t *deleg = NULL;
/*
* We can't "group" all NS-based delegations into a single
* `dns_deleg_t` because some of them might have glues, some
* other might not, and a `dns_deleg_t` can't have both
* addresses and NS names. Let's assume this is a GLUE-based
* deleg first.
*/
dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_NS_GLUES,
&deleg);
dns_rdataset_current(rctx->ns_rdataset, &rdata);
INSIST(rdata.type == dns_rdatatype_ns);
dns_rdata_tostruct(&rdata, &ns, NULL);
result = dns_message_findname(
rctx->query->rmessage, DNS_SECTION_ADDITIONAL, &ns.name,
dns_rdatatype_a, 0, NULL, &gluerdataset);
if (result == ISC_R_SUCCESS) {
cache_delegglue(delegset, deleg, &ttl, gluerdataset);
gluerdataset = NULL;
}
result = dns_message_findname(
rctx->query->rmessage, DNS_SECTION_ADDITIONAL, &ns.name,
dns_rdatatype_aaaa, 0, NULL, &gluerdataset);
if (result == ISC_R_SUCCESS) {
cache_delegglue6(delegset, deleg, &ttl, gluerdataset);
gluerdataset = NULL;
}
if (ISC_LIST_EMPTY(deleg->addresses)) {
/*
* There is actually no glues for this NSRRset, so this
* is actually a DNS_DELEGTYPE_NS_NAMES.
*/
deleg->type = DNS_DELEGTYPE_NS_NAMES;
dns_delegset_addns(delegset, deleg, &ns.name);
}
}
result = dns_delegset_insert(delegdb, rctx->ns_name, ttl, delegset);
dns_delegset_detach(&delegset);
return result;
}
static isc_result_t
check_section(void *arg, const dns_name_t *addname, dns_rdatatype_t type,
dns_rdataset_t *found, dns_section_t section) {
@ -9041,6 +9156,12 @@ rctx_referral(respctx_t *rctx) {
check_related, rctx, 0);
FCTX_ATTR_CLR(fctx, FCTX_ATTR_GLUING);
/*
* An NS-based delegation can be cached immediately (i.e. there is
* no DNSSEC validation).
*/
cache_delegns(rctx);
/*
* NS rdatasets with 0 TTL cause problems.
* dns_view_findzonecut() will not find them when we

View file

@ -173,6 +173,10 @@ destroy(dns_view_t *view) {
isc_refcount_destroy(&view->references);
isc_refcount_destroy(&view->weakrefs);
if (view->deleg != NULL) {
dns_delegdb_detach(&view->deleg);
}
if (view->order != NULL) {
dns_order_detach(&view->order);
}
@ -394,6 +398,10 @@ shutdown_view(dns_view_t *view) {
dns_resolver_shutdown(view->resolver);
}
if (view->deleg != NULL) {
dns_delegdb_shutdown(view->deleg);
}
rcu_read_lock();
adb = rcu_dereference(view->adb);
if (adb != NULL) {
@ -534,6 +542,7 @@ dns_view_createresolver(dns_view_t *view, unsigned int options,
RETERR(dns_resolver_create(view, options, tlsctx_cache, dispatchv4,
dispatchv6, &view->resolver));
isc_mem_create("ADB", &mctx);
dns_adb_create(mctx, view, &view->adb);
isc_mem_detach(&mctx);