OpenWrt mosdns + OpenClash 防DNS泄露 + Fake-IP 完整解决方案


前言

基于 ImmortalWrt 24.10.1 实际调试经验,适用于 mosdns 分流 + OpenClash fake-ip 模式的透明代理架构。
解决了 DNS 泄露、fake-ip 连接阻塞、DoT 端口封锁等常见问题。


一、架构总览

1.1 整体架构示意图

OpenWrt mosdns + OpenClash 防DNS泄露架构
防护机制
DoH 加密查询
域名分流路由
Fallback 不回退
零 DNS 泄露
关键约束
禁用 DoT 853
Bootstrap 用 UDP
DNS 域名不加 DIRECT
客户端层 — 手机 / 电脑 / 平板
浏览器
访问 google.com
App
访问 baidu.com
系统服务
NTP / Apple 等
DNS 入口 — dnsmasq (:53)
不缓存 / 不解析
cachesize=0, noresolv=1
转发到 mosdns
server=127.0.0.1#5335
Fake-IP 分配 — OpenClash DNS (:7874)
DNS 劫持
enable_redirect_dns=1
返回 fake-ip
198.18.0.1/16
上游指向 mosdns
127.0.0.1:5335
DNS 分流引擎 — mosdns (:5335)
geosite_cn
→ 国内 DoH 直连
geosite_!cn / fallback
→ 国外 DoH 经代理
blocklist / adlist
→ reject 广告拦截
透明代理 — OpenClash (nftables)
国内流量 → DIRECT
GEOIP,CN 直连
国外流量 → PROXY
MATCH,Others 代理隧道
外部 DNS 服务器
dns.alidns.com
阿里 DoH
doh.pub
腾讯 DoH
dns.google
Google DoH
cloudflare-dns.com
CF DoH
Bootstrap
223.5.5.5
119.29.29.29
国内 UDP 直连
缓存
mosdns: 8000 条
lazy_ttl: 24h
持久化转储
并发
concurrent: 2
threshold: 500ms

1.2 DNS 数据查询流转图

场景一:访问国内网站(如 baidu.com)

digraph dns_cn {
    rankdir=TB;
    node [shape=box, style="rounded,filled", fillcolor="#e0f2fe", color="#0284c7", fontname="Helvetica", fontsize=11, margin="0.2,0.15"];
    edge [color="#64748b", fontname="Helvetica", fontsize=9, fontcolor="#64748b"];
    bgcolor="transparent";

    browser [label="浏览器请求 baidu.com", fillcolor="#dbeafe", color="#3b82f6"];
    dnsmasq [label="dnsmasq (:53)\n转发到 OpenClash DNS"];
    clash_dns [label="OpenClash DNS (:7874)\n委托 mosdns 查询"];
    mosdns [label="mosdns (:5335)", fillcolor="#e0e7ff", color="#6366f1"];
    match [label="匹配 geosite_cn\n命中国内域名", fillcolor="#ecfdf5", color="#10b981"];
    doh_local [label="forward_local\n国内 DoH 服务器\nhttps://dns.alidns.com/dns-query", fillcolor="#ecfdf5", color="#10b981"];
    proxy [label="OpenClash 透明代理\n匹配 GEOIP,CN → DIRECT", fillcolor="#f5f3ff", color="#7c3aed"];
    real_ip [label="阿里 DNS 返回真实 IP\n例: x.x.x.x", fillcolor="#ecfdf5", color="#10b981"];
    direct [label="浏览器直连真实 IP\n访问 baidu.com", fillcolor="#dbeafe", color="#3b82f6"];
    result [label="完成\nISP 只看到 HTTPS 流量", fillcolor="#d1fae5", color="#059669", shape=oval];

    browser -> dnsmasq [label="DNS 查询 :53"];
    dnsmasq -> clash_dns [label="转发"];
    clash_dns -> mosdns [label="实际查询"];
    mosdns -> match;
    match -> doh_local [label="国内域名"];
    doh_local -> proxy [label="DoH :443"];
    proxy -> real_ip [label="直连返回"];
    real_ip -> direct [label="返回真实 IP"];
    direct -> result;
}

场景二:访问国外网站(如 github.com)

digraph dns_foreign {
    rankdir=TB;
    node [shape=box, style="rounded,filled", fillcolor="#e0f2fe", color="#0284c7", fontname="Helvetica", fontsize=11, margin="0.2,0.15"];
    edge [color="#64748b", fontname="Helvetica", fontsize=9, fontcolor="#64748b"];
    bgcolor="transparent";

    browser [label="浏览器请求 github.com", fillcolor="#dbeafe", color="#3b82f6"];
    dnsmasq [label="dnsmasq (:53)\n转发到 OpenClash DNS"];
    clash_dns [label="OpenClash DNS (:7874)\n委托 mosdns 查询"];
    mosdns [label="mosdns (:5335)", fillcolor="#e0e7ff", color="#6366f1"];
    match [label="匹配 geosite_!cn 或 fallback\n命中国外域名", fillcolor="#fef3c7", color="#d97706"];
    doh_remote [label="forward_remote\n国外 DoH 经代理隧道\ndns.google / cloudflare-dns.com", fillcolor="#fef3c7", color="#d97706"];
    fakeip [label="OpenClash 返回 fake-ip\ngithub.com → 198.18.x.x", fillcolor="#fce7f3", color="#db2777"];
    connect [label="浏览器连接 198.18.x.x:443", fillcolor="#e0f2fe", color="#0284c7"];
    intercept [label="mihomo 拦截\n查找 fake-ip 映射 → github.com\n匹配规则 → PROXY", fillcolor="#fce7f3", color="#db2777"];
    proxy [label="代理节点连接真实 IP\n返回数据给客户端", fillcolor="#f5f3ff", color="#7c3aed"];
    result [label="完成\nISP 看不到域名和 DNS", fillcolor="#d1fae5", color="#059669", shape=oval];

    browser -> dnsmasq [label="DNS 查询 :53"];
    dnsmasq -> clash_dns [label="转发"];
    clash_dns -> mosdns [label="实际查询"];
    mosdns -> match;
    match -> doh_remote [label="国外域名"];
    doh_remote -> fakeip [label="返回真实 IP\n但 OpenClash\n转为 fake-ip"];
    fakeip -> connect;
    connect -> intercept [label="透明代理拦截"];
    intercept -> proxy [label="代理隧道"];
    proxy -> result;
}

二、核心组件详解

2.1 dnsmasq — DNS 入口

角色:局域网 DNS 入口,不做任何解析,仅转发。

# /etc/config/dhcp
config dnsmasq
    option noresolv '1'              # 不读取系统 /etc/resolv.conf
    list server '127.0.0.1#5335'    # 所有查询转发给 mosdns
    option cachesize '0'            # 不缓存(由 mosdns 管理)
    option rebind_protection '0'    # 关闭 rebind 保护(fake-ip 会触发)
    option localuse '1'             # 路由器自身也走 dnsmasq

关键配置说明

配置项 原因
noresolv 1 不使用系统 DNS,确保所有查询走 mosdns
server 127.0.0.1#5335 转发目标为 mosdns
cachesize 0 禁用 dnsmasq 缓存,避免与 mosdns 缓存冲突
rebind_protection 0 fake-ip 返回的 198.18.x.x 会触发 rebind 保护导致解析失败

2.2 mosdns — DNS 分流引擎

角色:根据域名列表决定 DNS 查询走国内还是国外。

# /etc/config/mosdns
config mosdns
    option listen_port '5335'
    option bootstrap_dns '223.5.5.5'     # 国内 UDP DNS,用于预解析 DoH 域名
    option cache_size '8000'
    option lazy_cache_ttl '86400'        # 惰性缓存 24 小时
    option dns_leak '1'                  # 防泄露模式
    option redirect '0'                  # 不劫持 DNS(由 OpenClash 处理)

    # 国内 DNS — 加密查询,直连
    list local_dns 'https://dns.alidns.com/dns-query'
    list local_dns 'https://doh.pub/dns-query'
    list local_dns 'https://doh.360.cn/dns-query'

    # 国外 DNS — 加密查询,经代理隧道
    list remote_dns 'https://dns.google/dns-query'
    list remote_dns 'https://cloudflare-dns.com/dns-query'

关键配置说明

配置项 原因
bootstrap_dns 223.5.5.5 最关键。必须用国内 UDP DNS,用于解析 DoH 服务器域名。绝不能设为 8.8.8.8(需要加密连接,而 853 端口被封)
local_dns 国内 DoH 阿里/腾讯/360 的 DoH 服务,ISP 只能看到 HTTPS 流量
remote_dns 国外 DoH 只用 DoH,不用 DoT。DoT 端口 853 被国内 ISP 封锁
redirect 0 不劫持 DNS 流量,由 OpenClash 统一处理 fake-ip
dns_leak 1 fallback 时不会回退到国内 DNS,防止泄露

分流逻辑

digraph dns_routing {
    rankdir=TB;
    node [shape=box, style="rounded,filled", fontname="Helvetica", fontsize=10, margin="0.15,0.1"];
    edge [color="#64748b", fontname="Helvetica", fontsize=9, fontcolor="#64748b"];
    bgcolor="transparent";

    query [label="收到 DNS 查询", fillcolor="#dbeafe", color="#3b82f6"];
    geo_cn [label="geosite_cn\n国内域名", fillcolor="#ecfdf5", color="#10b981"];
    geo_ncn [label="geosite_!cn\n国外域名", fillcolor="#fef3c7", color="#d97706"];
    grey [label="greylist\n灰名单", fillcolor="#fef3c7", color="#d97706"];
    block [label="blocklist / adlist\n广告拦截", fillcolor="#fee2e2", color="#dc2626"];
    apple [label="geosite_apple\nApple 域名", fillcolor="#ede9fe", color="#7c3aed"];
    fallback [label="未匹配\nfallback", fillcolor="#fef3c7", color="#d97706"];

    fwd_local [label="forward_local\n国内 DoH 直连\n阿里 / 腾讯 / 360", fillcolor="#d1fae5", color="#059669"];
    fwd_remote [label="forward_remote\n国外 DoH 经代理\ndns.google / cloudflare", fillcolor="#fde68a", color="#b45309"];
    reject [label="reject\n返回 NXDOMAIN", fillcolor="#fecaca", color="#b91c1c"];
    apple_fb [label="apple_domain_fallback\n先国内查 → 非中国 IP\n则用 UDP 兜底", fillcolor="#ddd6fe", color="#6d28d9"];

    query -> geo_cn [label="匹配"];
    query -> geo_ncn [label="匹配"];
    query -> grey [label="匹配"];
    query -> block [label="匹配"];
    query -> apple [label="匹配"];
    query -> fallback [label="未匹配"];

    geo_cn -> fwd_local;
    geo_ncn -> fwd_remote;
    grey -> fwd_remote;
    block -> reject;
    apple -> apple_fb;
    fallback -> fwd_remote;
}

fallback 机制(防泄露核心)

{
  "primary": "forward_remote_upstream",
  "secondary": "forward_remote_upstream",
  "threshold": 500,
  "always_standby": true
}

primary 和 secondary 都使用同一个远程上游(国外 DoH),这是有意为之

  • 如果 secondary 用国内 DNS,fallback 时就会泄露国外域名查询
  • 容错靠 forward_remote 内部 concurrent: 2 并发查询两个国外 DoH 服务器
  • threshold: 500:如果第一个响应超过 500ms,继续等待另一个结果

2.3 OpenClash — 透明代理 + Fake-IP

角色:透明代理流量转发、fake-ip 地址分配、规则匹配分流。

# /etc/config/openclash
config openclash
    option en_mode 'fake-ip'              # fake-ip 模式
    option fakeip_range '198.18.0.1/16'   # fake-ip 地址池
    option store_fakeip '1'               # 持久化 fake-ip 映射
    option dns_port '7874'                # OpenClash DNS 端口
    option enable_redirect_dns '1'        # 启用 DNS 劫持
    option redirect_dns '1'               # 将客户端 DNS 劫持到 7874
    option enable_custom_dns '1'          # 启用自定义 DNS

DNS 上游配置

# OpenClash DNS 上游指向 mosdns
config dns_servers
    option type 'udp'
    option ip '127.0.0.1:5335'    # mosdns
    option enabled '1'
    option group 'nameserver'     # 必须是 nameserver,不能是 default

关键配置说明

配置项 原因
en_mode fake-ip 返回虚假 IP,让所有流量经过代理层,实现按域名匹配规则
enable_redirect_dns 1 劫持客户端 DNS 到 OpenClash,确保返回 fake-ip
dns_port 7874 OpenClash DNS 监听端口
DNS 上游 127.0.0.1:5335 指向 mosdns,保留防泄露分流能力
store_fakeip 1 持久化映射,重启不丢失

fake-ip 工作原理

digraph fakeip_flow {
    rankdir=TB;
    node [shape=box, style="rounded,filled", fontname="Helvetica", fontsize=10, margin="0.15,0.1"];
    edge [color="#64748b", fontname="Helvetica", fontsize=9, fontcolor="#64748b"];
    bgcolor="transparent";

    s1 [label="客户端查询 github.com", fillcolor="#dbeafe", color="#3b82f6"];
    s2 [label="OpenClash DNS 返回\n198.18.x.x (fake-ip)", fillcolor="#fce7f3", color="#db2777"];
    s3 [label="客户端连接 198.18.x.x:443", fillcolor="#e0f2fe", color="#0284c7"];
    s4 [label="mihomo 拦截\n查找映射:198.18.x.x → github.com", fillcolor="#fce7f3", color="#db2777"];
    s5 [label="按 github.com 匹配规则\n→ 走代理节点", fillcolor="#f5f3ff", color="#7c3aed"];
    s6 [label="代理节点连接真实 IP\n数据回传客户端", fillcolor="#d1fae5", color="#059669", shape=oval];

    s1 -> s2 [label="DNS 查询"];
    s2 -> s3 [label="返回虚假 IP"];
    s3 -> s4 [label="TCP 连接"];
    s4 -> s5 [label="域名映射"];
    s5 -> s6 [label="代理转发"];
}

为什么需要 fake-ip

模式 规则匹配依据 优点 缺点
redir-host 真实 IP 简单 只能按 IP 匹配,CDN 域名可能匹配错误
fake-ip 域名 精确匹配域名规则 需要 DNS 劫持配合

2.4 OpenClash 规则

rules:
  # LAN 直连
  - GEOIP,lan,DIRECT,no-resolve

  # 代理节点自身直连(确保代理通道可用)
  - DOMAIN,ws.proxy.domain,proxy
  - IP-CIDR,代理服务器IP/32,DIRECT

  # 分流规则
  - GEOIP,google,PROXY
  - GEOIP,telegram,PROXY
  - GEOIP,twitter,PROXY
  - GEOIP,CN,DIRECT

  # 兜底 — 未匹配走代理
  - MATCH,Others

关键原则

  • 不要dns.googlecloudflare-dns.com 等 DNS 服务器域名加 DIRECT 规则
  • 让它们走 MATCH,Others → PROXY,DoH 流量经代理隧道发出
  • 如果加了 DIRECT,会形成死循环:OpenClash 解析 dns.google → 需要 DNS → 再去查 → 死循环

三、常见问题与解决方案

3.1 DoT 端口 853 被封锁

现象dial tcp 8.8.8.8:853: i/o timeout

原因:国内运营商普遍封锁 853 端口(DNS over TLS)。

解决

  • mosdns remote_dns 只用 DoH(https://),不用 DoT(tls://
  • OpenClash DNS 上游不要使用 tls:// 类型的服务器
# 错误 ❌
list remote_dns 'tls://8.8.8.8'
list remote_dns 'tls://1.1.1.1'

# 正确 ✅
list remote_dns 'https://dns.google/dns-query'
list remote_dns 'https://cloudflare-dns.com/dns-query'

3.2 Bootstrap DNS 循环依赖

现象:所有 DNS 查询全部失败

原因:bootstrap 设为 8.8.8.8,但连接 8.8.8.8 需要加密(DoT/DoH),而加密连接又需要先解析 DNS → 死循环。

解决:bootstrap 必须是可靠的、直连可达的 UDP DNS。

# 错误 ❌
option bootstrap_dns '8.8.8.8'

# 正确 ✅
option bootstrap_dns '223.5.5.5'
option bootstrap_dns '119.29.29.29'

3.3 Fake-ip 连接阻塞(Pending)

现象:浏览器开发者工具中某些资源(如 avatars.githubusercontent.com)一直显示 pending。

原因:DNS 查询绕过了 OpenClash 的 fake-ip 机制,返回真实 IP。mihomo 无法正确按域名匹配规则,导致连接状态异常。

解决

  1. 启用 OpenClash DNS 劫持(enable_redirect_dns='1'
  2. OpenClash DNS 上游指向 mosdns
  3. 禁用 mosdns 的 DNS 劫持(redirect='0'),避免冲突
修复前:客户端 → mosdns → 真实 IP → 连接状态异常
修复后:客户端 → OpenClash DNS → fake-ip → mihomo 正确处理

3.4 DoH 直连死循环

现象TLS handshake timeout,国外域名全部解析失败。

死循环过程

digraph dns_loop {
    rankdir=TB;
    node [shape=box, style="rounded,filled", fontname="Helvetica", fontsize=10, margin="0.15,0.1"];
    edge [color="#64748b", fontname="Helvetica", fontsize=9, fontcolor="#64748b"];
    bgcolor="transparent";

    s1 [label="mosdns 查询 google.com\n→ forward_remote", fillcolor="#e0f2fe", color="#0284c7"];
    s2 [label="需要解析 dns.google 的 IP\n→ bootstrap 返回 8.8.8.8", fillcolor="#fef3c7", color="#d97706"];
    s3 [label="mosdns 直连 8.8.8.8:443\n发送 DoH 请求", fillcolor="#e0f2fe", color="#0284c7"];
    s4 [label="流量被 OpenClash 透明代理拦截\n匹配规则 → DIRECT", fillcolor="#fee2e2", color="#dc2626"];
    s5 [label="OpenClash 走 DIRECT\n需要解析 dns.google\n→ 再次触发 DoH 请求", fillcolor="#fee2e2", color="#dc2626"];
    loop [label="死循环!\n所有国外 DNS 查询失败", fillcolor="#fecaca", color="#b91c1c", shape=oval, fontcolor="#7f1d1d"];

    s1 -> s2;
    s2 -> s3;
    s3 -> s4;
    s4 -> s5;
    s5 -> loop [style=bold, color="#dc2626", penwidth=2];
    loop -> s1 [label="循环", style=dashed, color="#dc2626", constraint=false];
}

解决:OpenClash 规则中不要给 DNS 服务器域名加 DIRECT。

3.5 OpenClash DNS 泄露(nameserver 组配置错误)

现象:ipleak.net 检测到 DNS 泄露,显示 114.114.114.114 / 8.8.8.8 等公共 DNS。

原因:OpenClash 的 DNS 上游条目 group 设为了 default,实际写入了 default-nameserver(bootstrap DNS),而 nameserver 组为空。mihomo 检测到 nameserver 为空后自动填充硬编码默认值 ['114.114.114.114', '119.29.29.29', '8.8.8.8', '1.1.1.1'],导致明文 DNS 泄露。

解决:将 mosdns 条目的 group 从 default 改为 nameserver

# 查找mosdns条目(通常是最后一个dns_servers条目)
uci show openclash | grep '127.0.0.1:5335'

# 修改group为nameserver
uci set openclash.cfg536193.group='nameserver'  # 替换为实际section名
uci commit openclash
/etc/init.d/openclash restart

验证:检查生成的配置中 nameserver 组只包含 mosdns:

grep -A 5 'nameserver:' /etc/openclash/config.yaml
# 应该只有:
# nameserver:
#   - 127.0.0.1:5335#disable-ipv6=true
# 不应该有 114.114.114.114 / 8.8.8.8 等

3.6 阿里/腾讯 DoH 服务 502 错误

现象bad http status codes 502 with body [nginx]

原因:国内 DoH 服务端短暂异常。

影响:间歇性,通常几秒后自动恢复。

缓解:配置多个国内 DoH 上游(阿里 + 腾讯 + 360),mosdns 并发查询时一个失败另一个还能返回。


四、防 DNS 泄露原理

什么是 DNS 泄露?

当你通过代理访问国外网站时,如果 DNS 查询被发送到了国内 DNS 服务器(如 114.114.114.114),ISP 就能看到你在查询什么域名。即使网站流量走了代理,DNS 查询本身已经暴露了你的访问意图。

本方案如何防泄露

DNS 泄露防护三层机制
第一层:加密 DNS 查询
国内域名 → DoH (HTTPS)
ISP 只看到 HTTPS 流量,看不到 DNS 内容
国外域名 → DoH (HTTPS) 经代理隧道
ISP 完全看不到 DNS 查询
第二层:分流路由
mosdns 根据域名列表决定走国内还是国外 DNS
国外域名的 DoH 流量通过代理隧道发出
第三层:Fallback 不回退
fallback 的 primary 和 secondary 都用国外上游
即使一个失败,也不会回退到国内 DNS 泄露查询
三层防护确保:ISP 无法得知用户访问的国外域名

对比:有泄露 vs 无泄露

场景 有 DNS 泄露 本方案(无泄露)
访问 google.com DNS 查询发到 114.114.114.114,ISP 看到 google.com DNS 查询经代理隧道发到 dns.google,ISP 看不到
DoH 流量 直连 dns.google:443,可能被干扰 经代理隧道,ISP 只看到代理连接
fallback 回退到国内 DNS,泄露国外域名 只用国外上游,永不泄露

五、配置检查清单

检查项 正确配置 错误配置 影响
bootstrap_dns 223.5.5.5 / 119.29.29.29 8.8.8.8 / 1.1.1.1 所有 DNS 失败
mosdns remote_dns https://dns.google/dns-query tls://8.8.8.8 853 端口被封
OpenClash DNS 上游 127.0.0.1:5335 group=nameserver group=default 或直接用国内 DNS DNS 泄露(114/8.8 等明文查询)
OpenClash DNS 劫持 enable_redirect_dns='1' '0' fake-ip 不生效
mosdns DNS 劫持 redirect='0' '1' 与 OpenClash 冲突
OpenClash DNS 域名规则 不加 DIRECT,走 MATCH/Others 加了 DIRECT 死循环
dnsmasq rebind_protection 0 1 fake-ip 解析失败
dnsmasq cachesize 0 正数 与 mosdns 缓存冲突
dnsmasq server 127.0.0.1#5335 未设置 DNS 不走 mosdns

六、最终配置参考

dnsmasq (/etc/config/dhcp)

config dnsmasq
    option noresolv '1'
    list server '127.0.0.1#5335'
    option cachesize '0'
    option rebind_protection '0'
    option localuse '1'
    option ednspacket_max '1232'

mosdns (/etc/config/mosdns)

config mosdns
    option listen_port '5335'
    option bootstrap_dns '223.5.5.5'
    option cache_size '8000'
    option lazy_cache_ttl '86400'
    option dns_leak '1'
    option redirect '0'
    option local_dns_redirect '0'
    option concurrent '2'

    list local_dns 'https://dns.alidns.com/dns-query'
    list local_dns 'https://doh.pub/dns-query'
    list local_dns 'https://doh.360.cn/dns-query'

    list remote_dns 'https://dns.google/dns-query'
    list remote_dns 'https://cloudflare-dns.com/dns-query'

OpenClash (/etc/config/openclash)

config openclash
    option en_mode 'fake-ip'
    option fakeip_range '198.18.0.1/16'
    option store_fakeip '1'
    option dns_port '7874'
    option enable_redirect_dns '1'
    option redirect_dns '1'
    option enable_custom_dns '1'

# DNS 上游指向 mosdns(group 必须是 nameserver)
config dns_servers
    option type 'udp'
    option ip '127.0.0.1:5335'
    option enabled '1'
    option group 'nameserver'
    option disable_ipv6 '1'

七、适用环境

  • 系统:OpenWrt / ImmortalWrt 24.10+
  • mosdns 版本:5.x+
  • OpenClash 核心:Meta(mihomo)
  • 代理模式:fake-ip + rule
  • ISP 环境:国内运营商(853/DoT 端口被封锁)

手机扫码阅读

用友 NC 系统 IMsgCenterWebService SQL注入漏洞

孚盟云CRM BusiPriceOkPrint.aspx SQL注入漏洞

评 论