Denis Machard

My technical gists

Infrastructure architect by profession but always consider himself as a developer and an open source enthusiast.
@github @mastodon @rss

Route incoming UDP/TCP DNS queries to a pool of DoH servers with DNSdist

The goal of this tutorial is to send all your local DNS queries (udp/tcp) to a pool of DoH public resolvers with dnsdist load balancer. A dns cache is also enabled to optimize the traffic. We also assume you have a docker environement to follow this tutorial.

Prepare configuration

Before to start, install some useful python tools to prepare the configuration

pip install dnsdist_console j2cli

Now, we will create the configuration from the jinja template dnsdist-doh.j2 provided below. Download the template on your host before to continue.

The following variables need to be exported:

  • DNSDIST_PWD
  • DNSDIST_POOL

Update the list of resolvers and the password according to your needs:

export DNSDIST_PWD="superpassword" \
export DNSDIST_POOL='{"resolv": [{"addr": "8.8.8.8:443", "name": "dns.google"},{"addr": "1.1.1.1:443", "name": "cloudflare-dns.com"}]}'
export DNSDIST_API_KEY=$(python3 -c "from dnsdist_console import HashPassword as H;print(H().generate(\"$(echo $DNSDIST_PWD)\"))") \
export DNSDIST_WEB_KEY=$DNSDIST_API_KEY \
export DNSDIST_CONSOLE_KEY=$(python3 -c "from dnsdist_console import Key;print(Key().generate())")

Use the j2 command to generate the custom configuration

echo $DNSDIST_POOL | j2 --format=json dnsdist-doh.j2 > dnsdist-doh.lua

The configuration template is just provided for example, your can updated-it according to your needs. This config can be used to send all your local DNS queries to a pool of public resolvers.

Deploy dnsdist container

Deploy the dnsdist image with the custom DoH configuration

docker run -d -p 53:53/udp -p 53:53/tcp -p 8083:8083 --restart unless-stopped --name=dnsdist \
--volume=$PWD/dnsdist-doh.lua:/etc/dnsdist/conf.d/dnsdist.conf:ro powerdns/dnsdist-17:1.7.0-alpha2

Check if the container is running properly

 docker ps
CONTAINER ID   IMAGE                   COMMAND                  CREATED         STATUS         PORTS                                                                      NAMES
8ff7d92ddd1d   powerdns/dnsdist-17:1.7.0-alpha2     "/usr/bin/tini -- /u…"   2 seconds ago   Up 2 seconds   0.0.0.0:53->53/tcp, 0.0.0.0:8083->8083/tcp, 0.0.0.0:53->53/udp, 5199/tcp   dnsdist

Test DNS resolution

# dig @::1 www.google.fr +tcp +short
216.58.215.35

# dig @127.0.0.1 www.google.fr +short
216.58.215.35

Jinja configuration template

---------------------------------------------------
-- Administration
---------------------------------------------------

-- cli
setKey("{{ env("DNSDIST_CONSOLE_KEY") }}")
controlSocket("127.0.0.1:5199")
addConsoleACL("127.0.0.1/8, ::1/128")

-- api
webserver("0.0.0.0:8083")
setWebserverConfig({
  apiKey = "{{ env("DNSDIST_API_KEY") }}",
  password = "{{ env("DNSDIST_WEB_KEY") }}",
  acl = "192.168.0.0/16",
})

---------------------------------------------------
-- Dns services
---------------------------------------------------

-- udp/tcp dns listening
setLocal("0.0.0.0:53", {})

-- dns caching
pc = newPacketCache(10000, {})

---------------------------------------------------
-- Pools
---------------------------------------------------

pool_resolv = "resolvers"
caStore = "/etc/dnsdist/conf.d/tls-ca-bundle.pem"

-- members definitions
{% for res in resolv %}
newServer({
  address = "{{ res["addr"] }}",
  tls = "openssl",
  dohPath = "/dns-query",
  caStore = caStore,
  subjectName = "{{ res["name"] }}",
  validateCertificates = false,
  pool = pool_resolv,
})

{% endfor %}

-- set the load balacing policy to use
setPoolServerPolicy(roundrobin, pool_resolv)

-- enable cache
getPool(pool_resolv):setCache(pc)


---------------------------------------------------
-- Rules
---------------------------------------------------

-- matches all incoming traffic and send-it to the pool of resolvers
addAction(
  AllRule(),
  PoolAction(pool_resolv)
)
propulsed by hugo and hugo-theme-gists