* plugin/cache: do not cache SOA-less NODATA responses
An upstream may return NOERROR with a non-empty answer that still does not
resolve the question and without an SOA record to bound a negative TTL: a
CNAME chain that does not terminate in a record of the queried type at the
chain's terminal name (an incomplete recursion result from a forwarder).
Because the answer section is non-empty, response.Typify classifies it as
NoError (positive), so the cache plugin stores it keyed on <qname,qtype> and
replays the non-answer to clients until the TTL expires.
Per RFC 2308 section 5, negative responses without an SOA record SHOULD NOT be
cached. Following RFC 1034 section 3.6.2 and RFC 2308 sections 1 and 2.2, the
effective owner name is the target at the end of the CNAME chain, and the
response is NODATA unless it carries the queried type at that terminal name;
this holds for every query type, not just A/AAAA. Skip caching such a response
(mirroring the existing NameError && !hasSOA guard) and let the next query be
resolved upstream again.
An empty answer section is deliberately left cacheable: it is indistinguishable
from a legitimate NOERROR positive response that carries its data outside the
answer section (for example the whoami plugin).
Refs coredns#6958, coredns#5077, coredns#4987.
Signed-off-by: Nitin Nizhawan <nitin.nizhawan@gmail.com>
* plugin/cache: fail closed on malformed CNAME chains in isNODATA
canonicalName now returns a validity flag and rejects chains that are not a
single unambiguous path to a terminal name: an owner with more than one
distinct CNAME target (RFC 2181 section 10.1) and a revisited owner / CNAME
loop (RFC 1034 section 3.6.2). isNODATA treats an invalid chain as a
non-answer, so a SOA-less response with such a chain is not cached. This makes
the classification order-independent (previously the first CNAME per owner
won, so a two-target owner was cacheable or not depending on wire order) and
closes the loop-with-co-located-record case. Duplicate CNAME records naming
the same target are still tolerated.
Adds regression tests for the two-distinct-targets case in both orders, the
CNAME loop with a co-located A, and the tolerated duplicate-identical-CNAME
case.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d59b4564-a9df-425f-858e-aadee0f35581
Signed-off-by: Nitin Nizhawan <nitin.nizhawan@gmail.com>
* plugin/cache: split canonicalName into self-documenting helpers
Extract the per-owner CNAME lookup into uniqueCNAMETarget and loop detection
into a small case-insensitive nameSet type, leaving canonicalName as a short
driver. Signature and algorithm are unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d59b4564-a9df-425f-858e-aadee0f35581
Signed-off-by: Nitin Nizhawan <nitin.nizhawan@gmail.com>
---------
Signed-off-by: Nitin Nizhawan <nitin.nizhawan@gmail.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d59b4564-a9df-425f-858e-aadee0f35581
Keep cache timestamps and TTL calculations on the time values returned by the cache clock. Converting them with UTC strips Go's monotonic clock reading and can extend cached entries when the wall clock moves backward.
Add a regression test that verifies new cache items retain the original monotonic timestamp.
Fixes#5478.
Signed-off-by: houyuwushang <liuluoqianqiu@outlook.com>
Detect zone cuts while following CNAME and DNAME targets so partial answers carry the child NS and glue records instead of returning SERVFAIL or apex authority. Keep AA set for authoritative alias partial answers while clearing it for referral-only responses.
Fixes#6405Fixes#6627
Signed-off-by: houyuwushang <liuluoqianqiu@outlook.com>
* plugin/shed: add UDP overload protection plugin
UDP responses written back through one listener socket serialize on the
Go runtime's internal fdMutex, which allows at most 2^20-1 concurrent
operations per file descriptor and panics the process when exceeded.
CoreDNS serves UDP with one goroutine per query, all writing through the
shared packet connection, so a sustained overload parks every excess
in-flight query in that wait queue until the process dies with
"too many concurrent operations on a single file or socket". Observed
in production: ~2.8M goroutines and 60GiB RSS before the panic.
The shed plugin makes the panic structurally unreachable. It installs,
via Config.UDPDecorateWriterFunc, a per-socket bounded evict-oldest
stack drained newest-first by a single writer goroutine, so the fd
never sees more than one writer and residual capacity under overload
always goes to the freshest response. While a socket's stack is full,
arriving queries are dropped before any plugin runs. Drops are silent
(the client's resolver retries elsewhere) and counted in
coredns_shed_dropped_total{server, reason}.
plugin/shed/fdmutex_test.go demonstrates the failure and the fix with
one shared flood harness. Two subprocess tests reproduce the exact
runtime panic without the plugin's write discipline - one deterministic
(a held write plus >2^20 queued writers), one with nothing held or
mocked; both exercise the Go runtime rather than the plugin, so they
are gated behind SHED_FLOOD_TEST=1. The counterfactual - the same load
through the plugin's stack, completing with every response accounted
for as written or dropped - runs in every test invocation, including
-race, at 50k responders, and at the full 1.5M with SHED_FLOOD_TEST=1:
SHED_FLOOD_TEST=1 go test ./plugin/shed/
Signed-off-by: Ryan Brewster <rpb@anthropic.com>
* test: add shed e2e test
Query a shed-enabled server over UDP (the plugin's deferred
single-writer path) and TCP (which shed passes through), and check
that coredns_shed_dropped_total is exported with its reason label.
No-Verification-Needed: test-only change
Signed-off-by: Ryan Brewster <rpb@anthropic.com>
---------
Signed-off-by: Ryan Brewster <rpb@anthropic.com>
* plugin/file: resolve each additional section target only once
additionalProcessing walked the answer and appended each target's address records without keeping track of the targets it had already resolved. An answer holding several records that point at one target, two MX records differing only in preference for instance, therefore repeated that target's A, AAAA and RRSIGs once per record. SRV, SVCB and HTTPS take the same path and behaved the same way. plugin/backend_lookup.go already skips targets it has seen; the file plugin never did.
Targets are compared in canonical form because SRV targets are not lowercased on insert, so one target can reach additional processing spelled two ways while the zone's tree still matches it case-insensitively. The map is allocated on first use, as additionalProcessing runs for every answer and most carry no target at all.
Fixes#6626
Signed-off-by: baltasarblanco <baltablanco9008@gmail.com>
* plugin/file: move additional section tests to lookup_test.go
The cases exercise additionalProcessing, which lives in lookup.go, so they belong with the rest of the lookup tests. No test logic is changed.
Addresses review feedback.
Signed-off-by: baltasarblanco <baltablanco9008@gmail.com>
---------
Signed-off-by: baltasarblanco <baltablanco9008@gmail.com>
The warning added in #8216 passed the skipped file where the kept file
belongs and vice versa, so it logged "using <skipped> instead of <kept>"
— the reverse of what Walk actually does. Swap the arguments (and use
cleanPath for both) so the message names the file that is being used.
Strengthen TestWalkWarnsForDuplicateOrigin to assert the direction; it
only checked that both names appeared, so the inversion passed before.
Signed-off-by: maximilize <3752128+maximilize@users.noreply.github.com>
Make mtime reload tests tolerate slow scheduling and coarse filesystem
timestamp resolution. This keeps immediate rewrites observable and
avoids checking zone contents before the reload completes.
Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>
Plugin package tests previously ran only on Linux, so Windows-
specific failures were never caught. Run them in CI and make the
affected tests portable across platforms.
Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>
This PR fixes the forward plugin incorrectly retrying deterministic
local DNS message serialization failures as if they were upstream transport errors.
Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
Directory permission checks can be bypassed by privileged users and may
behave differently on filesystems with nonstandard permission semantics.
Make the bad-path case deterministic so TLS keylog tests remain portable
across supported environments.
Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>
DoH, DoQ, and DNS-over-gRPC unpack messages without the acceptance
checks used by UDP and TCP. An unauthenticated request with a large
QDCOUNT can therefore force excessive allocations while names are
decoded and exhaust server memory.
Enforce the same request policy across all server transports.
Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>
* plugin/proxyproto: Apply an explicitly configured default policy even when no allow list is present.
This PR fix the issue where explicitly configured default reject policy
is ignoreed when no allo list is present
Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
* golint fix
Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
---------
Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
* plugin/forward: Fix incorrect failover counter reset
This PR fixes the isseue where resetting the failover counter caused
retry the same upstreams until timeout instead of stopping after one pass.
Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
* golint fix
Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
---------
Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
A query for a name that sits below a closer empty non-terminal was wrongly
answered with a shallower wildcard record instead of NXDOMAIN. Per RFC 4592
2.2.1 a wildcard is the source of synthesis only when the wildcard owner's
parent is the closest encloser of the queried name; if an empty non-terminal
exists between that parent and the queried name, it is the closer encloser and
the shallower wildcard must not be expanded.
Guard the wildcard expansion in Zone.Lookup with closerENTExists, which walks
the strict ancestors of the queried name between the wildcard parent and the
name and reports whether any of them is an empty non-terminal.
Adds TestLookupWildcardRespectsCloserEmptyNonTerminal, which asserts NXDOMAIN
for a name below a closer empty non-terminal and keeps a no-regression case
where a plain wildcard with no closer empty non-terminal still applies.
Signed-off-by: Omkhar Arasaratnam <omkhar@linkedin.com>
Co-authored-by: Omkhar Arasaratnam <omkhar@linkedin.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Several tests slept a fixed duration then took a single snapshot of an
async result, racing whatever they waited on:
- forward health tests waited 20ms for the health-check goroutine to bump
an atomic counter,
- the auto plugin tests (dns + metrics) waited 50-110ms for a file-watch
reload to be picked up,
- the file ZoneReload test waited 30ms (self-described as could still be
racy) for a reload,
- the overloaded health test slept 1s for its background goroutine to fire
its first request.
Replace each with a bounded poll of the actual condition (the atomic
counter, a dns.Exchange response, a metrics scrape, z.ApexIfDefined, or a
channel signalled by the test's own handler) so they pass as soon as the
awaited state is reached and no longer flake when it is slower than the
fixed wait. Test-only.
Signed-off-by: Nikolaus Schuetz <nikolauspschuetz@gmail.com>
listener.Dnstap holds clientsMu.RLock() while iterating connected sink
clients. In the flush-error branch it called removeClient(c) synchronously,
but removeClient takes clientsMu.Lock(). A sync.RWMutex is not reentrant, so
the goroutine blocks forever waiting to acquire the write lock it can never
get while holding the read lock. The queued Lock() then blocks every
subsequent Dnstap broadcast and close(), and the goroutine leaks.
A flush error is the normal failure mode for a slow or disconnected sink
client (writeMsg buffers into framestream and succeeds; flush does the real
socket write and fails), so a single misbehaving client wedged the whole
listen path. Because Dnstap runs inline in the request-serving goroutine via
TapMessageWithMetadata, this could cascade into stalled request handling.
The write-error branch one line up already offloaded with `go removeClient(c)`.
Do the same in the flush-error branch and drop the early return so the
broadcast still reaches the remaining clients.
Assisted-by: Claude Opus 4.8
Signed-off-by: Pavel Lazureykis <pavel@lazureykis.dev>
LookupStaticHostV4/V6 dereferenced h.hmap and h.inline as call arguments,
which are evaluated before lookupStaticHostFamily takes the read lock. The
reload path (readHosts) swaps h.hmap under h.Lock() on every reload, so the
field read raced the swap for every A/AAAA lookup. This was introduced when
wildcard support (#8185) refactored these methods to pass the maps as
parameters; LookupStaticAddr still reads the fields inside the lock and was
unaffected.
Read h.hmap/h.inline inside the RLock by selecting the address family with a
bool instead of passing pre-dereferenced maps.
Also read h.mtime under the existing RLock in readHosts: it was read without a
lock while the reload writes it under h.Lock(), and readHosts runs from both
the OnStartup handler and the reload ticker goroutine.
Both races are confirmed by go test -race.
Signed-off-by: Pavel Lazureykis <pavel@lazureykis.dev>
* plugin/file: run additional processing for wildcard answers
The wildcard branch of Zone.Lookup returned a nil additional section, so a
wildcard-synthesized MX/SRV/SVCB/HTTPS answer with an in-bailiwick target
did not include the target's A/AAAA glue. The non-wildcard path already
does this, so call additionalProcessing in the wildcard branch as well.
Fixes#6629
Signed-off-by: Salih Muhammed <root@lr0.org>
* plugin/file: move wildcard additional test into wildcard_test.go
Requested in review: keep the wildcard tests in one file.
Signed-off-by: Salih Muhammed <root@lr0.org>
---------
Signed-off-by: Salih Muhammed <root@lr0.org>
Remove fixed TLS 1.2 cipher suite list and maximum TLS version so
crypto/tls can use its maintained defaults. Keep TLS 1.2 as the
minimum supported version.
Document the shared TLS default behavior for plugins that expose TLS
configuration and add coverage to ensure CoreDNS leaves Go-managed
TLS fields unset.
Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>