zdns
is a privacy-focused DNS
resolver and
DNS sinkhole.
Its primary focus is to allow easy filtering of unwanted content at the DNS-level, transport upstream requests securely, be portable and easy to configure.
- Control: Filter unwanted content at the DNS-level. Similar to Pi-hole.
- Fast: Parallel resolving over multiple resolvers, efficient filtering and caching of DNS requests. With pre-fetching enabled, cached requests will never block waiting for the upstream resolver. Asynchronous persistent caching is also supported.
- Reliable: Built with Go and miekg/dns - a mature DNS library.
- Secure: Protect your DNS requests from snooping and tampering using DNS over TLS or DNS over HTTPS for upstream resolvers.
- Self-contained: Zero run-time dependencies makes
zdns
easy to deploy and maintain. - Observable:
zdns
features DNS logging and metrics which makes it easy to observe what's going on your network. - Portable: Run it on your VPS, container, laptop, Raspberry Pi or home router. Runs on all platforms supported by Go.
zdns
is a standard Go package. Install with:
$ go install github.com/mpolden/zdns/...@latest
zdns
uses the TOML configuration format
and expects to find its configuration file in ~/.zdnsrc
by default.
See zdnsrc for an example configuration file. zdns.service contains an example systemd service file.
An optional command line option, -f
, allows specifying a custom configuration
file path.
zdns
supports logging of DNS requests. Logs are written to a SQLite database.
Logs can be inspected through the built-in REST API or by querying the SQLite
database directly. See zdnsrc
for more details.
Most operating systems expect to find their DNS resolver on UDP port 53. However, as this is a well-known port, any program listening on this port must have special privileges.
To work around this problem we can configure the firewall to redirect connections to port 53 to a non-reserved port.
The following examples assumes that zdns
is running on port 53000. See
zdnsrc
for port configuration.
# External requests
$ iptables -t nat -A PREROUTING -d -p udp -m udp --dport 53 -j REDIRECT --to-ports 53000
# Local requests
$ iptables -A OUTPUT -d 127.0.0.1 -p udp -m udp --dport 53 -j REDIRECT --to-ports 53000
- Edit
/etc/pf.conf
- Add
rdr pass inet proto udp from any to 127.0.0.1 port domain -> 127.0.0.1 port 53000
below the lastrdr-anchor
line. - Enable PF and load rules:
pfctl -ef /etc/pf.conf
A basic REST API provides access to request log and cache entries. The API is
served by the built-in web server, which can be enabled in zdnsrc
.
Read the log:
$ curl -s 'http://127.0.0.1:8053/log/v1/?n=1' | jq .
[
{
"time": "2019-12-27T10:43:23Z",
"remote_addr": "127.0.0.1",
"hijacked": false,
"type": "AAAA",
"question": "discovery.syncthing.net.",
"answers": [
"2400:6180:100:d0::741:a001",
"2a03:b0c0:0:1010::bb:4001"
]
}
]
Read the cache:
$ curl -s 'http://127.0.0.1:8053/cache/v1/?n=1' | jq .
[
{
"time": "2019-12-27T10:46:11Z",
"ttl": 18,
"type": "A",
"question": "gateway.fe.apple-dns.net.",
"answers": [
"17.248.150.110",
"17.248.150.113",
"17.248.150.10",
"17.248.150.40",
"17.248.150.42",
"17.248.150.51",
"17.248.150.79",
"17.248.150.108"
],
"rcode": "NOERROR"
}
]
Clear the cache:
$ curl -s -XDELETE 'http://127.0.0.1:8053/cache/v1/' | jq .
{
"message": "Cleared cache."
}
Metrics:
$ curl 'http://127.0.0.1:8053/metric/v1/?resolution=1m' | jq .
{
"summary": {
"log": {
"since": "2020-01-05T00:58:49Z",
"total": 3816,
"hijacked": 874,
"pending_tasks": 0
},
"cache": {
"size": 845,
"capacity": 4096,
"pending_tasks": 0,
"backend": {
"pending_tasks": 0
}
}
},
"requests": [
{
"time": "2020-01-05T00:58:49Z",
"count": 1
}
]
}
Note that log_mode = "hijacked"
or log_mode = "all"
is required to make
metrics available. Choosing hijacked
will only produce metrics for hijacked
requests.
The query parameter resolution
controls the resolution of the data points in
requests
. It accepts the same values as
time.ParseDuration and defaults to
1m
.
This is my personal opinion and not a objective assessment of Pi-hole.
-
Pi-hole has lots of dependencies and a large feature scope.
-
Buggy installation script. In my personal experience, the 4.3 installation script failed silently in both Debian stretch and buster LXC containers.
-
Installation method pipes
curl
tobash
. Not properly packaged for any distributions.