Enforcing correct DNS upstreams for internal zones

When you’re frequently working with internal DNS zones of a company whose DNS server sits behind a VPN, you’ll probably soon encounter DNS shenanigans where you’ll find that resolving internal domain names is a lot more tricky than it should be. I’ve found a way that works using dnsmasq, but I also found that you need to be careful to keep an overly-eager NetworkManager in check.

The problem

When you’re using a laptop that connects to many different wifi networks, which upstream DNS servers do you use? Usually it’s desirable to use the ones from the LAN you’re currently connected to, so that internal DNS names resolve properly wherever you are. Thus just configuring 8.8.8.8 as the upstream usually sucks. So you’ll need to find a way to have NetworkManager and your DNS resolver interact properly.

What works

apt-get install resolvconf dnsmasq, then create a file named /etc/dnsmasq.d/company.conf that points to the internal DNS server:

server=/internal.company.com/10.1.2.3

Now restart dnsmasq, then restart NetworkManager, connect your VPN and things should work. NetworkManager should call into resolvconf to set the upstream server when the Wifi is connected, resolvconf knows about dnsmasq and writes the changes into /run/dnsmasq/resolv.conf, and that one is included by the Debian dnsmasq configuration:

[10:36]root@mz1030-nb251:~# ps aux  |grep dnsmasq
dnsmasq    10783  0.0  0.0  14492  2532 ?        S    10:16   0:00 /usr/sbin/dnsmasq -x /run/dnsmasq/dnsmasq.pid -u dnsmasq -r /run/dnsmasq/resolv.conf -7 /etc/dnsmasq.d,.dpkg-dist,.dpkg-old,.dpkg-new --local-service --trust-anchor=something_long_and_boring

Notice the -r /run/dnsmasq/resolv.conf bit, this is the relevant part. All the name servers configured by the system will end up in this file, and the actual /etc/resolv.conf will always point directly at dnsmasq.

The resolvconf package is key in making this work, so be sure to have it installed.

What doesn’t work

Before this, I tried solving this through NetworkManager. NM also offers a dnsmasq.d directory where custom configuration files can be placed, so this looked like the perfect place to put these configs (and in fact, I found guides on the web that show to do it this way). Then you set dns=dnsmasq in /etc/NetworkManager/NetworkManager.conf and think you’re done, only to find out that the configuration does not care at all about what you configured.

It took me some digging to find out why that is, and journalctl -u NetworkManager had the answer: dnsmasq loads the config just fine, but when NetworkManager calls it via DBus to update the upstream name servers, these seem to override the configuration files and dnsmasq even explicitly logs that it is now using the new upstreams for the internal DNS zones, which is precisely the opposite of what I want it to do. Luckily I had an old laptop lying around with the config that worked.