When a resolver+auth server has a delegation on a local zone and has a
glue, the glue can only be for in-domain NS.
In this case, when the resolver is looking at the zonecut,
`dns_view_bestzonecut()` synthesizes a delegset from an NS rdataset
found in the local zone (the delegation inside auth zone), and ignores
the glues if any.
As a result, the delegset will contain a single delegation of type
DNS_DELEGTYPE_NS_NAMES, which leads to an ADB fetch. But it's actually an
in-memory fetch, because in this case, the fetch will immediately find
the A/AAAA glues from the local zone.
An alternative approach (not chosen here) would be to make
`dns_view_bestzonecut()`, when converting an NS rdataset into a
`dns_deleg_t`, check for glues for the delegation in the auth zone, and
add those in the `dns_deleg_t`. The delegation would be of type
DNS_DELEGTYPE_NS_GLUES which would avoid the ADB name lookup.
However, that's extra code, extra logic and complexities, for a lookup
that will be done in memory anyway, just a bit later. So for now, this
is not implemented that way.
The test is added, however, to confirm that there is no attempt from the
resolver to get the NS fron the child zone.
because the cache no longer stores delegation (parent-side) NS rrsets,
and authoritative (child-side) NS rrsets don't affect recursion,
it no longer makes sense for qpcache_find() to look for NS rrsets
and return DNS_R_DELEGATION. that code has been removed.
the cache still does search for covering DNAME records. the
check_zonecut() function has been renamed to check_dname() for clarity.
related changes:
- one test case has been removed from the mirror system test, because it
tested the behavior of a cached delegation.
- query_checkrrl() and rpz_rrset_find() have been updated so they no
longer expect cache responses to have DNS_R_DELEGATION response codes.
When dumping the cache, include the contents of the delegation
database. Add a new 'rndc dumpdb -deleg' option, which dumps
the delegation database exclusively.
While the delegation database dumping format mimic the zone file format,
the API does not use the `dns_master_style_t` configuration (i.e. to
specify how many spaces/tab are used between each RR fields), because
the generic API handling this relies on databse using `dns_rdataset_t`
as internal storage format. This can be improved later.
When a referral lookup is triggered by a QMIN query, it should be
exempt from the fetches-per-zone limit just as the QMIN query itself
is.
Also restart the test server between the fetches-per-server and
fetches-per-zone tests so that leftover statistics from the former
do not pollute the latter.
Another fix is because zone spills and general query drops are no longer
in a strict >= relation (on a parent-centric resolver), so check that
both counters are non-zero instead.
The resolver now uses glue addresses from the parent side of a
zonecut without triggering an additional address lookup. Update the
test involving a nameserver target name below a DNAME so that the
delegation does not use glue.
Stop storing the NS referral into the main cache when processing a
negative response. These records are already cached in the delegation
database and are not needed elsewhere.
Update dnssec tests that relied on parent-side NS RRsets being
returned in recursive query responses.
The ADB flushtree test was failing because the test zone
(flushtest.example.) uses an in-domain nameserver with parent glue,
so the ADB cache was never populated.
Add a new zone with an out-of-domain nameserver to force an ADB
lookup and ensure the flushtree test exercises the intended code
path.
Remove the dynamic NS update loop from the delegation expiry test.
With the delegation DB, it is sufficient to wait for the delegation
to expire (after 5 seconds) and verify that names below the removed
zone return NXDOMAIN.
In 'additional', pre-cache the A RRset for ns1.rt.example so the
additional-data handling in the cache can be tested; previously this
was cached as part of resolution, but now must be queried explicitly.
In 'cookie', pre-cache an NS to prevent a QMIN query from distorting
log results and causing a test failure.
In 'resolver', increase the expected query count in the timeout test.
Remove expected queries for ns2 addresses (and ns.b.stale.) from the
qmin system test. The parent-centric resolver no longer attempts to
get the child-side NS of the delegation, so these queries do not
occur.
As named is now parent-centric, the global query count can be lower (in
particular for queries which has a long delegation chain), as the
resolver doesn't proactively resolve an NS name when a glue is provided
by the parent-side of a zonecut.
Update camp system test to lower the max-query-count of ns9, to ensure
the max-query-count limit enforcement is still working.
Add the 'minimalresponses' system test in order to cover the multiple
combinations of server 'minimal-responses' configurations and contexts
(resolver, auth only, etc.) when filling the ANSWER, AUTHORITY and
ADDITIONAL sections.
Previously, when answering from the cache, and when minimal-responses
was not set, we added the best known zone cut to the authority section
of the response message, using dns_db_findzonecut() to look it up in
the DNS cache. Since the DNS cache will no longer be used to store
parent-side NS RRsets, it will now be possible for an ancestor node
to be used as the zone cut, leading to the wrong NS record being
included.
There are various ways we could correct this:
1. Use dns_deleg_lookup() instead of dns_db_findzonecut() to find the
zone cut. But currently, the deleg database stores only the server
addresses for the delegation, not the full NS RRset; this would need
to be changed.
2. Look up <name>/NS whenever we cache a referral; that way we'll get
the child-side NS RRset and cache that, and we can retrieve it when
building the response.
But the solution chosen here is simply not to look up the NS record
when answering from the cache, effectively making "minimal-responses
yes;" mandatory for queries answered from the cache.
System tests have been updated as needed, so they no longer expect
NS RRsets in the authority section of recursive responses.
Function `dns_view_bestzonecut()` now uses the delegation DB instead of
the main cache when looking up at the cache.
As a result, replace `dns_rdataset_t` (representing an NS RRset) with
`dns_delegset_t` in `dns_view_bestzonecut()` and
`dns_resolver_createfetch()` APIs. The resolver and query processing now
use the delegation DB instead of the cache for zonecut lookups.
In the case of the delegation lives in the local database, the locally
found `rdataset` is internally converted into a `dns_delegset_t` object.
From caller POV, it doesn't change anything: a delegation set is a
read-only object which can be used as long as needed and must be
detached one it's done with it.
Create an instance of the delegation database in the view created by
delv in resolver mode. As the default `named` configuration is not in
use, also specifically configure the `max-delegation-servers` setting
using the same default value than `named`.
The resolver now caches NS records and their A/AAAA glues from referral
answers into the delegation database.
A new `cache_delegns()` function extracts NS names and associated glue
addresses from the authority/additional sections of a referral answer
and use those informations to build a delegation set, which is then
inserted into the delegation database.
The created delegation set contains a delegation per NS RR. If the NS RR
has matching A/AAAA RR, the delegation only store the addresses and not
the name. (Note this is technically possible to group all NS RR which
doesn't have glues into a single delegation, and the implementation can
be changed in that way in the future).
Each view has its own instance of the delegation database (they are
never shared between views), but a server restart/reload preserve the
delegation database state.
Flushing the name when NTA expires causes problems for the ongoing
resolving process. Do not flush the name from the cache. Instead,
the resolver should do the flushing (this is planned to be merged
next).
The prescan and main update loops in DNS UPDATE processing both used the
same counter to index the maxbytype[] quota array. The prescan loop
always incremented the counter, but the main loop had 14 continue paths
that skipped the increment. This allowed an authenticated DDNS client to
craft an UPDATE message with padding records (e.g. CNAME+A pairs that
trigger CNAME-conflict skips) to shift the counter and read wrong quota
entries, bypassing per-type record limits entirely.
Fix by incrementing the counter unconditionally at the start of each
iteration in the main loop.
Since it is impossible to increase an isc_statsmulti counter and
retrieve the new counter atomically, and we need the output of
recursclients in order to compute ns_highwater_recursive, we change the
recursclients counter to an isc_stats one.
In the current statistics counter implementation, the statistics are
backed by an array of counters, which are updated via atomic operations.
This leads to contention, especially on high core count
machines.
This commit introduces a new isc_statsmulti_t counter that keeps a
separate array per thread. These counters are then aggregated only when
statistics are queried, shifting work off the critical path.
These changes lead to a ~2% improvement in perflab.
This adds the switch +[no]cookie to delv to control the sending of
DNS COOKIE options when sending requests. The default is to send
DNS COOKIE options.
The "nsec3-delegation" test was added in a release branch, before commit
67aca1f8c6 introduced the current system
test naming convention. Rename the test to comply with that convention.
Unlike new transports with a new dependency DNS-over-QUIC support will
be added incrementally due to the non-trivial amound of plumbing
required by libngtcp2. This will require non-functional QUIC code in the
main branch that won't be exposed for non-development builds.
Therefore, libngtcp2 is linked as an optional dependency only on
explicitly enabled development builds and cannot be required. This will
be changed with a `doq` meson build option once the server-side
functionality is complete for consumption.
Commit `604d8f0b967563b0ba9dcd4f09559fdd9e21dfbe` introduced during 9.19
development cycle a check to ensure the resolver never attempts to
lookup more than 20 NS names. This limit was introduced by
`3a44097fd6c6c260765b628cd1d2c9cb7efb0b2a` as part of the CVE-2022-2795.
However, this test relies on the fact that, at the time, the NS names
were processed in a specific order in the nameserver, as this snip from
the log (from a build on `604d8f0` branch) running the test illustrates:
```
24-Mar-2026 21:19:46.346 dispatch 0x7fdaa722d200: success, length == 19956, addr = 0x7fdaa0a7c102
24-Mar-2026 21:19:46.346 dispatch 0x7fdaa722d200: got valid DNS message header, /QR 1, id 14328
24-Mar-2026 21:19:46.346 dispatch 0x7fdaa722d200: search for response in bucket 7213: success
24-Mar-2026 21:19:46.354 received packet from 10.53.0.3#5300
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 14328
;; flags: qr aa; QUESTION: 1, ANSWER: 0, AUTHORITY: 999, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 1232
; COOKIE: baf16b0241efc700
;; QUESTION SECTION:
;large-referral.example.net. IN A
;; AUTHORITY SECTION:
;large-referral.example.net. 300 IN NS ns1.fake.redirect.com.
;large-referral.example.net. 300 IN NS ns2.fake.redirect.com.
;large-referral.example.net. 300 IN NS ns3.fake.redirect.com.
;large-referral.example.net. 300 IN NS ns4.fake.redirect.com.
;large-referral.example.net. 300 IN NS ns5.fake.redirect.com.
;large-referral.example.net. 300 IN NS ns6.fake.redirect.com.
;large-referral.example.net. 300 IN NS ns7.fake.redirect.com.
;large-referral.example.net. 300 IN NS ns8.fake.redirect.com.
;large-referral.example.net. 300 IN NS ns9.fake.redirect.com.
;large-referral.example.net. 300 IN NS ns10.fake.redirect.com.
;large-referral.example.net. 300 IN NS ns11.fake.redirect.com.
;large-referral.example.net. 300 IN NS ns12.fake.redirect.com.
;large-referral.example.net. 300 IN NS ns13.fake.redirect.com.
;large-referral.example.net. 300 IN NS ns14.fake.redirect.com.
;large-referral.example.net. 300 IN NS ns15.fake.redirect.com.
;large-referral.example.net. 300 IN NS ns16.fake.redirect.com.
;large-referral.example.net. 300 IN NS ns17.fake.redirect.com.
;large-referral.example.net. 300 IN NS ns18.fake.redirect.com.
;large-referral.example.net. 300 IN NS ns19.fake.redirect.com.
;large-referral.example.net. 300 IN NS ns20.fake.redirect.com.
```
This is not true anymore, as the NS are in a random order. Moreover,
commit `3c33e7d9370006b1599e3d99c0d5fa6a6dad7979` introduced the
randomization of the selection of the NS names to lookup, which make the
test potentially unreliable, as it now doesn't mean anything to check
the nameserver does not query `ns21.fake.redirect.com.`, as it could be
the first one, or in any position form the randomized list.
Another test has been added in commit
`c67b52684f11652b07afaa75a917f6f0355dbca6` which test both the
randomization of the NS name to be looked up, as well as the upper bound
limit of NS name lookup to be done.
For all those reasons, this specific legacy check is now removed.
The -C option, introduced in BIND 9.7, caused a backward-compatible
key to be generated, using private key format version 1.2, omitting the
creation date and other timing metadata. This made it possible to
generate keys that could be loaded by older versions of BIND.
Those older versions having reached end of life many years ago, the
option can now be removed, along with the "dnssec-settime -f" option,
which caused old-style keys to be upgraded.
previously, rpz_rrset_find() behaved differently depending on whether
a cache lookup returned DNS_R_DELEGATION or ISC_R_NOTFOUND. the former
indicates the presence of a cached NS rrset, and the latter indicates
that the cache is cold or that all NS rrsets above the query name have
expired. both results indicate that the caller should recurse, but
rpz_rrset_find() only recursed in the case of DNS_R_DELEGATION.
the nsip-wait-recurse and nsdname-wait-recurse test cases in the
rpzrecurse system test were dependent on this misbehavior. the test
server was configured with a lame delegation, so that recursion always
failed, but once the lame delegation was expired due to a zero TTL, the
cache returned ISC_R_NOTFOUND, which caused the recursion not to be
attempted. the test seemed to be observing a delay before recursion
succeeded, but it was actually observing a delay before recursion was
skipped. fixing this bug caused the test to fail.
the test server has now been reconfigured so that recursion succeeds
after a delay, instead of failing. now we're able to test that
we're waiting for the successful completion of recursion.
Race rndc reconfig (toggling between allow-update and update-policy)
against a stream of DNS UPDATEs for 5 seconds and verify that named
does not crash.
Before the fix, the race between send_update() and update_action()
reading the SSU table independently could trigger an assertion
failure (INSIST) when the zone's update policy changed between the
two reads.
All system tests previously using a hyphen have been renamed to use
underscore instead. A couple of symlinks were corrected and one path in
`nsec3-answer` adjusted accordingly.
Change the convention for system test directory names to always use an
underscore rather than a hyphen. Names using underscore are valid python
package names and can be used with standard `import` facilities in
python, which allows easier code reuse.
The temporary directories for test execution and their convenience
symlinks have been switched to using hyphens rather than underscores to
keep the pytest collection, filtering and .gitignore working as
expected.
Move all LMDB-based new zone database functions from server.c into
nzd.c to reduce the size of server.c and isolate the NZD/LMDB
interface. Rename load_nzf() to nzd_load_nzf() to match the nzd_
namespace.
Now that NZF write support is gone, remove the unused nzfwriter_t
typedef and nzfwriter parameter from delete_zoneconf(). Remove the
bool locked parameter and simplify the locking in do_modzone() and
rmzone() to unconditional lock/unlock pairs.
Drop the NZF (New Zone File) fallback for persisting runtime zone
configurations, making LMDB (NZD) the only storage backend. This
removes all #ifdef HAVE_LMDB conditionals, the meson 'lmdb' option,
and the NZF-related functions. LMDB is now a mandatory build
dependency.
The named-nzd2nzf tool is now always built.
If we are modifiying the zone, the zone must have been added before.
Don't overwrite this value on modifications.
Also it feels cleaner to pass added=false to configure_zone() in
do_modzone().
Some code paths try to lock an already locked view->newzone.lock.
For example, do_modzone() aqcuires the lock and then calls
delete_zoneconf(), that wants to acquire the same lock.
Add a parameter to delete_zoneconf() that informs the function if the
lock has already been acquired.