Bind NetworkManager managed dnsmasq to device when it appears

dnsmasqnetworkmanagervirtualbox

I have couple of servers running in VirtualBox interconnected by hosted-only network. The parent system is Ubuntu running NetworkManager which manages the DNS according to the connected network so my resolv.conf contains

nameserver 127.0.1.1

The VirtualBox is configured that the parent system has address 10.1.0.1 and the virtual servers are statically configured to 10.1.0.2, 10.1.0.3….

When I change network I always find (via nmcli) my actual nameserver and fill the value to each of the virtual servers. This is annoying as I frequently change the network and connect via different interfaces (wlan0, eth0).

My idea is to use the NetworkManager managed DNS server as server for the virtuals. But when I put nameserver 10.1.0.1 in resolv.conf of the virtual servers the DNS is not resolved.

Clearly the DNS server managed by the NetworkManager is bound only to 127.0.1.1.

I tried to add listen-interface=10.1.0.1 to /etc/NetworkManager/dnsmasq.d/<my-file> it works after NetworkManager restart but only when the Virtual Box is running and the vboxnet0 interface exists. When the Virtual Box is down the dnsmasq fails to start.

I also tried use bind-dynamic option to dnsmasq but it is incompatible with bind-interface added to dnsmasq command by NetworkManager.

Question: How can I make the NetworkManager to bind dnsmasq to 10.1.0.1 after vboxnet0 interface appears? Or how to make it to start another instance of dnsmasq forwarding 10.1.0.1 request to 127.0.1.1?

Additional info:

# ifconfig
...
vboxnet0  Link encap:Ethernet  HWaddr 0a:00:27:00:00:00  
inet addr:10.1.0.1  Bcast:10.1.0.255  Mask:255.255.255.0
...

# ps aux|grep dnsmasq
nobody    1252  0.0  0.0  52976  4108 ?        S    dub10   0:00 
/usr/sbin/dnsmasq --no-resolv --keep-in-foreground --no-hosts
--bind-interfaces --pid-file=/var/run/NetworkManager/dnsmasq.pid 
--listen-address=127.0.1.1 --cache-size=0 --conf-file=/dev/null 
--proxy-dnssec --enable-dbus=org.freedesktop.NetworkManager.dnsmasq 
--conf-dir=/etc/NetworkManager/dnsmasq.d

#netstat -antp|grep ':53'
tcp 0 0 127.0.1.1:53   0.0.0.0:* LISTEN 1252/dnsmasq

# nslookup google.com 127.0.1.1
Server:     127.0.1.1
Address:    127.0.1.1#53

Non-authoritative answer:
Name:   google.com
Address: 64.233.164

# nslookup google.com 10.1.0.1
;; connection timed out; no servers could be reached

Best Answer

This can be resolved (heh) with a wrapper script for dnsmasq to replace --bind-interfaces with --bind-dynamic, but for some reason NetworkManager hardcodes its search path, so first the original binary has to be moved out of the way (on dpkg-based systems, use a command like dpkg-divert --local --rename --divert /usr/local/sbin/dnsmasq --add /usr/sbin/dnsmasq).

Then create a new /usr/sbin/dnsmasq:

#!/bin/bash

args=("$@")
for (( i=0; i<${#args}; ++i )); do
    case "${args[i]}" in
    -z|--bind-interfaces)
        args[i]=--bind-dynamic
        ;;
    --)
        break
        ;;
    esac
done

exec /usr/local/sbin/dnsmasq "${args[@]}"

Change /usr/local/sbin/dnsmasq as needed to point to the original binary, mark the script as executable (chmod +x /usr/sbin/dnsmasq), and add whatever interface= lines to a file in /etc/NetworkManager/dnsmasq.d.

Caveats:

  1. Use of /usr/local/sbin might break if NetworkManager is updated to honor the PATH environment variable. If this is a concern, move the real executable to a non-PATH location such as /usr/lib (but see #2 below).
  2. Use of a location that isn't before /usr/sbin in $PATH will cause --bind-interfaces (-z) to be replaced when manually invoking dnsmasq on the command line.