2022-02-24

Replacing dnsmasq DNS with knot-resolver on OpenWRT

OpenWRT uses dnsmasq for DHCP and DNS services, and the DNS service caused some problems for me:

  • Latency when forwarding DNS requests is often higher than direct lookup.
  • Does not support DNS-over-TLS (DoT).
  • Forwarding to stubby adds DoT support but frequently has very high latency, and sometimes just fails completely.

Installing knot-resolver fixes these issues, but it has to be installed manually and I can't replace dnsmasq since I need the DHCP service so some configuration is needed.

Installation

First update the package index. In LUCI go to System -> Software and press Update lists, or in a terminal:

opkg update

Search for and install the knot-resolver package in LUCI, or:

opkg install knot-resolver

Stop and disable the service until the configuration is in place. In LUCI go to System -> Startup and stop and disable the kresd service, or:

service kresd stop
service kresd disable

Configuration

First disable DNS on dnsmasq. In LUCI go to Network -> DHCP and DNS -> Advanced Settings and set the DNS server port to 0. Press Save & Apply and check that nothing is listening on port 53 anymore:

netstat -tlnu

Next create a kresd configuration file in /etc/knot-resolver/kresd.conf:

log_target('syslog')

modules = {
  'policy',
  'hints > iterate',
  'serve_stale < cache',
  'workarounds < iterate',
  'stats',
  'predict'
}

net.ipv6 = false
net.listen('127.0.0.1', 53, { kind = 'dns' })
net.listen('127.0.0.1', 853, { kind = 'tls' })
net.listen('10.0.0.1', 53, { kind = 'dns' })
net.listen('10.0.0.1', 853, { kind = 'tls' })

cache.open(50 * MB, 'lmdb:///tmp/kresd/cache')

hints.add_hosts('/etc/knot-resolver/hosts.custom')

policy.add(policy.all(policy.TLS_FORWARD({
    { '1.1.1.1', hostname='cloudflare-dns.com' },
    { '1.0.0.1', hostname='cloudflare-dns.com' }
})))

predict.config({ window = 30, period = 48 })

Let's go through the settings. For details refer to the documentation.

  • log_target('syslog'): The default logging target is stdout, but I want messages to appear in the syslog together with the rest of the services.
  • modules: All the imported modules necessary to enable the settings used.
  • net.ipv6 = false: I don't need IPv6 support and the default is true. Remove this if you need to add IPv6 addresses.
  • net.listen(...): All the interfaces and ports to listen for DNS requests on. Replace the address with whatever the LAN address of your router is.
  • cache.open(...): Use a relatively small cache on non-persistent storage. Best-practice is to mount a dedicated tmpfs if you want non-persistence, but since /tmp is already mounted using tmpfs and has plenty of space I simply use that.
  • hints.add_hosts(...): A regular hosts file with all the static mappings on the network. If your system uses /etc/hosts it could point to that. Delete this line if you don't need static mappings.
  • policy.TLS_FORWARD(...): This is where DNS forwarding using TLS is enabled for all lookups. The example uses Cloudflare servers but any DNS server supporting DoT can be used.
  • predict.config(...): The prediction module is entirely optional. It refreshes cache entries based on usage patterns, time, or both depending on configuration.

Service configuration hack

The knot-resolver package doesn't integrate with uci and I didn't have time to create a clean solution, so I just edited the /etc/init.d/kresd script directly to use the custom configuration and disable the autogenerated configuration file:

  • Edit CONFIGFILE to point to /etc/knot-resolver/kresd.conf.
  • Comment out init_header, init_rootkey and any -a parameter lines in the start_service() function.

Remember that if you upgrade the package you might have to reapply these changes.

Now all that remains is to enable and start the service, and check that it is listening on all configured interfaces and ports:

service kresd enable
service kresd start
netstat -tlnu

Check that DNS works from a computer on the network:

dig example.com @10.0.0.1

Since DNS is disabled in dnsmasq it doesn't advertise any DNS server through DHCP anymore. Remedy that by adding a custom DHCP setting. In LUCI go to Network -> Interfaces and edit the interface for your LAN. Then go to DHCP Server -> Advanced Settings and add the option 6,10.0.0.1 to DHCP-Options. Save and apply and everything should work exactly as before, with all DNS lookups being encrypted and done by knot-resolver instead of dnsmasq.

You can test if TLS is used here:

https://1.1.1.1/help/