构建 DNS 终极形态:AdGuardHome + MosDNS + SmartDNS 级联解析与分流治理实战
引言
在当前的互联网基础设施中,DNS 虽轻量,却是决定网络吞吐质量的第一道闸门。DNS 劫持、递归污染、以及跨网访问引发的 CDN 调度失效,已成为开发者与高级玩家追求“极致响应”的主要障碍。
单一的工具往往存在短板:AdGuardHome 虽具备优秀的拦截 UI,但在大规模规则下的分流逻辑略显臃肿;SmartDNS 擅长并发测速却缺乏灵活的分流策略;MosDNS 拥有强大的插件化逻辑控制,却不具备直观的状态分析。
本文将深入探讨如何通过这三者的解耦与级联,构建一套高性能、零污染、自动优选的 DNS 治理体系:
AdGuardHome:作为流量入口,执行流式正则匹配,完成首轮去广告与审计。
MosDNS:充当中央分流器,基于 GeoIP 与域名列表实现国内/海外流量的逻辑解耦。
SmartDNS:作为底层递归服务器,利用多源并发 RTT 测速与大容量 Cache,确保解析链路的最短路径。
通过本方案,我们将实现:
解析延迟降低:充分利用系统缓存与并发查询机制。
CDN 调度重构:杜绝海外解析返回国内节点的情况,确保流量直达最优边缘节点。
隐私与安全:全量开启 DoH/DoT 加密,阻断运营商 DNS 劫持与 SNI 检测。
一、架构图解

数据流的“生命周期”
1. 过滤层 (AdGuard Home):此阶段 DNS 包仍是标准请求。AGH 执行流式正则匹配,主要任务是 流量清洗,剔除黑名单域名,保持上游请求的“纯净”。
2. 分流层 (MosDNS):这是逻辑决策中心。MosDNS 并不直接获取 IP,而是根据分流列表对请求进行“染色”。对于国内请求,它会封装 EDNS0 Client Subnet (ECS) 标签。此时,DNS 包从一个普通请求变成了一个“携带特定地理位置属性”的定向请求。
3. 递归加速层 (SmartDNS):这是物理执行层。它将接收到的请求通过加密隧道(DoH/DoQ)发送至全球。利用其多源并发(Race Condition)机制,它会在毫秒级选出最快回执。异步递归与 0ms 命中
1. 命中逻辑:当请求命中 SmartDNS 缓存且已过期时,系统会立即从内存读取旧 IP 返回给客户端。
2. 后台动作:在返回旧 IP 的同时,SmartDNS 会在后台异步发起新的一轮递归查询。
这种“先上车后补票”的机制,彻底抹平了跨国递归解析中常见的 200ms+ 物理时延。
域隔离与回退机制
1. 保护机制:针对海外域名(如 Gemini、Google),我们在remote_sequence中彻底剔除了 ECS 标签,防止泄露真实地理位置导致的服务不可用。
2. 回退逻辑:当海外 DoH 节点因网络波动超时,系统通过fallback策略切换到备份节点,确保内网服务不掉线。
二、Docker Compose配置
首先先贴出完整的docker-compose.yaml
version: '3.8'
services:
# ========================================================
# 1. AdGuard Home (前端:广告过滤与用户界面)
# 作用:处理客户端请求,过滤广告,提供查询日志。
# ========================================================
adguardhome:
container_name: adguardhome
image: adguard/adguardhome
restart: unless-stopped
volumes:
# --- 映射目录,方便设置配置文件 ---
- /opt/1panel/docker/compose/adguard/workdir:/opt/adguardhome/work
- /opt/1panel/docker/compose/adguard/confdir:/opt/adguardhome/conf
ports:
# --- 加密 DNS 服务端口 ---
- "10010:443/udp" # 用于 DoH/DoQ (UDP)
- "10010:443/tcp" # 用于 DoH/Web HTTPS
# --- Web 管理界面 ---
- "10086:80/tcp" # Web HTTP 界面
- "10000:3000/tcp" # 首次初始化端口
networks:
- dns_net
depends_on:
- mosdns
# ========================================================
# 2. MosDNS v5 (中控:分流决策与 ECS 伪装)
# 作用:分流逻辑中心。判断域名是走国内还是国外。
# ========================================================
mosdns:
container_name: mosdns
image: irinesistiana/mosdns:latest
restart: unless-stopped
volumes:
# 挂载配置目录
- /opt/1panel/docker/compose/adguard/mosdns:/etc/mosdns
# 挂载缓存目录,确保每天从 GitHub 下载的列表文件持久化
- /opt/1panel/docker/compose/adguard/mosdns_cache:/var/lib/mosdns
networks:
- dns_net
depends_on:
- smartdns
# ========================================================
# 3. SmartDNS (后端:上游并发查询与 IP 测速)
# 作用:执行中心。向多个加密 DNS 提问并选出延迟最低的 IP。
# ========================================================
smartdns:
container_name: smartdns
image: pymumu/smartdns
restart: unless-stopped
volumes:
# 方案:直接挂载整个目录,不搞单个文件伪装
- /etc/ssl/certs:/etc/ssl/certs:ro
# 额外挂载的 PKI 目录(这是 ca-bundle.crt 真正的老家,必须带上)不同系统路径可能有些不一样
- /etc/pki:/etc/pki:ro
# 挂载 SmartDNS 配置文件
- /opt/1panel/docker/compose/adguard/smartdns:/etc/smartdns
networks:
- dns_net
# ========================================================
# 网络定义:确保三个服务可以通过容器名互相访问
# ========================================================
networks:
dns_net:
driver: bridge三、容器详细配置
1. AdguardHome具体配置
首先登陆AdguardHome,按照compose配置里的端口登陆 (上述配置的这部分定义了首次登陆的端口- "10000:3000/tcp" # 首次初始化端口),并按照设置向导进行首次设置
设置DNS上游为MosDNS的端口

关闭Adguard的客户端子网设置

设置允许的客户端(后续有大用)

为Adguard申请证书,方便开启DNS加密,启用 DNS-over-HTTPS 与或DNS-over-TLS ,填写服务器域名(上面的配置文件只映射了DoH的端口,可以按需修改)

设置公钥私钥

设置DNS黑白名单,这里推荐使用以下黑名单
1.CHN: AdRules DNS List:https://adguardteam.github.io/HostlistsRegistry/assets/filter_29.txt
2.adblockfilters:https://raw.githubusercontent.com/217heidai/adblockfilters/main/rules/adblockdns.txt
3.neodevhost:https://raw.githubusercontent.com/neodevpro/neodevhost/master/adblocker
4.blackmatrix7:https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/rule/AdGuard/Advertising/Advertising.txt客户端设置(后续有大用,嫌麻烦也可以不设置)

2. MosDNS具体配置
MosDNS配置参考以下部分,由于compose中已经映射好文件路径(/opt/1panel/docker/compose/adguard/mosdns),可以直接新建文件命名为config.yaml在映射的目录然后复制以下配置保存使用
log:
level: info
production: true
plugins:
# --- 1. 数据源:你的 GitHub 国内域名列表 ---
- tag: "my_github_china_list"
type: "domain_set"
args:
files:
- "/etc/mosdns/mosdns_china_list.txt" #MosDNS分流规则,可以设置脚本自动下载更新
# --- 2. ECS 处理:上海 BGP IP 伪装 ---
- tag: "ecs_china"
type: "ecs_handler"
args:
forward: false
#preset: 218.242.0.1 # 上海电信根据自己客户端使用位置设置
preset: 219.136.128.50 # 广东电信
send: true
mask4: 24
# --- 3. 转发插件:分别指向 SmartDNS 的两个监听组 ---
# 指向 SmartDNS 的 mainland 组 (5354)
- tag: "forward_china"
type: "forward"
args:
upstreams:
- addr: "udp://172.22.0.2:5354"
# 指向 SmartDNS 的 remote 组 (5355)
- tag: "forward_remote"
type: "forward"
args:
upstreams:
- addr: "udp://172.22.0.2:5355"
# --- 4. 序列定义 ---
# 国内解析序列:插入上海 ECS -> 转发至 SmartDNS:5354
- tag: "local_sequence"
type: "sequence"
args:
- exec: $ecs_china
- exec: query_summary local_forward
- exec: $forward_china
- exec: accept
# 境外解析序列:不带任何 ECS -> 转发至 SmartDNS:5355 (彻底隔离)
- tag: "remote_sequence"
type: sequence
args:
- exec: query_summary remote_forward
- exec: $forward_remote
- exec: accept
# --- 5. 主分流逻辑 ---
- tag: "main_sequence"
type: "sequence"
args:
# 命中列表走国内序列
- matches:
- "qname $my_github_china_list"
exec: goto local_sequence
# 其余全部走境外序列
- exec: goto remote_sequence
# --- 6. 入口服务 ---
- tag: udp_server
type: udp_server
args:
entry: main_sequence
listen: :53
- tag: tcp_server
type: tcp_server
args:
entry: main_sequence
listen: :53分流规则更新的脚本可以这样写,设置每天更新
#!/bin/bash
# 进入配置目录
cd /opt/1panel/docker/compose/adguard/mosdns/
# --- 1. 下载 geosite.dat ---
wget -O geosite.dat.new https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat
GEOSITE_RES=$?
# --- 2. 下载你的 GitHub 域名列表 ---
wget -O mosdns_china_list.txt.new https://raw.githubusercontent.com/0yykk/AdguardHome/refs/heads/main/mosdns_china_list.txt
GITHUB_RES=$?
# --- 3. 判断并执行更新 ---
if [ $GEOSITE_RES -eq 0 ] && [ $GITHUB_RES -eq 0 ]; then
# 只有两个文件都下载成功,才进行替换
mv geosite.dat.new geosite.dat
mv mosdns_china_list.txt.new mosdns_china_list.txt
# 重启 MosDNS 使配置生效
docker restart mosdns
echo "$(date) - All files updated successfully" >> update.log
else
# 如果任一失败,清理掉新下载的临时文件
[ $GEOSITE_RES -eq 0 ] || echo "$(date) - geosite.dat update failed" >> update.log
[ $GITHUB_RES -eq 0 ] || echo "$(date) - mosdns_china_list.txt update failed" >> update.log
rm -f geosite.dat.new mosdns_china_list.txt.new
fi3. SmartDNS具体配置
SmartDNS配置参考以下部分,由于compose中已经映射好文件路径(/opt/1panel/docker/compose/adguard/smartdns),可以直接新建文件命名为smartdns.conf在映射的目录然后复制以下配置保存使用
# ========================================================
# 1. 监听端口 (对应 MosDNS 的转发目标)
# ========================================================
# 显式指定系统挂载进来的证书文件名
ca-file /etc/ssl/certs/ca-bundle.crt
# 国内组:处理大陆域名,开启测速和缓存,禁用 IPv6 干扰
bind [::]:5354 -group china -no-dualstack-selection -force-aaaa-soa -no-speed-check
# 境外组:处理国外域名,禁用测速(防止误选美国节点),禁用 IPv6 干扰
bind [::]:5355 -group remote -no-dualstack-selection -force-aaaa-soa
# ========================================================
# 2. 性能与缓存优化 (参考你提供的 NestingDNS 逻辑)
# ========================================================
log-level warn
cache-size 131072
cache-persist yes
cache-file /etc/smartdns/cache.dump
cache-checkpoint-time 3600
# 过期缓存服务:避免查询等待,响应极速 (4ms 的秘诀)
serve-expired yes
# 优化 1:将过期回复的 TTL 设为 0
# 这样客户端会立即使用该记录,但不会长期缓存它,强迫客户端下次查询时 SmartDNS 已完成后台刷新
serve-expired-reply-ttl 0
# 优化 2:缩短预取触发时间
# 21600 (6h) 太久了。设为 300 (5分钟) 表示在记录过期前 5 分钟,
# 如果有人访问,SmartDNS 就会自动去上游更新,保持缓存永远“新鲜”
serve-expired-prefetch-time 300
# 优化 3:配合预取开关
prefetch-domain yes
# 优化 4:保持缓存的热度
# 即使域名过期了,只要在 129600 秒(36小时)内有人访问,就先给旧结果,同步后台去拿新结果
serve-expired-ttl 129600
# 测速与响应模式
#speed-check-mode tcp:443,tcp:80,ping
speed-check-mode tcp:443,ping
response-mode first-ping
dualstack-ip-selection no
# TTL 修正
rr-ttl-min 60
rr-ttl-max 600
rr-ttl-reply-max 60
# ========================================================
# 3. 引导 DNS (用于解析 DoH/DoQ 域名)
# ========================================================
server 223.5.5.5 -bootstrap-dns
server 119.29.29.29 -bootstrap-dns
server 8.8.8.8 -bootstrap-dns
# ========================================================
# 4. 国内优选组 (China)
# ========================================================
# 利用 -subnet 指令,强制上游 DNS 返回适合广东电信网段的 IP 地址
# [UDP 53] 最稳的 ECS 传递方式,不涉及证书校验,此处单独指定subnet是因为测试发现MosDNS传过来的有时候会不生效所以再强制指定一次
server 223.5.5.5 -group china -exclude-default-group -subnet 219.136.128.50/24
server 119.29.29.29 -group china -exclude-default-group -subnet 219.136.128.50/24
# [QUIC/DoH/DoT] 加密链路,同样带上 -subnet 参数
server-quic quic://223.5.5.5 -group china -exclude-default-group -subnet 219.136.128.50/24
# 腾讯系:关闭证书校验防止日志报错,确保 -subnet 正常工作
server-https https://1.12.12.12/dns-query -group china -exclude-default-group -no-check-certificate -subnet 219.136.128.50/24
server-tls 1.12.12.12:853 -group china -exclude-default-group -no-check-certificate -subnet 219.136.128.50/24
# ========================================================
# 5. 境外优选组 (Remote) - 保持海外原生解析
# ========================================================
# 境外组禁止使用 -subnet 参数,确保返回离你海外服务器最近的节点
# Google
server-https https://8.8.8.8/dns-query -host-name dns.google -group remote -exclude-default-group
server-https https://8.8.4.4/dns-query -host-name dns.google -group remote -exclude-default-group
# Cloudflare
server-https https://1.1.1.1/dns-query -group remote -exclude-default-group
server-https https://1.0.0.1/dns-query -group remote -exclude-default-group
# AdGuard DNS (支持 QUIC)
server-quic quic://dns.adguard-dns.com -group remote -exclude-default-group
# Quad9
# 优先使用 QUIC 避免 HTTP 400 兼容性问题
server-quic quic://dns9.quad9.net -group remote -exclude-default-group
# 其次使用 TLS
server-tls 9.9.9.9:853 -host-name dns9.quad9.net -group remote -exclude-default-group更多有关SmartDNS配置的请参考官方文档
- 0
- 0
-
分享