How to Use “AdGuard Home Handle Client Requests” with VPN Domain-Based Routing on GL.iNet Routers

How to Use “AdGuard Home Handle Client Requests” with VPN Domain-Based Routing on GL.iNet Routers

Background

GL.iNet firmware can route selected domains through a VPN Client by asking dnsmasq to add the IP addresses returned for those domains to an ipset. The VPN policy rules then match the destination IP against that set and apply the appropriate VPN routing mark.

However, when Applications → AdGuard Home → AdGuard Home Handle Client Requests is enabled, DNS requests sent by LAN clients to the router are redirected directly to AdGuard Home on port 3053:

-A adg_redirect -p tcp -m addrtype --dst-type LOCAL -j REDIRECT --to-ports 3053
-A adg_redirect -p udp -m addrtype --dst-type LOCAL -j REDIRECT --to-ports 3053

This allows AdGuard Home to see each client's real LAN IP address instead of seeing all requests as 127.0.0.1. The side effect is that these DNS requests bypass dnsmasq, so dnsmasq no longer sees the DNS answers and cannot populate the VPN domain ipset.

This tutorial adds a small synchronization helper that converts GL.iNet's generated dnsmasq rules into AdGuard Home's ipset_file format. It preserves real client IP reporting in AdGuard Home while restoring VPN domain-based routing.


Important Note / Disclaimer:

The procedure in this tutorial manually modifies GL.iNet firmware files over SSH.

  • No Official Support: This is not an officially supported GL.iNet feature. GL.iNet customer service may not be able to troubleshoot problems caused by these modifications.
  • Advanced Users Only: This tutorial is intended for users who are comfortable with SSH and OpenWrt command-line operations. An incorrect change to DNS, firewall, or VPN scripts may interrupt network access.
  • Firmware-Specific: The implementation depends on the current GL.iNet VPN policy scripts and may need to be adjusted for future firmware releases.
  • Traditional ipset Only: Due to AdGuard Home only supports Linux ipset, not GL.iNet's fw4 nftset format. The helper safely skips synchronization when route_policy.global.use_fw4=1.

Verified Devices & Firmware Compatibility

This method has been tested with the following environment:

  • Device: Flint 2 (GL-MT6000)
  • Firmware: v4.9.0 release
  • AdGuard Home: v0.107.73
  • Firewall/VPN domain set mode: fw3 / traditional ipset
  • VPN Client: WireGuard, with domain rules configured under the VPN Dashboard

Other GL.iNet models using the same GL SDK scripts may also work, but they have not been verified. Before proceeding, confirm that the following files and commands exist:

test -f /usr/bin/rtp2.sh
test -f /etc/init.d/adguardhome
test -f /etc/AdGuardHome/config.yaml
test -x /usr/bin/AdGuardHome
command -v ipset

Prerequisites

Before proceeding, make sure that:

  1. You can connect to the router through SSH.
  2. AdGuard Home is enabled under Applications → AdGuard Home.
  3. AdGuard Home Handle Client Requests is enabled.
  4. A VPN Client is connected.
  5. At least one VPN policy uses To → Specified Domain / IP List.
  6. The router is using traditional ipset, not fw4 nftset mode.

You can check the last requirement with:

uci -q get route_policy.global.use_fw4

An empty result or 0 indicates the traditional ipset path on the tested firmware. If the command returns 1, do not use this method.

For verification later, add ip.sb to the VPN domain list and leave ipinfo.io outside the list.


Part 1: Install the Integration

Step 1: Connect to the Router through SSH

Connect to the router as root. For example:

ssh root@192.168.8.1

Step 2: Run the One-Paste Installation Block

Copy the entire block below, paste it into the SSH terminal once, and press Enter. No download, separate script upload, or preliminary chmod command is required.

sh <<'GL_AGH_VPN_IPSET'
set -e

RTP2=/usr/bin/rtp2.sh
AGH_INIT=/etc/init.d/adguardhome
AGH_CONFIG=/etc/AdGuardHome/config.yaml
SYNC=/usr/bin/agh-ipset-sync.sh
RUNTIME_DIR=/var/run/AdGuardHome
RUNTIME_FILE=$RUNTIME_DIR/ipset.conf
DNSMASQ_FILE=/var/run/dnsmasq/via_domain
CONFIG_NEW=/tmp/AdGuardHome-config.yaml.$$

cat >"$SYNC" <<'GL_AGH_SYNC'
#!/bin/sh

agh_config="/etc/AdGuardHome/config.yaml"
agh_ipset_file="/var/run/AdGuardHome/ipset.conf"
dnsmasq_ipset_file="/var/run/dnsmasq/via_domain"
tmp_file="${agh_ipset_file}.tmp"
use_fw4="$(uci -q get route_policy.global.use_fw4)"

[ "$(uci -q get adguardhome.config.enabled)" = "1" ] || exit 0
[ "$(uci -q get adguardhome.config.dns_enabled)" = "1" ] || exit 0
[ -f "$agh_config" ] || exit 0
[ -f "$dnsmasq_ipset_file" ] || exit 0

mkdir -p /var/run/AdGuardHome

if [ "$use_fw4" = "1" ]; then
    logger -t rtp2 "AdGuard Home ipset sync skipped: nftset mode is not supported by AdGuard Home"
    exit 0
fi

awk -F= '$1 == "ipset" {
    rule = $2
    sub("^/", "", rule)
    if (rule != "") print rule
}' "$dnsmasq_ipset_file" | LC_ALL=C sort -u >"$tmp_file"

if [ -f "$agh_ipset_file" ] && cmp -s "$tmp_file" "$agh_ipset_file"; then
    rm -f "$tmp_file"
    exit 0
fi

mv "$tmp_file" "$agh_ipset_file"
chmod 0644 "$agh_ipset_file"
logger -t rtp2 "Updated AdGuard Home ipset rules"

if ! pgrep -f '^/usr/bin/AdGuardHome ' >/dev/null; then
    logger -t rtp2 "AdGuard Home is not running; ipset rules will load on next start"
    exit 0
fi

logger -t rtp2 "Restarting AdGuard Home to load updated ipset rules"
if ! /etc/init.d/adguardhome restart >/dev/null 2>&1; then
    logger -t rtp2 "Failed to restart AdGuard Home after ipset update"
    exit 1
fi
GL_AGH_SYNC

chmod 0755 "$SYNC"

if ! grep -qF '/usr/bin/agh-ipset-sync.sh' "$RTP2"; then
    sed -i '/^[[:space:]]*do_restore_old_setting[[:space:]]*$/a\
        # GL-AGH-VPN-IPSET\
        /usr/bin/agh-ipset-sync.sh' "$RTP2"
fi

grep -qF '/usr/bin/agh-ipset-sync.sh' "$RTP2"

if ! grep -qF '/var/run/AdGuardHome/ipset.conf' "$AGH_INIT"; then
    sed -i '/^[[:space:]]*mkdir -p \/etc\/AdGuardHome[[:space:]]*$/a\
    # GL-AGH-VPN-IPSET: runtime rules are stored on tmpfs\
    mkdir -p /var/run/AdGuardHome\
    touch /var/run/AdGuardHome/ipset.conf\
    chmod 0644 /var/run/AdGuardHome/ipset.conf' "$AGH_INIT"
fi

grep -qF '/var/run/AdGuardHome/ipset.conf' "$AGH_INIT"

mkdir -p "$RUNTIME_DIR"
: >"$RUNTIME_FILE"

if [ "$(uci -q get adguardhome.config.enabled)" = "1" ] && \
   [ "$(uci -q get adguardhome.config.dns_enabled)" = "1" ] && \
   [ -f "$DNSMASQ_FILE" ]; then
    awk -F= '$1 == "ipset" {
        rule = $2
        sub("^/", "", rule)
        if (rule != "") print rule
    }' "$DNSMASQ_FILE" | LC_ALL=C sort -u >"$RUNTIME_FILE"
fi

chmod 0644 "$RUNTIME_FILE"

grep -q '^  ipset_file:' "$AGH_CONFIG"
cp -p "$AGH_CONFIG" "$CONFIG_NEW"

sed -i \
    's|^  ipset_file:.*|  ipset_file: /var/run/AdGuardHome/ipset.conf|' \
    "$CONFIG_NEW"

sh -n "$SYNC"
sh -n "$RTP2"
sh -n "$AGH_INIT"

/usr/bin/AdGuardHome \
    --check-config \
    -c "$CONFIG_NEW" \
    -w /etc/AdGuardHome

if pgrep -f '^/usr/bin/AdGuardHome ' >/dev/null; then
    /etc/init.d/adguardhome stop >/dev/null 2>&1
fi

cp -p "$CONFIG_NEW" "$AGH_CONFIG"
rm -f "$CONFIG_NEW" /etc/AdGuardHome/ipset.conf

if [ "$(uci -q get adguardhome.config.enabled)" = "1" ]; then
    /etc/init.d/adguardhome start >/dev/null 2>&1
fi

echo "Done: AdGuard Home VPN domain ipset integration installed."
echo "Runtime rules: /var/run/AdGuardHome/ipset.conf (tmpfs)"
GL_AGH_VPN_IPSET

If the installation succeeds, the final output should include:

configuration file is ok
Done: AdGuard Home VPN domain ipset integration installed.
Runtime rules: /var/run/AdGuardHome/ipset.conf (tmpfs)

The installation block is idempotent: running it again updates the helper but does not insert duplicate calls into rtp2.sh or duplicate runtime initialization into the AdGuard Home init script.


Part 2: How the Integration Works

The installation makes four changes:

  1. It creates /usr/bin/agh-ipset-sync.sh.
  2. It calls that helper from /usr/bin/rtp2.sh after do_restore_old_setting.
  3. It configures AdGuard Home to read /var/run/AdGuardHome/ipset.conf.
  4. It updates /etc/init.d/adguardhome so the runtime file exists before AdGuard Home starts after a reboot.

When the VPN domain list is updated, GL.iNet regenerates /var/run/dnsmasq/via_domain. For example:

ipset=/google.com/dst_net5742
ipset=/ip.sb/dst_net5742

The helper converts those entries into AdGuard Home's format on /var/run/AdGuardHome/ipset.conf:

google.com/dst_net5742
ip.sb/dst_net5742

The helper only runs when both settings below are enabled:

adguardhome.config.enabled='1'
adguardhome.config.dns_enabled='1'

If the generated file has not changed, AdGuard Home is not restarted. If it has changed, AdGuard Home is restarted so that it reloads dns.ipset_file.

The generated file is stored below /var, which resolves to /tmp on the tested router. Therefore, repeated domain-list updates do not repeatedly write the generated rules to flash storage.


Part 3: Verify the Configuration

Step 1: Confirm Both AdGuard Home Options Are Enabled

Run:

uci -q get adguardhome.config.enabled
uci -q get adguardhome.config.dns_enabled

Both commands should return:

1

Step 2: Check the Generated Rules

Run:

cat /var/run/dnsmasq/via_domain
echo "---"
cat /var/run/AdGuardHome/ipset.conf

The first file uses dnsmasq syntax, while the second file should contain the equivalent AdGuard Home rules without the ipset=/ prefix.

Step 3: Check the VPN Destination Set

First identify the set name:

cat /var/run/dnsmasq/via_domain

Then inspect it. For example:

ipset list dst_net5742

After a client resolves a listed domain, the set should contain the IPv4 addresses returned for that domain.

Step 4: Test from a LAN Client

From a computer connected to the router, resolve the VPN-routed domain through the router:

nslookup ip.sb 192.168.8.1

Then compare a listed domain with an unlisted IP-check service:

curl -4 https://ip.sb
curl -4 https://ipinfo.io/ip

If only ip.sb is in the VPN domain list, the two commands should normally return different public IP addresses:

  • ip.sb should show the VPN exit IP.
  • ipinfo.io should show the normal WAN exit IP.

Step 5: Confirm “AdGuard Home Handle Client Requests” Is Still Active

Run:

iptables -t nat -L adg_redirect -n -v

The UDP or TCP packet counters should increase when LAN clients send DNS queries to the router. Client entries in the AdGuard Home query log should continue to show individual LAN IP addresses rather than 127.0.0.1.

Step 6: Check Synchronization Logs

Run:

logread | grep -E 'AdGuard Home ipset|Restarting AdGuard Home'

When the VPN domain list changes, you should see messages similar to:

rtp2: Updated AdGuard Home ipset rules
rtp2: Restarting AdGuard Home to load updated ipset rules

Part 4: Behavior When AdGuard Home Is Disabled

The helper exits immediately unless both AdGuard Home and AdGuard Home Handle Client Requests are enabled.

If AdGuard Home Handle Client Requests is disabled, LAN DNS queries return to the normal dnsmasq path. In that mode, GL.iNet's original dnsmasq integration can populate the VPN domain set directly, so the additional AdGuard Home synchronization is unnecessary.


Part 5: Firmware Upgrades

Firmware upgrades may overwrite the following modified or added files:

/usr/bin/rtp2.sh
/usr/bin/agh-ipset-sync.sh
/etc/init.d/adguardhome

After upgrading firmware, verify the implementation again:

grep -F '/usr/bin/agh-ipset-sync.sh' /usr/bin/rtp2.sh
test -x /usr/bin/agh-ipset-sync.sh
grep -F '/var/run/AdGuardHome/ipset.conf' /etc/init.d/adguardhome

If any check fails, run the one-paste installation block again. Reinstalling the integration after each firmware upgrade is safer than preserving old firmware system scripts across versions, because GL.iNet may change the underlying VPN or AdGuard Home startup logic.


Part 6: How to Revert the Changes

The original firmware copies of rtp2.sh and the AdGuard Home init script are available in OpenWrt's read-only ROM. To remove the integration without resetting the rest of the AdGuard Home configuration, run:

/etc/init.d/adguardhome stop

cp -p /rom/usr/bin/rtp2.sh /usr/bin/rtp2.sh
cp -p /rom/etc/init.d/adguardhome /etc/init.d/adguardhome

rm -f /usr/bin/agh-ipset-sync.sh
rm -f /var/run/AdGuardHome/ipset.conf

sed -i 's|^  ipset_file:.*|  ipset_file: ""|' \
    /etc/AdGuardHome/config.yaml

/usr/bin/AdGuardHome \
    --check-config \
    -c /etc/AdGuardHome/config.yaml \
    -w /etc/AdGuardHome

/etc/init.d/vpn-client restart
/etc/init.d/adguardhome start

Do not replace /etc/AdGuardHome/config.yaml with the ROM copy unless you intend to discard your current AdGuard Home settings.


Troubleshooting

AdGuard Home Does Not Start

Check the configuration and runtime file:

ls -l /var/run/AdGuardHome/ipset.conf
/usr/bin/AdGuardHome \
    --check-config \
    -c /etc/AdGuardHome/config.yaml \
    -w /etc/AdGuardHome
logread | grep -i AdGuardHome | tail -n 50

The AdGuard Home Rule File Is Empty

Check the GL.iNet-generated source file:

cat /var/run/dnsmasq/via_domain

If it is empty, verify that a VPN policy is enabled and that domains have been added under VPN → VPN Dashboard → To → Specified Domain / IP List.

Domains Still Use the WAN Connection

Check all three stages:

cat /var/run/dnsmasq/via_domain
cat /var/run/AdGuardHome/ipset.conf
ipset list

Then clear the DNS cache on the client or reconnect it and resolve the domain again.

The Router Uses fw4 / nftset

If this command returns 1:

uci -q get route_policy.global.use_fw4

the helper logs a warning and exits. This tutorial does not add nftables-set support to AdGuard Home.


References