Access control and blocking
Who may query, how fast, and which names never resolve.
Who may query
accesslist = ["127.0.0.1/32", "::1/128", "192.168.0.0/16"]
CIDR ranges allowed to query this resolver. Queries from anywhere else are dropped without a reply before any resolution work happens: the access list runs near the front of the middleware chain, ahead of the cache and the resolver.
The shipped default is ["0.0.0.0/0", "::0/0"], which allows everyone, and so
does an empty list or a file that leaves the key out. That is right for a
resolver on loopback and wrong for one on a public address. An open recursive
resolver on the internet will be found and used for reflection attacks, so
narrow this before you bind to a reachable address.
Watch dns_accesslist_denied_total to see whether anything is being dropped.
Rate limits
ratelimit = 0 # cache hits per second, per answer bucket; 0 disables
clientratelimit = 0 # queries per minute, per client IP; 0 disables
Both are off by default. clientratelimit is the more useful of the two on a
resolver serving known clients: it contains one misbehaving host without
capping the server.
ratelimit is not a server-wide ceiling. It limits how often cached answers
are served. Each cached answer is mapped, by a hash of its cache key, to one of
997 shared token buckets, each refilling at ratelimit tokens a second with a
burst of the same size; answers that hash to the same bucket share it. Answers
cached under an ECS scope are the exception: they all share a single bucket,
so with [ecs] on, every scoped hit draws from the same ratelimit budget. A
cache hit over its bucket’s rate is dropped without a reply. Cache misses, and
everything answered before the cache, are not limited by it, and these drops
are not counted in dns_ratelimit_exceeded_total.
clientratelimit keeps two buckets per client address. Queries whose source is
proved, by a TCP or QUIC handshake or by a valid DNS server cookie (RFC 7873),
draw from one; plain UDP queries without a valid cookie draw from the other. A
flood sent in a client’s name from spoofed addresses therefore cannot spend
the quota of that client’s real traffic. Clients behind one NAT address share
its quota. A UDP client over its quota that sent a cookie gets a BADCOOKIE
reply carrying a server cookie, at most one a second; its retry with that
cookie is served from the proved bucket. Anything else over the quota is
dropped. Loopback clients are exempt.
Each query over a client’s quota, dropped or answered with a BADCOOKIE
challenge, increments dns_ratelimit_exceeded_total.
Cookie secret
cookiesecret = ""
The server cookies above are RFC 9018 cookies, a SipHash over the client
cookie, a timestamp and the client’s address, keyed by cookiesecret. The key
is not in the generated file. Exactly 32 hex digits are used as the key itself.
Any other text is hashed into a key, the same text always to the same key.
Left empty, sdns generates 16 random bytes at startup, so the cookies it hands
out change on every restart and clients relearn them.
Members of an anycast set must share one fixed value, so a cookie one member issued is valid at the others. Between sdns servers any identical string will do. When other DNS software takes part in the set, use 32 hex digits, since that is the key form RFC 9018 servers have in common.
Reflection and amplification defence
reflexenabled = false
reflexblockmode = true
reflexlearningmode = false
# reflexthreshold = 0.7
Tracks per-IP behaviour to identify spoofed sources being used for reflection. Off by default.
The two modes matter. With reflexlearningmode = true detections are logged and
nothing is blocked, which is how you calibrate the threshold against your own
traffic. reflexblockmode = false also only logs, but at debug level, so at
loglevel = "info" its detections do not appear in the log. Turn on blocking
after you have watched the detections for a while and are satisfied they are
not your own clients. The threshold is a score between 0 and 1, and lower is
more aggressive.
Blocking names
blocklists = [
"https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts",
]
blocklist = ["ads.example.com"]
whitelist = ["important.example.com"]
nullroute = "0.0.0.0"
nullroutev6 = "::0"
blocklists are URLs downloaded once at startup, into the blacklists
directory under directory; there is no periodic refresh, so restart sdns to
fetch newer copies. blocklist is a manual list in the configuration file;
whitelist wins over both. It exempts names from this blocklist only: RPZ
policy still applies to a whitelisted name. A blocked A query answers
nullroute and a blocked AAAA answers nullroutev6.
Entries can also be managed at runtime through the API without a restart:
curl http://127.0.0.1:8080/api/v1/block/set/ads.example.com
curl http://127.0.0.1:8080/api/v1/block/exists/ads.example.com
curl http://127.0.0.1:8080/api/v1/block/remove/ads.example.com
For policy that goes beyond a name list, rewriting to a CNAME, matching on the client’s address or on the address in the answer, vendor feeds over AXFR, or a shadow mode that counts what enforcement would do, use Response Policy Zones instead. RPZ runs after the blocklist in the chain and is the richer of the two.
blocklistdir is deprecated; the directory is created under directory
automatically.
Local answers from a hosts file
hostsfile = "/etc/hosts"
hostsfilechecknames = true
hostsfilezones = ["home.arpa."]
Serves entries from a hosts file directly. Empty hostsfile disables it.
hostsfilechecknames and hostsfilezones are independent filters: the
first checks each name; the second limits names to configured DNS zones.
Setting hostsfilechecknames = true alone checks names without limiting their
zones. An empty zone list disables zone filtering. With both options omitted
or at their defaults, hosts-file importing keeps its existing behavior.
When name checking is enabled, SDNS accepts ASCII RFC 952/1123-style
hostname labels containing letters, digits, and hyphens. Each label must begin
and end with a letter or digit, and may contain hyphens between them. Labels
are limited to 63 octets and names to 255 octets on the DNS wire. A trailing
dot is optional. A leading *. is allowed for a wildcard entry. An invalid
name is skipped with a warning that identifies its file and line; it contributes no
forward answer or PTR record. For a line with aliases, each name is considered
individually. The first accepted normal name is promoted to the row’s primary
name for CNAME and PTR processing. Equivalent names in one row are imported once
when either filter is enabled; addresses from separate rows still aggregate.
A surviving wildcard keeps the existing wildcard-row precedence and does not
create a PTR record.
hostsfilezones entries are DNS zones. Matching is label-aware and
case-insensitive: a zone matches its apex and subdomains, so corp.example
matches printer.corp.example but not notcorp.example. Root . and
names with or without a trailing dot are accepted. A name outside every
configured zone continues through normal resolution.
A name the file lists with addresses of one family only gets NODATA for the other family; the query is not sent upstream. For answers scoped to particular client networks rather than served to everyone, use views.
Per-domain metrics
domainmetrics = false
domainmetricslimit = 1000
Tracks query counts per domain, exported as dns_domain_queries_total. Off by
default because the cardinality is unbounded on a public resolver; that is
what the limit is for. 0, or leaving the key out, means unlimited, which on a
busy resolver will consume memory until something gives.
Suppress AAAA answers
block_aaaa = false
Off by default, including when omitted. Set it to true to answer normal
single-question IN/AAAA client queries with NOERROR and empty Answer and
Authority sections (NODATA). It applies to IPv4 and IPv6 clients on every DNS
transport, including queries with RD=0 or CD=1. Other types and classes keep
their usual behavior.
Use this setting for intentionally IPv4-only downstream networks. Suppressing AAAA makes IPv6-only destinations unreachable to those clients. IPv6 listeners, outbound IPv6 connectivity and internal lookups of nameserver and resolver addresses are unaffected. The setting does not remove IPv6 addresses in other record types, including HTTPS/SVCB address hints, or prevent a client from using an IPv6 address it already knows.
Suppression runs after access controls, EDNS and DDR, ahead of hosts files,
views, blocklists, RPZ, cache and DNS64. Their AAAA answers and synthesis are
therefore bypassed even when cached. The resolver.arpa zone remains under
DDR control. Enabling DNS64 alongside block_aaaa is accepted; suppression
wins for client AAAA queries.
These replies describe local policy, not authenticated denial from the
authoritative zone: AA and AD are clear, RA is set, and the client’s RD and CD
are preserved. No SOA or DNSSEC denial proof is supplied, so there is no
SOA-based negative-cache lifetime; validating clients may reject the
unsigned answer. EDNS clients receive EDE 0 (Other), with text
“AAAA response suppressed by policy”; clients without EDNS receive no OPT.
The dns_aaaa_blocked_total counter counts successfully written policy replies.