双出口路由器上折腾了半天,出口终于通了、浏览器也能打开谷歌。但紧接着发现另一个问题:国内网站变慢了。
原因很简单——我把系统 DNS 直接改成了 8.8.8.8。它不区分国内外:查 taobao 也返回一个海外边缘节点(实测 155.x),TCP+TLS 握手从 50ms 级别涨到 766ms。
于是有了这篇:DNS 也要分流。
一、光换 DNS 为什么不够
境外站点的坑是「DNS 污染」:国内公共 DNS 对某些域名返回假地址(比如把域名解析到 2001::1 或者干脆返回别家公司的 IP),你连过去当然超时。
而 8.8.8.8 反过来的坑是「CDN 就近性丢失」。同一个域名,两个 DNS 给的地址完全不是一回事:
站点 走 8.8.8.8 走国内 DNS
www.baidu.com 103.235.46.96 (香港) 本地运营商节点
www.taobao.com 155.102.181.212 本地运营商节点(国内 DNS 返回的具体地址属于本地运营商网段,这里不贴了,反正同一个省内节点,延迟差一个数量级。)
所以真正需要的是按解析结果判断:先问国内 DNS,如果它返回的 IP 落在 CN 地址段里,说明这是正常的国内解析,采用;如果不在,说明要么是境外站点、要么国内 DNS 给错了,改问境外 DNS。
二、动手:Windows + mosdns
选 mosdns 的理由:单文件 exe、纯 DNS 分流、不带任何代理功能、配置是纯文本,出问题一眼能看明白。
1. 放文件
C:\DNSSplit\
mosdns.exe # GitHub release 里的 windows-amd64
cn.txt # CN 地址表,一行一个 CIDR
intl_domain.txt # 强制走境外的域名
config.yamlcn.txt 用现成的 china 列表就行;我直接导出了路由器地址表里的那份(4000 多条),保证 DNS 判定和路由分流的口径一致。
2. intl_domain.txt(防劫持兜底)
geoip 判定有个盲区:如果国内 DNS 把境外域名劫持到国内镜像(返回的是 CN 的 IP),「IP 属于 CN 就采用」的规则会误判。所以把容易被劫持的域名单独列出来强制走境外:
domain:google.com
domain:googleapis.com
domain:gstatic.com
domain:youtube.com
domain:ytimg.com
domain:googlevideo.com
domain:googleusercontent.com
domain:recaptcha.net(domain: 前缀表示匹配该域名及其所有子域)
3. config.yaml
log:
level: info
file: "./mosdns.log"
plugins:
- tag: cache
type: cache
args:
size: 8192
lazy_cache_ttl: 86400
- tag: cn_up
type: forward
args:
concurrent: 2
upstreams:
- addr: "114.114.114.114"
- addr: "223.5.5.5"
- addr: "119.29.29.29"
- tag: intl_up
type: forward
args:
concurrent: 2
upstreams:
- addr: "8.8.8.8"
- addr: "8.8.4.4"
- tag: cn_ip
type: ip_set
args:
files:
- "./cn.txt"
- tag: intl_domain
type: domain_set
args:
files:
- "./intl_domain.txt"
- tag: main
type: sequence
args:
- exec: $cache
- matches: qname $intl_domain
exec: $intl_up
- matches: qname $intl_domain
exec: accept
- exec: $cn_up
- matches: resp_ip $cn_ip
exec: accept
- exec: $intl_up
- tag: udp_srv
type: udp_server
args:
entry: main
listen: "127.0.0.1:53"
- tag: tcp_srv
type: tcp_server
args:
entry: main
listen: "127.0.0.1:53"主流程就五步:查缓存 → 命中强制境外域名就直接走境外 → 否则问国内 → 应答 IP 在 CN 表里就收下 → 不在就改问境外。
4. 开机自启
mosdns 不是服务程序,用计划任务拉起来就行(SYSTEM、开机触发、不限运行时长,跑在 session 0 所以没有黑窗口):
$act = New-ScheduledTaskAction -Execute 'C:\DNSSplit\mosdns.exe' `
-Argument 'start -c "C:\DNSSplit\config.yaml" -d "C:\DNSSplit"' `
-WorkingDirectory 'C:\DNSSplit'
$trg = New-ScheduledTaskTrigger -AtStartup
$pri = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
$set = New-ScheduledTaskSettingsSet -ExecutionTimeLimit ([TimeSpan]::Zero) `
-RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1)
Register-ScheduledTask -TaskName 'mosdns-dns-split' -Action $act -Trigger $trg `
-Principal $pri -Settings $set -Force
Start-ScheduledTask -TaskName 'mosdns-dns-split'5. 系统 DNS 指过去
netsh interface ip set dns name="Ethernet0" static 127.0.0.1 primary
netsh interface ip add dns name="Ethernet0" 8.8.8.8 index=2
ipconfig /flushdns第二行是兜底:万一 mosdns 没起来,Windows 会退到 8.8.8.8,不至于整机没网。
6. 验证
nslookup www.google.com 127.0.0.1 # 应该返回真 Google 段
nslookup www.baidu.com 127.0.0.1 # 应该返回本地运营商段
netstat -ano | findstr :53 # 确认是 mosdns 在监听三、效果
站点 只走 8.8.8.8 分流后
google.com 128 ms 133 ms
baidu.com 139 ms 70 ms
taobao.com 766 ms 52 ms
qq.com 165 ms 75 ms谷歌几乎没变(本来就该走境外),国内站点全部回到本地节点。真实浏览器实测四个站点都是 200:
https://www.google.com/ 200 OK Google
https://www.youtube.com/ 200 OK YouTube
https://www.baidu.com/ 200 OK 百度一下,你就知道
https://www.taobao.com/ 200 OK 淘宝四、三个坑
1. mosdns 的 forward 执行完不会终止流程
一开始写成「命中境外域名 → 走境外上游」,结果 fonts.gstatic.com 还是返回国内 DNS 的劫持地址。原因是 exec 只是把应答写进上下文,后面的国内上游会覆盖它。必须在后面再补一条显式 accept:
- matches: qname $intl_domain
exec: $intl_up
- matches: qname $intl_domain
exec: accept2. 53 端口绑不上,5353 反而被占
想先用 5353 做验收,结果 bind 报 10013(权限不允许)——本机的 mDNS 之类把 5353 占了。而 53 其实没有任何程序监听,直接绑 53 就行。别想当然。
3. 纯 socket 通过 ≠ 浏览器能打开
用 IP 直连能拿到 200,浏览器还是打不开——因为浏览器走的是系统 DNS 解析出来的那个(被污染的)地址。必须用真实浏览器验一遍,status 和 title 都对才算过。
五、维护与回滚
- 换 CN 地址表:更新
cn.txt后重启 mosdns - 加域名:往
intl_domain.txt追加一行domain:xxx,重启生效 - 日志:
C:\DNSSplit\mosdns.log - 想撤:把系统 DNS 改回原来那个即可,mosdns 不碰其他任何东西

💬 评论 (0)
还没有评论,来抢沙发吧。