Repository navigation
v1.8.3
The correctness release. The cache now serves only what it can vouch for: a denial lives exactly as long as its zone says, a signature that has lapsed or has not yet begun is never handed out from cache, and a client that did not ask for DNSSEC receives none of it. Two cold-cache validation failures that answered SERVFAIL on a freshly started resolver are fixed, ANY is declined the same way on every path, and RPZ feeds in the shape most vendors publish now load. Recommended for all deployments, and especially for validating ones.
The cache serves only what it vouches for, and a denial's TTL is a ceiling (#612)
RFC 2308 says how long a resolver may cache a denial: the smaller of the SOA's TTL and its MINIMUM field. sdns lifted anything shorter to five seconds. A zone that publishes a one-second negative TTL now gets one second, and a denial with no lifetime, or with no SOA at all, is not cached.
Taking that floor away exposed how the cache judged signatures, and the rest of this section is the rule every change below enforces: the cache serves only what it vouches for, at a TTL it can honour, with no DNSSEC record a DO=0 client did not ask for.
- A signature counts only inside its validity period, at both ends (RFC 4035 §5.3.1). One whose inception has not arrived no longer keeps an RRset alive.
- Signatures are grouped per RRset: section, owner, signer, class and covered type must all agree, and names compare as DNS names, escapes decoded and case folded over ASCII only. During a key rollover the outgoing key's lapsed signature no longer condemns an RRset the incoming key still covers, and a signature from one zone at a delegation no longer vouches for the other zone's records.
- An entry stores only the signatures it vouches for. A lapsed or pending signature goes out in the first response as the authority sent it and is absent from every hit. It used to come back on hits with the entry's TTL, a zero returned as an hour.
- A signed RRset in the additional section is served complete or not at all, kept only when its signatures and its own received TTL cover the entry's lifetime. The additional section bounds neither the lifetime nor the AD bit (RFC 4035 §3.2.3).
- An explicit
RRSIGquery is cached whole or not at all. - AD is never cached over lapsed data, on any admission path.
- A DO=0 query receives exactly the authenticating type it asked for, in every section, on a miss, on a hit, as bytes, and from the aggressive NSEC cache and the NXDOMAIN subtree cut alike (RFC 4035 §3.2.1).
- An alias hit whose target is resolved fresh now applies the same checks to the target as a miss does: AD withdrawn over a lapsed signature and the TTL bounded by the target's own records. It was serving the target at its raw TTL.
- A delegation lease in its last fraction of a second is served with a TTL of zero rather than rounded up past the parent's grant. Every other remainder under a second still rounds up to one, so a hit never carries a zero TTL.
Hit paths cost the same as before; admission pays 30 to 40 ns per response for the extra passes.
Cold-cache DNSSEC fixes (#625)
Two failures that answered SERVFAIL on a freshly started resolver and cleared once the cache was warm, found and fixed by @Du-vy.
- A truncated probe no longer wins the race. An exploration probe measures only the UDP round trip and does not fall back to TCP, so on a truncated reply it could finish ahead of the leader still completing TCP fallback, and its partial answer, missing the RRSIGs of a large DNSKEY set, became the result. Zones with big key sets failed validation until warm. The probe's truncated reply is now set aside while its peers are in flight.
- Skip-level delegations under a signed parent validate. When a signed parent's servers also host an unsigned child, they can refer straight past the child's cut to a grandchild, and the grandchild's DS cannot be checked against the parent. sdns now walks the intermediate cuts: one proven insecure by a signed NSEC or NSEC3 (NS bit set, no DS or SOA, or opt-out) makes everything below it insecure, a signed one is descended into, and anything else fails closed.
ANY is declined on every path (#617)
ANY is answered NOTIMP by design, and now that holds in every mode. A whole-server forwarder and a forward zone no longer hand the question upstream, the cache no longer answers it from failure backoff or a subtree cut, and the refusal is neither cached as a failure nor taken as the recovery that resets a zone's backoff: the next question for the same name resolves normally. NOTIMP from an upstream is unchanged, so failover still moves to the next server.
RPZ feeds with a relative apex load (#610)
Many feeds, abuse.ch URLhaus among them, write their SOA as @ with every rule relative to it and leave the apex to the consuming server. A file zone now accepts origin for this:
[[rpz.zone]]
name = "urlhaus"
file = "/var/lib/sdns/urlhaus.rpz"
origin = "rpz.urlhaus.abuse.ch."A file whose SOA is absolute needs nothing new. Without origin a relative feed fails to compile and sdns -t says so.
Documentation at sdns.dev (#611)
The documentation moved out of the README into a site: getting started, configuration by what a setting does, a page per feature, deployment, a full config key reference, the benchmarks, and the first reference for all of the metrics sdns exports. The README keeps install, a quick start and a summary.
Dependencies
quic-go 0.62.0, golang.org/x/sys 0.48.0, x/sync 0.23.0, x/time 0.16.0, prometheus client_model 0.6.3, and CI action updates (#614, #615, #616, #618, #619, #620, #621, #622, #626).
Upgrading
A drop-in replacement for 1.8.2. No setting is renamed or removed. Configuration files keep working; the startup notice about an older config version is informational, and regenerating the file picks up the 1.8.3 template.