With the parent-centric resolver, dns_view_bestzonecut() consults the
delegation DB (view->deleg) rather than the main cache for the closest
zonecut. Root is never the target of a referral, so it never lands in
delegdb; bestzonecut therefore falls through to the hints lookup on
every query whose closest ancestor is root. prime_done() only called
dns_root_checkhints(), which logs discrepancies but does not update
any store bestzonecut looks at, so the fresh root NS records obtained
by priming were never used and priming kept re-firing.
Rename view->hints to view->rootdb and refresh it when a priming
fetch completes: the '.' NS rdataset is replaced with the fetched
one, and for each listed nameserver the matching A/AAAA glue is
copied from the response's ADDITIONAL section. Only glue for names
that actually appear as NS targets is accepted, so a hostile response
cannot inject unrelated records. Glue the response did not carry is
left untouched, so the hints-file records loaded at startup remain as
a fallback.
Each view gets its own rootdb: the previous shared
named_g_server->in_roothints is gone, and configure_view() calls
dns_rootns_create() per view when the class-IN defaults are needed.
That keeps the priming writer one-per-DB, so concurrent priming in
different views cannot race on the same zone-DB version.
The rootdb refresh runs synchronously from the resolver response path,
so records go straight from the wire into rootdb with no cache round
trip and no dependency on DNSSEC validation state. A new
DNS_FETCHOPT_PRIMING option marks the priming fetch; prime_done()
itself is now pure cleanup.
Track the rootdb freshness window in view->rootdb_expires and trigger
re-priming lazily from dns_view_find() and bestzonecut_rootdb() only
when the window has elapsed. Stale records are still served while the
fresh priming fetch is in flight.
Drop dns_root_checkhints() and its helpers; the rootdb is now the
authoritative source the resolver consults.
The filename of the catalog member zones are generated dynamically
based on the zone's name. If the zone's name is too long or if it
contains special characters the name's digest is used instead.
Since '%' and '$' are now treated as special characters in the zone
names (see !10779), add these characters to the list of the special
characters.
The setfilename() function uses case-insensitive strcasestr() when
matching the possible tokens, but then one of the token parsers
uses case-sensitive INSIST checks which can assert when, for example,
matching '%X' and INSIST only accepts '%x'.
The case-insensitivity is documented, which means it's the parser
that needs to be fixed, not the matcher.
Convert the character to lowercase before checking the token's
validity.
The `DNS_DBFIND_NOEXACT` flag name is ambiguous, as it does not clearly
indicate the lookup behavior (e.g., sibling, child, or parent).
Rename it to `DNS_DBFIND_ABOVE` to better reflect that the lookup
targets a closer ancestor name.
Expired delegation nodes are naturally replaced when the resolver
fetches fresh data, and any remaining stale nodes are reclaimed by
SIEVE eviction under memory pressure.
delegdb_cleanup() was overwriting the caller-supplied 'requested'
value with (hiwater - lowater), so every overmem cleanup tried to
free the full watermark band regardless of how much memory the new
delegation actually needed. Drop the override so the caller's size
is used: we now walk the SIEVE only until we have reclaimed enough
room for the new node, leaving unrelated entries in place.
dns_delegset_fromnsrdataset() used isc_g_mctx for the transient
delegset it builds from a DNS NS rdataset. That hides delegation
data in the global default context instead of accounting it against
the subsystem that owns it: a resolver fctx, a view, or a query
context.
Take an explicit mctx parameter so callers can direct the allocation
to the right place, and update the three call sites:
- lib/dns/view.c:1189 (dns_view_bestzonecut fallback) uses view->mctx
- lib/dns/resolver.c:7071 (resume_dslookup) uses fctx->mctx
- lib/ns/query.c:8672 (query_delegation_recurse) uses the client
manager's mctx
Also tighten delegdb cleanup to run inside the same write transaction
as the insert: delegdb_node_prepare() now returns the size of the new
node, and delegdb_cleanup() takes the caller's open qp so that the
overmem reclamation and the insert share one commit instead of doing
two nested write transactions.
dns__deleg_lookup() with DNS_DBFIND_NOEXACT is supposed to return
the deepest proper ancestor of the lookup name. It called
getparentnode() to step up from an exact match, but getparentnode()
only iterated while the chain length was >= 2. When the chain
contained a single entry (the exact match itself with no ancestor
stored in the trie), the loop did not execute and left the caller
looking at the exact match. The subsequent isactive() check then
returned success and the function reported the exact match as the
"deepest ancestor", violating NOEXACT semantics.
This was observable as the resolver picking the child-side
delegation for an at-parent type (e.g. a DS query for a TLD), then
sending the query to the child's own nameservers and recovering via
the "chase DS servers" path.
Have getparentnode() set '*node' to NULL when it cannot find an
active proper ancestor, and make dns__deleg_lookup() NULL-check
before returning, matching the canonical NOEXACT implementation in
dns_zt_find(). Update the deleg unit test to expect NOTFOUND for
the top-level-no-parent case.
When the validator needs a DS RRset and the cache does not have it,
get_dsset() falls back to creating a fresh fetch. Without a hint, the
resolver picks the closest known zone cut for the DS query, and in the
parent-centric resolver that can land on a delegation at the DS owner
name itself (the child side). This can happens when the parent
delegation is expired, or if the zonecut of the parent doesn't match the
labels in the name.
Querying the child for its own DS records yields NODATA from the apex of
the zone, which sends the resolver into the "chase DS servers" recovery
path and costs two extra round trips for a parent delegation we already
had cached in the delegation database.
Look up the parent zone in the delegation database before kicking
off the fetch, and pass any usable delegation to the resolver as a
hint. When the hint is present, the resolver sends the DS query
straight to the parent's nameservers and the chase path is avoided
entirely.
To support this, create_fetch() now takes optional 'domain' and
'delegset' parameters that are forwarded to dns_resolver_createfetch().
All other call sites pass NULL.
When a zone filename is defined in named.conf which will be
written to by the server - i.e., secondary or dynamically updated
zones - there is a test at configuration time to ensure that the
filename is non-unique.
This test is run before the zone is actually created, so a zone
configured using a template may not have had its filename expanded
yet. This can cause a configuration to fail because, for example,
multiple zones appear to using the filename "$name.db".
This has been fixed by calling dns_zone_expandzonefile() from
isccfg_check_zoneconf(), to expand the names when checking for
uniqueness.
This adds a new API call dns_zone_expandzonefie(), which will enable
named-checkconf to expand filenames the same way the server does in
dns_zone_setfile().
When processing a catalog zone member's primaries definition and
there is a TXT record containing an invalid name TSIG key name,
dns_name_free was incorrectly called triggering an assertion.
This has been fixed.
Move disptype and transport into dispatch_hash() and dispatch_match()
so that the match function is the single source of truth for whether
two TCP dispatches are interchangeable. This replaces the post-loop
disptype filter in dispatch_gettcp() and makes the disptype field in
struct dispatch_key actually used.
TCP dispentries no longer use the global QID hash table at all.
Responses are matched by scanning disp->active, and sequential
per-dispatch IDs (bounded by the pipelining limit) are unique
within a single dispatch by construction. Since TCP delivers
only data we asked for on a specific connection, the per-peer
uniqueness that the global table enforced was never actually
needed for TCP.
DNS_DISPATCHOPT_FIXEDID is plumbed through dns_request_createraw
-> get_dispatch -> dns_dispatch_createtcp so FIXEDID TCP requests
always get a fresh isolated dispatch — the caller-supplied ID
then cannot collide with any other in-flight query either.
Cap the number of in-flight queries on a single shared TCP dispatch.
When the limit is reached, the dispatch is removed from the hash
table so subsequent queries get a fresh connection. The existing
dispatch continues serving its queries until they complete.
This bounds the blast radius of a connection drop: at most N queries
fail simultaneously instead of all queries to that server.
The default limit is 256. It can be overridden for testing via
'named -T tcppipelining=N'.
Previously, the user of dns_dispatch API had to first call
dns_dispatch_gettcp() and if that failed create a new TCP dispatch with
dns_dispatch_createtcp(). This has been changed and the TCP connection
reuse happens transparently inside dns_dispatch_createtcp(). There are
separate buckets for dns_resolver, dns_request and dns_xfrin units, so
these don't get mixed together.
Enable jemalloc background threads and reduce dirty page decay time from
10s to 1s so that unused memory is returned to the OS sooner. As an
additional safety net, trigger a decay pass after every 16 MiB of frees
(rate-limited to once per second) to handle bursts that the background
thread might not catch in time. On glibc, fall back to malloc_trim(0)
with the same volume-based trigger.
Move the following functions to the zoneproperties source files, as
they are simple get functions:
- dns_zone_getgluecachestats
- dns_zone_getkeystores
- dns_zone_getrequesttransporttype
In order to make zone.c more readable, we are splitting it up in
separate source files. This moves the zonemgr to its own file
("zonemgr.c").
Since this code accesses the zone structure directly, move the
'struct dns_zonemgr' and its prerequisites to "zone_p.h".
The helper functions 'forward_cancel()', 'zone_xfrdone()',
'zmgr_start_xfrin_ifquota()', and 'zmgr_resume_xfrs() need to be
internally accessible to both source files.
Note: This commit does not compile.
The name 'isdelegation()' was confusing. This function is not checking
whether this message is a delegation, but whether the denial of
existence proofs in this message is a proof of a referral to an
unsigned zone.
The name 'is_unsecure_referral()' is more appropriate.
The isdelegation() was changed to return an isc_result_t because the
idea was to have a separate return value DNS_R_NSEC3ITERRANGE to signal
to the caller we could not verify the proof because of too many
iterations in the NSEC3 record, or perhaps ISC_R_UNEXPECTED for a more
generic cause that verification was not done.
But this would make error handling more fragile and all we care about
is whether we can reliably say the NS bit was not set.
If we can not reliably say so, we have to treat it as an insecure
referrral.
Since the answer is either yes or no, we can revert back to returning
a boolean value.
Replicating CI failures requires the developer to piece together the
sanitizer flags by hand, reducing ergonomics.
Fix this problem by embedding the relevant settings to the executables.
Symbol resolution still needs manual intervention by setting the env
variable `*SAN_SYMBOLIZER_PATH`. However, this doesn't affect any behavior.
When the xfrin_recv_done() function decides to retry the transfer
using AXFR because of a previous error, it calls the xfrin_reset()
function which calls dns_db_closeversion() on 'xfr->ver'. The problem
is that the ixfr processing of a previous message could be still
in process in a worker thread, which then can use freed 'xfr->ver'.
If there is an ongoing worker thread delay the AXFR retry until after
the worker thread has finished its work.
When receiving NSEC records via IXFR, the node was not marked with
havensec because the condition checked the uninitialized output
rdataset type instead of the input rdataset type. This caused
queries for empty non-terminal names in NSEC-signed zones received
via IXFR to return the zone apex NSEC instead of the correct
covering NSEC record.
The bug was introduced in f4b4f030.
Replace the separate pointer+length field pairs in dns_rdata_nsec3_t
(salt/salt_length, next/next_length, typebits/len) and
dns_rdata_nsec3param_t (salt/salt_length) with isc_region_t. This
makes the structs self-describing and eliminates a class of
length-mismatch bugs.
The dns_zone_setnsec3param() signature is updated to take
isc_region_t *salt instead of separate saltlen and salt arguments.
Function signatures for dns_nsec3_addnsec3, dns_db_getnsec3parameters,
and related internal functions still use separate pointer+length pairs
and should be updated in a follow-up.
dns_zone_getloadtime(), dns_zone_getexpiretime(),
dns_zone_getrefreshtime(), and dns_zone_getrefreshkeytime()
cannot fail, so return void instead of ISC_R_SUCCESS.
The caller is supposed to hold the zone lock for 'inline_raw()' and
'inline_secure()', but when adding 'REQUIRE(LOCKED_ZONE(zone));' to
these functions it turned out to be not always the case.
In order to make zone.c more readable, we are splitting it up in
separate source files. This moves the set and get functions to its
own file ("zoneproperties.c").
Since this code accesses the zone structure directly, move the
'struct dns_zone' and its prerequisites to "zone_p.h".
The helper functions 'inline_raw()', 'inline_secure()',
'dns_zone_setview_helper()', 'zone_settimer(), 'set_resigntime()', and
'zone_freedbargs()' need to be internally accessible to both source
files.
A few set/get functions remain in zone.c for now:
- dns_zone_getserial
- dns_zone_getversion
- dns_zone_setviewcommit
- dns_zone_setviewrevert
- dns_zone_get_rpz_num
- dns_zone_set_parentcatz
- dns_zone_get_parentcatz
- dns_zone_setrawdata
- dns_zone_setskr
- dns_zone_getskrbundle
- dns_zone_setnsec3param
- dns_zone_setoption
- dns_zone_getoptions
- dns_zone_getrequesttransporttype
- dns_zone_getredirecttype
- dns__zone_getnotifyctx
- dns_zone_getgluecachestats
- dns_zone_setplugins
- dns_zone_setserial
- dns_zone_getxfr
- dns_zone_getkeystores
When `dns_view_bestzonecut()` is called with a NULL `delegsetp`, it
calls `bestzonecut_zone()` with a NULL `rdataset` pointer but there is a
non-guarded de-reference of the `rdataset` pointer in
`bestzonecut_zone()`.
In practice, the only current situation where `dns_view_bestzonecut()`
is called with NULL `delegsetp` is from a case of `seek_ds()` _and_ the
non-guarded dereference occurs only if there is a static-stub local
zone matching the zonecut `seek_ds()` is looking for. It's unclear if
such flow is actually possible.
The `rdataset` is now always valid inside `dns_view_bestzonecut()`. (It
was initially set only if `delegsetp` was set to avoid extra works in
the qpzone, which can be skipped when `rdataset` is NULL, but this
doesn't really make a difference, considering we are in a slow path
considering the result wasn't found in this case.)
The local variable `zfname` was released in the cleanup part of the
function if not NULL, but it turns out it is now always NULL at that
point.
The flow can get to that part only in two cases: either `zfname` is not
NULL, and then it's ownership is moved to a different variable (thus, it
is now NULL), or `zfname` is already NULL.
Removing the bit of deadcode releasing it.
After gss_accept_sec_context() succeeds, the GSS context is passed
to dst_key_fromgssapi() which transfers ownership to the dst_key.
If a subsequent operation fails (dst_key_fromgssapi itself,
dns_tsigkey_createfromkey, or dns_tsigkeyring_add), the cleanup
label frees the dst_key but only if it was created. If the failure
happened before dst_key_fromgssapi, the GSS context was orphaned.
Delete the GSS context in the cleanup path when it was not
transferred to a dst_key.
When gss_accept_sec_context() completes successfully but
gss_display_name() returns an empty principal, the GSS context
was leaked — it was neither stored in a key nor deleted.
Delete the context and reject with BADKEY in this case. This
should only occur due to a GSS library bug, since a completed
context should always have a valid principal.
Use pre-increment (++ring->generated) instead of post-increment
(ring->generated++) so the comparison against DNS_TSIG_MAXGENERATEDKEYS
happens after counting the new key. With post-increment, one extra key
beyond the limit was allowed before eviction kicked in.
Check for existing non-expired TSIG keys before accepting a new
GSS-API negotiation. Per RFC 3645 Section 4.1.1:
- If a key exists and has not expired, reject with BADNAME
- If a key exists but has expired, delete it and start fresh
Previously, an expired GSS key would permanently block
re-negotiation for that name until the server was restarted.
Use BADKEY rather than BADNAME to avoid creating an oracle for
key name enumeration by unauthenticated attackers.
The dns_rdatavec_subtractrdataset function would copy the old header
using memmove but the old header includes fields such as trust and
reference counts that are atomic.
While the values of those fields were never used, it did cause a benign
race condition. This commit refactors dns_rdatavec_subtractrdataset and
dns_rdatavec_merge not to use memmove.
Now that we track the references at the vecheader level, binding an
rdataset is no longer guaranteed to keep its node alive. Therefore
remove the node pointer from the rdataset, and instead decide whether
glue is required by explicitely passing the owner name to addglue.