技术

代理迁移: 从 Trojan-go 迁移到 Reality,但是BUG+2

记录从 Trojan-go 迁移到 VLESS-Reality 的经历,以及一个排查了很久的 TUN 路由环路问题

我的代理软件基本是常驻TUN模式的,一直使用自己建的 Trojan-go 节点,三年过去了一直很平静

但是最近开始不停报 connection forcibly closed by remote host。在 git push 的时候批量爆发,一开始以为是 VPS 那边出了问题,尝试开ipv6来减轻被探测程度,一两天后,ipv6也批量爆发 An existing connection was forcibly closed by the remote host 后来从某些妙妙小论坛上交叉验证,发现 GFW 应该是能识别 TLS 指纹了——Go 的 JA3 指纹是固定的,一看就知道是代理流量,可能会直接砍连接

那就换 VLESS + XTLS-Reality 吧。毕竟 Trojan-go 也很久没有维护了,Reality不需要域名证书,不需要 cron 脚本跑续签,还能模拟大厂的 TLS 指纹

我用某卖显卡的厂商作为伪装域名,它是个大 CDN,证书变动不频繁,跟科技行业沾边也不突兀 另外 Xray-core #6356 提到 www.microsoft.com 的证书超过 8192 字节有兼容性问题,某显卡大厂不受这个影响。

但是把Reality节点搭好后,相同的 bug 还是产生了,还买一赠二多了两个 bug ,日志里经常会报open connection to xxx using outbound ...... i/o timeout 以及 received real certificate (potential MITM or redirection) 为什么会这样呢?新的节点代理一切正常,并且只会在TUN模式下产生这样的问题

折腾了大半天,最后发现出问题的不是代理协议本身,是 V2rayN 的 TUN 模式搞出来的路由环路

正常流程应该是上面这样,但实际跑起来并不是


排错过程

排错断断续续花了十几个小时,试了一大堆东西,现在回头看,大部分是在应用层瞎折腾

降级 xray-core、重启 sing-box、调大超时参数——这些常规操作都没用。接着试了把 TUN 的 MTU 从 9000 调到 1500,顺手把 inbound 超时从默认值拉到 600 秒,还是照报不误。至少排除了 MTU 的锅

关掉 IPv6 这一步有点意思。我发现 sing-box TUN 启动时会同时创建 IPv4 和 IPv6 接口,而我的路由器只分配了 IPv4。这可能导致双栈路由环路——IPv6 流量被 TUN 截住后找不到出口,反复循环。关了之后错误频率确实降了一些,但没根除。而且一些依赖 IPv6 的 CDN 节点访问反而变慢了,所以这不是正解。这里还有个值得注意的点:IPv6 天然不依赖 NAT,每个设备有公网地址,但很多家庭路由器在双栈模式下的路由和防火墙策略本身就是半残的,TUN 在双栈场景下更容易产生意料之外的路由行为

调整 ip_is_private 规则 翻日志时看到一堆 192.168.x.x:7680 ... io timeout 的错误,一开始以为是代理有问题,查了才发现是 Windows 的 Network Discovery 服务在扫局域网设备,端口 7680 是正常行为,跟代理毫无关系。但这个噪音提醒我检查了路由规则——由于我平时的使用场景比较特殊,这台开发机的 v2rayN 始终设置的"全局"模式,会绕过 ip_is_private,导致局域网流量也被送进代理。路由改成"绕过大陆"后,ip_is_private 生效了,局域网流量不再走代理。不过核心问题还是没解决,只是把噪音清掉了。

调整 sing-box DNS 策略 把 DNS 策略从 prefer_ipv4 改成系统默认,想着可能是 DNS 解析优先级的问题。没用

端口调整 最后怀疑是针对443端口的特殊行为,试了改成 8443 端口,一样没用

到这里八次尝试全翻车了,怀疑问题不在应用层,应该是在网络层


环路是怎么发生的

注意,这个BUG只在win环境 + TUN模式下遇到,非TUN模式或者mac环境暂时没有遇到

后来去翻了 sing-box 的日志,有这样的一行:

process_name: xray.exe bypass

sing-box TUN 有一个 process_name 绕过规则,理论上 xray-core 的出站连接应该绕过 TUN 直连物理网卡。但 find_process 在 Windows 上被标为实验性功能——进程枚举需要管理员权限,高并发下进程名查找不稳定,加上 v2rayN 是 Electron 多进程架构,匹配更困难

这个规则一失效,就会这样:

xray-core 尝试连接 VPS → sing-box TUN 拦截 → 重新路由进 xray-core → 再次尝试连接 → 再次被拦截 → ... → 约 60 秒超时。因为 process_name 绕过是间歇性工作的,超时后的重连有时会穿透 TUN 到达 VPS,但此时连接状态已经混乱,VPS 无法识别为合法的 Reality 握手,返回真实证书 → Reality 握手失败 → received real certificate

就是个死循环

这里直接 curl 是测不出丢包率的。因为环路并不是每次连接都会触发,process_name 绕过规则在大部分时候可能正常工作,只是偶尔失效。curl 跑一两次碰不到失效窗口,看起来丢包率 0%。但实际使用中 TUN 模式持续跑着大量连接,process_name 总会有那么几次匹配不上,这时候丢包的感觉就很明显了。这也是排错时容易被误导的地方,ping 和 curl 测完一切正常,但代理用起来就是断断续续的


解决

翻 Issue 发现几个相关的:

  • Xray-core #6356:Reality 与 microsoft.com 证书 > 8192 字节的兼容性问题
  • sing-box #3858route_exclude_address 使用 /32 可能创建聚合路由
  • Throne #1477:同一个路由环路问题
  • v2rayN #9456:对应功能请求

v2rayN 7.22.6(2026-06-07)已经合入了 route_exclude_address

那么操作很简单:

  1. v2rayN → 设置 → TUN 模式设置
  2. 找到 route_exclude_address
  3. 填入 vps的ip/32
  4. 重启

必须用 CIDR 格式。我一开始填了裸 IP,sing-box 直接起不来:

netip.ParsePrefix("vps的ip"): no '/'

加上 /32 就好了。route_exclude_address 在系统路由表里加一条排除路由,让 VPS IP 的流量不经过 TUN 网卡,直接走物理网卡。

修复后观测错误日志从一晚上万条降到三位数的数量,剩下来的是从 win的Network Discovery露出来的,直接无视即可 从体感高丢包率到无丢包感,可以算是很大的进步了


为什么 process_name 绕过不靠谱

我的场景里有两个代理组件在跑:

  • xray-core:负责 Reality 出站,连接 VPS
  • sing-box TUN:负责系统级流量劫持,把流量路由到 xray-core 的 SOCKS

理论上 sing-box 应该放过 xray-core 的出站流量,让 xray-core 自己走物理网卡连 VPS。但 process_name 规则一失效,xray-core 的出站流量就被 TUN 重新吃进去了。

这个问题的本质是两个代理组件之间缺乏可靠的隔离机制。在 Windows 上用 process_name 做隔离本身就是走钢丝

翻到 v2rayN #4539 时发现有类似的问题被讨论过了,两三百行的评论。虽然那还是 v6.31 时代的事,但核心矛盾一直没变——只要 TUN 模式和主核不是同一个程序,就必然存在谁来放过谁的隔离需求。v2rayN 作为 GUI 壳子,底层跑的是 sing-box 和 xray-core 两个互相不知道对方存在的进程,靠进程名匹配来做隔离,本质上是个 best-effort 的方案,不能说是很靠谱的方案

不过好在作者也添加了方便修复的入口,最后 bug 也得以解决了


扩展阅读:Reality 握手流程

Reality 的核心思路是用目标网站的证书做伪装,认证通过返回临时证书,认证失败则收到真实证书。文章里的环路问题就是在"认证失败"这条路径上触发的——xray-core 在环路中超时重连,拿到了 VPS 的真实证书,握手直接失败。


参考

评论加载中...