Skip to content

DNS over TLS

ronnylov edited this page Jun 17, 2019 · 34 revisions

Wikipedia describes DNS over TLS like this:

DNS over TLS (DoT) is a security protocol for encrypting and wrapping Domain Name System (DNS) queries and answers via the Transport Layer Security (TLS) protocol. The goal of the method is to increase user privacy and security by preventing eavesdropping and manipulation of DNS data via man-in-the-middle attacks.

Normally DNS queries and answers are not encrypted which makes it possible for ISP, goverments or other people that want to spy to do man-in-the middle attacks, for instance replace or block answers from DNS servers. By using protocols with encryption it becomes much harder to do this. DNS over TLS is one way to do it. DNS over HTTPS is another way.

Stubby is an application that acts as a local DNS Privacy stub resolver (using DNS-over-TLS). Stubby encrypts DNS queries sent from a client machine (desktop or laptop) to a DNS Privacy resolver increasing end user privacy.

Unbound is a validating, recursive, caching DNS resolver. It is designed to be fast and lean and incorporates modern features based on open standards.

We are going to use Stubby in combination with Unbound - Unbound provides a local cache and Stubby manages the upstream TLS connections (since Unbound cannot yet re-use TCP/TLS connections). You can get the same result by combining Stubby with dnsmasq as described here.

Installing Stubby on debian involves compiling from source code and may be a bit complicated for normal users. Combining it with Unbound also involves some configuration. A much easier way to do it is to use precompiled docker images. Matthew Vance has developed a docker solution that sets this configuration up. You can find them on dockerhub too - Stubby and Unbound.

This allows you to run a Stubby for better DNS over TLS support than Unbound provides without losing the performance benefits of having a local caching DNS resolver.

Install Docker and Docker Compose

You need docker and docker-compose to follow this guide. If you have not already installed it you can take a look at our Docker and Docker Compose guide.

Install stubby-docker

First we create the directory structure. I make a hidden directory .docker-compose with sub-directory dns and then switch to this directory:

$ mkdir -p .docker-compose/dns
$ cd .docker-compose/dns

If you don't already have git installed you must install it:

$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo apt-get install git

Now we clone the github repository for stubby-docker. It will download the files from github.

$ git clone https://github.com/MatthewVance/stubby-docker.git

It should have created a sub-directory stubby-docker. So whe change to this directory and take a look.

$ cd stubby-docker
$ ls
docker-compose.yml  LICENSE  README.md  stubby  unbound

So the files are there. Let's look at README It say:

Run these containers with the following command:

docker-compose up -d

Next, point your DNS to the IP of your Docker host running the Unbound container.

So we try it:

$ docker-compose up -d
Creating network "stubby-docker_dns" with the default driver
Pulling stubby (mvance/stubby:latest)...
latest: Pulling from mvance/stubby
54f7e8ac135a: Pull complete
66170bcb01a5: Pull complete
5881139a0be6: Pull complete
a03aecab42b3: Pull complete
55c847a40f60: Pull complete
Digest: sha256:e93b3268e8a07b7cce38d22e8b4edfe6cd54b0087edd21692419005e601262c8
Status: Downloaded newer image for mvance/stubby:latest
Pulling unbound (mvance/unbound:1.9.1-stubby)...
1.9.1-stubby: Pulling from mvance/unbound
54f7e8ac135a: Already exists
3eccb92b6ccd: Pull complete
a70020235b32: Pull complete
87c7b21b9491: Pull complete
f8d023a3f8fb: Pull complete
4cdd69e842f9: Pull complete
7fe28bf832ab: Pull complete
Digest: sha256:31154ca953ed07cfd0b1c411f5fe745aae3f29f496691cb6f89c21ec5a408db4
Status: Downloaded newer image for mvance/unbound:1.9.1-stubby
Creating stubby-docker_stubby_1 ... done
Creating stubby-docker_unbound_1 ... error

ERROR: for stubby-docker_unbound_1  Cannot start service unbound: driver failed programming external connectivity on endpoint stubby-docker_unbound_1 (fd86825918aa736aa63edf8392b2c7f745b32ae8e95b621a1d1d1b3c57a8d9ad): Error starting userland proxy: listen udp 0.0.0.0:53: bind: address already in use

ERROR: for unbound  Cannot start service unbound: driver failed programming external connectivity on endpoint stubby-docker_unbound_1 (fd86825918aa736aa63edf8392b2c7f745b32ae8e95b621a1d1d1b3c57a8d9ad): Error starting userland proxy: listen udp 0.0.0.0:53: bind: address already in use
ERROR: Encountered errors while bringing up the project.

It downloads docker images from docker-hub and tried to start the containers. I got an error when starting unbound container because I already have unbound running on this machine. Docker is forwarding the container to localhost and it conflicts with local unbound. I could stop unbound.service but I don't want it to forward to localhost so I edit docker-compose.yml and remove ports section. First make sure container is stopped:

$ docker-compose down

Then edit docker-compose.yml file

$ nano docker-compose.yml

Made it look like this:

version: '3'
services:
  stubby:
    image: "mvance/stubby:latest"
    networks:
     - dns
    restart: unless-stopped
  unbound:
    image: "mvance/unbound:1.9.1-stubby"
    depends_on:
      - "stubby"
    networks:
     - dns
    volumes:
      - ./unbound/a-records.conf:/opt/unbound/etc/unbound/a-records.conf:ro
    restart: unless-stopped

networks:
  dns:

The difference between original is that i removed this:

    ports:
     - "53:53/udp"

These lines tells docker to forward port 53 to localhost and it cause the conflict with my unbound.service

So we try to start it again (this time it does not need to download containers, already done):

$ docker-compose up -d
Creating network "stubby-docker_dns" with the default driver
Creating stubby-docker_stubby_1 ... done
Creating stubby-docker_unbound_1 ... done

It looks like it started successfully! OK then how to use it? According to the README

Next, point your DNS to the IP of your Docker host running the Unbound container.

How do I know "the IP of your Docker host running the Unbound container"? It is possible to look it up but I want to set a specific known IP-address and to be sure it is used every time. I fiddlered around quite a bit to make this work and ended up creating a dedicated network bridge in docker for DNS servers and set fixed ip addresses for each container. All this is controlled by editing the docker-compose.yml file.

So first stop the containers docker-compose down and edit the docker-compose.yml file until it looks like this: