esc
↑↓ move ↵ open

Resolution and DNSSEC

Root servers, validation, QNAME minimisation, timeouts and work limits.

Root servers

rootservers  = ["198.41.0.4:53", "170.247.170.2:53"]   # list shortened here
root6servers = ["[2001:503:ba3e::2:30]:53"]            # list shortened here

The generated file ships the full published list for both families; the example above shows only the first entries. You would only edit these to point at a private root deployment.

You do not need to remove the IPv6 list on a host without IPv6 transit: sdns probes for transit at startup and leaves the IPv6 roots out of the rotation when the probe fails. ipv6access = true overrides the probe when it misjudges the network.

If you want the root served locally instead of queried, see Local root zone.

DNSSEC validation

dnssec   = "on"     # "on" or "off"
# rootkeys = [...]  # root trust anchors; elided here, the generated file lists them
rfc8198  = true
rfc9520  = true

dnssec = "on" validates every signed zone and refuses to serve data that fails validation. rootkeys holds the root trust anchors in DNSKEY presentation format; the generated file ships the published KSKs, and sdns tracks anchor rollovers on its own (RFC 5011) once running.

Signatures are verified for RSA with SHA-1, SHA-256 and SHA-512, ECDSA P-256 and P-384, Ed25519, and ML-DSA-44 (algorithm 18), the post-quantum signature of draft-westerbaan-dnssec-mldsa. A zone signed only with an algorithm outside that list, Ed448 among them, is treated as unsigned rather than failed, as RFC 4035 requires. A zone signed with several algorithms validates when any one of its signatures does (RFC 6840 §5.11), so a zone whose ML-DSA-44 signatures are broken while its classic ones hold still validates. Some resolvers refuse that case, to guard against a future quantum downgrade; sdns follows the RFC.

rfc8198 lets a validated NSEC/NSEC3 record answer later negative queries without another authoritative lookup. rfc9520 caches resolution failures and failed-authority state. Both default to on and both are kill switches rather than tuning knobs, turning either off costs upstream traffic and, in the case of rfc9520, standards conformance. Exact negative caching and RFC 8020 NXDOMAIN subtree cuts stay active with rfc8198 = false.

With dnssec = "off", or in whole-server forwarder mode (forwarderservers set), sdns turns off both RFC 8020 subtree cuts and RFC 8198 reuse, whatever rfc8198 says. Both reuse a denial for names it was never asked about, which needs validation sdns performed itself; a forwarder’s AD bit is only a claim on the wire. Exact negative caching still applies.

If a trust anchor file is ever corrupted, the authoritative source for the root KSKs is data.iana.org/root-anchors.

QNAME minimisation

qname_max_minimize_count = 10
qname_minimize_one_label = 4

sdns sends upstream servers only the labels the current delegation already justifies, rather than the whole query name (RFC 9156). Past the budget the full name goes out, so every delegation below that point sees all of it.

qname_max_minimize_count is how many minimised queries one lookup may spend; 0 disables minimisation, and the RFC recommends 10. qname_minimize_one_label is how many of those add a single label before the remaining labels are grouped over the queries left; 0 selects the RFC’s suggested 4. The shipped 10/4 is the RFC’s own recommendation.

sdns -t refuses a negative qname_max_minimize_count. While minimisation is on, it also refuses a negative qname_minimize_one_label, and one larger than qname_max_minimize_count.

The older qname_min_level, which counted delegation depth rather than queries, is still read when qname_max_minimize_count is unset.

Timeouts and depth

timeout      = "2s"      # per upstream query
querytimeout = "10s"     # for the whole client query
maxdepth     = 30        # recursion depth ceiling

timeout bounds one exchange with one upstream server. querytimeout bounds everything sdns will do for a single client question, retries and nested lookups included. maxdepth stops resolution loops.

For bounds on aggregate work rather than time, outbound attempts, internal queries, DNSSEC operations, see the recursion firewall, which is the mechanism designed for that and which also owns the RFC 9520 failure cache.

TCP connection pooling

tcpkeepalive      = false
roottcptimeout    = "5s"
tldtcptimeout     = "10s"
tcpmaxconnections = 100

Keeps TCP connections to root and TLD servers alive between queries. Off by default. It helps when a large share of upstream traffic is truncated into TCP; it costs sockets otherwise. tcpmaxconnections = 0 uses the built-in 100.

Locally served empty zones

emptyzones = []

Answers queries for the private-address reverse zones locally instead of leaking them to the root (RFC 6303). An empty list uses the built-in set, which is what you want; list zones explicitly only to narrow it.

RFC 6303 is the citation that applies to a resolver: it defines the zone list and says the resolver should answer them itself. The often-quoted RFC 7534 is a different document, it describes how to run an AS112 node, the sink that catches these queries when a resolver does not answer them.

resolver.arpa

resolver.arpa, the zone that describes this server itself (RFC 9462), is always answered locally and never sent upstream, with [ddr] off as well. Its apex answers SOA and NS; every other name and type in it gets NODATA, except the _dns.resolver.arpa SVCB records when DDR is on.

Query classes

sdns answers class IN only, on every path, forwarder mode and forwarded zones included. A CHAOS question the chaos setting does not answer gets REFUSED, and any other class gets NOTIMP with Extended DNS Error 21 (Not Supported). See Identification.