From e5f963262a3a517e1541ae271a8dd21a8e8b79c0 Mon Sep 17 00:00:00 2001 From: Colin Vidal Date: Mon, 2 Feb 2026 13:50:38 +0100 Subject: [PATCH 1/3] extends named -T so ADB settings can be tweaked ADB entry window and ADB min cache time can be tweaked using `named -T adbentrywindow=` and `named -T adbmincache=`. While those values doesn't needs to be exposed to the operator, this can be needed to be able to system test ADB behaviors without having to wait as long as those values are by default. --- bin/named/main.c | 7 +++++++ lib/dns/adb.c | 34 ++++++++++++++++++++++------------ 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/bin/named/main.c b/bin/named/main.c index 92d3b2b9b2..984c01c85a 100644 --- a/bin/named/main.c +++ b/bin/named/main.c @@ -120,6 +120,9 @@ extern unsigned int dns_zone_mkey_hour; extern unsigned int dns_zone_mkey_day; extern unsigned int dns_zone_mkey_month; +extern unsigned int dns_adb_entrywindow; +extern unsigned int dns_adb_cachemin; + static bool want_stats = false; static char absolute_conffile[PATH_MAX]; static char saved_command_line[4096] = { 0 }; @@ -723,6 +726,10 @@ parse_T_opt(char *option) { transferstuck = true; } else if (!strncmp(option, "tat=", 4)) { named_g_tat_interval = atoi(option + 4); + } else if (!strncmp(option, "adbentrywindow=", 15)) { + dns_adb_entrywindow = atoi(option + 15); + } else if (!strncmp(option, "adbcachemin=", 12)) { + dns_adb_cachemin = atoi(option + 12); } else { fprintf(stderr, "unknown -T flag '%s'\n", option); } diff --git a/lib/dns/adb.c b/lib/dns/adb.c index 06256be588..8721d8f6ff 100644 --- a/lib/dns/adb.c +++ b/lib/dns/adb.c @@ -63,12 +63,8 @@ /*! * For type 3 negative cache entries, we will remember that the address is * broken for this long. XXXMLG This is also used for actual addresses, too. - * The intent is to keep us from constantly asking about A/AAAA records - * if the zone has extremely low TTLs. */ -#define ADB_CACHE_MINIMUM 10 /*%< seconds */ #define ADB_CACHE_MAXIMUM 86400 /*%< seconds (86400 = 24 hours) */ -#define ADB_ENTRY_WINDOW 60 /*%< seconds */ #define ADB_HASH_SIZE (1 << 12) @@ -284,6 +280,12 @@ ISC_REFCOUNT_TRACE_DECL(dns_adbentry); ISC_REFCOUNT_DECL(dns_adbentry); #endif +/* + * ADB settings that can be tweaked with named -T option + */ +unsigned int dns_adb_entrywindow = 60; +unsigned int dns_adb_cachemin = 10; + /* * Internal functions (and prototypes). */ @@ -457,10 +459,10 @@ enum { * Due to the ttlclamp(), the TTL is never 0 unless the trust is ultimate, * in which case we need to set the expiration to have immediate effect. */ -#define ADJUSTED_EXPIRE(expire, now, ttl) \ - ((ttl != 0) \ - ? ISC_MIN(expire, ISC_MAX(now + ADB_ENTRY_WINDOW, now + ttl)) \ - : INT_MAX) +#define ADJUSTED_EXPIRE(expire, now, ttl) \ + ((ttl != 0) ? ISC_MIN(expire, \ + ISC_MAX(now + dns_adb_entrywindow, now + ttl)) \ + : INT_MAX) /* * Error states. @@ -533,8 +535,12 @@ inc_adbstats(dns_adb_t *adb, isc_statscounter_t counter) { static dns_ttl_t ttlclamp(dns_ttl_t ttl) { - if (ttl < ADB_CACHE_MINIMUM) { - ttl = ADB_CACHE_MINIMUM; + if (ttl < dns_adb_cachemin) { + /* + * Avoid to constantly ask about A/AAAA records if the zone has + * extremely low TTLs. + */ + ttl = dns_adb_cachemin; } if (ttl > ADB_CACHE_MAXIMUM) { ttl = ADB_CACHE_MAXIMUM; @@ -567,7 +573,11 @@ import_rdataset(dns_adbname_t *adbname, dns_rdataset_t *rdataset, case dns_trust_additional: case dns_trust_pending_answer: case dns_trust_pending_additional: - rdataset->ttl = ADB_CACHE_MINIMUM; + /* + * Avoid to constantly ask about A/AAAA records if the zone has + * extremely low TTLs. + */ + rdataset->ttl = dns_adb_cachemin; break; case dns_trust_ultimate: rdataset->ttl = 0; @@ -972,7 +982,7 @@ new_adbentry(dns_adb_t *adb, const isc_sockaddr_t *addr, isc_stdtime_t now) { .quota = adb->quota, .references = ISC_REFCOUNT_INITIALIZER(1), .adb = dns_adb_ref(adb), - .expires = now + ADB_ENTRY_WINDOW, + .expires = now + dns_adb_entrywindow, .loop = isc_loop_ref(isc_loop()), .magic = DNS_ADBENTRY_MAGIC, }; From f623ab1fb31ee8cf9a04d2063ac15986c6790ea0 Mon Sep 17 00:00:00 2001 From: Colin Vidal Date: Fri, 30 Jan 2026 17:09:18 +0100 Subject: [PATCH 2/3] fetch loop detection improvements The fetch loop detection occured in two places: when `dns_resolver_createfetch()` is invoked (looking up through the parent fetches chain and stops the fetch if a parent fetch is the same qname and qtype) and right after calling `dns_adb_findname()` in the resolver (stops the fetch if the current fetch is the same name from the ADB lookup, and ADB lookup needs to fetch it). Regarding fetch loop detection at the `dns_resulver_createfetch()` entry, there are case where both qname and qtype are similar but the zonecut is different. This will then query different name servers and get different responses. For instance, the following delegation parent-side (both for `foo.example.` and `dnshost.example.`): foo.example. 3600 NS ns.dnshost.example. dnshost.example. 3600 NS ns.dnshost.example. ns.dnshost.example. 3600 A 1.2.3.4 Then the child-side of `dnshost.example.`: dnshost.example. 300 NS ns.dnshost.example. ns.dnshost.example. 300 A 1.2.3.4 Then the child-side of `foo.example.`: foo.example 3600 NS ns.dnshost.example. a.foo.example 300 A 5.6.7.8 Obviously, there is a misconfiguration between the parent-side and the child-side of `dnshost.example` (the mismatch of the TTL), but, this happens... Because the resolver is currently child-centric, the parent-side delegation's glue of `dnshost.example.` will be overriden by the child-side of the delegation. Once both A records will expires, the resolver will attempt to find out the A RRs but will start from the `foo.example.` zonecut, as the delegation itself is still valid. Then the resolver will attempt to resolve `ns.dnshost.example.`, still using the `foo.example.` zonecut, which will immediately trigger another attempt to resolve `ns.foo.example.` (because the A RR is expired). This is, however _not_ a loop, because the second attempt will have `dnshost.example.` zonecut. And this changes everything, because the resolver detects the A name is in-domain, and pass a flag to ADB so `dns_view_find()` won't use the cache. As a result, the zonecut will be `.`, and the hints (root servers) will be queried instead. From that point, they'll return the parent-side delegation, which includes the glue for `ns.dnshost.example/A`, and the resolution can continue. Previously, this wouldn't be possible because a loop would be detected from the second attempt to looking `ns.foo.example/A` and would result in a SERVFAIL. Now, the loop detection is relaxed as the loop is detected if the qname, qtype _and_ zonecut are equals. This commit also changes the way the loop detection post `dns_adb_createfind()` works. From the same example above, there would be two ADB fetches with the same name, but with two different ADB flags (the first one without DNS_ADB_STARTATZONE, the second one with that flag). It means that there will be two fetches out of those two ADB lookups, both legit, and not a loop (i.e. it won't be stuck). To differenciate between a find which has a pending fetch (which could be from another find the current find has been attached to), a new find option `DNS_ADBFIND_STARTEDFETCH` is introduced, which tells that the current has did started a fetch. That way, if a find doesn't have `DNS_ADBFIND_STARTEDFETCH` option but has pending fetches, we know this is a find attached to a similar find so this is a loop. Otherwise, with `DNS_ADBFIND_STARTEDFETCH`, we know that even if there is a pending fetch, this is not a loop as the fetch has just been started --- lib/dns/adb.c | 4 +++ lib/dns/include/dns/adb.h | 4 +++ lib/dns/resolver.c | 62 +++++++++++++++++++++++++++++++-------- 3 files changed, 58 insertions(+), 12 deletions(-) diff --git a/lib/dns/adb.c b/lib/dns/adb.c index 8721d8f6ff..2e61c75534 100644 --- a/lib/dns/adb.c +++ b/lib/dns/adb.c @@ -2028,6 +2028,10 @@ post_copy: find->cbarg = cbarg; } + if (wanted_fetches) { + find->options |= DNS_ADBFIND_STARTEDFETCH; + } + *findp = find; UNLOCK(&adbname->lock); diff --git a/lib/dns/include/dns/adb.h b/lib/dns/include/dns/adb.h index dfa52236dc..399e665ee8 100644 --- a/lib/dns/include/dns/adb.h +++ b/lib/dns/include/dns/adb.h @@ -194,6 +194,10 @@ struct dns_adbfind { */ #define DNS_ADBFIND_STATICSTUB 0x00001000 #define DNS_ADBFIND_NOVALIDATE 0x00002000 +/*% + * This specific find created a fetch + */ +#define DNS_ADBFIND_STARTEDFETCH 0x00010000 /*% * The answers to queries come back as a list of these. diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index f475af5ffd..f09b502ba6 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -1087,6 +1087,9 @@ set_stats(dns_resolver_t *res, isc_statscounter_t counter, uint64_t val) { } } +static bool +waiting_for_fetch(fetchctx_t *fctx, const dns_name_t *name, + dns_rdatatype_t type, const dns_name_t *domain); static void valcreate(fetchctx_t *fctx, dns_message_t *message, dns_adbaddrinfo_t *addrinfo, dns_name_t *name, dns_rdatatype_t type, dns_rdataset_t *rdataset, @@ -3340,9 +3343,10 @@ sort_finds(dns_adbfindlist_t *findlist, unsigned int bias) { } /* - * Return true iff the ADB find has a pending fetch for 'type'. This is - * used to find out whether we're in a loop, where a fetch is waiting for a - * find which is waiting for that same fetch. + * Return true iff the ADB find has an already pending fetch for 'type'. This + * is used to find out whether we're in a loop, where a fetch is waiting for a + * find which is waiting for that same fetch. So if the current find actually + * started the fetch, we know it can't be a loop, so we returns false. * * Note: This could be done with either an equivalence check (e.g., * query_pending == DNS_ADBFIND_INET) or with a bit check, as below. If @@ -3361,7 +3365,11 @@ sort_finds(dns_adbfindlist_t *findlist, unsigned int bias) { * evil. */ static bool -waiting_for(dns_adbfind_t *find, dns_rdatatype_t type) { +already_waiting_for(dns_adbfind_t *find, dns_rdatatype_t type) { + if ((find->options & DNS_ADBFIND_STARTEDFETCH) != 0) { + return false; + } + switch (type) { case dns_rdatatype_a: return (find->query_pending & DNS_ADBFIND_INET) != 0; @@ -3471,22 +3479,25 @@ findname(fetchctx_t *fctx, const dns_name_t *name, in_port_t port, * We don't know any of the addresses for this name. * * The find may be waiting on a resolver fetch for a server - * address. We need to make sure it isn't waiting on *this* + * address. We need to make sure it isn't waiting before *this* * fetch, because if it is, we won't be answering it and it * won't be answering us. */ - if (waiting_for(find, fctx->type) && dns_name_equal(name, fctx->name)) { - fctx->adberr++; + if (already_waiting_for(find, fctx->type) && + dns_name_equal(name, fctx->name)) + { isc_log_write(DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, "loop detected resolving '%s'", fctx->info); + fctx->adberr++; if ((find->options & DNS_ADBFIND_WANTEVENT) != 0) { dns_adb_cancelfind(find); } else { dns_adb_destroyfind(&find); fetchctx_detach(&fctx); } + return; } @@ -10126,12 +10137,27 @@ get_attached_fctx(dns_resolver_t *res, isc_loop_t *loop, const dns_name_t *name, return ISC_R_SUCCESS; } +static bool +is_samedomain(const dns_name_t *domain1, const dns_name_t *domain2) { + if (domain1 == NULL && domain2 == NULL) { + return true; + } + + if (domain1 == NULL || domain2 == NULL) { + return false; + } + + return !dns_name_compare(domain1, domain2); +} + static bool waiting_for_fetch(fetchctx_t *fctx, const dns_name_t *name, - dns_rdatatype_t type) { + dns_rdatatype_t type, const dns_name_t *domain) { while (fctx != NULL) { if (type == fctx->type && !dns_name_compare(name, fctx->name)) { - return true; + if (is_samedomain(domain, fctx->domain)) { + return true; + } } fctx = fctx->parent; } @@ -10181,18 +10207,30 @@ dns_resolver_createfetch(dns_resolver_t *res, const dns_name_t *name, log_fetch(name, type); - if (waiting_for_fetch(parent, name, type)) { + /* + * This fetch loop detection enable to guard against loop scenarios + * where the DNSSEC is involved. See + * `4d307ac67a0e3f9831c9a4e66ac481e2f9ceebb5`. This is a complementary + * detection with the ADB lookup loop detection (in `findname()`). + */ + if (waiting_for_fetch(parent, name, type, domain)) { if (isc_log_wouldlog(ISC_LOG_INFO)) { char namebuf[DNS_NAME_FORMATSIZE + 1]; char typebuf[DNS_RDATATYPE_FORMATSIZE]; + char domainbuf[DNS_NAME_FORMATSIZE + 1] = { 0 }; dns_name_format(name, namebuf, sizeof(namebuf)); dns_rdatatype_format(type, typebuf, sizeof(typebuf)); + if (domain != NULL) { + dns_name_format(domain, domainbuf, + sizeof(domainbuf)); + } isc_log_write(DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(2), - "fetch loop detected resolving '%s/%s'", - namebuf, typebuf); + "fetch loop detected resolving '%s/%s " + "(in '%s'?)", + namebuf, typebuf, domainbuf); } return DNS_R_LOOPDETECTED; } From 4f8788310b125de12e6ea447337345b609d0e3ba Mon Sep 17 00:00:00 2001 From: Colin Vidal Date: Fri, 30 Jan 2026 15:48:18 +0100 Subject: [PATCH 3/3] system test for the ADB fetch loop detection Add a system test which implement the scenario described in the previous commit "relaxes fetch loop detection". --- .../system/expiredglue/ns1/named.conf.j2 | 39 +++++++++++++ bin/tests/system/expiredglue/ns1/root.db | 24 ++++++++ .../system/expiredglue/ns2/named.conf.j2 | 37 +++++++++++++ bin/tests/system/expiredglue/ns2/tld.db | 28 ++++++++++ .../system/expiredglue/ns3/dnshoster.tld.db | 24 ++++++++ .../system/expiredglue/ns3/example.tld.db | 22 ++++++++ .../system/expiredglue/ns3/named.conf.j2 | 42 ++++++++++++++ bin/tests/system/expiredglue/ns4/named.args | 1 + .../system/expiredglue/ns4/named.conf.j2 | 37 +++++++++++++ bin/tests/system/expiredglue/ns4/root.hint | 14 +++++ .../system/expiredglue/tests_expiredglue.py | 55 +++++++++++++++++++ 11 files changed, 323 insertions(+) create mode 100644 bin/tests/system/expiredglue/ns1/named.conf.j2 create mode 100644 bin/tests/system/expiredglue/ns1/root.db create mode 100644 bin/tests/system/expiredglue/ns2/named.conf.j2 create mode 100644 bin/tests/system/expiredglue/ns2/tld.db create mode 100644 bin/tests/system/expiredglue/ns3/dnshoster.tld.db create mode 100644 bin/tests/system/expiredglue/ns3/example.tld.db create mode 100644 bin/tests/system/expiredglue/ns3/named.conf.j2 create mode 100644 bin/tests/system/expiredglue/ns4/named.args create mode 100644 bin/tests/system/expiredglue/ns4/named.conf.j2 create mode 100644 bin/tests/system/expiredglue/ns4/root.hint create mode 100644 bin/tests/system/expiredglue/tests_expiredglue.py diff --git a/bin/tests/system/expiredglue/ns1/named.conf.j2 b/bin/tests/system/expiredglue/ns1/named.conf.j2 new file mode 100644 index 0000000000..5ad42a185a --- /dev/null +++ b/bin/tests/system/expiredglue/ns1/named.conf.j2 @@ -0,0 +1,39 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +options { + query-source address 10.53.0.1; + notify-source 10.53.0.1; + transfer-source 10.53.0.1; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.1; }; + recursion no; + dnssec-validation no; +}; + +view "default" { + zone "." { + type primary; + file "root.db"; + }; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; diff --git a/bin/tests/system/expiredglue/ns1/root.db b/bin/tests/system/expiredglue/ns1/root.db new file mode 100644 index 0000000000..41c97bf445 --- /dev/null +++ b/bin/tests/system/expiredglue/ns1/root.db @@ -0,0 +1,24 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +. IN SOA marka.isc.org. a.root.servers.nil. ( + 2010 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) +. NS a.root-servers.nil. +a.root-servers.nil. A 10.53.0.1 + +tld. NS ns.tld. +ns.tld. A 10.53.0.2 diff --git a/bin/tests/system/expiredglue/ns2/named.conf.j2 b/bin/tests/system/expiredglue/ns2/named.conf.j2 new file mode 100644 index 0000000000..8851c3728d --- /dev/null +++ b/bin/tests/system/expiredglue/ns2/named.conf.j2 @@ -0,0 +1,37 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +options { + query-source address 10.53.0.2; + notify-source 10.53.0.2; + transfer-source 10.53.0.2; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.2; }; + recursion no; + dnssec-validation no; +}; + +zone "tld." { + type primary; + file "tld.db"; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; diff --git a/bin/tests/system/expiredglue/ns2/tld.db b/bin/tests/system/expiredglue/ns2/tld.db new file mode 100644 index 0000000000..d0f06ce30b --- /dev/null +++ b/bin/tests/system/expiredglue/ns2/tld.db @@ -0,0 +1,28 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +tld. IN SOA marka.isc.org. ns.tld. ( + 2010 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) +tld. NS ns.tld. +ns.tld. A 10.53.0.2 + +example.tld. NS ns.dnshoster.tld. +missing.tld. NS ns.missing.tld. +dnshoster.tld. NS ns.dnshoster.tld. + +; Delegation's glue has a TTL of 300 on parent-side +ns.dnshoster.tld. A 10.53.0.3 diff --git a/bin/tests/system/expiredglue/ns3/dnshoster.tld.db b/bin/tests/system/expiredglue/ns3/dnshoster.tld.db new file mode 100644 index 0000000000..37c5862736 --- /dev/null +++ b/bin/tests/system/expiredglue/ns3/dnshoster.tld.db @@ -0,0 +1,24 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +dnshoster.tld. IN SOA marka.isc.org. ns.dnshoster.tld. ( + 2010 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) + +; The TTL of the delegation's glue child-side is 2 seconds. +dnshoster.tld. NS ns.dnshoster.tld. +ns.dnshoster.tld. 2 A 10.53.0.3 +a.dnshoster.tld. 2 A 10.53.0.10 diff --git a/bin/tests/system/expiredglue/ns3/example.tld.db b/bin/tests/system/expiredglue/ns3/example.tld.db new file mode 100644 index 0000000000..4d49cae8be --- /dev/null +++ b/bin/tests/system/expiredglue/ns3/example.tld.db @@ -0,0 +1,22 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +example.tld. IN SOA marka.isc.org. ns.dnshoster.tld. ( + 2010 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) + +example.tld. NS ns.dnshoster.tld. +a.example.tld. 2 A 10.53.0.10 diff --git a/bin/tests/system/expiredglue/ns3/named.conf.j2 b/bin/tests/system/expiredglue/ns3/named.conf.j2 new file mode 100644 index 0000000000..91d86edb15 --- /dev/null +++ b/bin/tests/system/expiredglue/ns3/named.conf.j2 @@ -0,0 +1,42 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +options { + query-source address 10.53.0.3; + notify-source 10.53.0.3; + transfer-source 10.53.0.3; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.3; }; + recursion no; + dnssec-validation no; +}; + +zone "dnshoster.tld." { + type primary; + file "dnshoster.tld.db"; +}; + +zone "example.tld." { + type primary; + file "example.tld.db"; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; diff --git a/bin/tests/system/expiredglue/ns4/named.args b/bin/tests/system/expiredglue/ns4/named.args new file mode 100644 index 0000000000..6d2c4141df --- /dev/null +++ b/bin/tests/system/expiredglue/ns4/named.args @@ -0,0 +1 @@ +-D expiredglue-ns4 -m record -c named.conf -d 99 -g -4 -T adbentrywindow=0 -T adbcachemin=1 -T maxcachesize=2097152 diff --git a/bin/tests/system/expiredglue/ns4/named.conf.j2 b/bin/tests/system/expiredglue/ns4/named.conf.j2 new file mode 100644 index 0000000000..7dfe5deacc --- /dev/null +++ b/bin/tests/system/expiredglue/ns4/named.conf.j2 @@ -0,0 +1,37 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +options { + query-source address 10.53.0.4; + notify-source 10.53.0.4; + transfer-source 10.53.0.4; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.4; }; + recursion yes; + dnssec-validation no; +}; + +zone "." { + type hint; + file "root.hint"; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.4 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; diff --git a/bin/tests/system/expiredglue/ns4/root.hint b/bin/tests/system/expiredglue/ns4/root.hint new file mode 100644 index 0000000000..d7d0e1faba --- /dev/null +++ b/bin/tests/system/expiredglue/ns4/root.hint @@ -0,0 +1,14 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 999999 +. IN NS a.root-servers.nil. +a.root-servers.nil. IN A 10.53.0.1 diff --git a/bin/tests/system/expiredglue/tests_expiredglue.py b/bin/tests/system/expiredglue/tests_expiredglue.py new file mode 100644 index 0000000000..a7f3c3d137 --- /dev/null +++ b/bin/tests/system/expiredglue/tests_expiredglue.py @@ -0,0 +1,55 @@ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +import time + +import isctest + + +def test_expiredglue(ns4): + msg1 = isctest.query.create("a.example.tld.", "A") + res1 = isctest.query.udp(msg1, ns4.ip) + isctest.check.noerror(res1) + isctest.check.rr_count_eq(res1.answer, 1) + + msg2 = isctest.query.create("a.dnshoster.tld.", "A") + res2 = isctest.query.udp(msg2, ns4.ip) + isctest.check.rr_count_eq(res2.answer, 1) + + msg3 = isctest.query.create("ns.dnshoster.tld.", "A") + res3 = isctest.query.udp(msg3, ns4.ip) + isctest.check.rr_count_eq(res3.answer, 1) + + time.sleep(3) + + # Even if the glue is expired but the delegation is not, named + # is able to "recover" by looking up the hints again and does + # not bails out with a fetch loop detection. + res1_2 = isctest.query.udp(msg1, ns4.ip) + isctest.check.same_data(res1_2, res1) + + time.sleep(3) + res2_2 = isctest.query.udp(msg2, ns4.ip) + isctest.check.same_data(res2_2, res2) + + time.sleep(3) + res3_2 = isctest.query.udp(msg3, ns4.ip) + isctest.check.same_data(res3_2, res3) + + +def test_loopdetected(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'") + isctest.check.servfail(res)