docs: record pdns PDA removal, us4 firewalld ops, LAN DNS alternatives
- runbooks/pdns-health.md: note the legacy powerdns-admin (PDA) orphan was removed 2026-08-12 (W1N-59). - runbooks/ansible-operations.md: document the us4 firewalld reconciliation playbook scope (audited public zone only, fail-closed, no reload). - docs/agents/domain.md: single-context repo layout for domain docs. - docs/lan-dns-alternatives.md: notes on LAN DNS alternatives. - .gitignore: exclude local agent-harness config (.agents/ .claude/ .omp/ .mcp.json WATCHDOG.yml skills-lock.json) from the repo.
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
# Domain Docs
|
||||
|
||||
How the engineering skills should consume this repo's domain documentation when exploring the codebase.
|
||||
|
||||
## Before exploring, read these
|
||||
|
||||
- **`CONTEXT.md`** at the repo root, or
|
||||
- **`CONTEXT-MAP.md`** at the repo root if it exists — it points at one `CONTEXT.md` per context. Read each one relevant to the topic.
|
||||
- **`docs/adr/`** — read ADRs that touch the area you're about to work in. In multi-context repos, also check `src/<context>/docs/adr/` for context-scoped decisions.
|
||||
|
||||
If any of these files don't exist, **proceed silently**. Don't flag their absence; don't suggest creating them upfront. The `/domain-modeling` skill (reached via `/grill-with-docs` and `/improve-codebase-architecture`) creates them lazily when terms or decisions actually get resolved.
|
||||
|
||||
## File structure
|
||||
|
||||
Single-context repo (most repos):
|
||||
|
||||
```
|
||||
/
|
||||
├── CONTEXT.md
|
||||
├── docs/adr/
|
||||
│ ├── 0001-event-sourced-orders.md
|
||||
│ └── 0002-postgres-for-write-model.md
|
||||
└── src/
|
||||
```
|
||||
|
||||
Multi-context repo (presence of `CONTEXT-MAP.md` at the root):
|
||||
|
||||
```
|
||||
/
|
||||
├── CONTEXT-MAP.md
|
||||
├── docs/adr/ ← system-wide decisions
|
||||
└── src/
|
||||
├── ordering/
|
||||
│ ├── CONTEXT.md
|
||||
│ └── docs/adr/ ← context-specific decisions
|
||||
└── billing/
|
||||
├── CONTEXT.md
|
||||
└── docs/adr/
|
||||
```
|
||||
|
||||
## Use the glossary's vocabulary
|
||||
|
||||
When your output names a domain concept (in an issue title, a refactor proposal, a hypothesis, a test name), use the term as defined in `CONTEXT.md`. Don't drift to synonyms the glossary explicitly avoids.
|
||||
|
||||
If the concept you need isn't in the glossary yet, that's a signal — either you're inventing language the project doesn't use (reconsider) or there's a real gap (note it for `/domain-modeling`).
|
||||
|
||||
## Flag ADR conflicts
|
||||
|
||||
If your output contradicts an existing ADR, surface it explicitly rather than silently overriding:
|
||||
|
||||
> _Contradicts ADR-0007 (event-sourced orders) — but worth reopening because…_
|
||||
@@ -0,0 +1,592 @@
|
||||
# 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
|
||||
from — the current "AdGuard Home (AGH) + idle mosdns" setup on
|
||||
[`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.
|
||||
|
||||
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
|
||||
`127.0.0.1#7874`. A `mosdns` binary listens on `127.0.0.1:6052` but is **not**
|
||||
on the active path.
|
||||
- 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.
|
||||
3. **Either actually configure mosdns or delete it.** It is idle on the active
|
||||
path; leaving a half-configured listener is surface area with no benefit. If
|
||||
kept, be aware its cache strips EDNS0 and it performs no DNSSEC validation
|
||||
([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.
|
||||
|
||||
### 3.2 mosdns v5 — installed but idle
|
||||
|
||||
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)
|
||||
Reference in New Issue
Block a user