Workshop | 0. Access | 1. Resolver | 2. Authoritative | 3. Yet Another Zone | 4. Resiliency | 5. Validation | 6. Signing | 7. SoftHSM | 8. OpenDNSSEC | 9. DNS privacy

DNS privacy lab

In this exercise we will have a look at the privacy implications of DNS resolution and will learn how to harden our DNS set-up against some of these issues.

Resolver to authoritative name server privacy

We will first have a look at the data that is exposed in DNS transactions between the DNS resolver and the authoritative name server.

QNAME minimisation

Unbound already has a privacy feature enable by default. To get a better understanding of the potential privacy impact of DNS transactions we will disable this feature for now:

  1. Disable QNAME minimisation support in your Unbound configuration: qname-minimisation: no

We will use the logging functionality in Unbound to display all the outgoing query information. Unbound has to be configured with a verbosity of 3 or higher to log all outgoing queries.

  1. Restart your Unbound instance to make sure the cache is empty
  2. Monitor the Unbound log output for all outgoing queries (hint, grep for sending)
  3. Send a DNS query to Unbound (replace <team-number> with the number of your team): drill @res-<team-number>
  4. Observe which information from the query is exposed to which upstream name servers.

We will now repeat this exercise with QNAME minimisation enabled.

  1. Enable QNAME minimisation support in your unbound configuration: qname-minimisation: yes
  2. Restart your Unbound instance again to make sure the cache is empty
  3. Send the same DNS query to Unbound: drill @res-<team-number>
  4. Observe what information from the query is exposed to which upstream name servers. What are the differences?


Another way to limit the queries that Unbound has to send upstream is by loading authoritative DNS data into Unbound. In this exercise we will do this for the root zone.

  1. Add this to your Unbound configuration to have it transfer the root zone into the resolver, having this data already in the resolver means that we don’t have to send DNS queries to get it anymore:
     name: "."
     master:         #
     master:          #
     master:          #
     master:          #
     master:         #
     master:         #
     master:         #
     master:         #
     master: 2001:500:200::b      #
     master: 2001:500:2::c        #
     master: 2001:500:2d::d       #
     master: 2001:500:2f::f       #
     master: 2001:500:12::d0d     #
     master: 2001:7fd::1          #
     master: 2620:0:2830:202::132 #
     master: 2620:0:2d0:202::132  #
     fallback-enabled: yes
     for-downstream: no
     for-upstream: yes
  1. Restart Unbound and again send the same query: drill @res-<team-number> How do the queries that are send to the root name servers with the auth-zone compare to the earlier queries for the same domain name?

Aggressive NSEC

Yet another (and complimentary) way to reduce the number of outgoing queries is by using aggressive NSEC. Aggressive NSEC is at the moment disabled by default in Unbound. We will first use this default to observe the traffic without aggressive NSEC.

We first need to get some NSEC records in our cache.

  1. Send a query for a non-existing domain. Observe the returned NSEC records.

    drill -D @res-<team-number>

    For above query this is one of the NSEC records we get back: 3600    IN  NSEC
  2. Send a query for another domain that is covered by this NSEC record:

    drill -D @res-<team-number>

    If you now look at your Unbound log you will see that for both queries Unbound will contact the name server.

  3. Enable aggressive NSEC in your Unbound configuration:

    aggressive-nsec: yes
  4. Restart Unbound and send the same two queries:

    drill -D @res-<team-number>
    drill -D @res-<team-number>

    Can you see a difference in the queuries that are send to the name servers?


We will use Stubby to proxy the DNS queries originating from our laptop over an encrypted channel to Unbound.

  1. Install stubby on your laptop

    Have a look at for detailed installation instructions.

    Although the default stubby configuration is already privacy aware, we will start with an empty configuration file to get a better understanding of all the different options.

  2. Create a stubby configuration file names stubby-bangkok.yml and add this configuration:

      - 0::1
      - address_data:
    edns_client_subnet_private: 0

    Stubby will now listen on and ::1 for DNS queries and send these queries upstream to our test resolver ( If your laptop already has a process listening on port 53 for this address try to use another local address, like

TODO: edit ACL for ecs resolver.

  1. Run stubby:

    stubby -C stubby-bangkok.yml -l
  2. Test your configuration by sending a DNS query to stubby:

    drill @

EDNS client subnet, client information exposure

In the previous example we configured stubby to send all queries to a resolver that sends ECS information in queries going to the name servers. We are now going to look at the privacy implications of ECS.

  1. Query stubby for the TXT record of the domain.

    drill @ txt

    This test domain returns the received ECS prefix as TXT record. You will now see the prefix of your IP address, even though this information is usually hidden by using the resolver!

    The ECS RFC mandates that resolvers must use the received ECS option from the query if this is available and not override it themselves. This means that is we as client send a /0 option the ECS resolver should not reveal our address. It is possible to configure stubby to add the /0 option to every outgoing query.

  2. Enable the edns_client_subnet_private option in your stubby-bangkok.yml by setting it to 1 (which is also the default):

    edns_client_subnet_private: 1
  3. Query stubby again for the TXT record of the domain.

    drill @ txt
  4. Observe the difference in the information received at the name server. Note that the google name servers will display the source address of the resolver when receiving an ECS option without useful information (like a /0 prefix).

Stub to resolver privacy

We are now going to have a look at the privacy implications between the stub and the resolver. We are going to do this using stubby and your own Unbound installation.

  1. Change the stubby configuration to send all queries to your own Unbound resolver:

      - address_data: <your-Unbound-IP-address>

    Replace <your-Unbound-IP-address> with the IP address of the machine running Unbound.

DNS traffic on the wire

To get a better understanding of the privacy implications between the stub and the resolver we will use wireshark to monitor the network traffic.

  1. Install wireshark on your local machine (
  2. Start the capture on your network interface
  3. Limit the displayed traffic to traffic that is going to and from your resolver, e.g. using the filter ip.addr==<your-Unbound-IP-address> where <your-Unbound-IP-address> the is replaced by address of your Unbound instance.
  4. Send a DNS query via stubby to your Unbound resolver:

    drill @
  5. Observe in wireshark the DNS information that is visible on the wire. This data will be visible to everybody that can somehow see the data that your machine is sending and receiving!

DNS encryption

In this part of the exercise we will encrypt the DNS traffic between stubby on our laptop and Unbound on the experimentation server. This will be done by sending all the DNS transactions over TLS (DoT).

We will start by configuring Unbound to be a TLS server. For this a TLS certificate is needed. In this exercise we will request a Let’s Encrypt certificate using certbot.

  1. Install certbot on the resolver server:

    apt install python3-certbot
  2. Request a certificate for the domain of your resolver:

    certbot certonly --standalone -d res-<team-number>

    Replace <team-number> with your team number.

    Your newly generated CA signed certificate is now available at /etc/letsencrypt/live/res-<team-number> The key matching this certificate is located at /etc/letsencrypt/live/res-<team-number> Time to tell Unbound where to find these files.

  3. Add these lines to your Unbound configuration:

    tls-service-pem: "/etc/letsencrypt/live/res-<team-number>"
    tls-service-key: "/etc/letsencrypt/live/res-<team-number>"
    port: 853

    Over TCP on port 853 only TLS connection are will be accepted by Unbound now. It is however still possible to send unencrypted queries over UDP to port 853, let’s disable UDP to the client to make sure all our queries to Unbound will be encrypted.

  4. In the Unbound configuration:

    do-udp: no
    udp-upstream-without-downstream: yes

    Now that Unbound (only) accepts DNS queries over TLS we should change the stubby configuration to use TLS for the outgoing queries.

  5. Edit you stubby configuration to always send queries over a TLS connection:


Because we requested a certificate that is signed by a trusted CA we can use the CAs store that is (probably) already on your machine for the authentication.

  1. Edit your stubby.conf to only send queries when the TLS connection is authenticated, and specify the location of the CAs you trust:

    tls_ca_path: "/etc/ssl/certs/"
  2. Edit the upstream_recursive_server stubby configuration to specify the domain name in the certificate that will be used for authentication:

     - address_data: <your-Unbound-IP-address>
       tls_auth_name: "res-<team-number>"
  3. Send a query to stubby and observe using whireshark the data on the wire between stubby and Unbound