← 返回首页

DNS 分流实录:国内走国内、境外走境外(Windows + mosdns)

双出口路由器上折腾了半天,出口终于通了、浏览器也能打开谷歌。但紧接着发现另一个问题:国内网站变慢了。

原因很简单——我把系统 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 给的地址完全不是一回事:

CODE
站点              走 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. 放文件

CODE
C:\DNSSplit\
    mosdns.exe          # GitHub release 里的 windows-amd64
    cn.txt              # CN 地址表,一行一个 CIDR
    intl_domain.txt     # 强制走境外的域名
    config.yaml

cn.txt 用现成的 china 列表就行;我直接导出了路由器地址表里的那份(4000 多条),保证 DNS 判定和路由分流的口径一致。

2. intl_domain.txt(防劫持兜底)

geoip 判定有个盲区:如果国内 DNS 把境外域名劫持到国内镜像(返回的是 CN 的 IP),「IP 属于 CN 就采用」的规则会误判。所以把容易被劫持的域名单独列出来强制走境外:

CODE
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

CODE
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 所以没有黑窗口):

CODE
$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 指过去

CODE
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. 验证

CODE
nslookup www.google.com 127.0.0.1     # 应该返回真 Google 段
nslookup www.baidu.com  127.0.0.1     # 应该返回本地运营商段
netstat -ano | findstr :53            # 确认是 mosdns 在监听

三、效果

配图

CODE
站点              只走 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:

CODE
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:

CODE
- matches: qname $intl_domain
  exec: $intl_up
- matches: qname $intl_domain
  exec: accept

2. 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 不碰其他任何东西
最后提醒一句:DNS 分流和出口分流是两件事。路由上的 routing-mark 配好,只代表包能出去;DNS 决定包往哪个 IP 发。两边都对了,浏览器才真的打开——这次就是出口先通了、DNS 卡了最后一道。

💬 评论 (0)

今日还可评论 3 条

还没有评论,来抢沙发吧。