2026-08-12 17:36:52 +08:00
# Home-LAN DNS alternatives for the windy LAN (research, 2026-08)
**Status: research only. No configuration was changed.** This page evaluates
resolvers/splitters that are genuinely better than — or meaningfully different
2026-08-12 22:12:32 +08:00
from — the current "AdGuard Home (AGH) + mosdns" setup on
2026-08-12 17:36:52 +08:00
[`dns.windy.lan` ](../hosts/dns.windy.lan.md ) (`.36` ), for a GFW-constrained
China home LAN. Claims are cited to primary sources (official repos, official
docs, upstream READMEs); anything not verified is flagged as such.
2026-08-12 22:12:32 +08:00
> 2026-08-12: facts in this page's scope recap were refreshed by W1N-56 live
> verification — mosdns on `.1` is **not idle**, it is clash's
> `nameserver`/`default-nameserver` (DIRECT-rule real-IP resolution); the
> canonical decision record is
> [`lan-dns-architecture.md`](lan-dns-architecture.md) (final verdict aligned,
> Phase 0 kill-test evidence incl. a measured upstream-blackhole degradation
> gap).
2026-08-12 17:36:52 +08:00
Scope recap (from [`lan-overview.md` ](lan-overview.md ), verified 2026-08-06):
- Clients get DNS via EdgeRouter DHCP option 6 → AGH `192.168.66.36:53` .
- AGH upstreams: `dns.alidns.com` + `doh.pub` DoH (load-balanced), fallback
`https://adg.chans.xyz/dns-query` . **DNSSEC disabled** (known-bad-signature
check failed on the selected path). Rewrites: `hass.local` / `hass.windy.lan` .
- `gfw` OpenWrt (`.1` ) runs OpenClash fake-ip + TPROXY; dnsmasq → clash DNS
2026-08-12 22:12:32 +08:00
`127.0.0.1#7874` . `mosdns` on `127.0.0.1:6052` is clash's
`nameserver` /`default-nameserver` (DIRECT-rule real-IP resolution: domestic →
AGH `.36:53` , foreign → `223.5.5.5` /`119.29.29.29` ); it is **not** in the LAN
client query path.
2026-08-12 17:36:52 +08:00
- No local authoritative PTR source yet; private reverse DNS is a known gap.
---
## 1. TL;DR / recommendation
**The current stack is already 80% of the answer.** AGH is a strong LAN DNS
front-end (filtering, rewrites, per-client upstreams, query log, web UI) and its
upstream layer — **per-domain upstreams** plus a **per-domain list loaded from a
file** (`upstream_dns_file` ) — is exactly the mechanism the official docs
recommend for accelerating China CDN domains while keeping everything else on a
trusted path. [AGH configuration: upstreams ](https://adguard-dns.io/kb/adguard-home/configuration/ ).
The genuinely worthwhile changes, in order of value:
1. **Add geo-split inside AGH** via `upstream_dns_file` fed by a converted
`accelerated-domains.china.conf` ([felixonmars/dnsmasq-china-list ](https://github.com/felixonmars/dnsmasq-china-list )):
domestic CDN domains → `dns.alidns.com` / `doh.pub` ; everything else →
the trusted foreign path (currently `adg.chans.xyz` ). This is a documented
AGH use case, requires **no new daemon** , and removes the need for mosdns.
This is the top recommendation.
2. **Re-enable real DNSSEC** by putting validation behind AGH: AGH's
`enable_dnssec` only sets the DO bit — it does not validate
([AGH config: DNSSEC ](https://adguard-dns.io/kb/adguard-home/configuration/ )).
The two realistic ways are (a) point the foreign/trusted default upstream at
a validating resolver ([unbound ](https://unbound.docs.nlnetlabs.nl/en/latest/ ),
[blocky ](https://0xerr0r.github.io/blocky/latest/configuration/#dnssec-validation ))
and re-test a known-bad-signature domain; or (b) insert a validating
resolver (blocky is the lightest) between AGH and the upstreams.
2026-08-12 22:12:32 +08:00
3. **mosdns on `.1` is resolved, not idle** — it is clash's
`nameserver` /`default-nameserver` (DIRECT-rule real-IP resolution, verified
2026-08-12), so "delete it" is off the table; its role is documented in
[`lan-dns-architecture.md` ](lan-dns-architecture.md ) §1. If a future change
moves this role to an AGH-side companion, keep in mind mosdns's cache strips
EDNS0 and it performs no DNSSEC validation
2026-08-12 17:36:52 +08:00
([mosdns v5 executable plugins ](https://irine-sistiana.gitbook.io/mosdns-wiki/mosdns-v5/ru-he-pei-zhi-mosdns/ke-zhi-xing-cha-jian.md )).
Top-3 alternatives worth pursuing (see §3 for detail):
| Rank | Option | Why |
|------|--------|-----|
| 1 | **AGH with China-list geo-split (`upstream_dns_file`)** | Documented AGH pattern; single box; no new service; keeps filtering/rewrites/UI. |
| 2 | **Blocky as validating backend behind AGH** | The only "new software" option that adds real in-process DNSSEC validation + conditional per-domain upstreams + ECS in one static binary ([blocky README ](https://github.com/0xERR0R/blocky ), [config ](https://0xerr0r.github.io/blocky/latest/configuration/ )). |
| 3 | **Unbound as validating recursive resolver** (replaces forwarders for the foreign path, or whole path) | True validation, full recursion (fail-open by nature), private `local-zone` s; heavier ops than AGH's file-driven split. |
Explicitly **not** recommended as replacements here: smartdns and chinadns-ng
(both excellent *splitters* , but neither validates DNSSEC and both lack AGH's
filtering/UI/query-log layer, so they add a daemon without closing the DNSSEC
gap); mihomo/sing-box DNS as the primary path (couples DNS to the proxy and is
fail-closed; keep for proxy-side concerns only); knot-resolver/dnsdist (overkill
for a single-operator home LAN).
---
## 2. Requirement matrix
Legend: ** ●** native/built-in · ** ◐** possible with config/lists · ** ○** absent/
not applicable. "Geo-split" = route domestic vs foreign names to different
upstreams. "Anti-pollution" = a mechanism to avoid/adjudicate poisoned answers
(IP-verdict or trusted-upstream routing). "DNSSEC" = performs validation
in-process (not just forwards DO).
| Candidate | Geo-split | Anti-pollution | DNSSEC (validate) | Cache | Private names / rewrites | Ops simplicity | License |
|---|---|---|---|---|---|---|---|
| AGH (current) | ◐ per-domain upstreams + list file | ◐ via trusted foreign upstream | ○ (DO bit only) | ● | ● rewrites, per-client, private-PTR | ● Docker + UI | GPL-3.0 |
| mosdns v5 | ● domain/ip list matchers | ◐ forward foreign→trusted | ○ | ● (strips EDNS0) | ● hosts/redirect/reverse_lookup | ◐ single binary, YAML, no UI | GPL-3.0 |
| smartdns | ● nameserver groups + domain lists | ● bogus-nxdomain / blacklist-ip / trusted groups | ○ (no option in config ref) | ● serve-expired | ● address / local-domain / lease file | ◐ single binary, optional WebUI plugin | GPL-3.0 |
| chinadns-ng | ● chnlist/gfwlist + tag:none IP-test | ● IP verdict via chnroute ipset/nftset | ○ | ● cache/stale/verdict | ◐ hosts / dns-rr-ip | ◐ single static binary, config file | AGPL-3.0 |
| dnsmasq-china-list | ◐ (data only) | ◐ (via host resolver) | ◐ via host | ◐ via host | ◐ via host | ◐ feed lists | WTFPL |
| unbound | ◐ forward-zones / RPZ / views | ◐ forward-zones + bogus-nxdomain | ● | ● serve-expired | ● local-zone / local-data | ◐ config daemon, no UI | BSD-style (NLnet) |
| blocky | ◐ conditional per-domain + client groups | ◐ blocking lists + conditional routing | ● | ● prefetch | ● customDNS / rewrite / hosts | ◐ single binary, YAML, REST (no full web UI) | Apache-2.0 |
| Technitium | ◐ conditional-forwarder zones / apps | ◐ blocked lists + forwarding | ● | ● persistent | ● zones, stub, split-horizon | ● .NET + web console | GPL-3.0 |
| sing-box | ● DNS rules (geoip/geosite) | ● rule-based servers + (proxy) sniffing | ○ | ● LRU + optimistic | ● hosts / local server | ◐ single binary, JSON | GPLv3-family (metadata "other") |
| mihomo | ● nameserver-policy + fallback-filter | ● geoip verdict + geosite | ○ | ● (cache-algorithm) | ● hosts; fake-ip-filter for `.lan` | ◐ single binary, YAML | not cleanly verifiable (repo obfuscated) |
| knot-resolver | ◐ policy modules | ◐ policy + RPZ | ● | ● persistent | ◐ hints / local data | ◐ systemd, Lua config | open source (CZ-NIC) |
| dnsdist | ◐ Lua rules (custom) | ◐ custom policies | ○ (balancer, not validator) | ○ (no cache of its own) | ○ | ○ power tool | GPL (PowerDNS) |
Notes:
- "Geo-split" for AGH/blocky/unbound/Technitium is real but requires feeding a
China domain list; chinadns-ng/mihomo additionally offer the **IP-verdict**
path for domains not in any list (query both, adopt CN result only if the
answer IP is mainland).
- mosdns v5's `cache` plugin ignores request EDNS0 and strips response EDNS0
([cache plugin ](https://irine-sistiana.gitbook.io/mosdns-wiki/mosdns-v5/ru-he-pei-zhi-mosdns/ke-zhi-xing-cha-jian.md )) —
relevant because AGH in front of it relies on the DO bit for DNSSEC-capable
upstreams.
- License for mihomo/sing-box/knot-resolver marked conservative: GitHub
metadata is "other"/custom or deliberately obfuscated; see §3 caveats.
---
## 3. Per-candidate evaluation
### 3.1 AdGuard Home — advanced upstream routing / built-ins
What it is: Go DNS proxy + adblock + DHCP, LAN DNS front-end
([official ](https://adguard-dns.io/kb/adguard-home/overview/ )).
Capabilities relevant here (all from the official configuration page):
- **Per-domain upstreams** dnsmasq-style: `[/domain/]upstream` , wildcards,
`#` = "default upstreams", empty `//` = unqualified names
([upstreams for domains ](https://adguard-dns.io/kb/adguard-home/configuration/#upstreams-for-domains )).
- **List from file** `upstream_dns_file` — the docs *explicitly* call out China
CDN acceleration via dnsmasq lists, with the `server=/0-100.com/114.114.114.114`
→ `[/0-100.com/]114.114.114.114` conversion
([loading upstreams from file ](https://adguard-dns.io/kb/adguard-home/configuration/#upstreams-from-file )).
- Upstream modes: `load_balance` , `parallel` , `fastest_addr` ; plus `fallback_dns`
used only when primary upstreams fail
([config file: dns ](https://adguard-dns.io/kb/adguard-home/configuration/ )).
- Per-client upstreams (`clients.persistent[].upstreams` ), rewrites
(`filtering.rewrites` , incl. wildcard), `local_ptr_upstreams` for private PTR,
ECS (`edns_client_subnet` with `use_custom` coarse prefix), optimistic cache
([same page ](https://adguard-dns.io/kb/adguard-home/configuration/ )).
- **DNSSEC is DO-bit only**: `enable_dnssec` "defines whether the proxy should
set the DO flag in the upstream requests" — validation must happen upstream
([same page ](https://adguard-dns.io/kb/adguard-home/configuration/ )).
- DoH/DoT/DoQ/DoH3 serving, `bind_hosts` /ACL guidance
([running securely ](https://adguard-dns.io/kb/adguard-home/running-securely/ )).
Verdict: **Already installed and capable of the geo-split itself.** The current
setup under-uses it: only a load-balanced CN pair + fallback, no per-domain
routing and no validating upstream. This is the cheapest "better" state — see §5.
2026-08-12 22:12:32 +08:00
### 3.2 mosdns v5 — installed, active as clash nameserver (gateway-side)
2026-08-12 17:36:52 +08:00
What it is: "一个 DNS 转发器" (a DNS forwarder) — plugin-based, sequence-driven
([README ](https://github.com/IrineSistiana/mosdns ), GPL-3.0, ~3.7k★).
What it does (verified from the v5 wiki and source tree):
- Servers: `udp_server` , `tcp_server` (TLS→DoT), `quic_server` , `http_server`
(DoH); upstreams in `forward` support `udp` , `tcp` , `tls` , `https` , `quic` ,
HTTP/3, concurrent racing (`concurrent: n` picks the fastest) and socks5
([server plugins ](https://irine-sistiana.gitbook.io/mosdns-wiki/mosdns-v5/ru-he-pei-zhi-mosdns/fu-wu-qi-cha-jian.md ),
[executable plugins ](https://irine-sistiana.gitbook.io/mosdns-wiki/mosdns-v5/ru-he-pei-zhi-mosdns/ke-zhi-xing-cha-jian.md )).
- Geo-split: v5 data providers are ** `domain_set` / `ip_set` (text list files)**
plus `qname` /`resp_ip` matchers — verified from the current source tree
([plugin/data_provider ](https://github.com/IrineSistiana/mosdns/tree/main/plugin/data_provider ))
— and an `ipset` /`nftset` exec plugin to push answer IPs to kernel sets. The
old v4-style `geosite` /`geoip` `.dat` plugins are **not present** in the v5
tree; the v5 wiki's own matcher page currently states there are no matcher
plugins to document
([matcher page ](https://irine-sistiana.gitbook.io/mosdns-wiki/mosdns-v5/ru-he-pei-zhi-mosdns/pi-pei-qi-cha-jian.md )).
Plan on chnlist/gfwlist-style text lists, not `geosite.dat` .
- Cache: yes, incl. optional lazy cache and disk dump; **request EDNS0 is
ignored and response EDNS0 stripped** by the cache plugin
([cache ](https://irine-sistiana.gitbook.io/mosdns-wiki/mosdns-v5/ru-he-pei-zhi-mosdns/ke-zhi-xing-cha-jian.md )).
- Private names: `hosts` (domain-rules style, not OS /etc/hosts syntax),
`redirect` , `arbitrary` (zone records), `reverse_lookup` (PTR/HTTP lookup).
- Ops: single binary + YAML; `mosdns service install` ships a systemd/launchd
helper ([v5 overview ](https://irine-sistiana.gitbook.io/mosdns-wiki/mosdns-v5.md ));
Docker image exists. No web UI of its own.
Verdict: capable splitter/forwarder, but **adds no DNSSEC and no filtering
layer**, and its cache interferes with EDNS0/DO handling. As a *back-end* splitter
behind AGH it is a legitimate choice only if DNSSEC stays off. Given AGH can do
the same per-domain split natively (3.1), mosdns's marginal value here is
concurrent upstream racing and ipset/nftset integration — neither is needed at
this LAN's scale. Either wire it up properly or remove it.
### 3.3 smartdns
What it is: local DNS server that queries multiple upstreams, **speed-tests the
answer IPs and returns the fastest**; DoH/DoT/DoQ/DoH3; GPL-3.0, ~11.2k★
([README ](https://github.com/pymumu/smartdns )).
Capabilities (from the official config reference and FAQ):
- Multi upstream + "returns the fastest IP", unlike dnsmasq all-servers
([README ](https://github.com/pymumu/smartdns )).
- Domain groups: `server ... -group <name>` + `nameserver /domain/group` routing,
per-`bind` port flags (`-group` , `-no-speed-check` …), client rules/MAC/IP
([config options ](https://pymumu.github.io/smartdns/configuration/ )).
- Anti-pollution tooling: `bogus-nxdomain` (return NXDOMAIN for poisoned IPs),
`blacklist-ip` , `whitelist-ip` , `ignore-ip` , `ipset` /`nftset` export
([same page ](https://pymumu.github.io/smartdns/configuration/ )).
- ECS: global `edns-client-subnet` and per-server `-subnet`
([same page ](https://pymumu.github.io/smartdns/configuration/ )).
- Cache: `cache-size` , `serve-expired` (RFC-like stale), `prefetch-domain` ,
persistent cache file ([same page ](https://pymumu.github.io/smartdns/configuration/ )).
- Private names: `address` , `cname` , `local-domain` , `dnsmasq-lease-file`
([same page ](https://pymumu.github.io/smartdns/configuration/ )).
- **DNSSEC: no validation option appears anywhere in the official config
reference or FAQ** — its pollution model is blacklist/whitelist + trusted
groups + speed selection, not DNSSEC ([config options ](https://pymumu.github.io/smartdns/configuration/ ),
[FAQ ](https://pymumu.github.io/smartdns/faq/ )). Flagged: verify on the version
you deploy before relying on it.
Verdict: the classic China-home "best-IP" resolver; good splitter, no DNSSEC,
speed-test model optimizes for latency rather than anti-pollution correctness.
Not better than AGH+China-list for this LAN; at most a back-end splitter behind
AGH, with the same DNSSEC caveat as mosdns.
### 3.4 chinadns-ng / chinadns2 / dnsmasq-china-list
**chinadns-ng** (the requested "china-dns-ng"; actual repo `zfl9/chinadns-ng` ,
AGPL-3.0, Zig, ~1.4k★) is the maintained rewrite of shadowsocks/ChinaDNS:
- Two upstream groups (china / trust) + `chnlist.txt` / `gfwlist.txt` domain
lists; domains are tagged `chn` /`gfw` /`none`
([README ](https://github.com/zfl9/chinadns-ng )).
- `tag:none` names are queried on **both** upstreams and the china answer is
adopted only if its A/AAAA is a mainland IP (tested against a `chnroute`
ipset/nftset loaded into the kernel); verdict caching avoids re-testing and
leaks ([README: 原理/verdict-cache ](https://github.com/zfl9/chinadns-ng )).
- Cache with stale + pre-refresh + optional persistence; DoT upstream
(wolfssl build); `hosts` + `dns-rr-ip` local records; `nftset` add for
chn/gfw IPs; **no DoH by design** and **no DNSSEC** — the author's stated
philosophy is "one job, done well"
([README ](https://github.com/zfl9/chinadns-ng )).
- Resource footprint is tiny: ~140 KB baseline, ~2.4 MB with 73k+ chnlist +
5.7k gfwlist entries ([README ](https://github.com/zfl9/chinadns-ng )).
**chinadns2** (`zfl9/chinadns2` ) is the older C predecessor; effectively
superseded by chinadns-ng for new deployments (README not directly fetched —
treat as legacy line).
**dnsmasq-china-list** (felixonmars, ~6.1k★) is data, not a daemon:
`accelerated-domains.china.conf` , `bogus-nxdomain.china.conf` ,
`apple.china.conf` , `google.china.conf` , with generators for **dnsmasq,
unbound, bind, dnscrypt-proxy**
([README ](https://github.com/felixonmars/dnsmasq-china-list ), WTFPL per repo).
Verdict: chinadns-ng is the strongest *pure splitter* for GFW networks (IP
verdict beats pure list-based routing for unknown domains), but it cannot
validate DNSSEC and brings no filtering UI. As AGH's backend it duplicates what
AGH's per-domain upstreams already do; its IP-test mode requires shipping
`chnroute` ipset/nftset into the host. dnsmasq-china-list is best used as the
**data feed** for the AGH `upstream_dns_file` recommendation in §5.
### 3.5 unbound
What it is: validating, recursive, caching resolver from NLnet Labs
([docs ](https://unbound.docs.nlnetlabs.nl/en/latest/ )).
- **Real DNSSEC validation by default** (trust anchor, chain of trust); the
official home-network guide turns it on explicitly
([home resolver guide ](https://unbound.docs.nlnetlabs.nl/en/latest/use-cases/home-resolver.html )).
- Full recursion → does not hard-depend on any upstream or proxy; serve-expired
(RFC 8767), aggressive NSEC, DoH/DoT/DoQ serving and TLS upstreams,
forward-zone/stub-zone/authority-zone, RPZ filtering, views, ECS module
([docs index ](https://unbound.docs.nlnetlabs.nl/en/latest/ )).
- Private names: `local-zone` /`local-data` for `*.windy.lan` -style names
([unbound.conf(5) ](https://unbound.docs.nlnetlabs.nl/en/latest/manpages/unbound.conf.html )).
- No built-in China split: you assemble it with forward-zones fed by
dnsmasq-china-list (`make unbound` generator) + `bogus-nxdomain` ; no UI, no
per-client grouping comparable to AGH.
Verdict: the gold standard for the **validation** half. Best used as (a) the
validating upstream behind AGH for the foreign/trusted path, or (b) a full
recursive resolver replacing the forwarders if you accept losing AGH-style
filtering/UI on top — keep AGH in front for that. System-package based, heavier
to operate than blocky but battle-tested.
### 3.6 blocky
What it is: Go DNS proxy + ad-blocker, "fast and lightweight", single static
binary, stateless, Apache-2.0, ~6.9k★
([README ](https://github.com/0xERR0R/blocky )).
- **In-process DNSSEC validation**: `dnssec.validate` with DO bit, RRSIG
verification, chain-of-trust, NSEC/NSEC3, custom trust anchors, SERVFAIL on
bogus ([DNSSEC validation docs ](https://0xerr0r.github.io/blocky/latest/configuration/#dnssec-validation )).
- Upstreams: `parallel_best` (2 random resolvers, fastest answer), `strict` ,
`random` ; per-client/per-subnet upstream **groups** ; UDP/TCP/DoT/DoH/DoQ/DoH3;
DNS stamps; bootstrap DNS
([upstreams ](https://0xerr0r.github.io/blocky/latest/configuration/#upstreams-configuration )).
- Conditional forwarding + `customDNS` mapping/rewrite (the AGH-rewrite
equivalent), hosts files, per-domain upstream routing
([custom DNS / conditional ](https://0xerr0r.github.io/blocky/latest/configuration/#custom-dns )).
- ECS: `ecs.useAsClient` / `ecs.forward`
([ECS ](https://0xerr0r.github.io/blocky/latest/configuration/#edns-client-subnet-options )).
- Cache with min/max TTL + **prefetching** ; optional **Redis** cache/state sync
between instances; query log to SQLite/Postgres/CSV; Prometheus metrics; REST
API ([README ](https://github.com/0xERR0R/blocky ),
[config ](https://0xerr0r.github.io/blocky/latest/configuration/ )).
- No full web admin UI (metrics/REST/logs only) — an ops trade-off vs AGH's UI.
Verdict: the most attractive *new software* option for this LAN **as a backend
behind AGH**: it adds real DNSSEC validation + conditional upstream routing +
ECS with a single binary and YAML. It has no China-IP-verdict split built in —
feed it the China domain list via `conditional.mapping` /upstream groups, which
is fine at this scale. One caveat: no GUI means AGH stays the human-facing
front, so AGH→blocky is strictly additive.
### 3.7 Technitium DNS Server
What it is: self-hosted authoritative **and** recursive DNS server, .NET,
web console, GPL-3.0, ~9.5k★
([README ](https://github.com/TechnitiumSoftware/DnsServer )).
- **DNSSEC validation** for recursive resolution, forwarders, and conditional
forwarders (RSA/ECDSA/EdDSA, NSEC/NSEC3); can also *serve* signed zones
([README ](https://github.com/TechnitiumSoftware/DnsServer )).
- Conditional forwarder zones + bulk conditional forwarding app; blocked-domain
lists with regex support and per-client variants; split-horizon/geolocation
via DNS Apps; ECS; QNAME minimization
([README ](https://github.com/TechnitiumSoftware/DnsServer )).
- Serving side: DoH/DoT/DoQ/DoH3 server, built-in DHCP, persistent cache,
caching with serve-stale/prefetch, clustering, HTTP/SOCKS5 proxy for DNS
(e.g. over Tor) ([README ](https://github.com/TechnitiumSoftware/DnsServer )).
- Heavier footprint (needs .NET; Docker image available) and a full web console
with many features this LAN won't use.
Verdict: capable and genuinely feature-rich (a real AGH alternative in the
"everything in one box" sense — filtering, private zones, validation, DHCP), but
it's more moving parts than this LAN needs, and its geo-split still requires
manual conditional-forwarder lists. Not chosen over the lighter AGH+backend
approach.
### 3.8 sing-box / mihomo built-in DNS as the split resolver (fake-ip)
The "third option": let the proxy engine's DNS own resolution, AGH on top.
**sing-box** DNS object: multiple server types (local, udp, tcp, tls, https,
http3, quic, fakeip, hosts, dhcp, mdns…), rule-based server selection by
geoip/geosite, LRU cache + optimistic serving, per-query timeout, `client_subnet`
(ECS), `reverse_mapping`
([sing-box DNS docs ](https://sing-box.sagernet.org/configuration/dns/ )).
**mihomo** (Clash.Meta lineage; docs at
[wiki.metacubex.one ](https://wiki.metacubex.one/en/config/dns/ )):
`nameserver-policy` (geosite/rule-set/domain keys) routes specific domains to
specific resolvers; `fallback` + `fallback-filter` (geoip=CN, geosite=gfw,
ipcidr, domain) adjudicate pollution — a CN resolver's answer is adopted only if
the IP is mainland, otherwise the overseas fallback's answer is used;
`fake-ip` /`redir-host` enhanced mode, `fake-ip-filter` with e.g. `'*.lan'` to
keep local names on real-IP; per-DNS-server ECS; cache-algorithm
([mihomo DNS config ](https://wiki.metacubex.one/en/config/dns/ )).
Assessment for THIS LAN:
- The **pollution adjudication is strong** (geoip-verdict fallback, geosite
lists), and mihomo already runs on the gateway — so "clash DNS as splitter" is
tempting.
- But the DNS service is **coupled to the proxy** : foreign resolution rides the
proxy path, so when OpenClash/subscription is down, fake-ip mapping and
foreign lookups break (partial fail-open only if `direct-nameserver` /fallback
are carefully set). The LAN requirement says **must not hard-depend on the
proxy (fail-open)**.
- fake-ip adds an indirection layer for anything in front of it (AGH on top
resolves client IPs against fake-ip ranges; leaks/loops need careful rules).
- Neither engine **validates DNSSEC** (no RRSIG verification).
- sing-box repo license shows "other" in GitHub metadata (not cleanly
verifiable); mihomo's repo currently carries **deliberately obfuscated content**
("Void Terminal" parody) — treat `wiki.metacubex.one` as the authoritative
docs and expect the GitHub surface to change.
Verdict: keep clash/mihomo DNS exactly where it is (proxy-side, TPROXY/fake-ip),
do **not** make it the LAN resolver of record. If you ever want its IP-verdict
quality outside the proxy, chinadns-ng gives the same idea with zero proxy
dependency.
### 3.9 knot-resolver / dnsdist — power-resolver options
**knot-resolver** (CZ-NIC): minimal caching validating resolver, modular/Lua,
full DNSSEC validation, forwarding over TLS, query policies, RPZ, views/ACLs,
DNS64, persistent cache, serve-stale, even XDP fast-path
([docs ](https://knot-resolver.readthedocs.io/en/stable/ )). As powerful as
unbound but with more configuration surface (Lua); overkill for a one-operator
home LAN, though it would do the validating-resolver role well.
**dnsdist** (PowerDNS): "highly DNS-, DoS- and abuse-aware loadbalancer" —
routes traffic to backend servers, Lua/YAML config, runtime console, metrics
([overview ](https://dnsdist.org/ )). It is a **balancer, not a validator/cache**
— it fronts other resolvers. Overkill; only relevant if you wanted a
multi-backend DNS LB, which this LAN does not.
### 3.10 Emerging / also-considered options
- **AdGuard Home + dnsmasq-china-list** — covered in §3.1/§5; this is the
"emerging best practice" for China CDN splits on AGH and is officially
documented.
- **pi-hole** — adblock/dashboard equivalent of AGH but no per-domain upstream
routing worth choosing it over AGH here (not deeply verified for this write-up;
AGH already satisfies the role).
- **dnscrypt-proxy** — encrypted forwarder with stamp support; a transport
option, not a splitter/validator (not deeply verified for this write-up).
- **coredns** — plugin-based; geo-split is DIY via plugins; no DNSSEC
validation by default (not deeply verified for this write-up).
### 3.11 Other popular options (survey supplement, 2026-08-12)
Follow-up survey of additional popular solutions not covered above, evaluated
against this LAN's constraints (fail-open, keep DNS on `.36` , DNSSEC goal).
None of these change the §4/§5 recommendation.
**Encrypted-forwarder micro-tools (AGH downstream options, not replacements):**
- **dnscrypt-proxy** — the classic OpenWrt encrypted forwarder with
China-list support and DNS-stamp routing. No in-process DNSSEC validation and
no filtering UI; overlaps with AGH's own DoH upstream layer, so its marginal
value here is low.
- **dnsproxy** (AdGuardTeam) — lightweight DoH/DoT/DoQ forwarder/server.
Functionally a subset of AGH's upstream layer; only useful if forwarding logic
is deliberately split out of AGH.
- **Stubby** — dnsmasq→stubby→DoT (privacy-community pattern). Pure
forwarding, no split/filter/validation; adopting it alone would be a
downgrade from AGH.
**Managed / cloud DNS (zero-ops, not self-hosted):**
- **NextDNS / ControlD / AdGuard DNS / Cloudflare** — hosted filtering, logs,
per-device policies. This LAN already self-hosts AGH + a private
`adg.chans.xyz` fallback, so a cloud service would be a downgrade in control
(data leaves the LAN). Only realistic use: add one as an extra foreign-path
upstream inside AGH's `upstream_dns_file` .
**Heavier all-in-one resolvers:**
- **PowerDNS Recursor** — real DNSSEC validation + Lua policy, authoritative
and recursive in one. Capable but overlaps unbound; over-provisioned here.
- **BIND9** — classic authoritative/recursive; can validate DNSSEC and, more
interestingly, serve as a local **authoritative zone** that would close the
private-PTR gap. As a LAN resolver it lacks AGH's filtering/UI and is heavier
to operate; a small dnsmasq authoritative zone is a lighter way to achieve the
PTR goal (still deferred until a local authoritative source exists).
- **hickory-dns / trust-dns** (Rust) — emerging recursive resolver, DNSSEC
friendly, smaller ecosystem/ops track record than unbound/blocky; not yet
worth switching for this LAN.
**Popular stack patterns (structure, not new software):**
- **Pi-hole + unbound** — the most common global self-hosted combo
(filtering front-end + validating backend). AGH already occupies the
Pi-hole role here (and does more), so the equivalent is **AGH + unbound/
blocky** — exactly the report's recommendation #2 .
- **dnsmasq + china-list + smartdns** (classic OpenWrt trio) — routes the
China list on the gateway itself. Equivalent to co-locating DNS with the
proxy host (`.1` ), which violates the fail-open requirement; not recommended
for this LAN.
Verdict: the survey adds no better candidate. dnsproxy/dnscrypt-proxy duplicate
AGH's upstream layer, cloud DNS is a control downgrade, and the only genuinely
new capability (a local authoritative source for PTR) is better served by a
small dnsmasq authoritative zone than by replacing the resolver.
---
## 4. Architecture recommendation for this LAN
### 4.1 Preferred architecture (change is config-only)
```
clients (DHCP option 6 = .36)
│ UDP/TCP :53
▼
AGH .36 (filtering, rewrites, query log, per-client upstreams)
│ upstream_dns_file:
│ [/cn-domain-list/] dns.alidns.com doh.pub ← CN CDN domains (China list)
│ default: https://adg.chans.xyz/dns-query … ← trusted/foreign path
└→ validating resolver (unbound OR blocky) for the foreign path (optional phase 2)
```
- Front = AGH stays the single LAN DNS box (filtering/rewrites/UI/query log
are its strong suit and are already operating).
- Split = AGH per-domain upstreams fed by a converted dnsmasq-china-list; no
new daemon. This is the documented AGH pattern
([upstreams from file ](https://adguard-dns.io/kb/adguard-home/configuration/#upstreams-from-file )).
- Validation = add a validating resolver behind AGH for the trusted path
(blocky simplest; unbound most battle-tested) and re-run the known-bad-signature
check that failed before; then flip `enable_dnssec` .
### 4.2 Why not the alternatives as front-ends
- **smartdns / chinadns-ng as the LAN resolver**: they are pure splitters —
no adblock layer, no query log/UI, no DNSSEC. Replacing AGH with either is a
capability downgrade; behind AGH they duplicate AGH's built-in split while
adding a daemon and losing validation. Only chinadns-ng's IP-verdict mode is
genuinely beyond AGH, and it needs kernel ipset/nftset plumbing.
- **mosdns as the AGH backend**: viable splitter, but no validation and its
cache strips EDNS0/DO ([cache plugin ](https://irine-sistiana.gitbook.io/mosdns-wiki/mosdns-v5/ru-he-pei-zhi-mosdns/ke-zhi-xing-cha-jian.md )),
which fights the DNSSEC goal. It is already idle on the box — configure it
deliberately or remove it.
- **mihomo/sing-box DNS as the resolver of record**: fail-closed + proxy-coupled
+ no validation. Keep as proxy-side concern (§3.8).
- **knot-resolver / dnsdist / Technitium**: capable but over-provisioned;
Technitium is the only one that would *replace* AGH wholesale, and there's no
benefit worth the migration here.
### 4.3 Deployment location
- **Keep DNS on `dns.windy.lan` (.36)**. It is already the DHCP-advertised
resolver; it is a separate VM from the proxy host; DNS therefore stays
independent of OpenClash state (fail-open), which is an explicit requirement.
- **Do not move it to `gfw` (.1)**: the gateway is where OpenClash injects
TPROXY/fake-ip/DNS-hijack rules; co-locating LAN DNS there couples DNS to the
proxy and its restart/update lifecycle.
- A standalone resolver VM adds nothing: both current VMs already sit on the
same PVE hypervisor ([lan-overview.md ](lan-overview.md ) §Positioning facts),
so a hypervisor outage takes out either placement equally; a second physical
host for HA is out of scope for a home LAN.
- If you ever run a validating resolver + AGH on `.36` , verify outbound from
`.36` to the foreign upstreams is not re-hijacked by OpenClash (loop check
already mandated in the [AGH review ](adguard-home-official-review-2026-08.md )).
### 4.4 Fail-open, DNSSEC, private names — by candidate
| Concern | How the recommended stack behaves |
|---|---|
| Fail-open when proxy/subscription down | AGH forwards directly to DoH upstreams; `.36` 's outbound is not forced through the proxy in normal ops (no TUN policy routing on `.36` — [dns host facts ](../hosts/dns.windy.lan.md )). With unbound/blocky behind, foreign resolution recurses/validates directly, independent of OpenClash. Avoid mihomo-DNS-as-resolver, which is proxy-coupled. |
| DNSSEC validation | Only unbound, blocky, knot-resolver, Technitium validate in-process. AGH sets DO only; mosdns/smartdns/chinadns-ng/mihomo/sing-box do not. Plan: validate behind AGH, or accept "validating public upstream" (confirm with `dig +dnssec` /known-bad test). |
| Private names / rewrites | AGH `rewrites` (already in use for `hass.windy.lan` ) + `local_ptr_upstreams` once a local PTR source exists. blocky: `customDNS` mapping/rewrite + hosts. unbound: `local-zone` . All adequate. |
| Query log / visibility | AGH is the best at this of everything evaluated (14-day anonymized log already configured). |
---
## 5. What would make the current AGH + mosdns setup genuinely better
Concrete, in increasing effort:
1. **Implement the China-list geo-split in AGH itself**
(`upstream_dns_file` + converted `accelerated-domains.china.conf` , default
upstreams = trusted foreign path, `fallback_dns` kept). Official AGH docs
describe exactly this pattern
([loading upstreams from file ](https://adguard-dns.io/kb/adguard-home/configuration/#upstreams-from-file ));
list source: [dnsmasq-china-list ](https://github.com/felixonmars/dnsmasq-china-list ).
Wire a refresh path (cron/ansible) so the list stays current. Re-test CDN
resolution and the DNSSEC known-bad domain after.
2. **Put a validating resolver on the trusted path** (unbound or blocky), re-run
the known-bad-signature check, then enable AGH DNSSEC. Without this, AGH's
`enable_dnssec` is only a DO-flag — the exact reason it is currently off
([AGH DNSSEC semantics ](https://adguard-dns.io/kb/adguard-home/configuration/ ),
[host facts ](../hosts/dns.windy.lan.md )).
3. **Either fully configure mosdns (systemd service, sequence, lists) or remove
it.** Leaving an idle `127.0.0.1:6052` listener documented as "not the active
path" is drift. If kept, plan around no-EDNS0 cache + no validation; if
removed, drop the listener and its config to reduce surface.
4. **Close the private-PTR gap** : once a local authoritative source exists (e.g.
dnsmasq on `gw` , or a tiny authoritative zone), point AGH
`local_ptr_upstreams` at it as the AGH review recommends
([AGH review ](adguard-home-official-review-2026-08.md ));
don't set it before that source exists
([dns host facts ](../hosts/dns.windy.lan.md )).
5. **Optional: ECS** for CDN geo-accuracy — AGH `edns_client_subnet.use_custom`
with a coarse fixed prefix (or blocky `ecs.forward` ) if measurements show a
benefit; note many CN resolvers ignore ECS
([AGH ECS ](https://adguard-dns.io/kb/adguard-home/configuration/ )).
If the DNS engineering budget is one afternoon, do #1 + #3 . If the goal is
"real DNSSEC or nothing", do #1 + #2 + #3 . Replacing the stack is only
justified if you want to abandon AGH's UI/filtering entirely — nothing evaluated
here beats it on that axis for this LAN.
---
## Caveats / not verified
- **Live behavior not tested**: all capability claims are from primary docs
reviewed 2026-08-12; DNSSEC behavior of `dns.alidns.com` /`doh.pub` /the
`adg.chans.xyz` path and mosdns's actual version on `.36` need on-box
`dig +dnssec` verification (per [adguard-home-health ](../runbooks/adguard-home-health.md )).
- **smartdns DNSSEC**: the official config reference lists no DNSSEC option;
if a newer version added one, it is not reflected here
([config options ](https://pymumu.github.io/smartdns/configuration/ )).
- **mosdns geosite/geoip**: v5 source tree (fetched 2026-08-12) contains only
`domain_set` /`ip_set` data providers; if a `geosite.dat` plugin exists in a
release branch, it is not in `main`
([plugin/data_provider ](https://github.com/IrineSistiana/mosdns/tree/main/plugin/data_provider )).
- **mihomo**: the GitHub repo currently shows deliberately obfuscated metadata
(see §3.8); capabilities cited from
[wiki.metacubex.one ](https://wiki.metacubex.one/en/config/dns/ ).
sing-box/knot-resolver/dnsdist license identifiers via GitHub metadata are
"other"/custom — treat the specific SPDX ids with caution.
- **chinadns2** README was not retrieved (404 on the raw URL); treated as the
legacy predecessor of chinadns-ng and not evaluated in depth.
- Obsidian/personal notes were not consulted; this is upstream-docs-only.
## Related docs
- [lan-overview.md ](lan-overview.md ) — full topology (verified 2026-08-06)
- [hosts/dns.windy.lan.md ](../hosts/dns.windy.lan.md ) — AGH host facts
- [hosts/gfw.windy.lan.md ](../hosts/gfw.windy.lan.md ) — OpenClash facts
- [adguard-home-official-review-2026-08.md ](adguard-home-official-review-2026-08.md ) — prior AGH config review
- [runbooks/adguard-home-health.md ](../runbooks/adguard-home-health.md )