dnsmasq & WireGuard

August 4, 2021
dnsmasq - VPN - Ubuntu - WireGuard

WARNING: This post is over a year old. Some of the information this contains may be outdated.

I tried to build a WireGuard Hub in Ubuntu 20.04, but this time considering the performance of DNS forwarding, I decided to build a dnsmasq on the Hub, so that the DNS resolution results can be cached, which can improve access performance.

Ubuntu (since Ubuntu 12.04.) no longer installs dnsmasq by default , but relies on systemd-resolve to coordinate and manage the DNS resolution of the machine. I only use Ubuntu as the server, so I usually read the DNS list of the VPS or cloud service provider directly, kill systemd-resolve, and install dnsmasq to take over the resolution.

I only want dnsmasq to forward the DNS requests of the WireGuard interface. So I encountered a strange problem. When I started dnsmasq, the local DNS resolution failed. dnsmasq is configured as follows

no-resolv
no-hosts

cache-size=10000
strict-order

server=192.168.15.1
listen-address=192.168.14.11
#bind-dynamic
bind-interfaces
interface=wg0
except-interface=lo

Observing carefully, it turns out that by default, nameserver=127.0.0.53 in resolve.conf, and after dnsmasq is started, it becomes 127.0.0.1. But my dnsmasq did not take over 127.0.0.1 at all.

Checked the settings of systemd-resolve, there are no related options.

Looking back at resolvconf, I found the code to update resolv.conf in /etc/resolvconf/update.d/dnsmasq. However, due to system integrity and maintainability considerations, this script must be immovable, and this script does not provide any method to terminate the execution. You can only find it elsewhere.

dnsmasq is called by me to start and stop in WireGuard Up/Down. So look at the startup script /etc/init.d/dnsmasq. There is a start_resolvconf method inside.

start_resolvconf()
{
# If interface "lo" is explicitly disabled in /etc/default/dnsmasq
# Then dnsmasq won't be providing local DNS, so don't add it to
# the resolvconf server set.
        for interface in $DNSMASQ_EXCEPT
        do
                [ $interface = lo ] && return
        done

# Also skip this if DNS functionality is disabled in /etc/dnsmasq.conf
        if grep -qs '^port=0' /etc/dnsmasq.conf; then
                return
        fi

        if [ -x /sbin/resolvconf ] ; then
                echo "nameserver 127.0.0.1" | /sbin/resolvconf -a lo.$NAME
        fi
        return 0
}

Obviously, the problem can only be solved if the first for block can exit. Neither of the following ifs can be touched.

The solution is to add

DNSMASQ_EXCEPT=no 

in /etc/default/dnsmasq.

In addition, there is an IGNORE_RESOLVCONF in this script. Adding it alone is useless. I added both.

~# cat /etc/wireguard/scripts/wg0-postup.sh 
#!/bin/bash

# 。。。。。。。

echo "IGNORE_RESOLVCONF=yes" > /etc/default/dnsmasq
echo "DNSMASQ_EXCEPT=lo" >> /etc/default/dnsmasq

/etc/init.d/dnsmasq start

exit 0 

Update 8/7/2021 :

I explored networking features to spoof packets from the socket owned by WireGuard. Jordan Whited have post about "WireGuard Endpoint Discovery and NAT Traversal using DNS-SD" in section Doubling down on WireGuard have idea to use SRV record and abou CoreDNS (a plugin-based DNS server written in Go). CoreDNS plugin that takes DNS-SD queries and returns information about associated WireGuard peers.

This post based on post in sskaje blog.

Photo by Ferhat Deniz Fors on Unsplash.