One way to set up AdGuard Home alongside dnsmasq - dummy ftw!

Most guides to installing AdGuard Home (AGH) on OpenWRT move dnsmasq aside to a port other than :53 (which has the side effect of disabling the DHCP dns-server option), 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 dns-server via DHCP to reverse the side effect above.

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.

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:

  1. create a dummy interface (akin to loopback), excluded from dnsmasq
  2. run AGH on that
  3. configure dnsmasq to deliver the dummy's IP as the DHCP DNS server, for the VLANs you want to use AGH on by default
  4. 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 192.168.254.254/32 and fd00::254:254/128; can be bigger than a /32 (/128) if you have grander plans
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/32'
uci set network.dummy.ip6addr='fd00::254:254/128'
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
fd00::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, set bind_hosts to be only the above dummy IP address(es), e.g.
dns:
  bind_hosts:
    - 192.168.254.254
    - fd00::254:254
  port: 53
  • configure AGH's upstream servers to whatever you want to use for Internet names, and localhost 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 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"
uci commit dhcp
/etc/init.d/dnsmasq restart

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}

% 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 agh against 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

You can put AGH as primary DNS, (for instance) 1.1.1.1 as 2nd DNS, then tell dnsmasq to strictly follow the upstream DNS order.

AGH will be queried 1st, unless it's down.

How are you upgrading that AGH is not installed? If you use any of the ASU methods, it should be there at first boot...

Yes a dummy interface is different then loopback but similar.

But regarding DNSmsaq. You could also make listen a dnsmasq instance on a single address but sure a dedicated interface is valid too. Thanks for your post.

Attended Sysupgrade (LuCI web UI) and owut (command line) let you upgrade OpenWrt to a new base image that includes all the currently installed packages. Makes working with a configuration that relies on additional packages being installed a lot easier and less likely to break during upgrades.

I just setup Adguard on port 54 and add rules to redirect all clients to it, no need to touch any other DNS settings easy peasy :slightly_smiling_face:

That DNS 'primary' and 'secondary' can be used for choosing between two non-identical DNS servers is a very common misconception. It can certainly "work" (well enough), but it can also be the source of tearing your hair out. As you note with dnsmasq 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.

But yes, important to recognise a third option: use dnsmasq as the primary - as normal - and set AGH as an upstream with strict-order enabled. Whilst technically a hack (see above) this approach is simple and will work well most of the time, and will still "work" (slowly) when AGH is down/not there. So I guess that's still a win!

A fourth option!

I have openwrt on an x86 w/ resized ext4 root; yes I can automate the repartitioning+fs-resize but I like to also use upgrades as an opportunity to clean house, and "reinstalling almost from scratch" is also a good opportunity to ensure my recovery docs are correct. Be good to have a fully declarative openwrt setup... (on the to-do list).

Great writeup @benl

cleaner than the usual "AGH takes over :53" approach, especially since it survives AGH being (temporarily) uninstalled after an upgrade.

If you're up for it, you could add this as a new page to the documentation β€” something like .../dns/adguard-home-dummy-interface under https://openwrt.org/docs/guide-user/services/dns/. It feels like it deserves its own page rather than being folded into the existing AdGuard Home guide, since it's a genuinely different architecture (opt-in per VLAN) rather than just a variant of the port-move method.

Ta for the feedback.

Done: https://openwrt.org/docs/guide-user/services/dns/adguard-home-dummy-interface

@benl I updated my blog post to reflect my current method for running Adguard along with dnsmasq

Nice work I liked your alternative failsafe method. Just for comparison here's my setup:

After flashing a fresh OpenWrt image on my RPi4, I run a custom setup.sh script to configure my WAN & install AGH package, restore a backup that expands the root partition & applies my shifted-port/PBR configurations & finally I replace the AGH package binary with the latest release directly from its GitHub repository.

I have a similar setup with luci-app-pbr so Google can see my real IP instead of the VPN's :grin:

I'm doing same here :

  • AGH listening on 5353 port
  • Dnsmasq advertising router ip 192.168.1.254 to clients
  • This rule in firewall redirect clients requests to AGH :
config redirect                                                                                                                                                                                                                                                                                                                                                         
        option target 'DNAT'                                                                                                                                                                                                                                                                                                                                            
        option name 'dns'                                                                                                                                                                                                                                                                                                                                               
        option src 'lan'                                                                                                                                                                                                                                                                                                                                                
        option src_dport '53'                                                                                                                                                                                                                                                                                                                                           
        option dest_ip '192.168.1.254'                                                                                                                                                                                                                                                                                                                                  
        option dest_port '5353'                                                                                                                                                                                                                                                                                                                                         
        list proto 'udp'                                                                                                                                                                                                                                                                                                                                                
        option src_dip '192.168.1.254'

I guess the firewall approach has the advantage of not requiring an additional package to be installed and configured (kmod-dummy for the dummy interface). Mind you I could also create an alias on one of the router's existing interfaces instead of the dummy; the dummy seems cleaner though - this is the sort of thing it's intended for.

For people taking the redirect/DNAT approach, can I suggest you create an alias IP for any interface on your router, bind AGH to that, advertise that IP as the DNS server, and use your NAT rule to redirect traffic to it. That way clients can still have functioning DNS even when AGH is down by manually configuring the regular router gateway address. e.g. on macOS this could be an alternative network location config.

One more thing I only just discovered: I've had AGH running for a while and my phone's always been a bit off, I now know why: at least some Android devices (e.g. Samsung OneUI 8) require two DNS servers,and if the DHCP server only delivers one they'll happily add 8.8.8.8. In other words, if you only deliver one, some of your DNS traffic will be sent to Google instead of AGH.

The fix is to create an alias for AGH to also bind to (e.g. have it listen to .254 and .253) and advertise both of those to clients.

Now I've done that I'm seeing all (regular) DNS from the phone heading through AGH.

Or you know, just set AGH as the first upstream dns and set up a NAT forwarding rule to force it to use AGH. Yes, once AGH is down then there is no more domain resolving, but that's already a degraded system state, you should be taking care of it anyway. And btw, what a great inherent alerting system, you immediately know when something is wrong.

My setup is easier, if AGH stops working I just disable the redirect to port 54 rules :grin:

it's exactly the same logic though. just with native luci-based fw4 interface instead of PBR.