Life is fantastic

Life

构建 DNS 终极形态:AdGuardHome + MosDNS + SmartDNS 级联解析与分流治理实战

2026-05-01

引言

在当前的互联网基础设施中,DNS 虽轻量,却是决定网络吞吐质量的第一道闸门。DNS 劫持、递归污染、以及跨网访问引发的 CDN 调度失效,已成为开发者与高级玩家追求“极致响应”的主要障碍。

单一的工具往往存在短板:AdGuardHome 虽具备优秀的拦截 UI,但在大规模规则下的分流逻辑略显臃肿;SmartDNS 擅长并发测速却缺乏灵活的分流策略;MosDNS 拥有强大的插件化逻辑控制,却不具备直观的状态分析。

本文将深入探讨如何通过这三者的解耦与级联,构建一套高性能、零污染、自动优选的 DNS 治理体系:

  • AdGuardHome:作为流量入口,执行流式正则匹配,完成首轮去广告与审计。

  • MosDNS:充当中央分流器,基于 GeoIP 与域名列表实现国内/海外流量的逻辑解耦。

  • SmartDNS:作为底层递归服务器,利用多源并发 RTT 测速与大容量 Cache,确保解析链路的最短路径。

通过本方案,我们将实现:

  1. 解析延迟降低:充分利用系统缓存与并发查询机制。

  2. CDN 调度重构:杜绝海外解析返回国内节点的情况,确保流量直达最优边缘节点。

  3. 隐私与安全:全量开启 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
fi

3. 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配置的请参考官方文档