Minor Release
This contains security hardening and bug fixes.
Security
- Bumped
golang.org/x/textto v0.40.0 to address GO-2026-5970 - Discarded upstream answers whose question does not echo the request - defense in depth against cache poisoning. A compromised or misbehaving upstream could return an answer for a different name (e.g. records for
attacker.examplein response to a query forvictim.example), which would be cached under the legitimate request key and served to later queries. The answer is now validated against the request's question (case-insensitive name plus Qtype/Qclass, per RFC 1035 §4.1.2) before it is served or cached; a mismatch is logged at debug level and the upstream is skipped, failing safe to the next upstream or SERVFAIL
Fixed
- Partitioned the DNS cache by EDNS Client Subnet (ECS). With
cache_enable = true, one cache entry was shared by every client asking the same name against the same upstream, so a response tailored for subnet A could be served to subnet B (returning the wrong CDN/policy answer, per RFC 7871 §7.3). Both cache paths are now keyed by a canonical ECS tuple so different subnets never share an entry, while same-subnet queries still share one - Fixed the DoQ resolver corrupting upstream responses by calling
SetReplyon the already-unpacked answer, which forced the RCODE to NOERROR and overwrote the Question section. This masked upstream failures from the proxy's failover logic (a SERVFAIL looked like a successful empty response) and corrupted the Question served to clients. Only the downstream transaction ID is now restored, preserving the upstream RCODE, Question, and answers - matching the DoH and DoT resolvers - Treated the 464XLAT CLAT source range as local rather than WAN. On IPv6-only cellular carriers and iPhone hotspots, local DNS queries can arrive with a source in the RFC 7335 IPv4 Service Continuity Prefix (192.0.0.0/29), which was classified as a WAN client and refused when
allow_wan_clientswas unset (the default), breaking DNS resolution on the affected connection. The 192.0.0.0/29 range is now recognized as local - Preserved the fallback listener port across config reload in macOS DNS-intercept mode. When mDNSResponder owns
*:53, ctrld falls back to127.0.0.1:5354, but an hourly CD config reload reverted the in-memory port to 53 while the listener stayed on 5354, causing an endless watchdog "force reload" loop anddigtimeouts. The actual bound listener IP/port is now restored into the in-memory config after reload - Skipped internal logging in
--silentmode. In cd mode,--silentstill created and grew a log file and reset the global log level back to debug, overriding the level--silenthad set. ctrld now neither creates the internal log file nor writes debug logs when silent is set - Restored system DNS during an invalid-device uninstall, so DNS is no longer left pointing at the stopped listener when uninstalling with an invalid device