A recursive DNS resolver with DNSSEC validation, written in Go.
Documentation · Install · Configuration · Benchmarks
SDNS resolves from the root, validates answers against the DNSSEC trust anchors, and caches them. It serves DNS over TLS, HTTPS and QUIC alongside plain UDP and TCP, and answers warm cache hits from the bytes it already holds.
Full documentation lives at sdns.dev. This file covers installing it and getting a first answer out of it.
go install github.com/semihalev/sdns@latestPre-built binaries for Linux, macOS, Windows and the BSDs, plus .deb and
.rpm packages, are on the
releases page. The full
architecture matrix is in the
installation guide.
# Docker. Loopback because the default access list allows every client; -c and
# directory = "/var/lib/sdns" in the file because the image has no WORKDIR, so
# a relative state directory resolves to /db and misses the volume entirely.
docker run -d --name sdns \
-p 127.0.0.1:53:53 -p 127.0.0.1:53:53/udp \
-v sdns-data:/var/lib/sdns -v "$PWD/sdns.conf:/etc/sdns.conf:ro" \
ghcr.io/semihalev/sdns:latest -c /etc/sdns.conf
# macOS
brew install semihalev/tap/sdns && brew services start sdns
# Linux
snap install sdns
# Arch
yay -S sdns-gitImages are published to
ghcr.io/semihalev/sdns
and c1982/sdns on every tagged release.
Pin a tag in production rather than following latest, the
installation page names
the current one, since this file cannot.
# Starting without a config writes one, documented in place, and uses it.
sdns
# Check a config the way the server will read it, before restarting.
sdns -t -c /etc/sdns.conf
# Ask it something.
dig @127.0.0.1 example.com A +dnssecAn answer with the ad flag was validated. The first query is slow while the
resolver primes the root and fetches the trust anchor; after that it is served
from cache.
See Your first configuration
for the handful of settings worth changing straight away, in particular
accesslist, which allows everyone by default.
Resolution. Recursive from the root with DNSSEC validation, QNAME minimisation (RFC 9156), aggressive NSEC use (RFC 8198), NXDOMAIN subtree cuts (RFC 8020), failure caching (RFC 9520), and Extended DNS Errors (RFC 8914). Optionally the root zone served from a ZONEMD-verified local copy (RFC 8806), or expired answers as a last resort when resolution fails (RFC 8767).
Transports. UDP, TCP, DoT (RFC 7858), DoH with HTTP/3 (RFC 8484), DoQ
(RFC 9250). Warm wire-eligible cache hits are served allocation-free, with
batched recvmmsg/sendmmsg on Linux.
Policy. Response Policy Zones with name, client-address and answer-address triggers, file and TSIG-signed AXFR feeds, and a shadow mode whose counters predict what enforcement would do. Blocklists, per-client views, access lists, rate limits, and reflection-attack detection.
Other namespaces. Per-zone conditional forwarding, whole-server forwarder mode, Kubernetes cluster DNS, DNS64 synthesis (RFC 6147), EDNS Client Subnet (RFC 7871), locally served zones (RFC 6303).
Operations. Prometheus metrics, an HTTP API, dnstap, a recursion firewall that bounds the work one request may cause, serving bounds derived from the machine at startup, and a validation gate that reports every configuration problem at once.
The documentation covers each of these, including what they cost and what they deliberately do not do.
Throughput measurements, the methodology, resolver comparisons and their caveats are in the benchmarks document.
make all # generate, tidy, test, build
make test # tests only
go build # binary onlyConventions a patch is expected to follow (plain testing idioms with no
assertion library, no live-network tests, gofmt and golangci-lint clean)
are on the building and testing
page. The middleware interface and the plugin contract are documented
there too.
Pull requests are welcome. For significant changes, please open an issue first so the approach can be discussed.
Please review CONTRIBUTING.md before submitting patches.