2026
A Linux DNS policy resolver: block, rewrite, refuse or forward queries by rule, answer over UDP, TCP, DoT and DoH, and control it all through an HTTP API and a terminal UI.
dnsd is a Linux daemon that answers DNS queries according to rules and forwards the rest to upstream resolvers. dnsctl is its control panel: a full-screen terminal UI plus a CLI, both talking to dnsd's HTTP API.
| Area | Capability |
|---|---|
| Listen | Classic DNS over UDP and TCP, plus DoT and DoH |
| Upstreams | Plain DNS (UDP, falling back to TCP), DoT (tls://…), DoH (https://…) |
| Outbound | bind_ip / bind_iface per profile, per upstream or globally |
| Policy | block, refuse, drop, sinkhole, rewrite, forward |
| Blocklists | Bulk ad / tracker / malware domain sets, hot-reloaded |
| Telemetry | QPS, top domains, top blocked, top clients, query log, errors |
| State | Optional JSON state file, written atomically, survives restarts |
block returns NXDOMAIN, refuse returns REFUSED, drop doesn't answer,
sinkhole and rewrite synthesise an A/AAAA/CNAME, forward sends the
query to a specific upstream list, and allow falls through.SO_BINDTODEVICE — so recursive traffic leaves through the
tunnel you meant.Bulk blocklists match a name and every parent suffix, so blocking
tracker.example also blocks a.b.tracker.example.
make build
./bin/dnsd --listen 127.0.0.1:51920 --token dev-token \
--dns-listen 127.0.0.1:5353 --state-file /tmp/dnsd-state.json --allow-insecure
export DNSCTL_URL=http://127.0.0.1:51920 DNSCTL_TOKEN=dev-token
./bin/dnsctl block ads.example
./bin/dnsctl rewrite app.corp 10.77.0.10
dig @127.0.0.1 -p 5353 app.corp +short
The control API listens on loopback by default and requires a bearer token,
compared in constant time. dnsd refuses to start with an empty or
development token on a non-loopback control address unless you pass
--allow-insecure. The bundled systemd unit grants only
CAP_NET_BIND_SERVICE.
Go, Bubble Tea for the TUI. Licensed under the GNU AGPL v3.