From: Ondřej Surý Date: Wed, 6 May 2026 10:37:03 +0000 (+0200) Subject: Drop in-domain NS without glue from the delegation set X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=28483b3b73a2a06bd55713cfc32cff0e9e552138;p=thirdparty%2Fbind9.git Drop in-domain NS without glue from the delegation set Pull the dns_message_findname() lookups into cache_delegglue() and cache_delegglue6() so each helper now owns its glue lookup and returns the number of addresses cached. cache_delegns() splits referrals into two cases: in-domain (the NS name is below the delegation point) and sibling/in-bailiwick. An in-domain NS without glue is unresolvable by definition - the resolver would have to ask the very server it's trying to find. Log "missing mandatory glue" at notice level and skip the deleg entirely rather than leaving an unusable entry in the set. A new dns_delegset_freedeleg() undoes a fresh dns_delegset_allocdeleg() so the rest of the delegation set is preserved. --- diff --git a/bin/tests/system/expiredglue/tests_expiredglue.py b/bin/tests/system/expiredglue/tests_expiredglue.py index a7f3c3d137c..5e5ac5b4d54 100644 --- a/bin/tests/system/expiredglue/tests_expiredglue.py +++ b/bin/tests/system/expiredglue/tests_expiredglue.py @@ -45,11 +45,12 @@ def test_expiredglue(ns4): isctest.check.same_data(res3_2, res3) -def test_loopdetected(ns4): +def test_missing_mandatory_glue(ns4): msg = isctest.query.create("a.missing.tld.", "A") with ns4.watch_log_from_here() as watcher: res = isctest.query.udp(msg, ns4.ip) - # However, this is a valid fetch loop, and named detects it. - watcher.wait_for_line("loop detected resolving 'ns.missing.tld/A'") + # The NS for missing.tld. is in-domain and has no glue, so + # named drops the delegation rather than chasing it. + watcher.wait_for_line("missing mandatory glue for ns.missing.tld") isctest.check.servfail(res) diff --git a/lib/dns/deleg.c b/lib/dns/deleg.c index 7b2560ca47a..5c8a767be24 100644 --- a/lib/dns/deleg.c +++ b/lib/dns/deleg.c @@ -422,6 +422,21 @@ dns_delegset_allocdeleg(dns_delegset_t *delegset, dns_deleg_type_t type, *delegp = deleg; } +void +dns_delegset_freedeleg(dns_delegset_t *delegset, dns_deleg_t **delegp) { + REQUIRE(DNS_DELEGSET_VALID(delegset)); + REQUIRE(delegp != NULL && *delegp != NULL); + REQUIRE(ISC_LIST_EMPTY((*delegp)->addresses)); + REQUIRE(ISC_LIST_EMPTY((*delegp)->names)); + + dns_deleg_t *deleg = *delegp; + *delegp = NULL; + + ISC_LIST_UNLINK(delegset->delegs, deleg, link); + + isc_mem_put(delegset->mctx, deleg, sizeof(*deleg)); +} + void dns_delegset_addaddr(dns_delegset_t *delegset, dns_deleg_t *deleg, const isc_netaddr_t *addr) { diff --git a/lib/dns/include/dns/deleg.h b/lib/dns/include/dns/deleg.h index 907d4ffc688..3b4573cf0a0 100644 --- a/lib/dns/include/dns/deleg.h +++ b/lib/dns/include/dns/deleg.h @@ -151,6 +151,12 @@ dns_delegset_allocset(dns_delegdb_t *db, dns_delegset_t **delegsetp); void dns_delegset_allocdeleg(dns_delegset_t *delegset, dns_deleg_type_t type, dns_deleg_t **delegp); +/* + * Free the deleg struct and remove it from the delegation set. Can't + * be used on delegation set already attached in the DB. + */ +void +dns_delegset_freedeleg(dns_delegset_t *delegset, dns_deleg_t **delegp); /* * Add a new IP into a delegation. Can't be used on a delegation from a diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index 49eb16cb594..e0575ab8a83 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -6640,10 +6640,19 @@ name_external(const dns_name_t *name, dns_rdatatype_t type, respctx_t *rctx) { return false; } -static void +static size_t cache_delegglue(dns_delegset_t *delegset, dns_deleg_t *deleg, dns_ttl_t *ttl, - dns_rdataset_t *rdataset) { + respctx_t *rctx, const dns_name_t *nsname) { + dns_rdataset_t *rdataset = NULL; size_t naddrs = 0; + isc_result_t result; + + result = dns_message_findname(rctx->query->rmessage, + DNS_SECTION_ADDITIONAL, nsname, + dns_rdatatype_a, 0, NULL, &rdataset); + if (result != ISC_R_SUCCESS) { + return 0; + } if (rdataset->ttl < *ttl) { *ttl = rdataset->ttl; @@ -6664,12 +6673,22 @@ cache_delegglue(dns_delegset_t *delegset, dns_deleg_t *deleg, dns_ttl_t *ttl, break; } } + return naddrs; } -static void +static size_t cache_delegglue6(dns_delegset_t *delegset, dns_deleg_t *deleg, dns_ttl_t *ttl, - dns_rdataset_t *rdataset) { + respctx_t *rctx, const dns_name_t *nsname) { + dns_rdataset_t *rdataset = NULL; size_t naddrs = 0; + isc_result_t result; + + result = dns_message_findname(rctx->query->rmessage, + DNS_SECTION_ADDITIONAL, nsname, + dns_rdatatype_aaaa, 0, NULL, &rdataset); + if (result != ISC_R_SUCCESS) { + return 0; + } if (rdataset->ttl < *ttl) { *ttl = rdataset->ttl; @@ -6690,6 +6709,7 @@ cache_delegglue6(dns_delegset_t *delegset, dns_deleg_t *deleg, dns_ttl_t *ttl, break; } } + return naddrs; } /* @@ -6713,6 +6733,8 @@ cache_delegns(respctx_t *rctx) { dns_fixedname_t fparent; dns_name_t *parent = dns_fixedname_initname(&fparent); size_t labels; + size_t ns_count = 0; + size_t max_servers = fctx->res->view->max_delegation_servers; isc_result_t result; FCTXTRACE("cache_delegns"); @@ -6730,14 +6752,11 @@ cache_delegns(respctx_t *rctx) { dns_name_getlabelsequence(rctx->ns_name, 1, labels - 1, parent); } - size_t ns_count = 0; - size_t max_servers = fctx->res->view->max_delegation_servers; - 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; + size_t naddrs = 0; if (ns_count >= max_servers) { break; @@ -6758,32 +6777,41 @@ cache_delegns(respctx_t *rctx) { INSIST(rdata.type == dns_rdatatype_ns); dns_rdata_tostruct(&rdata, &ns, NULL); - if (labels > 1 && dns_name_issubdomain(&ns.name, parent)) { - 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; + /* in-domain GLUE */ + if (dns_name_issubdomain(&ns.name, rctx->ns_name)) { + naddrs += cache_delegglue(delegset, deleg, &ttl, rctx, + &ns.name); + naddrs += cache_delegglue6(delegset, deleg, &ttl, rctx, + &ns.name); + if (naddrs == 0) { + INSIST(ISC_LIST_EMPTY(deleg->addresses)); + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(&ns.name, namebuf, + sizeof(namebuf)); + + isc_log_write(DNS_LOGCATEGORY_RESOLVER, + DNS_LOGMODULE_RESOLVER, + ISC_LOG_NOTICE, + "missing mandatory glue for %s", + namebuf); + dns_delegset_freedeleg(delegset, &deleg); } + continue; + } - 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; - } + /* in-bailiwick/sibling GLUE */ + if (labels > 1 && dns_name_issubdomain(&ns.name, parent)) { + naddrs += cache_delegglue(delegset, deleg, &ttl, rctx, + &ns.name); + naddrs += cache_delegglue6(delegset, deleg, &ttl, rctx, + &ns.name); } - if (ISC_LIST_EMPTY(deleg->addresses)) { + if (naddrs == 0) { + INSIST(ISC_LIST_EMPTY(deleg->addresses)); /* - * There is actually no glues for this NSRRset, so this - * is actually a DNS_DELEGTYPE_NS_NAMES. + * There are 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);