How to randomize DNS resolver selection in Knot Resolver

Knot DNS Resolver lets you configure up to four forwarding DNS over TLS servers, and will automatically prefer the fastest server. However, Knot Resolver is highly configurable and you can also configure it to do more exotic things like spread your DNS queries out randomly among a selection of DNS resolvers.

Please refer to How to get faster and more secure DNS resolution with Knot Resolver for an introduction to Knot Resolver and some basic setup and configuration. This article picks up from the end of the previous article where we’d setup forwarding to an external DNS over TLS resolver.

Configuration files in Knot Resolver are Lua scripts, meaning that you’ve got the full capabilities of the Lua language at your disposal. I hope to demonstrate the flexibility this gives Knot over alternative DNS servers. This also gives you plenty of opportunities to shoot yourself in the foot when it comes to breaking things, and reducing stability and performance. That being said, this setup should work just fine unless you’re running a large scale deployment of Knot Resolver.

Why randomize DNS resolvers

DNS forwarders get a lot of information about the websites you visit and your habits. You send them the addresses of every website you visit and your apps and devices share details about themselves with your DNS forwarder.

You can reduce the ability of your DNS provider to build a complete profile of your internet activities by using more than one provider. Using multiple providers also encourages innovation and competition in the DNS market.

Neither Quad9 and Cloudflare — the two DNS over TLS providers we’ll use later in the examples configurations in this article — didn’t exist at the beginning of . Yet both offer better services than what existed previously. Both promises to not collect any data, store any logs, and provide their services free-of-charge to the internet community.

We didn’t have the additional security and privacy offered by DNS over TLS either as there weren’t anyone who offered implementations and services.

Configuration

To do anything randomly, you’ll need a source of entropy. The following configuration example loads the math module in Lua and seeds the generator with the current time. We don’t need a high level of entropy to make a random server selection, so this will suffice.

require 'math'
math.randomseed(os.time())

Next, we’ll need to overcome some of Knot Resolver’s constraints. The policy.TLS_FORWARD action is limited to no more than four IP addresses to DNS resolvers. We’ll still use this action, but populate it from a table containing tables of up-to- four primary and secondary IPv4 and IPv6 addresses for the public DNS resolvers we’ll be using.

The below configuration example shows such a table of tables with Quad9 and Cloudflare Resolver:

-- SPDX-License-Identifier: CC0-1.0

tls_bundle='/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem'

dns_providers = {
  { -- Quad9
    {'9.9.9.9',
     hostname='dns.quad9.net', ca_file=tls_bundle},
    {'149.112.112.112',
     hostname='dns.quad9.net', ca_file=tls_bundle},
    {'2620:fe::fe',
     hostname='dns.quad9.net', ca_file=tls_bundle},
  },
  { -- Cloudflare Resolver
    {'1.1.1.1',
     hostname='cloudflare-dns.com', ca_file=tls_bundle},
    {'1.0.0.1',
     hostname='cloudflare-dns.com', ca_file=tls_bundle},
    {'2606:4700:4700::1111',
     hostname='cloudflare-dns.com', ca_file=tls_bundle},
    {'2606:4700:4700::1001',
     hostname='cloudflare-dns.com', ca_file=tls_bundle},
  }
}

All the root certificates needed to establish DNS over TLS connections is stored in the system-provided TLS CA bundle. I stored this in a variable to reduce repetition. See the last section of the previous configuration example for more details.

You can find a list of free-of-charge public resolvers in the DNS Privacy Project wiki if you wish to configure additional DNS resolvers.

If you prefer one provider over another, you can duplicate its row and insert it at the end of the table to increase its chance of being chosen. The following example duplicates the first row (Quad9) making it twice as likely to be used as the second row (Cloudflare Resolver.) Note that indexes in Lua start from 1 and not 0.

table.insert(dns_providers, dns_providers[1])

Now that we’ve got a source of randomness and a table of DNS resolvers, all that remains is to make random selections from the table and use the selection to resolve DNS.

The following function adds a policy that uses a custom filter which chooses a random DNS resolver from our table and returns a policy.TLS_FORWARD action which uses said resolver.

policy.add(function (request, query)
  return policy.TLS_FORWARD(dns_providers[math.random(1, #dns_providers)])
end)

This policy replaces the policy.ALL filter we used in the previous configuration example. If you don’t want the selection to be entirely random, you can find an example of tuning a custom filter function based on the query in the Knot Resolver Modules documentation.