Back in January I updated my Secure Hotel Wi-Fi Sharing Raspberry Pi solution to the new Debian Jessie build. Unfortunately the way in which the system decided which network interface to use for DNS lookups had changed and DNS lookups were no longer reliably sent through the VPN tunnel. As a quick fix I changed the DHCP settings in the Raspi Server to instruct all Wi-Fi clients to circumvent the Raspi’s DNS server and instead go to a public DNS server immediately. Not a pretty solution but it did it’s job. Recently, I had a bit of time to investigate a bit further and came up with a better solution.
So What’s The Problem With Going To A Public DNS Server Directly?
Circumventing the local DNS server on the Raspberry Pi Wi-Fi/VPN gateway did its job but had two disadvantages:
Firstly, if used at a hotel that blocks public DNS access before a username and password is entered on a landing page it’s not possible to get to the hotel’s landing page in the first place.
And secondly, once the VPN link is established, going to a public DNS resolver circumvents the DNS resolver of the VPN server. While this works for the majority of cases it is a problem if there are servers in the VPN server’s network that the client wants to reach. Without the VPN tunnel, they would be accessed via the Internet and Network Address Translation, i.e. their IP address and the IP address of the VPN server are the same. Once a VPN tunnel is established, the VPN client gateway can no longer route packets to that IP address as it is used as the VPN tunnel server endpoint and hence the servers are no longer reachable. As a consequence, a DNS server at the VPN tunnel server side has to return local IP addresses (192.168.x.x) for local servers that can be routed through the VPN tunnel instead of the global IP address under which those servers are usually reachable.
DNS Selection
When Raspian upgraded its foundation from Debian Wheezy to Debian Jessie, the algorithm that decided which DNS server to query seems to have changed and requests were no longer only going through the VPN tunnel. The program keeping track of available DNS servers is resolvconf. In an off-the shelf Raspbian installation the program is not installed as most people usually only have one network interface anyway and DNS server IP addresses are simply put into /etc/resolv.conf. For the Wifi/VPN gateway application, however, availability of resolvconf is a requirement of OpenVPN or Dnsmasq so it is installed alongside. Dnsmasq is required because resolvconf only keeps track of available DNS servers but does not forward DNS requests to those servers itself.
The decision which DNS server to ask for domain name resolution is simple if there is only one network interface. Once there is a VPN tunnel, however, there are two network interfaces that have access to one or more DNS servers on the other end, i.e. over the physical Wi-Fi or Ethernet interface directly and through the VPN tunnel interface. Resolvconf is made aware of DNS servers on both interfaces but unfortunately Raspbian (based on Debian Jessie) no longer ensures that only the DNS servers available through the VPN tunnel must used to prevent DNS request leakage outside the VPN tunnel.
Resolvconf
Two commands come in handy to check which DNS servers are available on the VPN Client Gateway:
resolvconf -l # list DNS servers available resolvconf -i # list network interfaces with DNS servers
To make life more complicated there seem to be several implementations of resolvconf. While Raspbian (based on Debian Jessie) uses the BSD variant that has those options implemented, Ubuntu 14.04 (based on Debian Wheezy) uses a different implementation which does not.
Circumventing Resolvconf When The Tunnel Is Up
Resolvconf only administrates the DNS server IP addresses that can be used while dnsmasq is used as a DNS server on the Raspberry VPN Client Gateway which in turn forwards requests to the external DNS server. By default, dnsmasq uses the list of DNS servers assembled by resolvconf. To ensure all DNS requests go through the VPN tunnel, however, dnsmasq can be instructed with the ‘no-resolv‘ option to only use DNS servers defined in its own configuration file in /etc/dnsmasq.conf. Here is how this looks like:
server=10.8.0.1 # activate no-resolve to only use the DNS server above # to prevent DNS leakage outside the VPN tunnel no-resolv
If no VPN tunnel is established, dnsmasq has to be reconfigured to use the DNS server list provided by resolvconf as the DNS server configured statically above is not reachable anymore:
server=8.8.8.8 # use this DNS server if resolvconf # offers no other option
Note that 8.8.8.8 will only be used by dnsmasq as DNS server if the DNS server IP addresses provided by resolvconf are not working.
How To Switch the Dnsmasq Configuration
There are several possibilities to switch the dnsmasq configuration. From an architectural point of view the best option would be to use interface up/down scripts that can be run when OpenVPN establishes and tears down a tunnel. Here, it would even be possible to extract the DNS server IP addresses provided by the VPN server and insert it into the Dnsmasq configuration. It is easy, however, to forget the up/down script invocation in OpenVPN configuration files. Things would still work but DNS requests would not be transported through the tunnel. In other words this is a risky approach.
I therefore decided to change the Dnsmasq configuration and restart the service in the start and stop scripts that establish and tear down the OpenVPN tunnels (start-vpn.sh and stop-vpn-common.sh) in the Pi’s home directory. This has the disadvantage that the DNS server IP addresses that must be used while the VPN tunnel is established have to be configured manually. For most people (who do not use local servers as described above), a public DNS server (such as 8.8.8.8) is the right choice. The advantage of the approach is, however, that omitting the up/down scripts from the OpenVPN configuration does not result in DNS leakage outside the tunnel.
Yes, I wished things would be more straight forward but at least I learned a lot while coming up with this solution and I hope sharing this might be useful to some of you, too.