DNS Leaks with Network Namespaces


If you use a Virtual Private Network (VPN) in a network namespace, your DNS requests may be leaked to your DNS provider if you use systemd-resolvd as a DNS resolver. These DNS requests can expose the sites you are visiting or your general geographic location whilst using your VPN.

In general, a DNS leak is when DNS requests that should be sent to your VPN provider for name resolution are sent to your otherwise regular DNS provider. Tools such as DNS Leak Test can show you who is resolving your DNS requests. For a properly setup and secured VPN, the only servers responding to your DNS requests should be owned by your VPN provider.

When running a system-wide VPN, DNS leaks may be prevented by simply ensuring your system is using your VPN provider’s DNS servers. Generally this means checking NetworkManager or /etc/resolv.conf points to the right IP addresses.

However, as mentioned in my Wireguard article, a network namespace may be used to run your VPN in a parallel and isolated networking stack such that only certain applications may use the VPN. Essentially, when an application is launched in that network namespace, the sole way for that application to access the network is through the VPN’s tunneling network adapter. So if the application can only access the network via the VPN how do DNS requests bypass it? Afterall, DNS requests are just simple network requests.

To understand, we must first discuss how an application resolves a host or domain name. Generally speaking, on GNU/Linux systems, most applications use the GNU C (glibc) library at some point to resolve host and domain names. The GNU C library decides how to resolve that name by reading the Name Service Switch configuration file at /etc/nsswitch.conf.

Below is an example of /etc/nsswitch.conf configuration file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
passwd: files mymachines systemd
group: files mymachines systemd
shadow: files
publickey: files
hosts: files mymachines myhostname resolve [!UNAVAIL=return] dns
networks: files
protocols: files
services: files
ethers: files
rpc: files
netgroup: files

In this file we see many name categories (passwd, group, shadow, hosts, etc.) and then a list of name resolution methods (files, mymachines, dns, etc.) for each category.

Of interest is the hosts name category. The hosts name category tells the GNU C library how to resolve host or domain names.

In this example there are 5 methods the GNU C library will try, in sequence, to resolve a host or domain name. They are:

  1. files, resolve a host or domain name using /etc/hosts
  2. mymachines, if the host name is for a container registered with systemd-machined, resolve the IP of that container
  3. myhostname, resolve the requesting system’s host name
  4. resolve, resolve the host or domain name using systemd-resolvd
  5. dns, resolve the domain name using the DNS servers in /etc/resolv.conf

Take note of resolve, this method resolves the host or domain name using systemd-resolvd. It accomplishes this by making a name resolution request to the systemd-resolvd daemon over D-Bus. D-Bus is an inter-process communication mechanism (a method by which two independant process can communicate) that is not isolated by a network namespace.

Therefore, what is happening is that inside the network namespace an application makes a name resolution request. The files, mymachines, and myhostname methods fail to resolve the name, so we reach the resolve method. The request is made to systemd-resolvd over D-Bus. Since systemd-resolvd is not running in the network namespace it resolves the domain name using the DNS servers in /etc/resolv.conf over your unencrypted internet connection.

Aha! Now how do we solve this?

This is actually pretty simple to solve. Within a network namespace, the file /etc/netns/<name>/nsswitch.conf is bind mounted to /etc/nsswitch.conf. Essentially, applications running in the network namespace will see the contents of /etc/netns/<name>/nsswitch.conf when reading /etc/nsswitch.conf. So simply copy your /etc/nsswitch.conf file to /etc/netns/<name>/nsswitch.conf where <name> is the name of your network namespace, and remove the resolve [!UNAVAIL=return] method in the hosts category. Now name resolution will fallback to the dns method which uses the DNS servers in /etc/resolv.conf to resolve the name and performs this resolution in the application process within the network namespace!

Below is a corrected /etc/netns/<name>/nsswitch.conf configuration file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
passwd: files mymachines systemd
group: files mymachines systemd
shadow: files
publickey: files
hosts: files mymachines myhostname dns
networks: files
protocols: files
services: files
ethers: files
rpc: files
netgroup: files

But wait! What if /etc/resolv.conf contains your ISP’s DNS servers? This can still leak your requests even if they come from a VPN. For example, your ISP can see that you have one connection to your VPN server, and that server is sending DNS requests to your ISP. Theoretically, your requests could be correlated through timing. Not ideal.

We can solve this the same way we solved the previous problem. Within a network namespace, the file /etc/netns/<name>/resolv.conf is bind mounted to /etc/resolv.conf. So simply create /etc/netns/<name>/resolv.conf where <name> is the name of the network namespace, and populate it with your VPN provider’s DNS servers. Alternatively, sufficiently large and public DNS servers such as or Cloudflare’s 1.1.1.1 can be used if your VPN provider does not provide DNS servers.

Below is an example /etc/netns/<name>/resolv.conf file:

1
2
# Cloudflare DNS
nameserver 1.1.1.1

With these modifications, your DNS requests will not be leaked inadventently when using a VPN within a network namespace.