How to split your DNS requests when using a VPN
Published on 2016-11-14. Modified on 2019-04-02.
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 or Google, but this approach is ill-advised 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 local 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, 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 a 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.
In this example I'm going to use DNSMasq both as the local resolver for the entire LAN, and as a DNS splitter on the individual computers running on the LAN.
DNS server setup
I assume that on your local DNS server you have a list of computer hostnames with their associated IP addresses.
Whether you use .internal
or a real domain doesn't matter, but in this example I am going to use example.com
.
host-record=foo,foo.example.com,192.168.1.2 host-record=bar,bar.example.com,192.168.1.3 host-record=baz,baz.example.com,192.168.1.4
A host-record
is used for both DNS A records and for PTR records so we'll get both setup in one go.
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.
If you manage /etc/resolv.conf
manually simply edit it and make sure you have the right search
parameter so that all 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.
You might consider adding the search example.com
part to /etc/resolv.conf
on the DNS server on your LAN 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
manually.
DNSMasq
Now you have to install Dnsmasq on each computer.
On Debian based distributions you can use apt:
# apt install dnsmasq
On Arch Linux:
# pacman -S dnsmasq
Once Dnsmasq has been installed you need to edit /etc/dnsmasq.conf
and comment out this part:
# Include all files in a directory which end in .conf conf-dir=/etc/dnsmasq.d/,*.conf
Then create the file /etc/dnsmasq.d/lan.conf
and insert the following:
listen-address=127.0.0.1 server=/example.com/192.168.1.1 rev-server=192.168.1.0/24,192.168.1.1 # VPN remote resolvers. server=xxx.xx.xxx.xxx server=xxx.xx.xxx.xxx no-resolv no-hosts
You have to change the "xxx.xx.xxx.xxx" part to fit the DNS servers from you VPN provider.
You also need to make sure that your LAN DNS resolver comes first. In this case I am assuming that your LAN DNS resolver runs on a machine or gateway with the ip address 192.168.1.1.
Next you need to restart Dnsmasq:
# systemctl enable dnsmasq # systemctl start dnsmasq
Now, when you make a local DNS request the request will first be added the top-level domain example.com
to the hostname and then forwarded to your LAN DNS resolver on 192.168.1.1.
You can test 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
And you can do a reverse lookup too:
$ host 192.168.1.2 foo.example.com has address 192.168.1.2
If you try to resolve a hostname that doesn't belong to your local network you will get an empty reply like this:
$ nslookup bob Server: 127.0.0.1 Address: 127.0.0.1#53 Non-authoritative answer: *** Can't find bob: No answer
However, if you try to do a lookup on a domain on the Internet, your request will go through the remote DNS servers:
$ nslookup google.com Server: 127.0.0.1 Address: 127.0.0.1#53 Non-authoritative answer: Name: google.com Address: 172.217.20.110
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 manually.
You can do that with tcpdump
.
On Debian based distributions you can install tcpdump with apt:
# apt install tcpdump
On Arch Linux using pacman:
# pacman -S tcpdump
In this example I am assuming that your VPN connection runs on the tun0
interface, but you have to check your VPN settings.
Open up a terminal and perform a tcpdump
on tun0
as root:
# tcpdump -i tun0 udp port 53
At the same time open up another terminal and do the same with your LAN network interface:
# tcpdump -i enp3s0 udp port 53
Then open up yet another terminal and perform a DNS lookup for a domain on the Internet like yahoo.com:
$ nslookup yahoo.com
On the terminal where you're performing the nslookup
you will notice that the request goes to Dnsmasq on 127.0.0.1 as it should, but the terminal running with the tcpdump
for the tun0
interface will reveal that the request is actually forwarded to your VPN providers DNS servers:
IP 10.0.10.3.27248 > xxx.xx.xxx.xxx.53: 15420+ [1au] A? yahoo.com. (38)
The IP 10.0.10.3, in this case, is the IP address for the tun0
interface provided by the VPN server and the xxx.xx.xxx.xxx is the main DNS resolver at the VPN provider.
Next you can try a query for a local machine:
$ nslookup foo
Notice the response in the terminal with the tcpdump
for the local enp3s0
interface:
12:45:28.634272 IP bumblebee.32876 > _gateway.domain: 6674+ A? foo.example.com.
"bumblebee" is the hostname on the machine doing the nslookup
.
You need to be thorough when you validate that the right requests goes to the right DNS server.
That's it!