3191 字
16 分钟
VPS 上的分流:境内外判断交给 IP,域名只用来选解析器
2026-08-30
2026-10-06

环境与说明#

Xray-core v26.6.1,配置里的 warp 出站是保守侧的中转,它可以是 Cloudflare WARP 或其他出站,不重要。

文中的域名数据来自 @Loyalsoldier/v2ray-rules-dat 的 geosite.dat,构建日期 2026-08-30。换个上游或者换个构建时间,具体条目会有出入。

Loyalsoldier
/
v2ray-rules-dat
Waiting for api.github.com...
00K
0K
0K
Waiting...

下面假设读者自己写过分流规则,知道域名规则和 IP 规则各自什么时候生效。示例是 Xray 的服务端配置,不过整套思路和内核无关,sing-box 和 mihomo 一样能落地,客户端也适用,只是方向要反过来。

geosite 的 cn 里有什么#

Xray 官方的教程对这张表的描述是 「大杂烩,只要沾点中国关系的都往里丢」

把两张表拉出来对一遍,能看到不少域名同时命中:

CN full:www.gstatic.com
CN full:www.apple.com
CN full:init.itunes.apple.com
CN full:ocsp.apple.com
CN full:dl.google.com
CN full:fonts.gstatic.com
CN full:c.pki.goog
GEOLOCATION-!CN domain:gstatic.com
GEOLOCATION-!CN domain:www.apple.com
GEOLOCATION-!CN domain:init.itunes.apple.com
GEOLOCATION-!CN domain:ocsp.apple.com
GEOLOCATION-!CN domain:dl.google.com
GEOLOCATION-!CN domain:fonts.gstatic.com
GEOLOCATION-!CN domain:c.pki.goog

ocsp.globalsign.com 的情况稍有不同,它只命中 cn 里的 domain:globalsign.com。

条目这样写是存在合理性的。对于一个在北京的客户端,把 fonts.gstatic.com 当境内域名是合理的,Google Fonts(谷歌字体)服务确实有境内节点,直连比走代理快得多。

那它什么时候就不适用了呢?把同一份表搬到海外的服务端,「做路由决策的机器在境内」这个前提就没了,这些域名被归进 geosite:cn 也就从优势变成了误判。

这种归类也不是没人质疑,PR#328 的提议方觉得这类域名该单独成表,理由是客户端直连它们有 IP「送中」的风险,维护者不认可。

路由:境内外交给 IP 判断#

平时容易混在一起的,其实是不同层面的几个问题:

  1. 这条连接的目的 IP 落在哪一侧?只取决于地址本身。
  2. 这个域名由哪一侧的节点提供服务?取决于从哪里问,以及权威 DNS 怎么回。
  3. 这个域名属于谁,会不会被干扰?这是策略问题,和地理位置相关但不等价。

geosite:cn 回答的是第二个,而且前提是提问的人在境内。

在一台不希望代理回国流量的服务器上,更应当使用 ip 进行境内外的判断。服务器用自己的 IP 同时代理出国和回国流量,出入境时两条方向相反的流量在时间上可以被关联起来,且可以被识别(这件事社区讨论过很多,也有实测)

那么连接国内公司在海外的边缘节点会被关联与识别吗? 对于跨境链路上的观测方,答案是不会,但是站在服务提供方那一侧要 另算。

比换列表更省事的一步,是换成不依赖社区维护的判据,例如在客户端分流的场景下,对于微信和 QQ 这类程序,与其枚举腾讯的域名段(会滞后,而且它们的 CDN 域名还和别人共用),不如直接写进程规则。

对应的配置:

"routing": {
"domainStrategy": "IPIfNonMatch", // 域名规则不命中时解析成 IP 再匹配,IP 判据靠它生效
"rules": [
// api 必须在 geoip:private 之前,否则 API 自己的连接(目的是 127.0.0.1)会被拦掉
{ "type": "field", "inboundTag": ["api"], "outboundTag": "api" },
{ "type": "field", "inboundTag": ["dns-remote", "dns-remote-backup"], "outboundTag": "direct" },
{ "type": "field", "ip": ["geoip:private"], "outboundTag": "blocked" },
{ "type": "field", "protocol": ["bittorrent"], "outboundTag": "blocked" }, // 需要 inbound 开 sniffing
{ "type": "field", "ip": ["geoip:cn"], "outboundTag": "warp" }
// 到这里结束,一条指向 direct 的例外都没有,因为 direct 就是默认出站
]
}

境内解析器的查询请求会命中最后一条规则走 warp 出站,用不着也不应该为它单独写一条直连。

内置 DNS 服务器#

域名表只用来选解析器#

expectedIPs 对返回的 IP 的进行校验,只保留包含其列表中的地址;unexpectedIPs 反之。

Xray 构建解析器列表的顺序是命中 domains 条件的解析器按配置顺序排在前面,其余没标 skipFallback 的按配置顺序追加,核心将依次使用最终服务器列表中的服务器进行查询,并使用 expectedIPs 和 unexpectedIPs 过滤结果,为空则尝试列表中的下一个,第一个产出非空结果的解析器终止查询。

设置 expectedIPs: ["geoip:!cn"] 保留境外解析器答出的境外 IP,为空则使用列表中下一个解析器再次查询。境内解析器无过滤,并用 finalQuery 终止列表构建。

finalQuery 和 expectedIPs 不要一起开

Xray 的 finalQuery 为真的时候后面的解析器不再参与,而过滤到空恰恰要靠后面的解析器来兜,两个一起开,一次过滤失败就等于解析失败。

如果希望请求不至于失败,expectedIPs 里可以放一个 * 条目,过滤后为空时保留原始结果。

enableParallelQuery 会破坏这里依赖的顺序语义

这套做法整个建立在顺序语义上。enableParallelQuery:"true" 将启用分组并发,相邻且 clientIP、skipFallback、queryStrategy、tag、domains、过滤条件全都相同的服务器合成一组,组内谁先成功就用谁的地址,组间按顺序回落。

选用更合适的域名表#

这份 geosite.dat 里,cn 有十一万条多,绝大部分来自 china-list,也就是 dnsmasq-china-list 的 accelerated-domains,剩下的是 geolocation-cn 和 tld-cn,以及 apple.china 和 google.china 这两个 opt-in 文件。

dnsmasq-china-list 本身为选择解析器而建立,Apple 和 Google 是单独的 opt-in,符合在服务端选择解析器的需求。

部分域名需单独列出,比如 OCSP 域名,其是否单独处理需要取舍。Chrome 对叶证书不做在线 OCSP,实际依赖 CRLSets,Let’s Encrypt 在 2025 年停止 OCSP 服务并从证书里移除了 OCSP URL;

但 Windows 和 Safari 还是会查,这类客户端上它确实卡在 TLS 握手的关键路径上,一次境内解析加一跳绕行是能感觉到的。

设 ECS 防的是哪一种暴露#

为了让保守规则命中,可以给境内解析器设一个境内的 EDNS Client Subnet,让国内公司的服务返回大陆地址,IP 规则就会命中,绕行也就生效了。

上述域名虽然从境外解析会落到海外边缘节点,不存在回国流量,但提供服务的仍然是国内公司,例如使用代理登录微信、支付宝等行为会留下一条机房 IP 和一个国内身份的关联记录,这是存在风险的。

机器直连海外边缘节点留下的是机器的 IP,而走 ECS 并分流绕行至保守侧留下的是中转方的 IP,直接缓解了上述风险。但在实际中,搭建服务端的人有这个意识,用的人未必有,分流出问题,或者干脆故意连国内服务,代价都会记在机器的 IP 上。

这使得在服务端考虑这样的策略是有必要的,虽然 ban 掉所有回国流量也能解决风险,但代价是会影响用户的使用体验,官方文档 选 WARP 而不选黑洞也是这个理由。

不过要强调的是,这一套只是服务端的兜底。真正兼顾使用体验和安全的配置是在客户端用 process 进程名规则或者分应用代理。

对应的配置:

"dns": {
"queryStrategy": "UseIP",
"servers": [
// 主力解析器
{
"address": "https://1.1.1.1/dns-query",
"tag": "dns-remote",
"domains": [
"geosite:google-cn",
"geosite:apple-cn",
"geosite:microsoft@cn",
"geosite:steam@cn",
"geosite:gfw",
"domain:ocsp.globalsign.com",
"domain:status.rapidssl.com",
"domain:ocsp.r2m01.amazontrust.com",
"domain:oneocsp.microsoft.com",
"domain:c.lencr.org",
"domain:c.pki.goog"
],
"expectedIPs": ["geoip:!cn"],
"skipFallback": false, // 同时作为主力/默认解析器
"finalQuery": false,
"timeoutMs": 1000
},
// 境内解析器
// clientIP 是 per-server 的 EDNS Client Subnet 字段(全局的那个键叫 clientIp)
{
"address": "223.5.5.5",
"tag": "dns-cn-direct",
"domains": ["geosite:china-list"],
"clientIP": "192.0.2.1", // 换成境内 ISP 的 IP
"skipFallback": true, // 只服务命中的域名,不参与回落
"finalQuery": true,
"timeoutMs": 1500
},
// 兜底
{ "address": "https://8.8.8.8/dns-query", "tag": "dns-remote-backup", "timeoutMs": 2000 }
]
}

运行时有四种情况:

域名最终列表
google-cn / apple-cn / c.lencr.org 等(仅命中 dns-remote)[1.1.1.1 DoH, 8.8.8.8 DoH]
仅命中 china-list[223.5.5.5]
未命中任何规则[1.1.1.1 DoH, 8.8.8.8 DoH]
同时命中两者[1.1.1.1 DoH, 223.5.5.5]

如果有 DNS 解锁服务,将 "address": "https://1.1.1.1/dns-query" 替换为服务提供商的地址即可,解锁服务故障时会自动回落 dns-remote-backup。

应用层判定漏洞#

当 IPIfNonMatch 下的一个域名解析出多个地址时,每条 IP 规则会拿全部地址去试,任一命中就算命中。例如 www.bilibili.com 一次返回 20 个地址,只要其中一个落在 geoip:cn 里,整条连接就走保守侧,而实际建连用的可能是另一个地址:

22:48:55.438714 UDP:223.5.5.5:53 got answer: www.bilibili.com. -> [61.147.236.101, 183.131.147.48, 183.131.147.30, 183.131.147.29, 183.131.147.28, 183.131.147.27, 175.4.62.129, 175.4.62.128, 175.4.62.127, 119.84.174.97, 119.84.174.96, 119.84.174.68, 119.84.174.67, 114.230.222.141, 114.230.222.140, 114.230.222.139, 114.230.222.138, 61.147.236.104, 61.147.236.103, 61.147.236.102] 337.538933ms
22:48:55.438724 accepted tcp:www.bilibili.com:443 [inbound-443 -> warp]

在时序方面:

22:49:13.697898 UDP:223.5.5.5:53 got answer: www.jd.com. -> [] 4.000179582s <app/dns: record not found>
22:49:14.108768 DOH//1.1.1.1 got answer: www.jd.com. -> [140.150.36.51, 138.113.102.14] 411.088937ms
22:49:14.108794 accepted tcp:www.jd.com:443 [inbound-443 >> direct]
22:49:14.108804 UDP:223.5.5.5:53 cache HIT: www.jd.com. -> [1.194.193.81]

境内解析器用默认 4 秒超时返回空,回落至境外解析器,得到海外节点 ip ,路由据此判定直连。10 微秒后出站建连再次解析,这次命中缓存得到 1.194.193.81,一个大陆地址。判定用的是海外 IP,连接用的是大陆 IP。

缓存里的记录从哪来,日志没有直接记下,可能是境内解析器的应答在超时判定之后才到。

出站再解析一次不是默认行为,Xray 的 freedom 出站 domainStrategy 默认 AsIs,此时 Xray 将域名交给 Go 按操作系统 DNS 设置解析并连接,当填写其他值时使用内置 DNS 模块进行解析,才出现第二次内置查询。

只要有两个解析器可能对同一个域名给出不同答案,而其中一个不稳定,这个窗口就在。为了解决该问题,将 dns-cn-direct 的 timeoutMs 调整为 1500;并设置 finalQuery 阻止其使用 fallback 解析器,同样的超时行为将直接作为失败的解析,以防止分流错误。

仅内核判定存在局限性,可以考虑在内核之外加一道防火墙级的过滤器,监测物理网卡的出站,目的地址落在一个独立维护的 CN 地址集合里就拦截。该集合最好来源和内核不同并定时刷新,和内核的数据形成两份并集。

验证#

对访问日志的分析:

Terminal window
# 域名 + 出站
sed -nE 's#.*accepted tcp:([^ ]+) \[([^]]*)\].*#\2\t\1#p' access.log | sort -u
# 域名 + 解析器 + IP
sed -nE 's#.*(DOH//[^ ]+|UDP:[^ ]+) (got answer|cache HIT): ([^ ]+)\. -> \[([^]]*)\].*#\3\t\1\t\4#p' access.log | sort -u

被路由规则命中是 [inbound-443 -> warp],回落默认出站为 [inbound-443 >> direct]。

批量测试期望明确的域名:

类别例子
外国公司的境内服务www.gstatic.com, dl.google.com, www.apple.com, ocsp.globalsign.com, c.pki.goog
真正的境内服务www.baidu.com, www.bilibili.com, www.taobao.com
.cn 顶级域nvidia.cn, mirrors.tuna.tsinghua.edu.cn
境外对照组github.com, www.youtube.com, www.cloudflare.com
Terminal window
while read -r d; do curl -sI --max-time 5 "https://$d" >/dev/null; done < domains.txt

测服务端行为的时候客户端要切到全局模式,防止客户端分流的干扰。

总结#

  • 先确认社区建这张表要解决的问题,和自己的需求是不是一回事。
  • 服务端对境内外的判定适合用 IP,选解析器才用域名表,而且要用专为选解析器而建的表。
  • 对于服务端,IP 是比通用域名分类更合适的判据,但它仍然受 DNS、CDN、多地址、缓存、时序影响。

这样列表滞后带来的后果,就从「某天某个域名会走错,而且可能是有风险的那个方向」变成「某天某个域名会慢一点,扫一遍日志就能发现」。

附录:完整配置#

warp 是保守侧的中转出站,direct 是默认出站。

如果要用 warp 出站需要注意:

mtu 为 1280,Xray 的 wireguard 出站默认 1420 偏大,Cloudflare 自家客户端是 1280。

keepAlive 写 25,是由于正常情况下该出站几乎空闲,握手过期后重新握手要是撞上丢包,就足以让一个 5 秒超时的探测失败。

{
"dns": {
"queryStrategy": "UseIP",
"servers": [
{
"address": "https://1.1.1.1/dns-query",
"tag": "dns-remote",
"domains": [
"geosite:google-cn",
"geosite:apple-cn",
"geosite:microsoft@cn",
"geosite:steam@cn",
"geosite:gfw",
"domain:ocsp.globalsign.com",
"domain:status.rapidssl.com",
"domain:ocsp.r2m01.amazontrust.com",
"domain:oneocsp.microsoft.com",
"domain:c.lencr.org",
"domain:c.pki.goog"
],
"expectedIPs": ["geoip:!cn"],
"skipFallback": false,
"finalQuery": false,
"timeoutMs": 1000
},
{
"address": "223.5.5.5",
"tag": "dns-cn-direct",
"domains": ["geosite:china-list"],
"clientIP": "192.0.2.1", //换成境内 ISP 的 IP
"skipFallback": true,
"finalQuery": true,
"timeoutMs": 1500
},
{
"address": "https://8.8.8.8/dns-query",
"tag": "dns-remote-backup",
"timeoutMs": 2000
}
]
},
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{ "type": "field", "inboundTag": ["api"], "outboundTag": "api" },
{
"type": "field",
"inboundTag": ["dns-remote", "dns-remote-backup"],
"outboundTag": "direct"
},
{ "type": "field", "ip": ["geoip:private"], "outboundTag": "blocked" },
{ "type": "field", "protocol": ["bittorrent"], "outboundTag": "blocked" },
{ "type": "field", "ip": ["geoip:cn"], "outboundTag": "warp" }
]
},
"outbounds": [
{
"protocol": "freedom",
"tag": "direct",
"settings": { "domainStrategy": "UseIPv4" }
},
{
"protocol": "wireguard",
"tag": "warp",
"settings": {
"mtu": 1280,
"secretKey": "...",
"peers": [{ "publicKey": "...", "endpoint": "...", "keepAlive": 25 }]
}
},
{ "protocol": "blackhole", "tag": "blocked" }
]
}
VPS 上的分流:境内外判断交给 IP,域名只用来选解析器
https://blog.cobweb11.top/posts/geo-routing-on-overseas-vps/
作者
Mochi
发布于
2026-08-30
许可协议
CC0 1.0 Universal