Secure and private DNS with Knot Resolver

Update 5 March 2018: this post was updated to work around a problem with the RPZ file from abuse.ch being ignored because it contains CRLF instead of LF where Knot Resolver does not expect them (bug 453) and to fix an error in the configuration of the predict module.

Knot Resolver is a modern, feature-rich recursive DNS server. It is used by Cloudflare for its 1.1.1.1 public DNS service.

To install it on Debian, run:

# apt-get install knot-resolver knot-dnsutils lua-cqueues

The knot-dnsutils contains the kdig command which is useful for testing your DNS server. lua-cqueues is needed for automatic detection of changes in the RPZ file.

By default the kresd daemon will listen on localhost only (see /lib/systemd/system/kresd.socket). If you want it to be available on other addresses, you will need to override the kresd.socket file. Execute

# systemctl edit kresd.socket

This will create the file /etc/systemd/system/kresd.socket.d/override.conf. Add a ListenStream and ListenDatagram line for all addresses you want it to listen on. For example:

[Socket]
ListenStream=127.0.0.1:53
ListenDatagram=127.0.0.1:53
 
ListenStream=[::1]:53
ListenDatagram=[::1]:53
 
ListenStream=192.168.0.1:53
ListenDatagram=192.168.0.1:53

If you want to listen on all interfaces, it is enough to put this in the file:

ListenStream=53
ListenDatagram=53

You can do the same with kresd-tls.socket to define the addresses on which to listen over DNS-over-TLS requests (port 853).

Knot Resolver’s configuration file is /etc/knot-resolver/kresd.conf. I give an example configuration file with comments:

-- Default empty Knot DNS Resolver configuration in -*- lua -*-
-- Switch to unprivileged user --
user('knot-resolver','knot-resolver')

-- Set the size of the cache to 1 GB
cache.size = 1*GB

-- Uncomment this only if you need to debug problems.
-- verbose(true)

-- Enable optional modules
modules = {
  'policy',
  'view',
  'hints',
  'serve_stale < cache',
  'workarounds < iterate',
  'stats',
  'predict'
}

-- Accept all requests from these subnets
view:addr('127.0.0.1/8', function (req, qry) return policy.PASS end)
view:addr('[::1]/128', function (req, qry) return policy.PASS end)
view:addr('134.184.26.1/24', function (req, qry) return policy.PASS end)

-- Drop everything that hasn't matched
view:addr('0.0.0.0/0', function (req, qry) return policy.DROP end)

-- Use the urlhaus.abuse.ch RPZ list
policy.add(policy.rpz(policy.DENY, '/etc/knot-resolver/abuse.ch.rpz',true))


-- Forward all requests for example.com to 192.168.0.2 and 192.168.0.3
policy.add(policy.suffix(policy.FORWARD({'192.168.0.2', '192.168.0.3'}), {todname('example.com')}))

-- Uncomment one of the following stanzas in case you want to forward all requests to 1.1.1.1 or 9.9.9.9 via DNS-over-TLS.

-- policy.add(policy.all(policy.TLS_FORWARD({
--          { '1.1.1.1', hostname='cloudflare-dns.com', ca_file='/etc/ssl/certs/ca-certificates.crt' },
--          { '2606:4700:4700::1111', hostname='cloudflare-dns.com', ca_file='/etc/ssl/certs/ca-certificates.crt' },
-- 
-- })))

-- policy.add(policy.all(policy.TLS_FORWARD({
--           { '9.9.9.9', hostname='dns.quad9.net', ca_file='/etc/ssl/certs/ca-certificates.crt' },
--           { '2620:fe::fe', hostname='dns.quad9.net', ca_file='/etc/ssl/certs/ca-certificates.crt' },
-- })))

-- Prefetch learning (20-minute blocks over 24 hours)
predict.config({ window = 20, period = 72 })

I use the urlhaus.abuse.ch RPZ file, which contains a blacklist of malicious domains. You will have to download it first:

# cd /etc/knot-resolver
# curl https://urlhaus.abuse.ch/downloads/rpz/ | sed -e 's/\r$//' -e '/raw.githubusercontent.com/d'> /etc/knot-resolver/abuse.ch.rpz

I use sed to convert CRLF in LF (otherwise Knot Resolver fails to parse the file), and I filter out raw.githubusercontent.com. According to urlhaus.abuse.ch it hosts some malware, but there is too much useful stuff there too to block the domain completely.

In order to update it automatically, create /etc/systemd/system/update-urlhaus-abuse-ch.service:

[Unit]
Description=Update RPZ file from urlhaus.abuse.ch for Knot Resolver

[Service]
Type=oneshot
ExecStart=/bin/sh -c 'curl https://urlhaus.abuse.ch/downloads/rpz/ | sed -e 's/\r$//' -e '/raw.githubusercontent.com/d'> /etc/knot-resolver/abuse.ch.rpz'

and then create a timer which will run the service approximately every 10-15 minutes./etc/systemd/system/update-urlhaus-abuse-ch.timer:

[Unit]
Description=Update RPZ file from urlhaus.abuse.ch for Knot Resolver

[Timer]
OnCalendar=*:0/10
Persistent=true
RandomizedDelaySec=300

[Install]
WantedBy=timers.target

Use the first two commands to enable and start the timer. You can check the status using the last command:

# systemctl enable update-urlhaus-abuse-ch.timer
# systemctl start update-urlhaus-abuse-ch.timer
# systemctl list-timers

Now you need to enable and start one or more instances of kresd. kresd is single-threaded, so if you want to make use of all of your CPU cores, you can start as many instances as the numbers of cores you have. For example in order to enable and start 4 instances run this command:

# systemctl enable --now kresd@{1..4}.service

More information

One thought on “Secure and private DNS with Knot Resolver

Comments are closed.