]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Use the delegation database in get_dsset()
authorOndřej Surý <ondrej@isc.org>
Thu, 9 Apr 2026 07:44:29 +0000 (09:44 +0200)
committerColin Vidal <colin@isc.org>
Thu, 16 Apr 2026 09:28:13 +0000 (11:28 +0200)
When the validator needs a DS RRset and the cache does not have it,
get_dsset() falls back to creating a fresh fetch.  Without a hint, the
resolver picks the closest known zone cut for the DS query, and in the
parent-centric resolver that can land on a delegation at the DS owner
name itself (the child side). This can happens when the parent
delegation is expired, or if the zonecut of the parent doesn't match the
labels in the name.

Querying the child for its own DS records yields NODATA from the apex of
the zone, which sends the resolver into the "chase DS servers" recovery
path and costs two extra round trips for a parent delegation we already
had cached in the delegation database.

Look up the parent zone in the delegation database before kicking
off the fetch, and pass any usable delegation to the resolver as a
hint.  When the hint is present, the resolver sends the DS query
straight to the parent's nameservers and the chase path is avoided
entirely.

To support this, create_fetch() now takes optional 'domain' and
'delegset' parameters that are forwarded to dns_resolver_createfetch().
All other call sites pass NULL.

lib/dns/validator.c

index 0806dde03ab22947a18dd843fc619a2e2f5ed0c4..c5698a7c3ed28587932cdd99eecf45d0b848d6b0 100644 (file)
@@ -165,6 +165,7 @@ validator_logcreate(dns_validator_t *val, dns_name_t *name,
 
 static isc_result_t
 create_fetch(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type,
+            const dns_name_t *domain, dns_delegset_t *delegset,
             isc_job_cb callback, const char *caller);
 
 /*%
@@ -694,7 +695,7 @@ validator_callback_dnskey(void *arg) {
                if (result != DNS_R_BROKENCHAIN) {
                        expire_rdatasets(val);
                        result = create_fetch(val, &val->siginfo->signer,
-                                             dns_rdatatype_dnskey,
+                                             dns_rdatatype_dnskey, NULL, NULL,
                                              fetch_callback_dnskey,
                                              "validator_callback_dnskey");
                        if (result == ISC_R_SUCCESS) {
@@ -759,7 +760,7 @@ validator_callback_ds(void *arg) {
                if (result != DNS_R_BROKENCHAIN) {
                        expire_rdatasets(val);
                        result = create_fetch(val, val->name, dns_rdatatype_ds,
-                                             fetch_callback_ds,
+                                             NULL, NULL, fetch_callback_ds,
                                              "validator_callback_ds");
                        if (result == ISC_R_SUCCESS) {
                                result = DNS_R_WAIT;
@@ -992,10 +993,14 @@ check_deadlock(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type,
 }
 
 /*%
- * Start a fetch for the requested name and type.
+ * Start a fetch for the requested name and type.  When 'domain' and
+ * 'delegset' are non-NULL, they are passed as a delegation hint to the
+ * resolver, which will use them directly instead of looking up the
+ * closest known zone cut.
  */
 static isc_result_t
 create_fetch(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type,
+            const dns_name_t *domain, dns_delegset_t *delegset,
             isc_job_cb callback, const char *caller) {
        unsigned int fopts = 0;
        isc_result_t result;
@@ -1020,8 +1025,8 @@ create_fetch(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type,
 
        dns_validator_ref(val);
        result = dns_resolver_createfetch(
-               val->view->resolver, name, type, NULL, NULL, NULL, NULL, 0,
-               fopts, 0, val->qc, val->gqc, val->parent_fetch, val->loop,
+               val->view->resolver, name, type, domain, delegset, NULL, NULL,
+               0, fopts, 0, val->qc, val->gqc, val->parent_fetch, val->loop,
                callback, val, &val->edectx, &val->frdataset,
                &val->fsigrdataset, &val->fetch);
        if (result != ISC_R_SUCCESS) {
@@ -1245,7 +1250,8 @@ seek_dnskey(dns_validator_t *val) {
                 * We don't know anything about this key.
                 */
                RETERR(create_fetch(val, &siginfo->signer, dns_rdatatype_dnskey,
-                                   fetch_callback_dnskey, "seek_dnskey"));
+                                   NULL, NULL, fetch_callback_dnskey,
+                                   "seek_dnskey"));
                return DNS_R_WAIT;
 
        case DNS_R_NCACHENXDOMAIN:
@@ -1993,17 +1999,60 @@ get_dsset(dns_validator_t *val, dns_name_t *tname, isc_result_t *resp) {
                }
                break;
 
-       case ISC_R_NOTFOUND:
+       case ISC_R_NOTFOUND: {
+               dns_fixedname_t pfixed, fixed;
+               dns_name_t *pname = NULL, *fname = NULL;
+               dns_delegset_t *delegset = NULL;
+               const dns_name_t *parent = NULL;
+               unsigned int n;
+
+               /*
+                * Before kicking off a fetch, see whether the delegation
+                * database already knows a usable delegation for tname's
+                * parent zone.  When it does, pass that delegation to the
+                * resolver as a hint so the DS query is sent directly to
+                * the parent's nameservers without going through the
+                * "chase DS servers" recovery path.
+                *
+                * This lookup will succeed in the common case, but will fails
+                * in the parent delegation is expired or if the zonecut doesn't
+                * match the labels in the name. Without this hint, the resolver
+                * may fall back to a delegation at tname itself (the child
+                * side), send the DS query there, get NODATA from the apex of
+                * the zone and spend two extra round trips recovering from a
+                * delegation we already had cached.
+                */
+               n = dns_name_countlabels(tname);
+               if (n > 1) {
+                       pname = dns_fixedname_initname(&pfixed);
+                       dns_name_getlabelsequence(tname, 1, n - 1, pname);
+
+                       fname = dns_fixedname_initname(&fixed);
+                       result = dns_view_bestzonecut(val->view, pname, fname,
+                                                     NULL, 0, 0, true, true,
+                                                     &delegset);
+                       if (result == ISC_R_SUCCESS && delegset != NULL) {
+                               parent = fname;
+                       } else if (delegset != NULL) {
+                               dns_delegset_detach(&delegset);
+                       }
+               }
+
                /*
                 * We don't have the DS.  Find it.
                 */
-               result = create_fetch(val, tname, dns_rdatatype_ds,
-                                     fetch_callback_ds, "validate_dnskey");
+               result = create_fetch(val, tname, dns_rdatatype_ds, parent,
+                                     delegset, fetch_callback_ds,
+                                     "validate_dnskey");
+               if (delegset != NULL) {
+                       dns_delegset_detach(&delegset);
+               }
                *resp = DNS_R_WAIT;
                if (result != ISC_R_SUCCESS) {
                        *resp = result;
                }
                return ISC_R_COMPLETE;
+       }
 
        case DNS_R_NCACHENXDOMAIN:
        case DNS_R_NCACHENXRRSET:
@@ -3211,8 +3260,8 @@ seek_ds(dns_validator_t *val, isc_result_t *resp) {
                                        *resp = DNS_R_WAIT;
                                        result = create_fetch(
                                                val, tname,
-                                               dns_rdatatype_dnskey,
-                                               fetch_callback_dnskey,
+                                               dns_rdatatype_dnskey, NULL,
+                                               NULL, fetch_callback_dnskey,
                                                "seek_ds");
                                        if (result != ISC_R_SUCCESS) {
                                                *resp = result;
@@ -3256,7 +3305,7 @@ seek_ds(dns_validator_t *val, isc_result_t *resp) {
                 * We don't know anything about the DS.  Find it.
                 */
                *resp = DNS_R_WAIT;
-               result = create_fetch(val, tname, dns_rdatatype_ds,
+               result = create_fetch(val, tname, dns_rdatatype_ds, NULL, NULL,
                                      fetch_callback_ds, "seek_ds");
                if (result != ISC_R_SUCCESS) {
                        *resp = result;
@@ -3496,8 +3545,8 @@ proveunsecure(dns_validator_t *val, bool have_ds, bool have_dnskey,
                                         */
                                        result = create_fetch(
                                                val, fname,
-                                               dns_rdatatype_dnskey,
-                                               fetch_callback_dnskey,
+                                               dns_rdatatype_dnskey, NULL,
+                                               NULL, fetch_callback_dnskey,
                                                "seek_ds");
                                        if (result == ISC_R_SUCCESS) {
                                                result = DNS_R_WAIT;