With cache_enable = true, one cache entry was shared by every client
asking the same name against the same upstream: the cache key
({Qtype, Qclass, Name, Upstream}) and the osResolver hot-cache/singleflight
key ("name:qtype:") both ignored the EDNS Client Subnet (ECS). A response
tailored for subnet A was therefore served to subnet B.
A cached answer's records are scoped to the network that generated them
(RFC 7871 §7.3), so sharing them across subnets returns the wrong
CDN/policy answer. Rewriting only the ECS option on the shared answer is
worse: forwarders that validate the echoed ECS (e.g. dnsmasq with
add-subnet) then accept the wrong-subnet answer instead of rejecting it as
a mismatch.
Partition both cache paths by a canonical ECS tuple (family, source-prefix,
masked address) via the new dnscache.CanonicalECS: the LRU key gains an ECS
field and the singleflight/hot-cache key appends the canonical ECS. Same
subnet still shares an entry; different subnets (or address families) never
do. Only a request with no ECS option collapses to the shared empty
partition; a carried /0 keeps its own family-scoped token, since it is
forwarded with an ECS option and must stay distinguishable from a no-ECS
query (RFC 7871 §7.3.1). SetCacheReply no longer touches ECS and only
reconciles the EDNS Cookie.
Adds real cache-path regression tests (LRU and osResolver hot cache) that
serve a different A record per subnet and verify the second subnet never
receives the first's record.
Fixes https://github.com/Control-D-Inc/ctrld/issues/324
countHandler incremented its call counter after w.WriteMsg, but the DNS
client returns as soon as it receives the reply. A test reading the
counter right after Resolve returned could therefore observe a stale
zero, e.g. Test_Edns0_CacheReply intermittently failing on CI with
"cache not hit, server was called: 0" while passing on retry.
Increment the counter before writing the reply so it is guaranteed
visible once the client has the response. Verified by widening the
post-write window to reproduce the failure deterministically, then
confirming the reordered handler passes 500x and under -race.
- Add UpstreamConfig.VerifyMsg() method with proper EDNS0 support
- Replace hardcoded DNS messages in health checks with standardized verification method
- Set EDNS0 buffer size to 4096 bytes to handle large DNS responses
- Add test case for legacy resolver with extensive extra sections
For cached or singleflight messages, the edns0 cookie is currently
shared among all of them, causing mismatch cookie warning from clients.
The ctrld proxy should re-set client cookies for each request
separately, even though they use the same shared answer.
To guard ctrld from possible DoS to remote upstreams, this commit
implements following things:
- Optimizing multiple queries with the same domain and qtype to use
singleflight group, so there's only 1 query to remote upstreams at
any time.
- Adding a hot cache with 1 second TTL, so repeated queries will re-use
the result from cache if existed, preventing unnecessary requests to
remote upstreams.
os resolver debugging improvement
use first public non success answer when no LAN nameservers exist
use first public non success answer when no LAN nameservers exist
fix the os resolver test
fix test
use upstreamIS var
init map, fix watcher flag
attempt to detect network changes
attempt to detect network changes
cancel and rerun reinitializeOSResolver
cancel and rerun reinitializeOSResolver
cancel and rerun reinitializeOSResolver
ignore invalid inferaces
ignore invalid inferaces
allow OS resolver upstream to fail
dont wait for dnsWait group on reinit, check for active interfaces to trigger reinit
fix unused var
simpler active iface check, debug logs
dont spam network service name patching on Mac
dont wait for os resolver nameserver testing
remove test for osresovlers for now
async nameserver testing
remove unused test
With new leaking queries features, the initialization of OS resolver can
now lead to data race if queries are resolving while re-initialization
happens.
To fix it, using an atomic pointer to store list of nameservers which
were initialized, making read/write to the list concurrently safe.
Since the OS resolver only returns response with NOERROR first, it's
safe to use ControlD public DNS in parallel with system DNS. Local
domains would resolve only though local resolvers, because public ones
will return NXDOMAIN response.
For os resolver, ctrld queries against all servers concurrently, and get
the first success result back. However, if all server failed, the result
channel is not closed, causing ctrld hang.
Fixing this by closing the result channel once getting back all response
from servers.
While at it, also shorten the backoff time when waiting for network up,
ctrld should serve as fast as possible after network is available.
Updates #34