My Profile Photo

Sheogorath's Blog


Depending on the time of the day a friend, a colleague, a wise guy. The beauty of the world is its sense of humor to show humans their way by letting them search their own.


Configure DoT on systemd-resolved

I run own DNS-over-TLS (DoT) and DNS-over-HTTPS (DoH) servers at dns.shivering-isles.com for quite a while now. Finally I made it to configure my Fedora system to use these DNS servers for all DNS queries, not just the ones from the browser.

The knowledge gained by that, might be useful to others as well, therefore this article provides a little tutorial about configuring such a DoT endpoint in systemd-resolved on Fedora.

Configure systemd-resolved

First you can use this little generator script to make it easier to keep the DNS configuration up-to-date:

#!/bin/sh

RESOLVER="${1:-dns.shivering-isles.com}"

DNS_SERVERS=""

for dns in $(dig ${RESOLVER} +short && dig ${RESOLVER} +short AAAA); do
    DNS_SERVERS+="$dns#${RESOLVER} "
done

cat <<EOF
[Resolve]
DNS=$DNS_SERVERS
FallbackDNS=$DNS_SERVERS
Domains=~.
DNSOverTLS=yes
DNSSEC=yes
EOF

This script queries all IP addresses for dns.shivering-isles.com and appends #dns.shivering-isles.com at the end to inform systemd-resolved about the SNI name to use. As DoT also relies on x509 certificates, it’ll take the name from the remote certificate and validate it with the name provides after the #. If this is not correctly specified, DoT will simply fail due to untrusted connections.1

The output of the script is a configuration for systemd-resolved, that contains the DNS servers using DNS= and FallbackDNS=, sets them as default domain handlers using Domains=~., enables DoT using DNSOverTLS=yes and enforces DNSSEC2 using DNSSEC=yes. This makes sure that the global default is secure and actually uses dns.shivering-isles.com.

The next step is to use the configuration generator script to configure systemd-resolved:

$ sudo mkdir -p /etc/systemd/resolved.conf.d/
$ generate-dns-config.sh | sudo tee /etc/systemd/resolved.conf.d/shivering-isles-dns.conf
$ systemctl restart systemd-resolved

The directory /etc/systemd/resolved.conf.d/ can be used for drop-in configurations, allowing easier maintenance of the default configurations by the distribution. With sudo tee … you can drop the configuration into the file called shivering-isles-dns.conf and get the same output to your shell for validation. Finally you restart of systemd-resolved. If the DNS configuration ever turns out to be broken, removing the file and restarting the daemon is enough to go back to default settings.

Configure NetworkManager

To take actual advantage of this global configuration, you have to disable the per-interface configuration that NetworkManager provides. You can add another drop-in configuration, but this time for NetworkManager:

$ sudo mkdir -p /etc/NetworkManager/conf.d/
$ sudo tee /etc/NetworkManager/conf.d/01-dns.conf <<EOF
[main]
dns=none
systemd-resolved=false
EOF

dns=none tells NetworkManager to not touch /etc/resolv.conf as you still want to use the systemd-resolved service there. systemd-resovled=false tells NetworkManager to stop talking to systemd-resolved, and therefore no longer advertising any DNS settings from interface configurations to it.

The downside of this configuration is, that the DNS settings in NetworkManager are now useless. But in return all interfaces now use the global configuration in future, which is pointing at dns.shivering-isles.com.

To verify the result you can use the command resolvectl.

$ resolvectl
Global
         Protocols: LLMNR=resolve -mDNS +DNSOverTLS DNSSEC=yes/supported                                                                        
  resolv.conf mode: stub                                                                                                                        
Current DNS Server: 159.69.205.48#dns.shivering-isles.com                                                                                       
       DNS Servers: 159.69.205.48#dns.shivering-isles.com 159.69.93.12#dns.shivering-isles.com 2a01:4f8:c2c:4e81:cafe::2#dns.shivering-isles.com
                    2a01:4f8:c2c:5348::1#dns.shivering-isles.com                                                                                
        DNS Domain: ~.                                                                                                                          

Link 2 (enp0s25)
    Current Scopes: DNS LLMNR/IPv4 LLMNR/IPv6                                  
         Protocols: +DefaultRoute +LLMNR -mDNS +DNSOverTLS DNSSEC=yes/supported
Current DNS Server: 1.1.1.1                                                    
       DNS Servers: 1.1.1.1 9.9.9.9                                            
        DNS Domain: ~.                                                         

Link 3 (wwp0s20u4)
Current Scopes: none                                                       
     Protocols: -DefaultRoute +LLMNR -mDNS +DNSOverTLS DNSSEC=yes/supported

Link 4 (wlp3s0)
Current Scopes: LLMNR/IPv4 LLMNR/IPv6                                      
     Protocols: -DefaultRoute +LLMNR -mDNS +DNSOverTLS DNSSEC=yes/supported
    DNS Domain: ~.                                                         

Link 6 (virbr0)
Current Scopes: none                                                       
     Protocols: -DefaultRoute +LLMNR -mDNS +DNSOverTLS DNSSEC=yes/supported

Link 7 (virbr0-nic)
Current Scopes: none                                                       
     Protocols: -DefaultRoute +LLMNR -mDNS +DNSOverTLS DNSSEC=yes/supported

In this output you might notice that enp0s25 still has an own DNS server configured. To remove it, you can run resolvectl dns enp0s25 "".

Afterwards it should look like this:

$ resolvectl
Global
         Protocols: LLMNR=resolve -mDNS +DNSOverTLS DNSSEC=yes/supported                                                                        
  resolv.conf mode: stub                                                                                                                        
Current DNS Server: 159.69.205.48#dns.shivering-isles.com                                                                                       
       DNS Servers: 159.69.205.48#dns.shivering-isles.com 159.69.93.12#dns.shivering-isles.com 2a01:4f8:c2c:4e81:cafe::2#dns.shivering-isles.com
                    2a01:4f8:c2c:5348::1#dns.shivering-isles.com                                                                                
        DNS Domain: ~.                                                                                                                          

Link 2 (enp0s25)
Current Scopes: LLMNR/IPv4 LLMNR/IPv6                                      
     Protocols: -DefaultRoute +LLMNR -mDNS +DNSOverTLS DNSSEC=yes/supported
    DNS Domain: ~.                                                         

Link 3 (wwp0s20u4)
Current Scopes: none                                                       
     Protocols: -DefaultRoute +LLMNR -mDNS +DNSOverTLS DNSSEC=yes/supported

Link 4 (wlp3s0)
Current Scopes: LLMNR/IPv4 LLMNR/IPv6                                      
     Protocols: -DefaultRoute +LLMNR -mDNS +DNSOverTLS DNSSEC=yes/supported
    DNS Domain: ~.                                                         

Link 6 (virbr0)
Current Scopes: none                                                       
     Protocols: -DefaultRoute +LLMNR -mDNS +DNSOverTLS DNSSEC=yes/supported

Link 7 (virbr0-nic)
Current Scopes: none                                                       
     Protocols: -DefaultRoute +LLMNR -mDNS +DNSOverTLS DNSSEC=yes/supported

And that’s it. You can verify the result using Wireshark if you want. You should only see encrypted network traffic when you run:

$ resolvectl query shivering-isles.com                          
shivering-isles.com: 2a03:4000:1e:194::1       -- link: enp0s25
                     78.47.234.185             -- link: enp0s25
                     185.207.105.117           -- link: enp0s25

-- Information acquired via protocol DNS in 148.3ms.
-- Data is authenticated: yes

Consequences

All in all, this works very well so far, no more unencrypted DNS network traffic. This prevents network-based attacks on DNS as well as reducing the information exposure.3

This is an important step to more secure network communications. DNS is the backbone for basically all applications on the internet, so even when you don’t even notice the difference, in best case, it’ll help to prepare a better future internet.

Note: It is worth mentioning that this setup doesn’t work flawless with container technologies such as docker and podman, since containers will be unable to contact systemd-resolved while using network isolation. This is however solvable by providing the --dns-parameter or setting a specific, regular DNS server in the configuration. Of course, this means that the containers don’t benefit from secured DNS.

  1. If not SNI name is specified, DoT will fallback to the information it has, the IP address, to verify the certificate validity. 

  2. There are some known issues with systemd-resolved and DNSSEC at the time of writing this article. 

  3. Note that an attacker or network provider can still farm SNI headers to keep track of the websites you visit.