Stomping on resolv.conf

By Abhijit Menon-Sen <ams@toroid.org>

2011-01-10

A few weeks ago, I was asked to help solve a tricky DNS problem on a laptop that needed to connect to two openvpn networks. The first network had been set up long ago, and was outside our administrative control. It forced the client's /etc/resolv.conf to point to a nameserver that was accessible over the VPN and served a bogus .example-org TLD zone in addition to acting as a general resolver.

The problem arose when the machine was configured to connect to a new VPN. Here, clients were configured to connect to vpn.example.org. When they were in office, the DHCP-supplied nameserver would resolve that to 10.0.0.1. Outside the office, example.org's "real" nameserver would hand out the server's external IP address. This worked fine, but if the user connected to the old VPN first, the forced nameserver change meant that vpn.example.org no longer resolved to 10.0.0.1 inside office. But if we left resolv.conf unchanged, names in .example-org could not be resolved.

The solution involved many moving parts. First, I set up BIND on the laptop, and configured it to forward .example-org queries to the old VPN's nameserver.

zone "example-org" {
    type forward;
    forwarders { 172.77.66.1; };
}

Then I put "nameserver 127.0.0.1" in /etc/resolv.conf, and disabled the openvpn script that was changing resolv.conf after connecting. But then came the real problem—how to make BIND forward all other queries to the DHCP-supplied resolver, whether in office or outside? Sadly, there is no way to do this directly. BIND can be told to use a default forwarder or forwarders, but it has no way to discover which one to use.

The only thing to do is to rewrite the BIND configuration file with new forwarders (every time we get one via DHCP) and restart BIND. There is a hackish and confusingly-documented Debian program called resolvconf that can be used to do this (among many other things). When you install it, it turns resolv.conf into a symlink to /etc/resolvconf/run/resolv.conf, and runs the scripts in /etc/resolvconf/update.d when a new nameserver is discovered (e.g. when the DHCP client brings up an interface). It also rewrites resolv.conf, which I didn't want (127.0.0.1 was fine).

/etc/resolvconf/update.d/bind takes /etc/bind/named.conf.options, changes the forwarders {} section, and writes the result to /var/run/bind/named.options, which you can include in /etc/bind/named.conf instead of /etc/bind/named.conf.options. It then restarts named. I disabled the other update scripts, fixed /etc/apparmor.d/usr.sbin.named (which said /var/run/named/named.options instead of /var/run/bind/named.options, and as a result named couldn't read that file), and made /etc/resolv.conf a read-only plain old file (to discourage resolvconf from rewriting it, which it really wanted to do).

It works now, but it feels very hackish.