How to split your DNS requests when using a VPN

Posted on 14.11.2016 by Kim N. Lesmer.
This tutorial describes how you can split your DNS requests between a local DNS resolver and remote DNS resolvers in order to prevent a DNS leak when you use a VPN connection.

Normally when you set up a local DNS resolver you would keep a list of your local boxes with their associated local IP addresses and then just forward everything else to a remote resolver like OpenDNS, but this approach is ill-adviced if you're running a VPN connection on some of your computers as this setup is a security flaw that will cause a DNS leak.

If you run your entire local network through the VPN service for Internet connections then this issue isn't a problem as all requests runs through the VPN. But if you only run a single machine, like your desktop or laptop, through the VPN, while at the same time you use your local DNS resolver for DNS requests, you have created a security flaw that will cause your DNS requests to be leaked.

A DNS leak means that the true IP address of your Internet connection will be revealed to websites, despite the use of a VPN service to conceal it!

You don't have to run the entire network through the VPN connection in order to prevent DNS leakage, you can split your DNS requests between local requests and remote requests such that only the local requests goes to the local DNS resolver while the remote requests goes through the VPN connection.

Several methods exists that can split your DNS requests up between local ones and remote ones, but the easiest to set up is to use DNSMasq as a DNS splitter on the computers you run the VPN on. A positive side effect of this setup is also that you get DNS caching service on your computers.

In order for this to work correctly the best thing to do is to use a top-level domain on all your local machines, but you can also use something like .internal instead. In this setup I will assume that you are going to use a real domain, but you can change it to your liking.

If you're a programmer and you're developing online applications and have to deal with both testing and production setups, then one huge advantage of using a real domain is that you don't need to have separated setups. This will hopefully be obvious in a moment.

DNS server setup

I assume that on your local DNS server you have a list of computer hostnames with their associated IP addresses.

On Unbound it would look like this:

local-data: "foo IN A 192.168.1.2"
local-data: "bar IN A 192.168.1.3"
local-data: "baz IN A 192.168.1.4"

On DNSMasq it would look like this:

address=/foo/192.168.1.2
address=/bar/192.168.1.3
address=/baz/192.168.1.4

You need to change this setup into one that uses a top-level domain, whether you use .internal or a real domain doesn't matter, but in this example I am going to use example.com.

On Unbound it would then look like this:

local-data: "foo.example.com IN A 192.168.1.2"
local-data: "bar.example.com IN A 192.168.1.3"
local-data: "baz.example.com IN A 192.168.1.4"

On DNSMasq it would then look like this:

address=/foo.example.com/192.168.1.2
address=/bar.example.com/192.168.1.3
address=/baz.example.com/192.168.1.4

Client setup

resolv.conf

In order to avoid having to type foo.example.com every time you want to access machine foo, you can add the search parameter to your /etc/resolv.conf file. How you do that depends on whether you're manually editing /etc/resolv.conf or you have some kind of network manager handle that.

If you're using some kind of network manager you need to look into how to have it add the search parameter to /etc/resolv.conf. You cannot simply add it manually as your network manager will overwrite /etc/resolv.conf once the computer is rebooted or the connection is restarted.

On Linux Mint, Ubuntu, and several other Linux distributions the system uses the resolvconf package to control the /etc/resolv.conf file (please notice that the similarity between the names is not a mistake, resolvconf is a program while resolv.conf is a text file).

On Linux Mint for example /etc/resolv.conf is replaced by a symbolic link to /etc/resolvconf/run/resolv.conf which is dynamically generated. This is why /etc/resolv.conf contains the following warning:

DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN

On Linux Mint you can instead edit /etc/resolvconf/resolv.conf.d/base and add the search parameter there, it will then be used as a template for /etc/resolv.conf

If you manually handle /etc/resolv.conf simply edit it and make sure you have the right search parameter and that all future DNS requests goes to your local host on 127.0.0.1.

/etc/resolv.conf needs to look like this:

search example.com
nameserver 127.0.0.1

This means that all DNS requests goes to 127.0.0.1, which is were you will install DNSMasq and then have that split the requests between your local DNS resolver and a remote one.

The search parameter means that instead of writing ping foo.example.com you can simply write ping foo. The domain part example.com will then automatically be added to foo.

Once you have changed your settings you need to restart the network or simply reboot the machine.

You might consider adding the search example.com part to /etc/resolv.conf on your server box as well in case you're running other services than DNS on that box. That way you don't need to change scripts or firewall rules from foo to foo.example.com.

If you're a programmer and you're running with different setups on a production system and a development system the benefit is that you don't need to change configuration options in your different files.

I'll use PHP as an example.

// Production setting.
$hostname = 'foo.myproduction.com'

// Development setting.
$hostname = 'foo.testing.internal'

With the search parameter in place all you need is:

$hostname = 'foo'

If the application is running on the production machine myproduction.com will automatically be added to foo while testing.internal will be added to foo on the development machine.

DNSMasq

Now you have to install dnsmasq. On Debian based distributions dnsmasq exists in the repositories so you can use apt-get to install it.

# apt-get install dnsmasq

Once it has been installed you need to edit /etc/dnsmasq.conf and enter your local DNS resolver and the remote ones you want to use. You need to make sure that your local DNS resolver comes first. In this case I am assuming that your local DNS resolver runs on 192.168.1.1.

# Local DNS resolver.
server=/example.com/192.168.1.1
# OpenDNS remote resolvers.
server=208.67.222.222
server=208.67.222.220

Next you need to restart dnsmasq.

# /etc/init.d/dnsmasq restart

Or if you're using systemd:

# systemctl restart dnsmasq

Now, whenever you're making a DNS request all local requests will first be added the top-level domain example.com to the hostname and then forwarded to your local DNS resolver on 192.168.1.1.

You can test this by using dig or nslookup. If you're using dig you need to know that it does not use the search list from /etc/resolv.conf by default so you need to use it like this dig +search foo in order to test.

$ dig +search foo

;; ANSWER SECTION:
foo.example.com. 3461   IN  A   192.168.1.2

;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)

Or with nslookup.

$ nslookup foo

Server:     127.0.0.1
Address:    127.0.0.1#53

Non-authoritative answer:
Name:   foo.example.com
Address: 192.168.1.2

In both cases the foo hostname is added to the example.com domain part automatically and the request then goes to 127.0.0.1 which forwards the request to your local DNS resolver.

If you try to resolve a hostname that doesn't exists in your local DNS resolver you will get an empty reply like this:

$ dig +search bob
;; QUESTION SECTION:
;bob.               IN  A

Now comes the important part. In order to verify that your remote DNS requests goes to your remote resolvers through your VPN connection you need to listen to your VPN tunnel interface and verify it at that end. You can do that with tcpdump.

On Debian based distributions tcpdump exists in the repositories so you can use apt-get to install it.

# apt-get install tcpdump

In this case I am assuming that your VPN connection runs on the tun0 interface, but you have to check your VPN settings.

Open up a console and perform a tcpdump on tun0 as root:

# tcpdump -i tun0 -n

Next, open up another console and perform a dig for say yahoo.com:

$ dig yahoo.com

On the console where you're performing the dig you will notice that the request still goes to DNSMasq on 127.0.0.1, but the console with the tcpdump will reveal that the request is actually forwarded to OpenDNS via your tun0 VPN interface.

IP 10.0.10.3.27248 > 208.67.222.222.53: 15420+ [1au] A? yahoo.com. (38)

The IP 10.0.10.3 is the IP address for the tun0 interface and the 208.67.222.222 IP address is the main DNS resolver at OpenDNS.

If you have made a mistake or something isn't working right and the request goes to your local DNS resolver, the tcpdump will stay quiet and you won't be able to see the request. If this is the case you need to verify your DNSMasq setup.

If your VPN service has their own DNS servers it is maybe preferable to use theirs, but using other remote DNS servers isn't a problem because all remote DNS requests still run through the VPN interface.

That's it!

Feel free to email me any suggestions or comments.