From e986b19d0d61aa87348fe509933cadd5a05f24c9 Mon Sep 17 00:00:00 2001 From: Colin Vidal Date: Thu, 5 Feb 2026 09:46:01 +0100 Subject: [PATCH 1/6] Limit the number of addresses returned per ADB find The number of `dns_adbaddrfind_t` (NS address with metadata like SRTT) returned from an ADB NS name lookup is now limited by the caller. The default value (outside the resolver) uses `max-delegation-servers`, and the resolver, for a given fetch, start with `max-delegation-servers` and decrement it at each ADB fetch. This ensures that, for a given delegation, no more than 13 nameservers will be contacted. This is the same mechanism used when looking up `dns_adbaddrfind_t` from a list of glues (addresses). --- lib/dns/adb.c | 29 +++++++++++++++++++++++------ lib/dns/include/dns/adb.h | 4 +++- lib/dns/notify.c | 7 ++++--- lib/dns/resolver.c | 26 +++++++++++++++++--------- lib/dns/zone.c | 6 ++++-- 5 files changed, 51 insertions(+), 21 deletions(-) diff --git a/lib/dns/adb.c b/lib/dns/adb.c index f189664f80..f51b2a8821 100644 --- a/lib/dns/adb.c +++ b/lib/dns/adb.c @@ -1370,8 +1370,10 @@ log_quota(dns_adbentry_t *entry, const char *fmt, ...) { } static void -copy_namehook_lists(dns_adb_t *adb, dns_adbfind_t *find, dns_adbname_t *name) { +copy_namehook_lists(dns_adb_t *adb, dns_adbfind_t *find, dns_adbname_t *name, + size_t maxfindlen, size_t *findlen) { dns_adbentry_t *entry = NULL; + size_t count = 0; if ((find->options & DNS_ADBFIND_INET) != 0) { ISC_LIST_FOREACH(name->v4, namehook, name_link) { @@ -1391,6 +1393,12 @@ copy_namehook_lists(dns_adb_t *adb, dns_adbfind_t *find, dns_adbname_t *name) { * Found a valid entry. Add it to the find's list. */ ISC_LIST_APPEND(find->list, addrinfo, publink); + + count++; + if (maxfindlen - count == 0) { + SET_IF_NOT_NULL(findlen, count); + return; + } } } @@ -1412,8 +1420,16 @@ copy_namehook_lists(dns_adb_t *adb, dns_adbfind_t *find, dns_adbname_t *name) { * Found a valid entry. Add it to the find's list. */ ISC_LIST_APPEND(find->list, addrinfo, publink); + + count++; + if (maxfindlen - count == 0) { + SET_IF_NOT_NULL(findlen, count); + return; + } } } + + SET_IF_NOT_NULL(findlen, count); } static bool @@ -1735,7 +1751,7 @@ out: void dns_adb_createaddrinfosfind(dns_adb_t *adb, isc_netaddrlist_t *addrs, in_port_t port, unsigned int options, - isc_stdtime_t now, size_t maxaddrs, + isc_stdtime_t now, size_t maxfindlen, dns_adbfind_t **findp, size_t *findlen) { dns_adbfind_t *find = NULL; isc_sockaddr_t sockaddr = {}; @@ -1743,7 +1759,7 @@ dns_adb_createaddrinfosfind(dns_adb_t *adb, isc_netaddrlist_t *addrs, REQUIRE(DNS_ADB_VALID(adb)); REQUIRE(addrs != NULL); REQUIRE(findp != NULL && *findp == NULL); - REQUIRE(maxaddrs > 0); + REQUIRE(maxfindlen > 0); rcu_read_lock(); @@ -1794,7 +1810,7 @@ dns_adb_createaddrinfosfind(dns_adb_t *adb, isc_netaddrlist_t *addrs, ISC_LIST_APPEND(find->list, addrinfo, publink); (*findlen)++; - if (maxaddrs - *findlen == 0) { + if (maxfindlen - *findlen == 0) { break; } } @@ -1825,7 +1841,7 @@ dns_adb_createfind(dns_adb_t *adb, isc_loop_t *loop, isc_job_cb cb, void *cbarg, const dns_name_t *name, unsigned int options, isc_stdtime_t now, in_port_t port, unsigned int depth, isc_counter_t *qc, isc_counter_t *gqc, fetchctx_t *parent, - dns_adbfind_t **findp) { + size_t maxfindlen, dns_adbfind_t **findp, size_t *findlen) { isc_result_t result = ISC_R_UNEXPECTED; dns_adbfind_t *find = NULL; dns_adbname_t *adbname = NULL; @@ -1844,6 +1860,7 @@ dns_adb_createfind(dns_adb_t *adb, isc_loop_t *loop, isc_job_cb cb, void *cbarg, } REQUIRE(name != NULL); REQUIRE(findp != NULL && *findp == NULL); + REQUIRE(maxfindlen > 0); REQUIRE((options & DNS_ADBFIND_ADDRESSMASK) != 0); @@ -2056,7 +2073,7 @@ fetch: * Run through the name and copy out the bits we are * interested in. */ - copy_namehook_lists(adb, find, adbname); + copy_namehook_lists(adb, find, adbname, maxfindlen, findlen); post_copy: if (NAME_FETCH_A(adbname)) { diff --git a/lib/dns/include/dns/adb.h b/lib/dns/include/dns/adb.h index 7c1aef4cc5..82abbf98f5 100644 --- a/lib/dns/include/dns/adb.h +++ b/lib/dns/include/dns/adb.h @@ -285,7 +285,7 @@ dns_adb_createfind(dns_adb_t *adb, isc_loop_t *loop, isc_job_cb cb, void *cbarg, const dns_name_t *name, unsigned int options, isc_stdtime_t now, in_port_t port, unsigned int depth, isc_counter_t *qc, isc_counter_t *gqc, fetchctx_t *parent, - dns_adbfind_t **find); + size_t maxfindlen, dns_adbfind_t **find, size_t *findlen); /*%< * Main interface for clients. The adb will look up the name given in * "name" and will build up a list of found addresses, and perhaps start @@ -333,6 +333,8 @@ dns_adb_createfind(dns_adb_t *adb, isc_loop_t *loop, isc_job_cb cb, void *cbarg, * *\li find != NULL && *find == NULL. * + *\li findlen is optional, if not NULL, it will be set to the length of find. + * * Returns: * *\li #ISC_R_SUCCESS Addresses might have been returned, and events will be diff --git a/lib/dns/notify.c b/lib/dns/notify.c index 46766ea711..3b2ec98f5d 100644 --- a/lib/dns/notify.c +++ b/lib/dns/notify.c @@ -761,9 +761,10 @@ dns_notify_find_address(dns_notify_t *notify) { goto destroy; } - result = dns_adb_createfind(adb, loop, process_notify_adb_event, notify, - ¬ify->ns, options, 0, notify->port, 0, - NULL, NULL, NULL, ¬ify->find); + result = dns_adb_createfind( + adb, loop, process_notify_adb_event, notify, ¬ify->ns, + options, 0, notify->port, 0, NULL, NULL, NULL, + view->max_delegation_servers, ¬ify->find, NULL); dns_adb_detach(&adb); /* Something failed? */ diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index 742748d2f3..ba48796189 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -3375,7 +3375,8 @@ already_waiting_for(dns_adbfind_t *find, dns_rdatatype_t type) { static void findname(fetchctx_t *fctx, const dns_name_t *name, in_port_t port, unsigned int options, unsigned int flags, isc_stdtime_t now, - bool *overquota, bool *need_alternate, bool *have_address) { + bool *overquota, bool *need_alternate, bool *have_address, + size_t maxfindlen, size_t *findlen) { dns_adbfind_t *find = NULL; dns_resolver_t *res = fctx->res; bool unshared = ((fctx->options & DNS_FETCHOPT_UNSHARED) != 0); @@ -3416,7 +3417,7 @@ findname(fetchctx_t *fctx, const dns_name_t *name, in_port_t port, result = dns_adb_createfind(fctx->adb, fctx->loop, fctx_finddone, fctx, name, options, now, res->view->dstport, fctx->depth + 1, fctx->qc, fctx->gqc, fctx, - &find); + maxfindlen, &find, findlen); isc_log_write(DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3), "fctx %p(%s): createfind for %s - %s", @@ -3670,7 +3671,7 @@ fctx_getaddresses_addresses(fetchctx_t *fctx, isc_stdtime_t now, ISC_LIST_FOREACH(fctx->delegset->delegs, deleg, link) { dns_adbfind_t *find = NULL; - size_t maxaddrs = max_delegation_servers - *ns_processed; + size_t maxfindlen = max_delegation_servers - *ns_processed; size_t findlen = 0; if (*ns_processed >= max_delegation_servers) { @@ -3690,7 +3691,7 @@ fctx_getaddresses_addresses(fetchctx_t *fctx, isc_stdtime_t now, fetchctx_ref(fctx); dns_adb_createaddrinfosfind(fctx->adb, &deleg->addresses, fctx->res->view->dstport, options, - now, maxaddrs, &find, &findlen); + now, maxfindlen, &find, &findlen); if (find == NULL) { fetchctx_unref(fctx); @@ -3778,6 +3779,12 @@ shufflens: unsigned int static_stub = 0; unsigned int no_fetch = 0; dns_name_t *ns = nameservers[i]; + size_t maxfindlen = max_delegation_servers - *ns_processed; + size_t findlen = 0; + + if (*ns_processed >= max_delegation_servers) { + break; + } if (fctx->delegset->staticstub && dns_name_equal(ns, fctx->domain)) @@ -3794,15 +3801,15 @@ shufflens: } findname(fctx, ns, 0, stdoptions | static_stub | no_fetch, 0, - now, &overquota, need_alternatep, &have_address); + now, &overquota, need_alternatep, &have_address, + maxfindlen, &findlen); if (!overquota) { *all_spilledp = false; } + *ns_processed += findlen; - if (++(*ns_processed) >= max_delegation_servers) { - break; - } + INSIST(*ns_processed <= max_delegation_servers); } if (fctx->pending_running == 0 && !have_address) { @@ -3828,7 +3835,8 @@ fctx_getaddresses_alternate(fetchctx_t *fctx, isc_stdtime_t now, if (!a->isaddress) { findname(fctx, &a->_u._n.name, a->_u._n.port, stdoptions, FCTX_ADDRINFO_DUALSTACK, now, NULL, - NULL, NULL); + NULL, NULL, + fctx->res->view->max_delegation_servers, NULL); continue; } if (isc_sockaddr_pf(&a->_u.addr) != family) { diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 3414ed8d35..4ab816c1f2 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -17278,9 +17278,11 @@ checkds_find_address(dns_checkds_t *checkds) { isc_result_t result; unsigned int options; dns_adb_t *adb = NULL; + dns_view_t *view = NULL; REQUIRE(DNS_CHECKDS_VALID(checkds)); + view = checkds->zone->view; options = DNS_ADBFIND_WANTEVENT; if (isc_net_probeipv4() != ISC_R_DISABLED) { options |= DNS_ADBFIND_INET; @@ -17289,7 +17291,7 @@ checkds_find_address(dns_checkds_t *checkds) { options |= DNS_ADBFIND_INET6; } - dns_view_getadb(checkds->zone->view, &adb); + dns_view_getadb(view, &adb); if (adb == NULL) { goto destroy; } @@ -17297,7 +17299,7 @@ checkds_find_address(dns_checkds_t *checkds) { result = dns_adb_createfind( adb, checkds->zone->loop, process_checkds_adb_event, checkds, &checkds->ns, options, 0, checkds->zone->view->dstport, 0, NULL, - NULL, NULL, &checkds->find); + NULL, NULL, view->max_delegation_servers, &checkds->find, NULL); dns_adb_detach(&adb); /* Something failed? */ From 0fcaa37c3af3ccc5b603d6d0b46a6c8163fb2c7e Mon Sep 17 00:00:00 2001 From: Colin Vidal Date: Wed, 4 Feb 2026 10:18:42 +0100 Subject: [PATCH 2/6] Remove duplicate addresses from the resolver SLIST The SLIST (essentially `fctx->finds`, forwarders and dual-stack alternatives aside) can have duplicate server addresses when multiple in-domain nameservers share the same IP addresses: sub.example. NS ns1.sub.example. sub.example. NS ns2.sub.example. ns1.sub.example. A 1.2.3.4 ns1.sub.example. A 5.6.7.8 ns2.sub.example. A 1.2.3.4 ns2.sub.example. A 5.6.7.8 If both 1.2.3.4 and 5.6.7.8 fail to return a valid answer, the resolver would query each address twice. The problem is fixed by replacing the two-phase server selection (sort each find list by SRTT, sort finds by head SRTT) with a single linear scan in nextaddress() that finds the lowest-SRTT unmarked, non-duplicate address across all find lists. The old approach had a correctness bug: after sorting, the resolver picked the next address from the "current" find list rather than globally. For example, with find lists [1, 15, 26] and [3, 4, 5], the second pick would be SRTT 15 instead of the correct SRTT 3. The new approach is both simpler and correct: each call to nextaddress() walks all addresses, skips marked and duplicate entries, and returns the one with the lowest SRTT. While this walk is repeated for each server attempt, it operates on a small bounded list and is negligible compared to the network I/O of querying the server. --- lib/dns/resolver.c | 209 ++++++++++++++++++--------------------------- 1 file changed, 83 insertions(+), 126 deletions(-) diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index ba48796189..e66e602888 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -368,7 +368,16 @@ struct fetchctx { dns_message_t *qmessage; ISC_LIST(resquery_t) queries; dns_adbfindlist_t finds; - dns_adbfind_t *find; + /* + * This is a state to keep track of the latest upstream server which is + * being queried. See `nextaddress()`. + * + * `addrinfo` is basically a copy of `foundaddrinfo` but came from the + * response of the query, so fields like the SRTT/timing might have been + * altered. So it might be possible (?) to wrap those two in an union + * for clarity (and memory saving). + */ + dns_adbaddrinfo_t *foundaddrinfo; /* * altfinds are names and/or addresses of dual stack servers that * should be used when iterative resolution to a server is not @@ -1409,7 +1418,7 @@ fctx_cleanup(fetchctx_t *fctx) { dns_adb_destroyfind(&find); fetchctx_unref(fctx); } - fctx->find = NULL; + fctx->foundaddrinfo = NULL; ISC_LIST_FOREACH(fctx->altfinds, find, publink) { ISC_LIST_UNLINK(fctx->altfinds, find, publink); @@ -3251,89 +3260,6 @@ add_bad(fetchctx_t *fctx, dns_message_t *rmessage, dns_adbaddrinfo_t *addrinfo, classbuf, addrbuf); } -/* - * Sort addrinfo list by RTT. - */ -static void -sort_adbfind(dns_adbfind_t *find, unsigned int bias) { - dns_adbaddrinfo_t *best, *curr; - dns_adbaddrinfolist_t sorted; - - /* Lame N^2 bubble sort. */ - ISC_LIST_INIT(sorted); - while (!ISC_LIST_EMPTY(find->list)) { - unsigned int best_srtt; - best = ISC_LIST_HEAD(find->list); - best_srtt = best->srtt; - if (isc_sockaddr_pf(&best->sockaddr) != AF_INET6) { - best_srtt += bias; - } - curr = ISC_LIST_NEXT(best, publink); - while (curr != NULL) { - unsigned int curr_srtt = curr->srtt; - if (isc_sockaddr_pf(&curr->sockaddr) != AF_INET6) { - curr_srtt += bias; - } - if (curr_srtt < best_srtt) { - best = curr; - best_srtt = curr_srtt; - } - curr = ISC_LIST_NEXT(curr, publink); - } - ISC_LIST_UNLINK(find->list, best, publink); - ISC_LIST_APPEND(sorted, best, publink); - } - find->list = sorted; -} - -/* - * Sort a list of finds by server RTT. - */ -static void -sort_finds(dns_adbfindlist_t *findlist, unsigned int bias) { - dns_adbfind_t *best = NULL; - dns_adbfindlist_t sorted; - dns_adbaddrinfo_t *addrinfo, *bestaddrinfo; - - /* Sort each find's addrinfo list by SRTT. */ - ISC_LIST_FOREACH(*findlist, curr, publink) { - sort_adbfind(curr, bias); - } - - /* Lame N^2 bubble sort. */ - ISC_LIST_INIT(sorted); - while (!ISC_LIST_EMPTY(*findlist)) { - dns_adbfind_t *curr = NULL; - unsigned int best_srtt; - - best = ISC_LIST_HEAD(*findlist); - bestaddrinfo = ISC_LIST_HEAD(best->list); - INSIST(bestaddrinfo != NULL); - best_srtt = bestaddrinfo->srtt; - if (isc_sockaddr_pf(&bestaddrinfo->sockaddr) != AF_INET6) { - best_srtt += bias; - } - curr = ISC_LIST_NEXT(best, publink); - while (curr != NULL) { - unsigned int curr_srtt; - addrinfo = ISC_LIST_HEAD(curr->list); - INSIST(addrinfo != NULL); - curr_srtt = addrinfo->srtt; - if (isc_sockaddr_pf(&addrinfo->sockaddr) != AF_INET6) { - curr_srtt += bias; - } - if (curr_srtt < best_srtt) { - best = curr; - best_srtt = curr_srtt; - } - curr = ISC_LIST_NEXT(curr, publink); - } - ISC_LIST_UNLINK(*findlist, best, publink); - ISC_LIST_APPEND(sorted, best, publink); - } - *findlist = sorted; -} - /* * 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 @@ -3459,6 +3385,7 @@ findname(fetchctx_t *fctx, const dns_name_t *name, in_port_t port, } } } + if ((flags & FCTX_ADDRINFO_DUALSTACK) != 0) { ISC_LIST_APPEND(fctx->altfinds, find, publink); } else { @@ -4027,8 +3954,6 @@ out: * We've found some addresses. We might still be * looking for more addresses. */ - sort_finds(&fctx->finds, res->view->v6bias); - sort_finds(&fctx->altfinds, 0); return ISC_R_SUCCESS; } @@ -4140,6 +4065,76 @@ possibly_mark(fetchctx_t *fctx, dns_adbaddrinfo_t *addr) { } } +static dns_adbaddrinfo_t * +nextaddress(fetchctx_t *fctx) { + dns_adbaddrinfo_t *prevai = fctx->foundaddrinfo, *lowestsrttai = NULL; + unsigned int v6bias = fctx->res->view->v6bias, lowestsrtt = 0; + + /* + * Let's walk through the list of dns_adbaddrinfo_t to find the best + * next server address to query. This is linear on the number of + * dns_adbaddrinfo_t which are grouped in find list (for each ADB find). + */ + ISC_LIST_FOREACH(fctx->finds, find, publink) { + ISC_LIST_FOREACH(find->list, ai, publink) { + /* + * This address has been marked already, skip it. + */ + if (!UNMARKED(ai)) { + continue; + } + + /* + * This address is the same as the previously used + * address, it's a duplicate, mark it and skip it! + */ + if (prevai != NULL) { + if (prevai->entry == ai->entry) { + ai->flags |= FCTX_ADDRINFO_MARK; + continue; + } + } + + /* + * Mark and skip this address if incompatible (i.e. IPv6 + * address on a v4 only server, or for ACL reason, etc.) + */ + possibly_mark(fctx, ai); + if (!UNMARKED(ai)) { + continue; + } + + /* + * This address hasn't been tried yet and is a + * good candidate. Let's keep track of it if it + * has the lowest SRTT so far (or if there is no + * address with lowest SRTT found yet). + */ + unsigned int aisrtt = ai->srtt; + + if (isc_sockaddr_pf(&ai->sockaddr) != AF_INET6) { + aisrtt += v6bias; + } + + if (lowestsrttai == NULL || aisrtt < lowestsrtt) { + lowestsrttai = ai; + lowestsrtt = aisrtt; + continue; + } + } + } + + /* + * This is the next address to query. If this is NULL, we're done. + */ + if (lowestsrttai != NULL) { + lowestsrttai->flags |= FCTX_ADDRINFO_MARK; + } + fctx->foundaddrinfo = lowestsrttai; + + return lowestsrttai; +} + static dns_adbaddrinfo_t * fctx_nextaddress(fetchctx_t *fctx) { dns_adbfind_t *find = NULL, *start = NULL; @@ -4159,7 +4154,6 @@ fctx_nextaddress(fetchctx_t *fctx) { possibly_mark(fctx, ai); if (UNMARKED(ai)) { ai->flags |= FCTX_ADDRINFO_MARK; - fctx->find = NULL; fctx->forwarding = true; /* @@ -4180,44 +4174,7 @@ fctx_nextaddress(fetchctx_t *fctx) { fctx->forwarding = false; FCTX_ATTR_SET(fctx, FCTX_ATTR_TRIEDFIND); - find = fctx->find; - if (find == NULL) { - find = ISC_LIST_HEAD(fctx->finds); - } else { - find = ISC_LIST_NEXT(find, publink); - if (find == NULL) { - find = ISC_LIST_HEAD(fctx->finds); - } - } - - /* - * Find the first unmarked addrinfo. - */ - if (find != NULL) { - start = find; - do { - ISC_LIST_FOREACH(find->list, ai, publink) { - if (!UNMARKED(ai)) { - continue; - } - possibly_mark(fctx, ai); - if (UNMARKED(ai)) { - ai->flags |= FCTX_ADDRINFO_MARK; - faddrinfo = ai; - break; - } - } - if (faddrinfo != NULL) { - break; - } - find = ISC_LIST_NEXT(find, publink); - if (find == NULL) { - find = ISC_LIST_HEAD(fctx->finds); - } - } while (find != start); - } - - fctx->find = find; + faddrinfo = nextaddress(fctx); if (faddrinfo != NULL) { return faddrinfo; } From c9997e0dd9222178187a63d5b78447a430906574 Mon Sep 17 00:00:00 2001 From: Colin Vidal Date: Thu, 5 Feb 2026 11:20:11 +0100 Subject: [PATCH 3/6] Add system test for self-pointed glue deduplication Test the resolver's behavior with self-pointed glue where each NS has the same set of addresses. Verify that addresses are deduplicated and each unique IP is only queried once. Also test the NS processing limit (max-delegation-servers) and the ADB address limit (adbaddrslimit), both individually and combined. --- .../system/selfpointedglue/ns1/named.conf.j2 | 28 +++ bin/tests/system/selfpointedglue/ns1/root.db | 24 +++ .../system/selfpointedglue/ns2/named.conf.j2 | 28 +++ bin/tests/system/selfpointedglue/ns2/tld.db | 30 +++ .../system/selfpointedglue/ns3/example.tld.db | 184 ++++++++++++++++++ .../selfpointedglue/ns3/example2.tld.db | 33 ++++ .../selfpointedglue/ns3/example3.tld.db | 63 ++++++ .../system/selfpointedglue/ns3/named.conf.j2 | 49 +++++ .../system/selfpointedglue/ns4/named.args.j2 | 1 + .../system/selfpointedglue/ns4/named.conf.j2 | 59 ++++++ .../system/selfpointedglue/ns4/root.hint | 14 ++ .../selfpointedglue/tests_selfpointedglue.py | 157 +++++++++++++++ 12 files changed, 670 insertions(+) create mode 100644 bin/tests/system/selfpointedglue/ns1/named.conf.j2 create mode 100644 bin/tests/system/selfpointedglue/ns1/root.db create mode 100644 bin/tests/system/selfpointedglue/ns2/named.conf.j2 create mode 100644 bin/tests/system/selfpointedglue/ns2/tld.db create mode 100644 bin/tests/system/selfpointedglue/ns3/example.tld.db create mode 100644 bin/tests/system/selfpointedglue/ns3/example2.tld.db create mode 100644 bin/tests/system/selfpointedglue/ns3/example3.tld.db create mode 100644 bin/tests/system/selfpointedglue/ns3/named.conf.j2 create mode 100644 bin/tests/system/selfpointedglue/ns4/named.args.j2 create mode 100644 bin/tests/system/selfpointedglue/ns4/named.conf.j2 create mode 100644 bin/tests/system/selfpointedglue/ns4/root.hint create mode 100644 bin/tests/system/selfpointedglue/tests_selfpointedglue.py diff --git a/bin/tests/system/selfpointedglue/ns1/named.conf.j2 b/bin/tests/system/selfpointedglue/ns1/named.conf.j2 new file mode 100644 index 0000000000..fd83fc3c19 --- /dev/null +++ b/bin/tests/system/selfpointedglue/ns1/named.conf.j2 @@ -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. + */ + +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; +}; + +zone "." { + type primary; + file "root.db"; +}; diff --git a/bin/tests/system/selfpointedglue/ns1/root.db b/bin/tests/system/selfpointedglue/ns1/root.db new file mode 100644 index 0000000000..bfbf049b80 --- /dev/null +++ b/bin/tests/system/selfpointedglue/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 owner.root-servers.nil. 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/selfpointedglue/ns2/named.conf.j2 b/bin/tests/system/selfpointedglue/ns2/named.conf.j2 new file mode 100644 index 0000000000..2993832da2 --- /dev/null +++ b/bin/tests/system/selfpointedglue/ns2/named.conf.j2 @@ -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. + */ + +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"; +}; diff --git a/bin/tests/system/selfpointedglue/ns2/tld.db b/bin/tests/system/selfpointedglue/ns2/tld.db new file mode 100644 index 0000000000..66f7925d99 --- /dev/null +++ b/bin/tests/system/selfpointedglue/ns2/tld.db @@ -0,0 +1,30 @@ +; 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 owner.tld. 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.example.tld. +ns.example.tld. A 10.53.0.3 + +example2.tld. NS ns.example2.tld. +ns.example2.tld. A 10.53.0.3 + +example3.tld. NS ns.example3.tld. +ns.example3.tld. A 10.53.0.3 diff --git a/bin/tests/system/selfpointedglue/ns3/example.tld.db b/bin/tests/system/selfpointedglue/ns3/example.tld.db new file mode 100644 index 0000000000..2a599ee876 --- /dev/null +++ b/bin/tests/system/selfpointedglue/ns3/example.tld.db @@ -0,0 +1,184 @@ +; 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 owner.dnshoster.tld. ns.dnshoster.tld. ( + 2010 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) + +example.tld. NS ns.example.tld. +ns.example.tld. A 10.53.0.3 + +sub.example.tld. NS ns01.sub.example.tld. +sub.example.tld. NS ns02.sub.example.tld. +sub.example.tld. NS ns03.sub.example.tld. +sub.example.tld. NS ns04.sub.example.tld. +sub.example.tld. NS ns05.sub.example.tld. +sub.example.tld. NS ns06.sub.example.tld. +sub.example.tld. NS ns07.sub.example.tld. +sub.example.tld. NS ns08.sub.example.tld. +sub.example.tld. NS ns09.sub.example.tld. +sub.example.tld. NS ns10.sub.example.tld. + +ns01.sub.example.tld. A 10.53.0.5 +ns01.sub.example.tld. A 10.53.0.6 +ns01.sub.example.tld. A 10.53.0.7 +ns01.sub.example.tld. A 10.53.0.8 +ns01.sub.example.tld. A 10.53.0.9 +ns01.sub.example.tld. A 10.53.0.10 +ns01.sub.example.tld. A 10.53.1.1 +ns01.sub.example.tld. A 10.53.1.2 +ns01.sub.example.tld. A 10.53.2.1 +ns01.sub.example.tld. A 10.53.0.3 +ns01.sub.example.tld. A 127.0.0.1 +ns01.sub.example.tld. A 127.0.0.2 +; Those addresses won't be used (exceed the max-delegation-servers). +ns01.sub.example.tld. A 127.0.0.3 +ns01.sub.example.tld. A 127.0.0.4 + +ns02.sub.example.tld. A 10.53.0.5 +ns02.sub.example.tld. A 10.53.0.6 +ns02.sub.example.tld. A 10.53.0.7 +ns02.sub.example.tld. A 10.53.0.8 +ns02.sub.example.tld. A 10.53.0.9 +ns02.sub.example.tld. A 10.53.0.10 +ns02.sub.example.tld. A 10.53.1.1 +ns02.sub.example.tld. A 10.53.1.2 +ns02.sub.example.tld. A 10.53.2.1 +ns02.sub.example.tld. A 10.53.0.3 +ns02.sub.example.tld. A 127.0.0.1 +ns02.sub.example.tld. A 127.0.0.2 +ns02.sub.example.tld. A 127.0.0.3 +ns02.sub.example.tld. A 127.0.0.4 + +ns03.sub.example.tld. A 10.53.0.5 +ns03.sub.example.tld. A 10.53.0.6 +ns03.sub.example.tld. A 10.53.0.7 +ns03.sub.example.tld. A 10.53.0.8 +ns03.sub.example.tld. A 10.53.0.9 +ns03.sub.example.tld. A 10.53.0.10 +ns03.sub.example.tld. A 10.53.1.1 +ns03.sub.example.tld. A 10.53.1.2 +ns03.sub.example.tld. A 10.53.2.1 +ns03.sub.example.tld. A 10.53.0.3 +ns03.sub.example.tld. A 127.0.0.1 +ns03.sub.example.tld. A 127.0.0.2 +ns03.sub.example.tld. A 127.0.0.3 +ns03.sub.example.tld. A 127.0.0.4 + +ns04.sub.example.tld. A 10.53.0.5 +ns04.sub.example.tld. A 10.53.0.6 +ns04.sub.example.tld. A 10.53.0.7 +ns04.sub.example.tld. A 10.53.0.8 +ns04.sub.example.tld. A 10.53.0.9 +ns04.sub.example.tld. A 10.53.0.10 +ns04.sub.example.tld. A 10.53.1.1 +ns04.sub.example.tld. A 10.53.1.2 +ns04.sub.example.tld. A 10.53.2.1 +ns04.sub.example.tld. A 10.53.0.3 +ns04.sub.example.tld. A 127.0.0.1 +ns04.sub.example.tld. A 127.0.0.2 +ns04.sub.example.tld. A 127.0.0.3 +ns04.sub.example.tld. A 127.0.0.4 + +ns05.sub.example.tld. A 10.53.0.5 +ns05.sub.example.tld. A 10.53.0.6 +ns05.sub.example.tld. A 10.53.0.7 +ns05.sub.example.tld. A 10.53.0.8 +ns05.sub.example.tld. A 10.53.0.9 +ns05.sub.example.tld. A 10.53.0.10 +ns05.sub.example.tld. A 10.53.1.1 +ns05.sub.example.tld. A 10.53.1.2 +ns05.sub.example.tld. A 10.53.2.1 +ns05.sub.example.tld. A 10.53.0.3 +ns05.sub.example.tld. A 127.0.0.1 +ns05.sub.example.tld. A 127.0.0.2 +ns05.sub.example.tld. A 127.0.0.3 +ns05.sub.example.tld. A 127.0.0.4 + +ns06.sub.example.tld. A 10.53.0.5 +ns06.sub.example.tld. A 10.53.0.6 +ns06.sub.example.tld. A 10.53.0.7 +ns06.sub.example.tld. A 10.53.0.8 +ns06.sub.example.tld. A 10.53.0.9 +ns06.sub.example.tld. A 10.53.0.10 +ns06.sub.example.tld. A 10.53.1.1 +ns06.sub.example.tld. A 10.53.1.2 +ns06.sub.example.tld. A 10.53.2.1 +ns06.sub.example.tld. A 10.53.0.3 +ns06.sub.example.tld. A 127.0.0.1 +ns06.sub.example.tld. A 127.0.0.2 +ns06.sub.example.tld. A 127.0.0.3 +ns06.sub.example.tld. A 127.0.0.4 + +ns07.sub.example.tld. A 10.53.0.5 +ns07.sub.example.tld. A 10.53.0.6 +ns07.sub.example.tld. A 10.53.0.7 +ns07.sub.example.tld. A 10.53.0.8 +ns07.sub.example.tld. A 10.53.0.9 +ns07.sub.example.tld. A 10.53.0.10 +ns07.sub.example.tld. A 10.53.1.1 +ns07.sub.example.tld. A 10.53.1.2 +ns07.sub.example.tld. A 10.53.2.1 +ns07.sub.example.tld. A 10.53.0.3 +ns07.sub.example.tld. A 127.0.0.1 +ns07.sub.example.tld. A 127.0.0.2 +ns07.sub.example.tld. A 127.0.0.3 +ns07.sub.example.tld. A 127.0.0.4 + +ns08.sub.example.tld. A 10.53.0.5 +ns08.sub.example.tld. A 10.53.0.6 +ns08.sub.example.tld. A 10.53.0.7 +ns08.sub.example.tld. A 10.53.0.8 +ns08.sub.example.tld. A 10.53.0.9 +ns08.sub.example.tld. A 10.53.0.10 +ns08.sub.example.tld. A 10.53.1.1 +ns08.sub.example.tld. A 10.53.1.2 +ns08.sub.example.tld. A 10.53.2.1 +ns08.sub.example.tld. A 10.53.0.3 +ns08.sub.example.tld. A 127.0.0.1 +ns08.sub.example.tld. A 127.0.0.2 +ns08.sub.example.tld. A 127.0.0.3 +ns08.sub.example.tld. A 127.0.0.4 + +ns09.sub.example.tld. A 10.53.0.5 +ns09.sub.example.tld. A 10.53.0.6 +ns09.sub.example.tld. A 10.53.0.7 +ns09.sub.example.tld. A 10.53.0.8 +ns09.sub.example.tld. A 10.53.0.9 +ns09.sub.example.tld. A 10.53.0.10 +ns09.sub.example.tld. A 10.53.1.1 +ns09.sub.example.tld. A 10.53.1.2 +ns09.sub.example.tld. A 10.53.2.1 +ns09.sub.example.tld. A 10.53.0.3 +ns09.sub.example.tld. A 127.0.0.1 +ns09.sub.example.tld. A 127.0.0.2 +ns09.sub.example.tld. A 127.0.0.3 +ns09.sub.example.tld. A 127.0.0.4 + +ns10.sub.example.tld. A 10.53.0.5 +ns10.sub.example.tld. A 10.53.0.6 +ns10.sub.example.tld. A 10.53.0.7 +ns10.sub.example.tld. A 10.53.0.8 +ns10.sub.example.tld. A 10.53.0.9 +ns10.sub.example.tld. A 10.53.0.10 +ns10.sub.example.tld. A 10.53.1.1 +ns10.sub.example.tld. A 10.53.1.2 +ns10.sub.example.tld. A 10.53.2.1 +ns10.sub.example.tld. A 10.53.0.3 +ns10.sub.example.tld. A 127.0.0.1 +ns10.sub.example.tld. A 127.0.0.2 +ns10.sub.example.tld. A 127.0.0.3 +ns10.sub.example.tld. A 127.0.0.4 diff --git a/bin/tests/system/selfpointedglue/ns3/example2.tld.db b/bin/tests/system/selfpointedglue/ns3/example2.tld.db new file mode 100644 index 0000000000..bcab6e38c1 --- /dev/null +++ b/bin/tests/system/selfpointedglue/ns3/example2.tld.db @@ -0,0 +1,33 @@ +; 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 +example2.tld. IN SOA owner.dnshoster.tld. ns.dnshoster.tld. ( + 2010 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) + +example2.tld. NS ns.example2.tld. +ns.example2.tld. A 10.53.0.3 + +sub.example2.tld. NS ns01.sub.example2.tld. +sub.example2.tld. NS ns02.sub.example2.tld. +sub.example2.tld. NS ns03.sub.example2.tld. + +ns01.sub.example2.tld. A 10.53.1.1 +ns01.sub.example2.tld. A 10.53.0.5 +ns02.sub.example2.tld. A 10.53.1.2 +ns02.sub.example2.tld. A 10.53.0.6 +ns03.sub.example2.tld. A 10.53.2.1 +ns03.sub.example2.tld. A 10.53.0.7 diff --git a/bin/tests/system/selfpointedglue/ns3/example3.tld.db b/bin/tests/system/selfpointedglue/ns3/example3.tld.db new file mode 100644 index 0000000000..e2c522cc3e --- /dev/null +++ b/bin/tests/system/selfpointedglue/ns3/example3.tld.db @@ -0,0 +1,63 @@ +; 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 +example3.tld. IN SOA owner.dnshoster.tld. ns.dnshoster.tld. ( + 2010 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) + +example3.tld. NS ns.example3.tld. +ns.example3.tld. A 10.53.0.3 + +sub.example3.tld. NS ns01.sub.example3.tld. +sub.example3.tld. NS ns02.sub.example3.tld. +sub.example3.tld. NS ns03.sub.example3.tld. +sub.example3.tld. NS ns04.sub.example3.tld. +sub.example3.tld. NS ns05.sub.example3.tld. +sub.example3.tld. NS ns06.sub.example3.tld. +sub.example3.tld. NS ns07.sub.example3.tld. +sub.example3.tld. NS ns08.sub.example3.tld. +sub.example3.tld. NS ns09.sub.example3.tld. +sub.example3.tld. NS ns10.sub.example3.tld. + +ns01.sub.example3.tld. A 10.53.0.5 +ns01.sub.example3.tld. A 10.53.0.6 + +ns02.sub.example3.tld. A 10.53.0.5 +ns02.sub.example3.tld. A 10.53.0.6 + +ns03.sub.example3.tld. A 10.53.0.5 +ns03.sub.example3.tld. A 10.53.0.6 + +ns04.sub.example3.tld. A 10.53.0.5 +ns04.sub.example3.tld. A 10.53.0.6 + +ns05.sub.example3.tld. A 10.53.0.5 +ns05.sub.example3.tld. A 10.53.0.6 + +ns06.sub.example3.tld. A 10.53.0.5 +ns06.sub.example3.tld. A 10.53.0.6 + +ns07.sub.example3.tld. A 10.53.0.5 +ns07.sub.example3.tld. A 10.53.0.6 + +ns08.sub.example3.tld. A 10.53.0.5 +ns08.sub.example3.tld. A 10.53.0.6 + +ns09.sub.example3.tld. A 10.53.0.5 +ns09.sub.example3.tld. A 10.53.0.6 + +ns10.sub.example3.tld. A 10.53.0.5 +ns10.sub.example3.tld. A 10.53.0.6 diff --git a/bin/tests/system/selfpointedglue/ns3/named.conf.j2 b/bin/tests/system/selfpointedglue/ns3/named.conf.j2 new file mode 100644 index 0000000000..0c50ca8658 --- /dev/null +++ b/bin/tests/system/selfpointedglue/ns3/named.conf.j2 @@ -0,0 +1,49 @@ +/* + * 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; + 10.53.0.5; + 10.53.0.6; + 10.53.0.7; + 10.53.0.8; + 10.53.0.9; + 10.53.0.10; + 10.53.1.1; + 10.53.1.2; + 10.53.2.1; + }; + recursion no; + dnssec-validation no; +}; + +zone "example.tld." { + type primary; + file "example.tld.db"; +}; + +zone "example2.tld." { + type primary; + file "example2.tld.db"; +}; + +zone "example3.tld." { + type primary; + file "example3.tld.db"; +}; diff --git a/bin/tests/system/selfpointedglue/ns4/named.args.j2 b/bin/tests/system/selfpointedglue/ns4/named.args.j2 new file mode 100644 index 0000000000..68f1511c7c --- /dev/null +++ b/bin/tests/system/selfpointedglue/ns4/named.args.j2 @@ -0,0 +1 @@ +-D selfpointedglue-ns4 -m record -c named.conf -d 99 -g -T maxcachesize=2097152 -4 diff --git a/bin/tests/system/selfpointedglue/ns4/named.conf.j2 b/bin/tests/system/selfpointedglue/ns4/named.conf.j2 new file mode 100644 index 0000000000..09fbdd4e70 --- /dev/null +++ b/bin/tests/system/selfpointedglue/ns4/named.conf.j2 @@ -0,0 +1,59 @@ +/* + * 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. + */ +{% set maxdelegationservers = maxdelegationservers | default(None) %} + +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; + dnstap { resolver query; }; + dnstap-output file "dnstap.out"; + {% if maxdelegationservers %} + @maxdelegationservers@ + {% endif %} +}; + +/* + * Forcing TCP ensures that ADDITIONAL won't be truncated (responses won't have + * the TC flag, hence the resolver won't retry using TCP by itself, see + * https://datatracker.ietf.org/doc/html/rfc2181#section-9) + */ +server 10.53.0.3 { tcp-only true; }; +server 10.53.0.5 { tcp-only true; }; +server 10.53.0.6 { tcp-only true; }; +server 10.53.0.7 { tcp-only true; }; +server 10.53.0.8 { tcp-only true; }; +server 10.53.0.9 { tcp-only true; }; +server 10.53.0.10 { tcp-only true; }; +server 10.53.1.1 { tcp-only true; }; +server 10.53.1.2 { tcp-only true; }; +server 10.53.2.1 { tcp-only true; }; + +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/selfpointedglue/ns4/root.hint b/bin/tests/system/selfpointedglue/ns4/root.hint new file mode 100644 index 0000000000..d7d0e1faba --- /dev/null +++ b/bin/tests/system/selfpointedglue/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/selfpointedglue/tests_selfpointedglue.py b/bin/tests/system/selfpointedglue/tests_selfpointedglue.py new file mode 100644 index 0000000000..d559b76ea8 --- /dev/null +++ b/bin/tests/system/selfpointedglue/tests_selfpointedglue.py @@ -0,0 +1,157 @@ +# 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 os + +import isctest +import isctest.mark + +pytestmark = [isctest.mark.with_dnstap] + + +def line_to_ips_and_queries(line): + # dnstap-read output line example + # 05-Feb-2026 11:00:57.853 RQ 10.53.0.4:38507 -> 10.53.0.3:22047 TCP 56b sub.example.tld/IN/NS + _, _, _, _, _, dst, _, _, query = line.split(" ", 9) + ip, _ = dst.split(":", 1) + return (ip, query) + + +def extract_dnstap(ns, expectedlen): + ns.rndc("dnstap -roll 1") + path = os.path.join(ns.identifier, "dnstap.out.0") + dnstapread = isctest.run.cmd( + [isctest.vars.ALL["DNSTAPREAD"], path], + ) + + lines = dnstapread.out.splitlines() + assert expectedlen == len(lines) + return list(map(line_to_ips_and_queries, lines)) + + +# Because DNSTAP doesn't have ordering guarantee, the order doesn't matter here. +def expect_ip_and_query(expected_ips_and_queries, ips_and_queries): + found_count = 0 + for expected_ip, expected_query in expected_ips_and_queries: + found = False + for ip, query in ips_and_queries: + if ip == expected_ip and query == expected_query: + found = True + found_count += 1 + break + assert found + assert found_count == len(expected_ips_and_queries) + + +def expect_query(expected_query, expected_query_count, ips_and_queries): + count = 0 + for _, query in ips_and_queries: + if query == expected_query: + count += 1 + assert count == expected_query_count + + +def test_selfpointedglue1(ns4): + msg = isctest.query.create("a.sub.example.tld.", "A") + res = isctest.query.tcp(msg, ns4.ip) + isctest.check.servfail(res) + + # 4 queries to get to the delegation. + # 13 queries to delegation NS servers. + ips_and_queries = extract_dnstap(ns4, 17) + + # Thanks to the de-duplication, only the first 13 NS IPs are + # queried (once sub.example.tld. NS is found) instead of 13*10 + # (13 per NS, with 10 NS). + expect_ip_and_query( + [ + ("10.53.0.1", "./IN/NS"), + ("10.53.0.1", "tld/IN/NS"), + ("10.53.0.2", "example.tld/IN/NS"), + ("10.53.0.3", "sub.example.tld/IN/NS"), + ("10.53.0.3", "a.sub.example.tld/IN/A"), + ("10.53.0.5", "a.sub.example.tld/IN/A"), + ("10.53.0.6", "a.sub.example.tld/IN/A"), + ("10.53.0.7", "a.sub.example.tld/IN/A"), + ("10.53.0.8", "a.sub.example.tld/IN/A"), + ("10.53.0.9", "a.sub.example.tld/IN/A"), + ("10.53.0.10", "a.sub.example.tld/IN/A"), + ("10.53.1.1", "a.sub.example.tld/IN/A"), + ("10.53.1.2", "a.sub.example.tld/IN/A"), + ("10.53.2.1", "a.sub.example.tld/IN/A"), + ("10.53.0.3", "a.sub.example.tld/IN/A"), + ("127.0.0.1", "a.sub.example.tld/IN/A"), + ("127.0.0.2", "a.sub.example.tld/IN/A"), + ], + ips_and_queries, + ) + + +# This test is useful because the one above hits the max-delegation-servers +# from the first NS name lookup. This one doesn't, because there is only 2 +# addresses per NS, but the deduplication avoid the explosion of duplicate +# addresses. +def test_selfpointedglue2(ns4): + with ns4.watch_log_from_here() as watcher: + ns4.rndc("flush") + ns4.rndc("reload") + watcher.wait_for_line("running") + msg = isctest.query.create("a.sub.example3.tld.", "A") + res = isctest.query.tcp(msg, ns4.ip) + isctest.check.servfail(res) + + # 4 queries to get to the delegation. + # 2 queries to delegation NS servers. + ips_and_queries = extract_dnstap(ns4, 6) + + # Thanks to the de-duplication, only the first 2 NS IPs are + # queried (once sub.example.tld. NS is found) instead of 2*10 + # (2 per NS with 10 NS). + expect_ip_and_query( + [ + ("10.53.0.1", "./IN/NS"), + ("10.53.0.1", "tld/IN/NS"), + ("10.53.0.2", "example3.tld/IN/NS"), + ("10.53.0.3", "sub.example3.tld/IN/NS"), + ("10.53.0.5", "a.sub.example3.tld/IN/A"), + ("10.53.0.6", "a.sub.example3.tld/IN/A"), + ], + ips_and_queries, + ) + + +def test_selfpointedglue_nslimit(ns4, templates): + templates.render( + "ns4/named.conf", {"maxdelegationservers": "max-delegation-servers 2;"} + ) + with ns4.watch_log_from_here() as watcher: + ns4.rndc("flush") + ns4.rndc("reload") + watcher.wait_for_line("running") + + msg = isctest.query.create("a.sub.example2.tld.", "A") + res = isctest.query.tcp(msg, ns4.ip) + isctest.check.servfail(res) + + ips_and_queries = extract_dnstap(ns4, 6) + + # Checking the beginning of the resolution + expect_ip_and_query( + [ + ("10.53.0.1", "./IN/NS"), + ("10.53.0.1", "tld/IN/NS"), + ("10.53.0.2", "example2.tld/IN/NS"), + ("10.53.0.3", "sub.example2.tld/IN/NS"), + ], + ips_and_queries, + ) + + expect_query("a.sub.example2.tld/IN/A", 2, ips_and_queries) From 9bf3df7073b600aa519f5488c6c408aa45002336 Mon Sep 17 00:00:00 2001 From: Colin Vidal Date: Wed, 4 Mar 2026 18:25:32 +0100 Subject: [PATCH 4/6] Add SRTT-based server selection system test Verify that the resolver selects authoritative servers in increasing SRTT order. Four servers are configured with increasing response delays. 100 queries are sent, expecting most to go to the fastest server (ns2). Then ns2 stops responding, another 100 queries are sent and should go to ns3 (the next fastest), and so on through ns4 and ns5. Each query uses a unique name to avoid cache hits. --- bin/tests/system/srtt/README | 18 +++++ bin/tests/system/srtt/ans2/ans.py | 36 ++++++++++ bin/tests/system/srtt/ans3/ans.py | 36 ++++++++++ bin/tests/system/srtt/ans4/ans.py | 36 ++++++++++ bin/tests/system/srtt/ans5/ans.py | 36 ++++++++++ bin/tests/system/srtt/ns1/named.conf.j2 | 29 ++++++++ bin/tests/system/srtt/ns1/root.db | 36 ++++++++++ bin/tests/system/srtt/ns6/named.args | 1 + bin/tests/system/srtt/ns6/named.conf.j2 | 41 ++++++++++++ bin/tests/system/srtt/srtt_ans.py | 59 ++++++++++++++++ bin/tests/system/srtt/tests_srtt.py | 89 +++++++++++++++++++++++++ 11 files changed, 417 insertions(+) create mode 100644 bin/tests/system/srtt/README create mode 100644 bin/tests/system/srtt/ans2/ans.py create mode 100644 bin/tests/system/srtt/ans3/ans.py create mode 100644 bin/tests/system/srtt/ans4/ans.py create mode 100644 bin/tests/system/srtt/ans5/ans.py create mode 100644 bin/tests/system/srtt/ns1/named.conf.j2 create mode 100644 bin/tests/system/srtt/ns1/root.db create mode 100644 bin/tests/system/srtt/ns6/named.args create mode 100644 bin/tests/system/srtt/ns6/named.conf.j2 create mode 100644 bin/tests/system/srtt/srtt_ans.py create mode 100644 bin/tests/system/srtt/tests_srtt.py diff --git a/bin/tests/system/srtt/README b/bin/tests/system/srtt/README new file mode 100644 index 0000000000..c86a697931 --- /dev/null +++ b/bin/tests/system/srtt/README @@ -0,0 +1,18 @@ +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. + +ns1 is root + +ans{2-5} simulates four NS servers making authority on the same domain +`example.`. ans2 is the quickest to answer, followed by ans3, then ans4, with +ans5 being the slowest. + +ns6 is a resolver diff --git a/bin/tests/system/srtt/ans2/ans.py b/bin/tests/system/srtt/ans2/ans.py new file mode 100644 index 0000000000..147a65f828 --- /dev/null +++ b/bin/tests/system/srtt/ans2/ans.py @@ -0,0 +1,36 @@ +""" +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 dns.rcode + +from isctest.asyncserver import AsyncDnsServer, IgnoreAllQueries + +from ..srtt_ans import DelayedQnameRangeHandler + + +class Foo1ToFoo99Handler(DelayedQnameRangeHandler): + max_qname = 99 + delay = 0.0 + + +def main() -> None: + server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR) + server.install_response_handlers( + Foo1ToFoo99Handler(), + IgnoreAllQueries(), + ) + server.run() + + +if __name__ == "__main__": + main() diff --git a/bin/tests/system/srtt/ans3/ans.py b/bin/tests/system/srtt/ans3/ans.py new file mode 100644 index 0000000000..ecd590afd0 --- /dev/null +++ b/bin/tests/system/srtt/ans3/ans.py @@ -0,0 +1,36 @@ +""" +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 dns.rcode + +from isctest.asyncserver import AsyncDnsServer, IgnoreAllQueries + +from ..srtt_ans import DelayedQnameRangeHandler + + +class Foo1ToFoo199Handler(DelayedQnameRangeHandler): + max_qname = 199 + delay = 0.03 + + +def main() -> None: + server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR) + server.install_response_handlers( + Foo1ToFoo199Handler(), + IgnoreAllQueries(), + ) + server.run() + + +if __name__ == "__main__": + main() diff --git a/bin/tests/system/srtt/ans4/ans.py b/bin/tests/system/srtt/ans4/ans.py new file mode 100644 index 0000000000..af337c27fb --- /dev/null +++ b/bin/tests/system/srtt/ans4/ans.py @@ -0,0 +1,36 @@ +""" +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 dns.rcode + +from isctest.asyncserver import AsyncDnsServer, IgnoreAllQueries + +from ..srtt_ans import DelayedQnameRangeHandler + + +class Foo1ToFoo299Handler(DelayedQnameRangeHandler): + max_qname = 299 + delay = 0.08 + + +def main() -> None: + server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR) + server.install_response_handlers( + Foo1ToFoo299Handler(), + IgnoreAllQueries(), + ) + server.run() + + +if __name__ == "__main__": + main() diff --git a/bin/tests/system/srtt/ans5/ans.py b/bin/tests/system/srtt/ans5/ans.py new file mode 100644 index 0000000000..8bac83a798 --- /dev/null +++ b/bin/tests/system/srtt/ans5/ans.py @@ -0,0 +1,36 @@ +""" +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 dns.rcode + +from isctest.asyncserver import AsyncDnsServer, IgnoreAllQueries + +from ..srtt_ans import DelayedQnameRangeHandler + + +class Foo1ToFoo399Handler(DelayedQnameRangeHandler): + max_qname = 399 + delay = 0.15 + + +def main() -> None: + server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR) + server.install_response_handlers( + Foo1ToFoo399Handler(), + IgnoreAllQueries(), + ) + server.run() + + +if __name__ == "__main__": + main() diff --git a/bin/tests/system/srtt/ns1/named.conf.j2 b/bin/tests/system/srtt/ns1/named.conf.j2 new file mode 100644 index 0000000000..eb079c95ab --- /dev/null +++ b/bin/tests/system/srtt/ns1/named.conf.j2 @@ -0,0 +1,29 @@ +/* + * 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; }; + listen-on-v6 { none; }; + recursion no; + notify yes; +}; + +zone "." { + type primary; + file "root.db"; +}; diff --git a/bin/tests/system/srtt/ns1/root.db b/bin/tests/system/srtt/ns1/root.db new file mode 100644 index 0000000000..29ecd1d89d --- /dev/null +++ b/bin/tests/system/srtt/ns1/root.db @@ -0,0 +1,36 @@ +; 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 owner.root-servers.nil. a.root-servers.nil. ( + 2000042100 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) +. NS a.root-servers.nil. +a.root-servers.nil. A 10.53.0.1 + +; The idea is that the resolver would do 2 ADB lookups, so there would be 2 +; find list, both with 2 IPs in it. ns1 (which is actually ans2 and ans5) would +; have both the slowest and fastest addresses. ns2 (which is actually ans3 and +; ans4) would have two addresses in the middle. + +example. NS ns1.example. +example. NS ns1.example. +example. NS ns2.example. +example. NS ns2.example. + +ns1.example. A 10.53.0.2 ; delay is 0 +ns1.example. A 10.53.0.5 ; delay is 0.15 +ns2.example. A 10.53.0.4 ; delay is 0.08 +ns2.example. A 10.53.0.3 ; delay is 0.03 diff --git a/bin/tests/system/srtt/ns6/named.args b/bin/tests/system/srtt/ns6/named.args new file mode 100644 index 0000000000..b5de5874ec --- /dev/null +++ b/bin/tests/system/srtt/ns6/named.args @@ -0,0 +1 @@ +-D srtt-ns6 -m record -c named.conf -d 99 -g -T maxcachesize=2097152 -4 diff --git a/bin/tests/system/srtt/ns6/named.conf.j2 b/bin/tests/system/srtt/ns6/named.conf.j2 new file mode 100644 index 0000000000..1d27505a8e --- /dev/null +++ b/bin/tests/system/srtt/ns6/named.conf.j2 @@ -0,0 +1,41 @@ +/* + * 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.6; + notify-source 10.53.0.6; + transfer-source 10.53.0.6; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.6; }; + listen-on-v6 { none; }; + recursion yes; + dnssec-validation no; + dnstap { resolver query; }; + dnstap-output file "dnstap.out"; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.6 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "." { + type hint; + file "../../_common/root.hint"; +}; diff --git a/bin/tests/system/srtt/srtt_ans.py b/bin/tests/system/srtt/srtt_ans.py new file mode 100644 index 0000000000..9387486993 --- /dev/null +++ b/bin/tests/system/srtt/srtt_ans.py @@ -0,0 +1,59 @@ +""" +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. +""" + +from collections.abc import AsyncGenerator + +import abc + +import dns.rdataclass +import dns.rdatatype +import dns.rrset + +from isctest.asyncserver import DnsResponseSend, QnameQtypeHandler, QueryContext + + +class DelayedQnameRangeHandler(QnameQtypeHandler): + """ + Respond to queries for QNAMEs "foo1.example." through "foo.example." + with QTYPE=A, where must be defined by the subclass. Every response is + delayed by a fixed amount of time, which must also be defined (in seconds) + by the subclass. + """ + + @property + def qnames(self) -> list[str]: + return [f"foo{x}.example." for x in range(1, self.max_qname + 1)] + + qtypes = [dns.rdatatype.A] + + @property + @abc.abstractmethod + def max_qname(self) -> int: + raise NotImplementedError + + @property + @abc.abstractmethod + def delay(self) -> float: + raise NotImplementedError + + def __str__(self) -> str: + return f"{self.__class__.__name__}(foo[1-{self.max_qname}].example/A)" + + async def get_responses( + self, qctx: QueryContext + ) -> AsyncGenerator[DnsResponseSend, None]: + a_rrset = dns.rrset.from_text( + qctx.qname, 300, dns.rdataclass.IN, dns.rdatatype.A, "10.53.9.9" + ) + qctx.response.answer.append(a_rrset) + yield DnsResponseSend(qctx.response, delay=self.delay) diff --git a/bin/tests/system/srtt/tests_srtt.py b/bin/tests/system/srtt/tests_srtt.py new file mode 100644 index 0000000000..0ce18fcccf --- /dev/null +++ b/bin/tests/system/srtt/tests_srtt.py @@ -0,0 +1,89 @@ +# 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 os + +import isctest +import isctest.mark + +pytestmark = [isctest.mark.with_dnstap] + + +def line_to_dst_ips(line): + # dnstap-read output line example + # 05-Feb-2026 11:00:57.853 RQ 10.53.0.6:38507 -> 10.53.0.3:22047 TCP 56b fooXXX.example./IN/NS + _, _, _, _, _, dst, _, _, _ = line.split(" ", 9) + ip, _ = dst.split(":", 1) + return ip + + +def extract_dnstap(ns): + ns.rndc("dnstap -roll 1") + path = os.path.join(ns.identifier, "dnstap.out.0") + dnstapread = isctest.run.cmd( + [isctest.vars.ALL["DNSTAPREAD"], path], + ) + + lines = dnstapread.out.splitlines() + return map(line_to_dst_ips, lines) + + +def assert_used_auth(ns, authip): + ips = extract_dnstap(ns) + queries = 0 + matches = 0 + for ip in ips: + queries += 1 + if ip == authip: + matches += 1 + assert matches > 85 + assert queries <= 115 + + +def test_srtt(ns6): + for i in range(1, 100): + msg = isctest.query.create(f"foo{i}.example.", "A") + res = isctest.query.udp(msg, ns6.ip) + isctest.check.noerror(res) + assert len(res.answer[0]) == 1 + res.answer[0].ttl = 300 + assert str(res.answer[0]) == f"foo{i}.example. 300 IN A 10.53.9.9" + + assert_used_auth(ns6, "10.53.0.2") + + for i in range(100, 200): + msg = isctest.query.create(f"foo{i}.example.", "A") + res = isctest.query.udp(msg, ns6.ip) + isctest.check.noerror(res) + assert len(res.answer[0]) == 1 + res.answer[0].ttl = 300 + assert str(res.answer[0]) == f"foo{i}.example. 300 IN A 10.53.9.9" + + assert_used_auth(ns6, "10.53.0.3") + + for i in range(200, 300): + msg = isctest.query.create(f"foo{i}.example.", "A") + res = isctest.query.udp(msg, ns6.ip) + isctest.check.noerror(res) + assert len(res.answer[0]) == 1 + res.answer[0].ttl = 300 + assert str(res.answer[0]) == f"foo{i}.example. 300 IN A 10.53.9.9" + + assert_used_auth(ns6, "10.53.0.4") + + for i in range(300, 400): + msg = isctest.query.create(f"foo{i}.example.", "A") + res = isctest.query.udp(msg, ns6.ip) + isctest.check.noerror(res) + assert len(res.answer[0]) == 1 + res.answer[0].ttl = 300 + assert str(res.answer[0]) == f"foo{i}.example. 300 IN A 10.53.9.9" + assert_used_auth(ns6, "10.53.0.5") From c50a743794e6047d67e42a15ba639b01340b34de Mon Sep 17 00:00:00 2001 From: Colin Vidal Date: Wed, 1 Apr 2026 22:31:50 +0200 Subject: [PATCH 5/6] add max-delegation-servers tests for out domain NS Add a new system test which ensures that the `max-delegation-servers` limit is correctly respected also in the case a domain has only NS names (and no glues). In particular, this test when there are multiple NS names and multiples IPs per names. If the number of IP (even from the first picked NS name) reaches `max-delegation-servers`, and the resolution is not a success, the resolver won't attempt another NS name, as it already used all its "credit". --- .../nslimit_outdomain/ns1/named.conf.j2 | 28 +++ .../system/nslimit_outdomain/ns1/root.db | 24 +++ .../nslimit_outdomain/ns2/dnshoster.tld.db | 33 ++++ .../nslimit_outdomain/ns2/named.conf.j2 | 41 ++++ bin/tests/system/nslimit_outdomain/ns2/tld.db | 36 ++++ .../nslimit_outdomain/ns3/example.tld.db | 184 ++++++++++++++++++ .../nslimit_outdomain/ns3/example4.tld.db | 24 +++ .../nslimit_outdomain/ns3/named.conf.j2 | 30 +++ .../nslimit_outdomain/ns4/named.args.j2 | 1 + .../nslimit_outdomain/ns4/named.conf.j2 | 59 ++++++ .../system/nslimit_outdomain/ns4/root.hint | 14 ++ .../tests_nslimit_outdomain.py | 120 ++++++++++++ 12 files changed, 594 insertions(+) create mode 100644 bin/tests/system/nslimit_outdomain/ns1/named.conf.j2 create mode 100644 bin/tests/system/nslimit_outdomain/ns1/root.db create mode 100644 bin/tests/system/nslimit_outdomain/ns2/dnshoster.tld.db create mode 100644 bin/tests/system/nslimit_outdomain/ns2/named.conf.j2 create mode 100644 bin/tests/system/nslimit_outdomain/ns2/tld.db create mode 100644 bin/tests/system/nslimit_outdomain/ns3/example.tld.db create mode 100644 bin/tests/system/nslimit_outdomain/ns3/example4.tld.db create mode 100644 bin/tests/system/nslimit_outdomain/ns3/named.conf.j2 create mode 100644 bin/tests/system/nslimit_outdomain/ns4/named.args.j2 create mode 100644 bin/tests/system/nslimit_outdomain/ns4/named.conf.j2 create mode 100644 bin/tests/system/nslimit_outdomain/ns4/root.hint create mode 100644 bin/tests/system/nslimit_outdomain/tests_nslimit_outdomain.py diff --git a/bin/tests/system/nslimit_outdomain/ns1/named.conf.j2 b/bin/tests/system/nslimit_outdomain/ns1/named.conf.j2 new file mode 100644 index 0000000000..fd83fc3c19 --- /dev/null +++ b/bin/tests/system/nslimit_outdomain/ns1/named.conf.j2 @@ -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. + */ + +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; +}; + +zone "." { + type primary; + file "root.db"; +}; diff --git a/bin/tests/system/nslimit_outdomain/ns1/root.db b/bin/tests/system/nslimit_outdomain/ns1/root.db new file mode 100644 index 0000000000..bfbf049b80 --- /dev/null +++ b/bin/tests/system/nslimit_outdomain/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 owner.root-servers.nil. 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/nslimit_outdomain/ns2/dnshoster.tld.db b/bin/tests/system/nslimit_outdomain/ns2/dnshoster.tld.db new file mode 100644 index 0000000000..9540da4743 --- /dev/null +++ b/bin/tests/system/nslimit_outdomain/ns2/dnshoster.tld.db @@ -0,0 +1,33 @@ +; 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 owner.tld. ns.tld. ( + 2010 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) + +dnshoster.tld. NS ns1.dnshoster.tld. +ns1.dnshoster.tld. A 10.53.0.5 +ns1.dnshoster.tld. A 10.53.0.6 + +dnshoster.tld. NS ns2.dnshoster.tld. +ns2.dnshoster.tld. A 10.53.1.1 +ns2.dnshoster.tld. A 10.53.1.2 + +dnshoster.tld. NS ns3.dnshoster.tld. +ns3.dnshoster.tld. A 10.53.2.1 +ns3.dnshoster.tld. A 10.53.2.2 + + diff --git a/bin/tests/system/nslimit_outdomain/ns2/named.conf.j2 b/bin/tests/system/nslimit_outdomain/ns2/named.conf.j2 new file mode 100644 index 0000000000..037ac60fe0 --- /dev/null +++ b/bin/tests/system/nslimit_outdomain/ns2/named.conf.j2 @@ -0,0 +1,41 @@ +/* + * 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; + 10.53.0.5; + 10.53.0.6; + 10.53.0.7; + 10.53.1.1; + 10.53.1.2; + 10.53.2.2; + }; + recursion no; + dnssec-validation no; +}; + +zone "tld." { + type primary; + file "tld.db"; +}; + +zone "dnshoster.tld." { + type primary; + file "dnshoster.tld.db"; +}; diff --git a/bin/tests/system/nslimit_outdomain/ns2/tld.db b/bin/tests/system/nslimit_outdomain/ns2/tld.db new file mode 100644 index 0000000000..e29bf91f7d --- /dev/null +++ b/bin/tests/system/nslimit_outdomain/ns2/tld.db @@ -0,0 +1,36 @@ +; 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 owner.tld. ns.tld. ( + 2010 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) +tld. NS ns.tld. +ns.tld. A 10.53.0.2 + +example4.tld. NS ns.example4.tld. +ns.example4.tld. A 10.53.0.3 + +dnshoster.tld. NS ns1.dnshoster.tld. +ns1.dnshoster.tld. A 10.53.0.5 +ns1.dnshoster.tld. A 10.53.0.6 + +dnshoster.tld. NS ns2.dnshoster.tld. +ns2.dnshoster.tld. A 10.53.1.1 +ns2.dnshoster.tld. A 10.53.1.2 + +dnshoster.tld. NS ns3.dnshoster.tld. +ns3.dnshoster.tld. A 10.53.2.1 +ns3.dnshoster.tld. A 10.53.2.2 diff --git a/bin/tests/system/nslimit_outdomain/ns3/example.tld.db b/bin/tests/system/nslimit_outdomain/ns3/example.tld.db new file mode 100644 index 0000000000..2a599ee876 --- /dev/null +++ b/bin/tests/system/nslimit_outdomain/ns3/example.tld.db @@ -0,0 +1,184 @@ +; 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 owner.dnshoster.tld. ns.dnshoster.tld. ( + 2010 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) + +example.tld. NS ns.example.tld. +ns.example.tld. A 10.53.0.3 + +sub.example.tld. NS ns01.sub.example.tld. +sub.example.tld. NS ns02.sub.example.tld. +sub.example.tld. NS ns03.sub.example.tld. +sub.example.tld. NS ns04.sub.example.tld. +sub.example.tld. NS ns05.sub.example.tld. +sub.example.tld. NS ns06.sub.example.tld. +sub.example.tld. NS ns07.sub.example.tld. +sub.example.tld. NS ns08.sub.example.tld. +sub.example.tld. NS ns09.sub.example.tld. +sub.example.tld. NS ns10.sub.example.tld. + +ns01.sub.example.tld. A 10.53.0.5 +ns01.sub.example.tld. A 10.53.0.6 +ns01.sub.example.tld. A 10.53.0.7 +ns01.sub.example.tld. A 10.53.0.8 +ns01.sub.example.tld. A 10.53.0.9 +ns01.sub.example.tld. A 10.53.0.10 +ns01.sub.example.tld. A 10.53.1.1 +ns01.sub.example.tld. A 10.53.1.2 +ns01.sub.example.tld. A 10.53.2.1 +ns01.sub.example.tld. A 10.53.0.3 +ns01.sub.example.tld. A 127.0.0.1 +ns01.sub.example.tld. A 127.0.0.2 +; Those addresses won't be used (exceed the max-delegation-servers). +ns01.sub.example.tld. A 127.0.0.3 +ns01.sub.example.tld. A 127.0.0.4 + +ns02.sub.example.tld. A 10.53.0.5 +ns02.sub.example.tld. A 10.53.0.6 +ns02.sub.example.tld. A 10.53.0.7 +ns02.sub.example.tld. A 10.53.0.8 +ns02.sub.example.tld. A 10.53.0.9 +ns02.sub.example.tld. A 10.53.0.10 +ns02.sub.example.tld. A 10.53.1.1 +ns02.sub.example.tld. A 10.53.1.2 +ns02.sub.example.tld. A 10.53.2.1 +ns02.sub.example.tld. A 10.53.0.3 +ns02.sub.example.tld. A 127.0.0.1 +ns02.sub.example.tld. A 127.0.0.2 +ns02.sub.example.tld. A 127.0.0.3 +ns02.sub.example.tld. A 127.0.0.4 + +ns03.sub.example.tld. A 10.53.0.5 +ns03.sub.example.tld. A 10.53.0.6 +ns03.sub.example.tld. A 10.53.0.7 +ns03.sub.example.tld. A 10.53.0.8 +ns03.sub.example.tld. A 10.53.0.9 +ns03.sub.example.tld. A 10.53.0.10 +ns03.sub.example.tld. A 10.53.1.1 +ns03.sub.example.tld. A 10.53.1.2 +ns03.sub.example.tld. A 10.53.2.1 +ns03.sub.example.tld. A 10.53.0.3 +ns03.sub.example.tld. A 127.0.0.1 +ns03.sub.example.tld. A 127.0.0.2 +ns03.sub.example.tld. A 127.0.0.3 +ns03.sub.example.tld. A 127.0.0.4 + +ns04.sub.example.tld. A 10.53.0.5 +ns04.sub.example.tld. A 10.53.0.6 +ns04.sub.example.tld. A 10.53.0.7 +ns04.sub.example.tld. A 10.53.0.8 +ns04.sub.example.tld. A 10.53.0.9 +ns04.sub.example.tld. A 10.53.0.10 +ns04.sub.example.tld. A 10.53.1.1 +ns04.sub.example.tld. A 10.53.1.2 +ns04.sub.example.tld. A 10.53.2.1 +ns04.sub.example.tld. A 10.53.0.3 +ns04.sub.example.tld. A 127.0.0.1 +ns04.sub.example.tld. A 127.0.0.2 +ns04.sub.example.tld. A 127.0.0.3 +ns04.sub.example.tld. A 127.0.0.4 + +ns05.sub.example.tld. A 10.53.0.5 +ns05.sub.example.tld. A 10.53.0.6 +ns05.sub.example.tld. A 10.53.0.7 +ns05.sub.example.tld. A 10.53.0.8 +ns05.sub.example.tld. A 10.53.0.9 +ns05.sub.example.tld. A 10.53.0.10 +ns05.sub.example.tld. A 10.53.1.1 +ns05.sub.example.tld. A 10.53.1.2 +ns05.sub.example.tld. A 10.53.2.1 +ns05.sub.example.tld. A 10.53.0.3 +ns05.sub.example.tld. A 127.0.0.1 +ns05.sub.example.tld. A 127.0.0.2 +ns05.sub.example.tld. A 127.0.0.3 +ns05.sub.example.tld. A 127.0.0.4 + +ns06.sub.example.tld. A 10.53.0.5 +ns06.sub.example.tld. A 10.53.0.6 +ns06.sub.example.tld. A 10.53.0.7 +ns06.sub.example.tld. A 10.53.0.8 +ns06.sub.example.tld. A 10.53.0.9 +ns06.sub.example.tld. A 10.53.0.10 +ns06.sub.example.tld. A 10.53.1.1 +ns06.sub.example.tld. A 10.53.1.2 +ns06.sub.example.tld. A 10.53.2.1 +ns06.sub.example.tld. A 10.53.0.3 +ns06.sub.example.tld. A 127.0.0.1 +ns06.sub.example.tld. A 127.0.0.2 +ns06.sub.example.tld. A 127.0.0.3 +ns06.sub.example.tld. A 127.0.0.4 + +ns07.sub.example.tld. A 10.53.0.5 +ns07.sub.example.tld. A 10.53.0.6 +ns07.sub.example.tld. A 10.53.0.7 +ns07.sub.example.tld. A 10.53.0.8 +ns07.sub.example.tld. A 10.53.0.9 +ns07.sub.example.tld. A 10.53.0.10 +ns07.sub.example.tld. A 10.53.1.1 +ns07.sub.example.tld. A 10.53.1.2 +ns07.sub.example.tld. A 10.53.2.1 +ns07.sub.example.tld. A 10.53.0.3 +ns07.sub.example.tld. A 127.0.0.1 +ns07.sub.example.tld. A 127.0.0.2 +ns07.sub.example.tld. A 127.0.0.3 +ns07.sub.example.tld. A 127.0.0.4 + +ns08.sub.example.tld. A 10.53.0.5 +ns08.sub.example.tld. A 10.53.0.6 +ns08.sub.example.tld. A 10.53.0.7 +ns08.sub.example.tld. A 10.53.0.8 +ns08.sub.example.tld. A 10.53.0.9 +ns08.sub.example.tld. A 10.53.0.10 +ns08.sub.example.tld. A 10.53.1.1 +ns08.sub.example.tld. A 10.53.1.2 +ns08.sub.example.tld. A 10.53.2.1 +ns08.sub.example.tld. A 10.53.0.3 +ns08.sub.example.tld. A 127.0.0.1 +ns08.sub.example.tld. A 127.0.0.2 +ns08.sub.example.tld. A 127.0.0.3 +ns08.sub.example.tld. A 127.0.0.4 + +ns09.sub.example.tld. A 10.53.0.5 +ns09.sub.example.tld. A 10.53.0.6 +ns09.sub.example.tld. A 10.53.0.7 +ns09.sub.example.tld. A 10.53.0.8 +ns09.sub.example.tld. A 10.53.0.9 +ns09.sub.example.tld. A 10.53.0.10 +ns09.sub.example.tld. A 10.53.1.1 +ns09.sub.example.tld. A 10.53.1.2 +ns09.sub.example.tld. A 10.53.2.1 +ns09.sub.example.tld. A 10.53.0.3 +ns09.sub.example.tld. A 127.0.0.1 +ns09.sub.example.tld. A 127.0.0.2 +ns09.sub.example.tld. A 127.0.0.3 +ns09.sub.example.tld. A 127.0.0.4 + +ns10.sub.example.tld. A 10.53.0.5 +ns10.sub.example.tld. A 10.53.0.6 +ns10.sub.example.tld. A 10.53.0.7 +ns10.sub.example.tld. A 10.53.0.8 +ns10.sub.example.tld. A 10.53.0.9 +ns10.sub.example.tld. A 10.53.0.10 +ns10.sub.example.tld. A 10.53.1.1 +ns10.sub.example.tld. A 10.53.1.2 +ns10.sub.example.tld. A 10.53.2.1 +ns10.sub.example.tld. A 10.53.0.3 +ns10.sub.example.tld. A 127.0.0.1 +ns10.sub.example.tld. A 127.0.0.2 +ns10.sub.example.tld. A 127.0.0.3 +ns10.sub.example.tld. A 127.0.0.4 diff --git a/bin/tests/system/nslimit_outdomain/ns3/example4.tld.db b/bin/tests/system/nslimit_outdomain/ns3/example4.tld.db new file mode 100644 index 0000000000..f1c64be066 --- /dev/null +++ b/bin/tests/system/nslimit_outdomain/ns3/example4.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 +example4.tld. IN SOA owner.dnshoster.tld. ns.dnshoster.tld. ( + 2010 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) + +example4.tld. NS ns.example4.tld. +ns.example4.tld. A 10.53.0.3 +sub.example4.tld. NS ns1.dnshoster.tld. +sub.example4.tld. NS ns2.dnshoster.tld. diff --git a/bin/tests/system/nslimit_outdomain/ns3/named.conf.j2 b/bin/tests/system/nslimit_outdomain/ns3/named.conf.j2 new file mode 100644 index 0000000000..f7e03a82e7 --- /dev/null +++ b/bin/tests/system/nslimit_outdomain/ns3/named.conf.j2 @@ -0,0 +1,30 @@ +/* + * 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 "example4.tld." { + type primary; + file "example4.tld.db"; +}; diff --git a/bin/tests/system/nslimit_outdomain/ns4/named.args.j2 b/bin/tests/system/nslimit_outdomain/ns4/named.args.j2 new file mode 100644 index 0000000000..68f1511c7c --- /dev/null +++ b/bin/tests/system/nslimit_outdomain/ns4/named.args.j2 @@ -0,0 +1 @@ +-D selfpointedglue-ns4 -m record -c named.conf -d 99 -g -T maxcachesize=2097152 -4 diff --git a/bin/tests/system/nslimit_outdomain/ns4/named.conf.j2 b/bin/tests/system/nslimit_outdomain/ns4/named.conf.j2 new file mode 100644 index 0000000000..09fbdd4e70 --- /dev/null +++ b/bin/tests/system/nslimit_outdomain/ns4/named.conf.j2 @@ -0,0 +1,59 @@ +/* + * 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. + */ +{% set maxdelegationservers = maxdelegationservers | default(None) %} + +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; + dnstap { resolver query; }; + dnstap-output file "dnstap.out"; + {% if maxdelegationservers %} + @maxdelegationservers@ + {% endif %} +}; + +/* + * Forcing TCP ensures that ADDITIONAL won't be truncated (responses won't have + * the TC flag, hence the resolver won't retry using TCP by itself, see + * https://datatracker.ietf.org/doc/html/rfc2181#section-9) + */ +server 10.53.0.3 { tcp-only true; }; +server 10.53.0.5 { tcp-only true; }; +server 10.53.0.6 { tcp-only true; }; +server 10.53.0.7 { tcp-only true; }; +server 10.53.0.8 { tcp-only true; }; +server 10.53.0.9 { tcp-only true; }; +server 10.53.0.10 { tcp-only true; }; +server 10.53.1.1 { tcp-only true; }; +server 10.53.1.2 { tcp-only true; }; +server 10.53.2.1 { tcp-only true; }; + +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/nslimit_outdomain/ns4/root.hint b/bin/tests/system/nslimit_outdomain/ns4/root.hint new file mode 100644 index 0000000000..d7d0e1faba --- /dev/null +++ b/bin/tests/system/nslimit_outdomain/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/nslimit_outdomain/tests_nslimit_outdomain.py b/bin/tests/system/nslimit_outdomain/tests_nslimit_outdomain.py new file mode 100644 index 0000000000..228fb07918 --- /dev/null +++ b/bin/tests/system/nslimit_outdomain/tests_nslimit_outdomain.py @@ -0,0 +1,120 @@ +# 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 os + +import isctest +import isctest.mark + +pytestmark = [isctest.mark.with_dnstap] + + +def line_to_ips_and_queries(line): + # dnstap-read output line example + # 05-Feb-2026 11:00:57.853 RQ 10.53.0.4:38507 -> 10.53.0.3:22047 TCP 56b sub.example.tld/IN/NS + _, _, _, _, _, dst, _, _, query = line.split(" ", 9) + ip, _ = dst.split(":", 1) + return (ip, query) + + +def extract_dnstap(ns, expectedlen): + ns.rndc("dnstap -roll 1") + path = os.path.join(ns.identifier, "dnstap.out.0") + dnstapread = isctest.run.cmd( + [isctest.vars.ALL["DNSTAPREAD"], path], + ) + + lines = dnstapread.out.splitlines() + assert expectedlen == len(lines) + return list(map(line_to_ips_and_queries, lines)) + + +# Because DNSTAP doesn't have ordering guarantee, the order doesn't matter here. +def has_ip_and_query(expected_ips_and_queries, ips_and_queries): + found_count = 0 + for expected_ip, expected_query in expected_ips_and_queries: + for ip, query in ips_and_queries: + if ip == expected_ip and query == expected_query: + found_count += 1 + break + return found_count == len(expected_ips_and_queries) + + +# Test the max-delegation-servers limit on flow where ADB attempt +# a lookup from an NS name rather than directly with the NS addresses. +def test_nslimit_outdomain(ns4, templates): + templates.render( + "ns4/named.conf", {"maxdelegationservers": "max-delegation-servers 2;"} + ) + with ns4.watch_log_from_here() as watcher: + ns4.rndc("flush") + ns4.rndc("reload") + watcher.wait_for_line("running") + + msg = isctest.query.create("sub.example4.tld.", "A") + res = isctest.query.tcp(msg, ns4.ip) + isctest.check.servfail(res) + + ips_and_queries = extract_dnstap(ns4, 9) + + # The resolver first resolve example4.tld. and gets the NS for sub.example.tld. + # which is out-domain. So it resolves it. + assert has_ip_and_query( + [ + ("10.53.0.1", "./IN/NS"), + ("10.53.0.1", "tld/IN/NS"), + ("10.53.0.2", "example4.tld/IN/NS"), + ("10.53.0.3", "sub.example4.tld/IN/A"), + ("10.53.0.2", "dnshoster.tld/IN/NS"), + ], + ips_and_queries, + ) + + # Then, because max-delegation-servers is 2, the resolver will try to use either + # the NS ns1.dnshoster.tld or the NS ns2.dnshoster.tld. or the NS ns3.dnshoster.tld. + # + # What is important here, is that the NS of sub.example4.tld are _names_, so + # this is going through the dns_adb_createfind() flow, and it does stop after 2 + # queries (on the two IPs of one of the NS server above) and _won't_ try another + # NS name (becuse max-delegation-servers will be reached). + # + # Note that the sum of all the queries checked here is 8 and not 9. This is because + # when dnshoster.tld has been resolved, the resolver resolved 2 names. But the IPs + # of only one of the two names has been used. (This is checked below). + + used_ns1 = has_ip_and_query( + [ + ("10.53.0.2", "ns1.dnshoster.tld/IN/A"), + ("10.53.0.5", "sub.example4.tld/IN/A"), + ("10.53.0.6", "sub.example4.tld/IN/A"), + ], + ips_and_queries, + ) + + used_ns2 = has_ip_and_query( + [ + ("10.53.0.2", "ns2.dnshoster.tld/IN/A"), + ("10.53.1.1", "sub.example4.tld/IN/A"), + ("10.53.1.2", "sub.example4.tld/IN/A"), + ], + ips_and_queries, + ) + + used_ns3 = has_ip_and_query( + [ + ("10.53.0.2", "ns3.dnshoster.tld/IN/A"), + ("10.53.2.1", "sub.example4.tld/IN/A"), + ("10.53.2.2", "sub.example4.tld/IN/A"), + ], + ips_and_queries, + ) + + assert used_ns1 or used_ns2 or used_ns3 From 156039fef5fd6381dd0b79092c6032d702fc285c Mon Sep 17 00:00:00 2001 From: Colin Vidal Date: Thu, 2 Apr 2026 10:43:00 +0200 Subject: [PATCH 6/6] update `max-delegation-servers` documentation Clarify how `max-delegation-servers` is used in the resolver, in particular, the fact that it, in practice, caps the maximum outgoing queries to resolve a name at a given delegation point. --- doc/arm/reference.rst | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst index a42a08d326..10ba133ef1 100644 --- a/doc/arm/reference.rst +++ b/doc/arm/reference.rst @@ -4187,14 +4187,11 @@ Tuning .. namedconf:statement:: max-delegation-servers :tags: server - :short: Configure the maximum number of nameserver names considered for a delegation + :short: Configure the maximum number of nameservers considered for a delegation When looking up remote nameservers for a delegation, the list of nameserver names is sorted according to Canonical RR Ordering within an RRset (see - :rfc:`4034` Section 6.3), and the number of names for which :iscman:`named` - looks up IP addresses is capped at :any:`max-delegation-servers`. - - This capped list of nameserver names is then randomly shuffled every time + :rfc:`4034` Section 6.3). This list is then randomly shuffled every time :iscman:`named` needs additional remote addresses for those nameservers. This randomized selection works around situations where the first few nameserver names in the zone are unresponsive. @@ -4207,6 +4204,12 @@ Tuning outgoing DNS query is initiated only if the DNS resolver does not already have existing IP addresses for any of the nameserver names in the cache. + The known NS addresses for an NS name (cached from a previous resolution, or + the NS name has glues, or it is defined from a local zone or hints) are + counted as delegation servers. Thus, the maximum queries the resolver does + to resolve a name at a delegation point is capped at + :any:`max-delegation-servers`. + The default and recommended value is ``13``. This limit prevents excessive resource use while processing large or misconfigured delegations. The default value should only be increased in controlled environments where a remote