Skip to content

v1.8.2

Choose a tag to compare

@semihalev semihalev released this 01 Sep 09:12
· 81 commits to main since this release

The policy release: 1.8.1 rebuilt how sdns resolves, 1.8.2 adds what a resolver is allowed to answer. Response Policy Zones arrive complete — every trigger, every action, commercial feeds unmodified — alongside a locally served root zone (RFC 8806), stale answers as a last resort (RFC 8767), per-zone forwarding, and a configuration gate that judges the whole file at once. Everything new ships off or in shadow by default. Recommended for all deployments.

Response Policy Zones (#597, #598, #599, #601, #602)

A full RPZ subscriber: policy zones in the standard encoding — locally maintained files or vendor AXFR feeds — rewrite, deny, or drop answers for the names, client networks, and answer addresses they list. Existing commercial feeds work unmodified.

  • Every production trigger. QNAME rules (exact and wildcard), CLIENT-IP prefixes, and answer-address (rpz-ip) prefixes, IPv4 and IPv6 with family separation — a v6 rule can never match a v4 address or the reverse. All six standard actions: NXDOMAIN, NODATA, PASSTHRU, DROP, TCP-Only, and Local Data served as if authoritative for the query name, CNAME chasing included. Per-zone policy overrides replace a whole zone's actions, cname builds a walled garden, and disabled observes without consuming — a later zone still acts.
  • Shadow first, and the numbers predict the flip. The default mode counts and logs every match without rewriting anything. Counting is winner-bounded and identical between shadow and enforce — per query, one decision per zone, up to and including the first enabled winner — so a soak's counters describe exactly what enforcement will do, and rpz_action_total{zone,trigger,action,outcome} is the flip-decision dashboard.
  • Feeds keep themselves current. File zones reload on atomic replacement. AXFR zones follow their own SOA schedule — probe on refresh, transfer on serial change, retry on retry — with TSIG-signed transfers (hmac-sha1 through sha512) when the provider requires them. A source advertising an older serial is refused whole at the probe (RFC 1982), before any transfer is spent; a feed that cannot be refreshed past its SOA expire withdraws its rules and fails open rather than enforcing stale policy, and a push that parses to zero rules keeps the previous generation serving.
  • The cache stores the truth; policy is applied to the client. Answer-address policy is judged against what a query actually resolved to, but the stored answer is never modified — the verdict rides beside the cache entry, stamped with the policy generation it was computed under. A PASSTHRU-exempted client reads the truth from the very entry another client is rewritten from, and a reload re-evaluates already-cached answers on their next hit instead of waiting out the cache.
  • Priced for the serving path, and measured. A non-matching query allocates nothing, and a clean cache hit still serves from raw bytes with policy rules loaded — both pinned by allocation tests that run on every commit. On a 32-core host serving a warm corpus at a fixed offered rate, a two-million-rule shadow feed costs roughly 2 microseconds of CPU per query against the same host with RPZ disabled (≈45 → ≈47 µs), and about 190 bytes of memory per rule. Adding answer-address rules on top of that costs nothing measurable — the response-IP machinery (a wrapper per query, a verdict stamped beside every admitted entry, a gate on every byte-served hit) lands 0.1–0.7% away from the name-only feed, well inside the run-to-run spread of the control itself.
  • Rewritten answers carry the policy zone's SOA and Extended DNS Error 17 (Filtered), and never claim DNSSEC authenticity. Policy applies to client queries only — the resolver's own lookups are never policy-checked. sdns -t validates the whole [rpz] block with the same loaders the server runs.

Hyperlocal root zone (RFC 8806) (#582)

Serve the root zone from a verified local copy: AXFR from the root servers, ZONEMD-verified (RFC 8976) against the DNSSEC trust anchors, refreshed on the zone's own SOA schedule. Root referrals, junk-TLD NXDOMAINs, and questions at the root itself cost no upstream query — and the answers are provably the root zone, not whatever a transfer happened to deliver. Off by default (hyperlocal_root).

Stale answers as a last resort (RFC 8767) (#588, #589)

When resolution ends in SERVFAIL, an expired positive answer can serve instead of the failure — bounded by serve_stale_max_ttl (24h default), and always by the parent-granted delegation lease, which remains a hard ceiling: ghost-domain protection is not traded away for availability. Positive answers only, triggered by failure only. A stale alias whose target is still fresh serves the completed chain and is charged like the hit it is. Off by default (serve_stale).

The configuration is judged whole (#592)

sdns -t now checks every setting it can judge — addresses, ports, CIDRs, enumerated values, upstream formats, TLS files, RPZ zones — and reports all problems at once instead of dying on the first. Keys the file carries that no setting claims fail the test (a typo, or a key an older sdns understood); startup only warns, so an upgrade with a stale key cannot become an outage. The validators mirror the runtime: ports resolve through the same lookup dialing uses, TSIG keys parse with the same parser the feed loop runs, RPZ files load with the server's own loader — a config that passes is one the server can actually run.

Per-zone forwarding (#591)

[[forward_zone]] sends one zone's queries to its own recursive upstreams while everything else resolves normally — the classic stub/conditional-forwarding split. Most specific zone wins; servers accept the same UDP/TCP, DoT, and DoH forms as the global forwarder.

Resolver and validation fixes

  • A record that only claims to be a DNSKEY is skipped instead of asserted on (#596).
  • miekg/dns 1.1.73; the RSAMD5 key tag is computed per the RFC rather than delegated to the library (#594).
  • The SDNS_PPROF and debug switches read as booleans — any value used to count as on (#593).

Documentation

The README was audited key-by-key against the configuration it documents: every TOML section now reaches the master table, the RPZ operator reference lives in the README, and a section index sits above the configuration table.

Dependency updates: k8s.io/client-go 0.37.0, github/codeql-action 4.37.9.