WSL 之旅 1:网络连接错误

前情提要

Windows 不适合作为开发系统。这个结论的得出经过了好大一番波折,不过可以肯定基本上是个可靠的结论。

这番波折的起因要怪我手贱把明明已经被组策略阻断更新的 Windows 系统升级到了 24H2。升级完一看,坏了——硬盘的权限被改动,读写增删都变麻烦很多。捣鼓半天终于把 D 盘的权限搞了回来,以为回到舒适的操作环境了,结果又发现不对——OpenCode 的 @ 功能老是出岔子。对比实验一番,发现在权限全部属于自己的 D 盘上就没这问题,但到了没改回来权限的 C 盘上就有问题。咋办?改 C 盘权限呗!

结论是千万不要右击本地磁盘(C:)然后依次点击属性-安全-高级-更改,然后输入用户名点击确认,再勾选“替换子容器和对象的所有者”,最后点击确认啊!C 盘的权限是不能瞎改的!

瞎改的结果是 Edge 整个崩溃,WebView2 框架完全用不了,每次开机还都要先点掉几个错误提示。而且,Clash Verge 用的是 WebView2 框架,导致我完全看不了它的界面。一点都忍不了了,重装系统吧。唯一还算令人感动的要点是我用 Scoop 管理着不少应用程序,而且把 Scoop 本体和没法用 Scoop 管理的大部分应用程序都安装到了 D 盘。这使得重装就轻松很多。

这一次重装也正好是一个断舍离的机会。我决定把工作环境整个挪到 Linux 上。在学校社团群同侪们的建议下,也结合我没法立刻完全脱离 Windows 环境的考量,我最终选择了交互方便、即开即用的 WSL。

这个系列就是记录我在玩 WSL 时遇到的一些小问题,也是我杂记的第一篇。杂记不一定要遇到什么问题,即使遇到了什么问题也不一定记下什么解答,不过作为兴起兴落后的余韵罢了。虽然这么说,不过好消息是这次是有解决方法的。

奇怪的网络连接错误

根据各种地方看到的教程,我应用了 Mirrored 模式作为 WSL 的网络连接方式。这是 Windows 11 特有的功能,能让 WSL 和 Windows 11 系统共享同一个地址。一切配置都照计划进行,直到我通过这条命令打算安装 fnm:

1
curl -fsSL https://fnm.vercel.app/install | bash

然后 WSL 的命令行停了好久,最后吐出来一个超时。

但是,刚刚已经安装了几个包,网络看上去不像是不通的样子。难道 WSL 没有走代理?但是 ping google.com 是通的。是 curl 的问题吗?但 curl google.com 有返回值。而且,宿主机下执行相同的命令能立刻下载完毕。WSL 也配置了 dnsTunneling=true,按理说网络已经完美交给 Clash Verge 处理了。

寻找蛛丝马迹

于是用 curl -Iv https://fnm.vercel.app/install 打印出详细的握手过程:

1
2
3
4
5
* Host fnm.vercel.app:443 was resolved.
* IPv4: 198.18.0.16
* Trying 198.18.0.16:443...
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):

显示 IPv4 说明 curl 拿到了 Fake-IP,这意味着 DNS 解析和 TUN 劫持没问题。TCP 连接已经建立,但是当发出 TLS 握手后,服务器再也没有回应。

询问 AI,AI 告诉我“这几乎是 MTU(最大传输单元)问题的标准签名”。看上去问题找到了。

什么是 PMTU 黑洞?

为了验证猜想,我尝试在 WSL 中将默认网卡的 MTU 值从默认的 1500 调低到 1350:

1
sudo ip link set dev eth1 mtu 1350

结果 curl 秒开。但是,背后的原因是什么?AI 告诉了我一个“货车与限高杆”的比喻。

在网络传输中,数据包就是“货车”,而网卡的 MTU 设置就是道路上的“限高杆”。互联网的标准物理限高是 1500 字节。

在我们的情况中,WSL 发出一辆装满数据(Client Hello,通常比较大)、高度为 1500 的货车。这辆货车开进 Clash 的 TUN 虚拟网卡时,代理软件为了加密和转发,给它套了一层外壳,导致货车膨胀到了 1550(非真实值,仅用于理解)。然而,TUN 网卡自身的限高也是 1500。于是,这辆 1550 的货车在本地就因为超高被直接丢弃了。

尽管按理说,路由器或虚拟网卡发现包太大,应该发一个 ICMP(需要分片)的消息告诉 WSL:“包太大了,切小点再发”。但在 WSL 与 Clash Verge 的复杂交互中,这个 ICMP 报错消息被悄悄吞掉了。WSL 不断传大包,Clash Verge 不断丢弃——这就是 PMTU 黑洞。

解决方法与额外的一些坑

难道每次都要在 WSL 里重新限制一遍吗?太麻烦了。事实上,通过限制 Clash Verge 的 MTU 大小在 1350,WSL 就能正常接到“缩减包的大小”的反馈,也就能成功握手。尽管背后原理尚不清楚,不过确实能用了!

在这之前,我还尝试过把 Clash Verge 的 MTU 调制 9000。这样做确实让高 1550 的货车跑出了我的电脑,但是物理互联网的限高是 1500,货车还是因为超高被丢弃了。这导致我的 Gemimi 网页端直接被地区锁在外面了。

我又试了试直接关闭代理。这样竟然也可以正常 curl。看来,我们可以断定这完全不是一个由于 DNS 污染导致的问题,而仅仅是 MTU 的大小导致的问题。AI 告诉我,关闭 TUN 网卡后,流量由 Windows 原生的 TCP/IP 协议栈接管。Windows 底层非常健壮,它能够完美地和外网服务器协商 MSS(最大报文段长度),主动把发送的包切小,根本就不会产生 MTU 吞包黑洞。怪不得呢!

意外之喜

之前挂着代理死活打不开的学校内网网站,调整 MTU 之后也能开了!我猜,这是因为校园网内部部署了极其复杂的防火墙、行为管理和跨校区 VPN;每一次网络“套娃”都会增加数据包体积,导致校园网内部的实际“限高杆”只有 1400 左右。对于 Clash Verge 而言,初始就有 1500 高度的数据包过不去,但 1350 这个体积微小的数据包便正好避开了所有校园网内层层叠加的限高杆。

总结

这个问题花了我两个多小时解决。网上各种方案都有,不过我觉得都不是根本原因,而且也不一定治得了我的问题。现在回头一看,才发现无论问题的分析还是解答都很有意思,于是发帖记录,也给其他遇到类似问题的人提供一种可能的解法。