s0_Blog

Wildcard LAN DNS on OpenWRT with dnsmasq-full & authoritative zone

I've tried a few times over the years and run into dead-ends trying to get Wildcard CNAME DNS to work on my OpenWRT devices. Tonight I cracked it and it was simpler than I thought.

What do you mean, "wildcard LAN DNS"?

To be clear, I'm talking about having my router, which is the main resolving DNS server on my LAN network, return results for wildcard CNAMEs, like *.private.my.domain.

This would seem simple, and you can certainly type that into the LuCi CNAME list now. But it won't work, because of a decision in the DNSMasq codebase — wildcard CNAMEs are only permitted within what are called 'authoritative zones' or 'the authoritative domain' (a confusing use of a different meaning of domain, so I'll avoid it here).

--cname as long as the record name is in the authoritative domain. If the target of the CNAME is unqualified, then it is qualified with the authoritative zone name. CNAME used in this way (only) may be wildcards, as in

--cname=*.example.com,default.example.com

'Authoritative zones' means domain names that this instance of DNSMasq is the main DNS server for. You probably use your domain registrar, or maybe Cloudflare etc, as the authoritative DNS server for your domains. But in this case, I only need it to be authoritative within my LAN. There's no need for these domains to resolve outside of it.

In order to make this work, I need to have the DNSMasq instance run as the authoritative DNS server for the domain that I'm putting a wildcard under. I.e., for the CNAME *.private.my.domain, it must have an authoritative zone of at least private.my.domain. Now, I could make it authoritative for all of my.domain -- but I don't want to mess up the public DNS records on that one. That's why I created this private subdomain in the first place.

Step 0

Before we can actually do any of the config, we need to install the full version of DNSMasq over the default, slimmed-down version that ships with OpenWRT. This does mean you'll use some extra storage space and RAM on your device. Now, there's also a chicken-and-egg problem here. We can't have more than one version of DNSMasq installed at once, but if we uninstall the slim one first, we won't be able to resolve the package repo to download the new one!

Fortunately user mopsa on the OpenWRT forums came up with a quick fix:
Open SSH as root to your router, and run these commands to pre-download the new package, and install it from this cached file. You'll see some warnings about not replacing your config file, which can be safely ignored as we want to keep the rest of our config working as it was.

opkg update
cd /tmp/ && opkg download dnsmasq-full
opkg remove dnsmasq
opkg install dnsmasq-full --cache /tmp/
rm -f /tmp/dnsmasq-full*.ipk

Step 1

OpenWRT's /etc/config/dhcp file has most of the DNSMasq config in it, but doesn't have support for authoritative features yet. But the OpenWRT config parser in /etc/rc.d is clever enough to hide a raft of other features. For one, it pulls in /etc/dnsmasq.conf to globally configure all DNSMasq instances. If you're only running one on your LAN, this would be fine. But some of us network-touching weirdos do stuff like running multiple DNSMasq instances on multiple VLANs. And then these different instances would fight and fail when multiple tried to claim the authority over that authoritative zone, on different interfaces, that were already in use.

The key that unlocked this one was that OpenWRT's devs already saw this issue coming — it automatically pulls in config files of the format /etc/dnsmasq.{instance}.conf into only that instance's config!

So you'll want to create & edit the file /etc/dnsmasq.{instance}.conf, where {instance} is the name your DNSMasq instance is labelled with. If you only have one, you'll want to use the global file /etc/dnsmasq.conf.

# The 'glue record' goes here (domain that should point back to the interface on this device). Doesn't matter much for internal LAN DNS.
# Also optionally followed by an interface, which makes that interface authoritative-only, and it stops answering normal recursive resolver reqs.
# Leave it without an interface for LAN use!
auth-server=router.my.domain

# What's our authoritative domain? Wildcards must be below this level.
auth-zone=private.my.domain
# Who's allowed to receive authoritative records, I think.
# Leave commented out to allow any DNS clients on LAN to resolve these names.
#auth-peer=127.0.0.1,::1

Restart your DNSMasq with /etc/init.d/dnsmasq restart, and check the system logs for any indication there were issues.

Step 2

Now you can add a wildcard CNAME! You can do that by directly editing /etc/config/dhcp to add:

...
config cname
    option cname '*.private.my.domain'
    option target 'server.lan'
...

Or, do it in Luci's Network > DHCP & DNS > CNAME tab.

Check it

Try running the dig (or doggo, which is prettier), on a domain that would fall into the wildcard, but you definitely wouldn't have cached (in case you have local DNS caching). You should see the CNAME and then an A/AAAA resolution from the target domain!

> doggo ashtneoi.private.my.domain
NAME                         TYPE   CLASS  TTL   ADDRESS               NAMESERVER    
ashtneoi.private.my.domain.  CNAME  IN     593s  server.lan.           127.0.0.1:53      
server.lan.                  A      IN     593s  192.168.1.11          127.0.0.1:53      
ashtneoi.private.my.domain.  CNAME  IN     593s  server.lan.           127.0.0.1:53      
server.lan.                  AAAA   IN     477s  fe80::1:11            127.0.0.1:53

Conclusion

Now you can use this private wildcard CNAME to host all the subdomains you want on your reverse proxy server!