AdGuard Home as an optional resolver alongside dnsmasq
This page describes an arrangement for running AdGuard Home (AGH) where dnsmasq keeps operating independently as the primary resolver with AGH as an optional, opt-in resolver that individual VLANs or clients can be pointed at as desired.
You should also read AdGuard Home for more details on AGH itself, as well as the traditional installation approach. In summary, that approach is to install AGH as the 'primary' resolver, in turn pointing to dnsmasq for local names. (Slightly more detail: move dnsmasq aside to a port other than :53, run AGH on :53, then configure AGH to forward local names to dnsmasq at its new home, and finally configure dnsmasq to forcibly advertise the non-dnsmasq dns-server via DHCP.)
This works fine right up until you upgrade, when the AGH package is not installed yet. It also is a little inflexible (for example you may not want/need adblocking from all devices on the network or the openwrt host itself).
Switching things around, with dnsmasq up front and AGH following on, is also fragile in that DNS will still break when AGH's not installed (and you won't be able to install it, as you won't be able to resolve the openwrt package servers! Yes, trivially fixable but annoying nonetheless). And you also lose the AGH per-client config/reporting as everything to AGH is coming via dnsmasq/localhost.
Whilst we're here, there are even more ways of using AGH (each with drawbacks):
- configuring dnsmasq with AGH as an upstream and additional servers; this can certainly “work” but it can also be the source of tearing your hair out. Amongst other issues, it'll only work when strict-order is set, otherwise you'll get random behaviour; you'll also suffer timeouts when the “primary” upstream is down and that's unavoidable. Sane DNS configuration entails every upstream server being consistent in that they should all give functionally the same answer to the same requests; that's clearly (intentionally) not the case for AGH and any public DNS server.
- configure AGH on any unused port and use firewall rules to direct clients to it; i.e. client thinks it's asking the DNS server on :53 but it's redirected to AGH on :54
DNS via IPv6: if you run a dual IPv4/v6 local network, you'll be saving yourself a bit of grief if you only run AGH via IPv4. Your local clients, and AGH, will happily resolve IPv6 addresses (AAAA DNS records) via DNS over IPv4. Put simply: none of the Pv4-only config below hampers a perfect 10/10 IPv6 support score. The complexity with running an IPv6 DNS server comes down to inflexibility: IPv4 DHCP, especially on OpenWRT using dnsmasq, allows all sorts of options; vs. IPv6 which is far less flexible even with DHCPv6 (odhcp6c) and if you're using SLAAC only, you flat out can't do much at all re. selectively advertising anything. This inflexibility is a-ok if you're dedicating a whole subnet / VLAN to AGH, problems arise when you want a mixed bag of clients (as with the dhcp tagging option below).
One more thing: at least some Android devices (incl. Samsung OneUI 8) require two DNS servers to be configured. If the DHCP server only delivers one the phone will add 8.8.8.8 as the second. This will cause some of your DNS traffic to be sent to Google instead of AGH. The implementation below adds an alias for the second DNS server IP.
Below is an arrangement that essentially makes AGH “optional”, leaving the dnsmasq config operating independently of AGH for localhost. In my case I have AGH enabled for one VLAN with the others unfiltered.
The general approach is to:
- create a dummy interface (akin to loopback), excluded from dnsmasq
- run AGH on that
- configure dnsmasq to deliver the dummy's IP as the DHCP DNS server, for the VLANs you want to use AGH on by default
- clients can easily opt-out by using the router's regular interface address
1 - dummy interface
- pick any unused IP address in your network, I've used a tiny /30 with 192.168.254.254 and .253
apk add kmod-dummy echo 'options dummy numdummies=1' >> /etc/modules.conf echo '/etc/modules.conf' >> /etc/sysupgrade.conf uci set network.dummy=interface uci set network.dummy.proto='static' uci set network.dummy.device='dummy0' uci set network.dummy.ipaddr='192.168.254.254/30' uci set network.@alias[-1].interface='dummy' uci set network.@alias[-1].proto='static' uci set network.@alias[-1].ipaddr='192.168.254.253/30' uci commit network /etc/init.d/network restart
optional, give it a name (note, can't create both v4+v6 via LuCI, UI only supports one address?):
cat <<EOF >> /etc/hosts 192.168.254.254 agh EOF
- tell dnsmasq to ignore the dummy interface:
uci add_list dhcp.@dnsmasq[0].notinterface='dummy' uci commit dhcp /etc/init.d/dnsmasq restart
2 - configure AGH
- in
/etc/adguardhome/adguardhome.yaml, setbind_hoststo be only the above dummy IP address(es), e.g.
dns:
bind_hosts:
- 192.168.254.254
- 192.168.254.253
port: 53
- configure AGH's upstream servers to whatever you want to use for Internet names, and localhost (default :53, dnsmasq) for your local domain and unqualified names:
tls://dns.google [/home/]127.0.0.1 [//]127.0.0.1
- (re)start it:
service adguardhome restart
- verify you have both dnsmasq and AGH running, each to their own interfaces, and AGH's working:
# netstat -plan | grep ':53.*LISTEN' tcp 0 0 192.168.1.1:53 0.0.0.0:* LISTEN 5195/dnsmasq tcp 0 0 192.168.254.254:53 0.0.0.0:* LISTEN 5160/AdGuardHome tcp 0 0 192.168.254.253:53 0.0.0.0:* LISTEN 5160/AdGuardHome ... tcp 0 0 127.0.0.1:53 0.0.0.0:* LISTEN 5195/dnsmasq ... tcp 0 0 ::1:53 :::* LISTEN 5195/dnsmasq tcp 0 0 fd00::254:254:53 :::* LISTEN 5160/AdGuardHome ... # nslookup google.com agh Server: agh Address: 192.168.254.254:53 Non-authoritative answer: Name: google.com Address: 172.217.25.174 Non-authoritative answer: Name: google.com Address: 2404:6800:4006:806::200e
3 - tell dnsmasq to deliver AGH as the DNS server via DHCP on your desired interfaces:
uci add_list dhcp.lan.dhcp_option="6,192.168.254.254,192.168.254.253" uci commit dhcp /etc/init.d/dnsmasq restart
Or, instead of configuring AGH for all DHCP hosts in the VLAN, do it a client-by-client, in /etc/dnsmasq.conf:
dhcp-option=tag:agh,option:dns-server,192.168.254.254,192.168.254.253 # mac addresses of clients to use AGH: dhcp-host=cc:08:fa:11:11:11,set:agh dhcp-host=28:9f:04:22:22:22,set:agh
4 - test it
on macos, check the DNS server provided via DHCP:
% ipconfig getpacket en0 | grep domain_name_server
domain_name_server (ip_mult): {192.168.254.254, 192.168.254.253}
% nslookup google.com agh
Server: agh
Address: 192.168.254.254#53
Non-authoritative answer:
Name: google.com
Address: 172.217.25.174
- aside: two DNS name lookups happen in the above: the first for
aghagainst the system resolver (in turn, AGH and then dnsmasq) (A and AAAA; if you don't have an IPv6 configured for the dummy, you'll get a delay whilst macos runs through the search list to try and resolve it); the second is for google.com to AGH