技术指南
私有 DNS 上线:限制递归范围、验证加密与故障回退
把私有 DNS 上线拆成端口盘点、精确准入、加密边界、canary 切换、上游故障与客户端回滚。
自建 DNS 的首要目标不是堆叠协议、过滤表和转发器,而是让一组明确的客户端获得可观察、可撤销的解析服务。先写清服务对象、查询路径、隐私边界与失败回退,再决定是否需要过滤、DoH、DoT 或分流。任何时候都不要把递归解析器无条件开放给互联网。
来源提供组件清单,本文收敛为一次可回滚上线
归档来源完整覆盖 AdGuard Home、过滤列表、加密 DNS、Mosdns、Tailscale 与客户端配置,可保留的是端口盘点、私网接入、上游检查和日志排障思路。它也包含产品排行、固定性能结论、广告拦截率、过时规则语法、无条件停用 systemd-resolved 和复制即用的公网入口。本文删除这些保证,把范围收敛为一台私有转发解析器的准入、切换与故障恢复。
先把五个对象写进解析契约
| 对象 | 发布前必须回答 | 证据 |
|---|---|---|
| 客户端 | 哪些设备或网段可以查询 | 逐个 IP/CIDR、私网身份与测试设备 |
| 监听入口 | 监听哪个地址、端口和传输 | socket、主机防火墙与云防火墙 |
| 解析器 | 缓存、过滤、日志和保留策略是什么 | 版本化配置与运行状态 |
| 上游 | 由谁看到查询、失败时怎样处理 | 端点、证书、超时、失败样本 |
| 客户端回退 | 切换失败时恢复到哪里 | 旧配置、回滚命令与恢复时间 |
权威 DNS、递归 DNS 和本地 stub 不是同一角色。AdGuard Home 在这里作为私有转发解析器;它向上游查询并对下游提供缓存/过滤。域名注册商的权威服务器仍负责发布区域数据,systemd-resolved 等本机组件仍可能负责应用到解析器之间的本地路由。
先盘点端口、主机解析链和现有回退
date --utc --iso-8601=seconds
ss -lntup '( sport = :53 )'
resolvectl status
resolvectl dns
readlink -f /etc/resolv.conf
systemd-analyze cat-config systemd/resolved.conf看到 127.0.0.53:53 只说明本机 stub 正在监听,不能直接推出冲突方案。先确认 `/etc/resolv.conf` 模式、每条链路的 DNS、VPN/容器接管和断开新解析器后的回退。若确实要关闭 stub listener,先用 drop-in 保存候选和回滚文件,并在独立会话中证明主机仍能解析;不要删除 resolv.conf 或直接停掉服务。
固定发布物,并在解包前核对官方校验和
release=v0.107.78
asset=AdGuardHome_linux_amd64.tar.gz
base=https://github.com/AdguardTeam/AdGuardHome/releases/download/$release
curl --fail --location --remote-name "$base/$asset"
curl --fail --location --remote-name "$base/checksums.txt"
grep " ./$assetquot; checksums.txt > selected.sha256
sha256sum --check selected.sha256
tar -tzf "$asset" | sed -n '1,20p'容器部署同样要固定镜像摘要并保存 Compose 配置。官方 Docker 文档只在使用 DHCP 服务时明确要求 host networking;桥接和 host 模式会改变客户端地址、端口发布和 allowlist 语义,必须按实际网络验证,不能把 `network_mode: host` 当通用默认值。
候选配置同时限制监听与客户端
dns:
bind_hosts:
- 127.0.0.1
- 100.101.102.103 # 示例:本机实际的私网地址
port: 53
allowed_clients:
- 127.0.0.1
- 100.101.102.104 # 示例:明确授权的客户端
ratelimit: 20
refuse_any: true
upstream_dns:
- https://resolver.example/dns-query
upstream_timeout: 3s
cache_enabled: true
cache_optimistic: false`bind_hosts` 决定进程在哪些本地地址收包,`allowed_clients` 决定哪些来源获准查询,两者不能互相替代。主机防火墙和云安全组继续只允许管理网、LAN 或 tailnet 路径。`ratelimit` 与 `refuse_any` 是额外缓解,不是开放公网后的授权系统。
加密 DNS 只保护客户端到解析器这一跳
| 传输 | 得到什么 | 仍需处理 |
|---|---|---|
| 明文 DNS | 内网兼容性与简单故障定位 | 链路可见查询;必须限制在可信路径 |
| DoT | 客户端到解析器的 TLS 机密性与完整性 | 证书名、端口、授权、上游与日志 |
| DoH | 把查询映射到 HTTPS 交换 | HTTP/TLS 证书、代理信任、授权与元数据 |
| DoQ | 基于 QUIC 的加密 DNS 传输 | 客户端支持、UDP 路径、证书与授权 |
RFC 8484 明确 DoH 依赖底层 HTTP/TLS 安全,也提醒加密不能消除流量分析或解析器侧隐私问题。解析器和它选择的上游仍可能看到查询;‘使用 HTTPS’不能翻译成匿名、不可封锁、零泄漏或绝对更快。
反向代理只信任已知代理地址
AdGuard Home 的官方加密文档要求 DoH 反向代理进入 `trusted_proxies` 后,才把受支持的转发头解释为客户端地址。该列表只能包含真实代理地址或精确网段;否则攻击者可伪造来源,绕过按客户端配置的准入、统计或策略。代理层还要限制请求大小、速率和管理路径。
# 从客户端验证证书、HTTP 与 DNS 三层
curl --fail --silent --show-error \
--output /dev/null --write-out '%{http_code}\n' \
https://dns.example.com/dns-query
# 使用支持 DoH 的 dig 时再做真实查询;否则至少从同一路径检查 TLS
openssl s_client -connect dns.example.com:443 \
-servername dns.example.com -verify_return_error </dev/null上游选择写成隐私与故障策略,不写排行榜
- 保存上游运营方、端点、协议、隐私/日志政策和核对时间。
- 从部署网络验证证书、IPv4/IPv6、超时与错误响应,不用单次最快结果排名。
- 决定失败时返回 SERVFAIL、使用备用上游还是短时服务过期缓存,并写清最大窗口。
- 多个上游若策略不同,可能让过滤或 split DNS 失效;不要假设客户端或解析器严格按列表顺序使用。
- 过滤列表、重写和上游分别变更,一次只发布一个变量。
Tailscale 文档明确指出现代系统和其 DNS 代理可能并行查询、重排或负载均衡多个解析器。因此‘把旧 DNS 放第二位’不是可靠的主备设计。需要一致回答时,用一个受控解析入口在内部执行条件转发;需要回滚时,恢复客户端的上一份完整配置。
私网 DNS 与 MagicDNS 用明确域名边界共存
Tailscale 的 restricted nameserver 只处理指定搜索域,global nameserver 处理所有查询。私有解析器若使用 Tailscale IP,客户端还必须接受 tailnet DNS 设置并被 ACL 允许到达该地址。不要手工复制 MagicDNS 设备记录来‘共存’;先决定哪些后缀归私有解析器,其他名称继续由 MagicDNS 或公共解析路径处理。
过滤效果用代表性集合和误杀预算验收
| 集合 | 验收 | 失败动作 |
|---|---|---|
| 应解析域名 | 关键业务、更新、登录、支付和通知全部成功 | 恢复过滤版本并记录被拦规则 |
| 应阻断域名 | 已授权测试域名得到预期阻断响应 | 核对列表加载与匹配规则 |
| 未知域名 | 不因关键词或宽泛通配符误杀 | 缩小自定义规则 |
| 故障域名 | NXDOMAIN、SERVFAIL、超时能被区分 | 不要用重写掩盖上游故障 |
DNS 只能按名称回答或阻断,无法可靠区分同域上的广告与内容,也无法给出通用拦截率。过滤列表属于可变依赖:固定来源与更新时间,保存更新前后规则数量和差异,在 canary 客户端观察误杀后再扩大范围。
先显式查询,再切一台 canary 客户端
resolver=100.101.102.103
# 先绕过系统配置,分别验证 UDP、TCP 与负面响应
dig +time=2 +tries=1 @"$resolver" example.com A
dig +tcp +time=2 +tries=1 @"$resolver" example.com A
dig +time=2 +tries=1 @"$resolver" does-not-exist.invalid A
# 切换 canary 后,验证应用实际使用的系统路径
resolvectl query example.com
resolvectl statistics- 从获准客户端确认 UDP 和 TCP;从未授权来源确认被拒绝或无响应。
- 验证 A、AAAA、CNAME、NXDOMAIN、DNSSEC 相关域和一个关键业务域。
- 切换一台 canary,观察至少一个完整业务窗口的错误、延迟、上游状态和误杀。
- 再按设备组扩大,不同时修改 DHCP、tailnet、过滤列表、上游和缓存。
- 每一步保存旧 DNS 配置和 TTL/租约影响,能在约定时间内恢复。
回退是客户端配置事务,不是多填一个地址
预先定义触发门槛,例如连续查询超时、SERVFAIL 比例、关键域名失败、上游证书错误或误杀超过预算。触发后停止扩大范围,恢复 canary 或 DHCP/tailnet 的上一份解析配置,等待客户端重新取配置,再从系统路径复测。解析器恢复后先只接受测试客户端,不要立即重新全量切换。
记录能定位故障的最小数据
- 请求总量、响应码、上游、耗时分布、缓存命中和被阻断原因按时间聚合。
- 查询名、客户端地址和内部重写都可能敏感;缩短保留期、限制访问并避免无目的全量日志。
- 同时监控 UDP/TCP socket、进程重启、配置摘要、磁盘、时钟与证书到期。
- 从 resolver 主机、canary 和外部未授权网络分别运行探测,明确每个探测应成功还是失败。
- 任何自动更新都先下载、校验、比较、验证候选并保留上一版本。
隔离演练验证了准入、故障和恢复
VPScope 下载 AdGuard Home v0.107.78 Linux amd64 官方归档及 checksums.txt,SHA-256 `2070f644644be8299232f4a7bff857036fb1423563c1bf8c787e07aaf4f88278` 一致。程序只在 127.0.0.1 的高端口启动,完整候选通过 `--check-config`;一个一次性本地上游为测试域返回保留地址 192.0.2.22。
来自 127.0.0.1 的 UDP 与 TCP 查询都得到 192.0.2.22;从 127.0.0.2 发出的查询被精确 allowlist 拒绝并以非零退出。停止上游后,新查询返回 SERVFAIL;重启上游后,新的测试名称恢复。最后两个子进程停止,15355、15455、18125 端口关闭,临时目录由受限清理条件删除。这个演练不证明公网、真实 tailnet、TLS、过滤列表、生产缓存或服务管理器行为。
私有 DNS 上线前的可复查清单
- 客户端、监听地址、传输、上游、日志和回退对象已写入解析契约。
- 端口 53、systemd-resolved、VPN、容器与 resolv.conf 的现状已保存,没有盲停本机解析。
- 二进制或容器版本已固定并核对官方摘要,候选配置通过当前版本验证。
- 监听地址、allowed_clients、主机防火墙和云防火墙形成一致的最小准入。
- Web 管理面不向普通查询客户端开放,凭据、配置和查询日志按敏感数据保护。
- DoH/DoT/DoQ 的证书、授权与代理信任边界已单独验证,没有宣称匿名或绝对性能。
- 上游运营方、协议、超时、失败策略和隐私边界有当前记录。
- 过滤列表用应通过/应阻断/未知集合验收,并有误杀回退版本。
- 显式 UDP/TCP、未授权来源、canary 系统路径和关键业务查询均符合预期。
- 上游失败、SERVFAIL、恢复和客户端配置回滚已演练;所有临时进程与端口已清理。