Skip to content

Repository files navigation

gatewaydnsd

The GatewayDNS server daemon: a single static binary that serves DNS for a network. Install it on a VPS, a home server, a Raspberry Pi or in a container, point your router's DHCP at it, and it caches and filters for everything behind it.

It is one of three consumers of GatewayDNS Core, the engine. The other two are anyone embedding the library directly, and GatewayDNS Desktop.

Status

It resolves, filters and installs.

$ gatewaydnsd serve -config /etc/gatewaydns/gatewaydnsd.json
gatewaydnsd listening on 0.0.0.0:53

$ dig @127.0.0.1 example.com +short
172.66.147.243

$ dig @127.0.0.1 tracker.doubleclick.net
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN

Working today: UDP and TCP listeners; upstreams over DoH, DoT, TCP or UDP with failover between them; caching with negative caching and optional serve-stale; blocklists and allowlists in hosts, domain-list and Adblock formats; a token-authenticated management API with an optional query log; SIGHUP list reload; graceful shutdown that finishes queries already accepted; and packages for Debian, RPM and Alpine plus a container image.

Not yet: per-device policy from a device table, a Prometheus metrics endpoint, persistent query-log storage, and list auto-refresh.

Installing

Debian, Ubuntu, RPM, Alpine

Download the package for your architecture from the releases page, then:

sudo dpkg -i gatewaydnsd_1.0.0_amd64.deb      # or: rpm -i, apk add --allow-untrusted
sudo $EDITOR /etc/gatewaydns/gatewaydnsd.json
sudo gatewaydnsd check -config /etc/gatewaydns/gatewaydnsd.json
sudo systemctl enable --now gatewaydnsd       # or: rc-update add gatewaydnsd && rc-service gatewaydnsd start

The package does not start the service for you. Port 53 is nearly always already held — by systemd-resolved on a Debian desktop, by dnsmasq on a router — and a package that seized it on install would turn "I installed a DNS server" into "my machine lost DNS". Free the port first:

sudo systemctl disable --now systemd-resolved
printf 'nameserver 127.0.0.1\n' | sudo tee /etc/resolv.conf

The service runs as an unprivileged gatewaydns user with CAP_NET_BIND_SERVICE for port 53 and a systemd sandbox (the unit is worth reading before you trust it with your network).

Container

docker run -d --name gatewaydns \
  -p 53:53/udp -p 53:53/tcp \
  --cap-add NET_BIND_SERVICE \
  -v /etc/gatewaydns:/etc/gatewaydns:ro \
  ghcr.io/daboss2003/dnsd:latest

The image is scratch plus the binary and a CA bundle: no shell, no package manager, no libc. It runs as uid 65532. If you would rather not grant NET_BIND_SERVICE, set listen.addr to 0.0.0.0:5353 and publish it as -p 53:5353/udp.

From source

make build          # a static binary in ./gatewaydnsd
make dist           # every release target, into ./dist
make packages       # .deb, .rpm and .apk (needs nfpm)
make docker         # the container image

This module depends on the engine by version, like any other dependency, so it builds on its own with nothing else checked out.

To develop the two together, check them out side by side and add a go.work naming both — it is deliberately not committed, because a workspace in a repository silently overrides every collaborator's module resolution:

go work init ./dns ./dnsd ./dns-desktop

Configuring

gatewaydnsd config-default prints the full schema with every default filled in, and gatewaydnsd check -config FILE validates a file and reports every problem it finds rather than the first. Both are generated from the same code the daemon runs, so neither can drift from it.

Durations are written the way people write them: "5s", "250ms", "24h".

listen

Key Default Meaning
addr 127.0.0.1:5353 Address to bind. Port 53 is privileged.
udp / tcp true Either may be disabled; both cannot. A client that receives a truncated UDP answer retries over TCP, so disabling TCP makes every large answer unresolvable.
max_in_flight 2048 Concurrent queries before new ones are shed.
max_tcp_conns 512 Concurrent TCP connections.
query_timeout 5s Bound on answering one query.

upstream

Key Default Meaning
servers tls://1.1.1.1:853, tls://9.9.9.9:853 Endpoints in preference order, each scheme://address where scheme is udp, tcp, tls or https. A udp:// upstream is automatically paired with TCP to the same address.
strategy sequential sequential, race, round-robin or random. race is faster at the tail and leaks every name to every upstream raced, which is a privacy decision rather than a tuning one.
randomize false DNS-0x20 case randomisation. Adds entropy against off-path forgery; a small number of servers normalise case in replies and break outright with it on.
timeout 2s Bound on one upstream exchange.

cache

Key Default Meaning
enabled true
max_entries 10000
min_ttl / max_ttl 0s / 24h Clamp how long answers are held.
serve_stale 0s Answer from expired entries for this long when no upstream can be reached (RFC 8767). Off unless asked for: it is a deliberate correctness trade.

policy

Key Default Meaning
blocklists none Paths to list files, in hosts, domain-list or Adblock format. Remote URLs are not fetched: a resolver that cannot start because a list server is down is worse than one with a stale list.
allowlists none An allow rule beats a block rule whatever their specificity, which is how anyone recovers from a false positive in a list of a million names nobody can audit.
block nxdomain What a filtered client is told: nxdomain, refused or nodata. Every mode carries an RFC 8914 Extended DNS Error saying the name was filtered, so a client can tell "your gateway blocked this" from "no such name".

SIGHUP, or systemctl reload gatewaydnsd, re-reads the lists without dropping a query. A reload that fails leaves the previous rules in force. Changing anything else — an address, an upstream — needs a restart, because it means new sockets and a new resolver however it is dressed up.

api — the management API

Off unless configured, and worth understanding before turning on. It reads the query log and changes the filtering policy, so it is the most dangerous surface in the product.

Key Default Meaning
addr none Address to serve on. Put it on loopback, or on an interface that is not the one answering DNS. Empty means the API does not exist, which is the smallest attack surface available.
token none Bearer token, 32 characters minimum. Required. Compared in constant time.
token_file none Path to read the token from instead. Mode 0600, owned by the gatewaydns user. Trailing newlines are trimmed.
expose_log false Enables /api/v1/queries*, which returns a record of every name every device on the network looked up. Gated separately from everything else for that reason.
read_only false Refuses every mutating request.
allowed_origins none CORS origins, named explicitly. * is refused.
max_page_size 1000 Rows one query-log request may read.
log_retention 7 days / 1,000,000 {"max_age": "24h", "max_entries": 50000}. How much browsing history is kept in memory.

Endpoints: GET /healthz (unauthenticated, says nothing about the network), GET /api/v1/stats, GET /api/v1/cache, POST /api/v1/cache/flush, GET /api/v1/policy, POST /api/v1/policy/reload, and with expose_log, GET /api/v1/queries, /api/v1/queries/count and /api/v1/queries/top. Every failure is JSON with an error field, and every path under /api/v1 answers 401 to a caller with no credential — including paths that do not exist, so the route table is not an oracle.

logging

Key Default Meaning
level INFO DEBUG, INFO, WARN, ERROR.
format text text, json or syslog.

Signals

Signal Effect
SIGHUP Re-read the blocklists and allowlists. No query is dropped.
SIGINT, SIGTERM Graceful shutdown: stop accepting, finish what was accepted, then exit.

Why it is a separate repository

The engine has zero external dependencies and that is a promise to everyone who embeds it. A daemon has good reasons to want dependencies the engine must never have — a YAML parser so operators can write config files with comments, a Prometheus client so /metrics is the format every monitoring stack already scrapes. Keeping them apart means neither has to argue with the other.

See ADR 0015 in the engine repository.

Licence

Apache-2.0. See LICENSE.

About

GatewayDNS server daemon — a single static binary that serves filtered DNS for a network. Config file, management API, systemd/OpenRC units, .deb/.rpm/.apk and a container image.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages