# WSL MTU 卡顿

WSL2 主机是唯一一种会话能正常连上、随后干脆停住，而且两端都不报错的情形。
问题几乎从来不在 SSH 或 Tessera —— 而在 WSL2 所处的虚拟网卡的 MTU。当
Tessera 在连接过程中观察到这种典型卡顿时，会显示一张琥珀色的
*可能是 WSL2 / Tailscale 的 MTU 问题*卡片，并链接到这里。

## 症状

- 会话连上了，提示符出现了，短命令也运行正常。
- 任何会产生大量输出的操作都会冻结：对长文件执行 `cat`、很宽的 `ls`、一次
  完整的 tmux 重绘，或在[文件面板](https://bambouville.com/docs/zh-hans/files/)中传输文件。
- 没有任何东西报告失败。会话就是不动了。重新连接可以成功，然后在同类输出上
  再次卡住。
- 同一台主机在 mosh 下的表现比在 ssh 下好。

如果连接根本到不了提示符，同样的故障也可能吞掉了 SSH 的密钥交换 —— 那本身
就是一大块数据。下面的测试同样适用。

## 为什么会这样

WSL2 运行在 Hyper-V 虚拟交换机之后，它的 `eth0` 启动时的 MTU 是 1500。而当
Windows 主机实际是通过更小的通道接入网络时 —— VPN、PPPoE 或任何隧道 ——
超过真实路径 MTU 的数据包会在中途某处被丢弃。

路径 MTU 发现本应正好处理这种情况：无法转发该数据包的那一跳会返回一个 ICMP
*需要分片*，发送方随之退让。但许多 VPN 和防火墙会丢弃那个 ICMP，于是回应
永远不会到来。小包照常通过，大包则悄无声息地消失 —— 这就是 PMTU 黑洞。在
SSH 看来，这就像一条无缘无故停住的连接。

## 确认问题

在 WSL 内部，先看看接口声称的值：

```
ip link show eth0
```

然后找出这条路径上真正能通过的大小。下面的 ping 设置了「不分片」，负载加上
28 字节的头部即为总大小，因此 `-s 1472` 探测的是 1500 字节的数据包。目标要
选在同一条路径的另一端 —— 你要连接的服务器，或者任何公网地址：

```
ping -M do -s 1472 -c 1 example.com   # 1500 — usually times out
ping -M do -s 1372 -c 1 example.com   # 1400 — usually gets through
```

**超时**就是黑洞。（如果反而立即出现本地错误，说明该大小超过了你自己接口的
MTU —— 调低 `-s` 再试。）不断缩小范围，直到找出能得到回应的最大负载；加上
28，那就是你真实的路径 MTU。

Windows 一侧的等效命令，在 PowerShell 中执行：

```
ping -f -l 1472 example.com
netsh interface ipv4 show subinterfaces
```

第二条会列出每个网卡的 MTU。如果一个 VPN 网卡停在 1400，而 `vEthernet (WSL)`
是 1500，那就是你要找的不匹配。

## 在 WSL 中修复

把接口设为你测出的大小。这会立即生效，但在下次关机后失效：

```
sudo ip link set dev eth0 mtu 1400
```

重新连接，再做一遍原先会卡住的操作。如果输出顺畅，诊断就得到了确认。

要让它持久生效，就让 WSL 在启动时应用它。在发行版内的 `/etc/wsl.conf` 中：

```
[boot]
command = ip link set dev eth0 mtu 1400
```

然后在 Windows 中运行 `wsl --shutdown`，再重新启动该发行版。`[boot]` 命令
需要一个相对较新的 WSL —— 用 `wsl --version` 查看。

请使用你的 ping 测试得出的数值，而不是照抄 1400。1400 是一个几乎能通过所有
VPN 的保险值，但与你的路径相匹配的数值可以少浪费每个数据包的空间。

## 或者让 WSL 镜像宿主机

在 Windows 11 22H2 及更新版本上，配合 WSL 2.0+，镜像网络模式会把宿主机自身
的网络接口（包括 MTU）直接交给发行版，因此根本不会出现不匹配。在
`%UserProfile%\.wslconfig` 中：

```
[wsl2]
networkingMode=mirrored
```

用 `wsl --shutdown` 使其生效。这项改动影响的远不止 MTU —— localhost 和局域网
可达性的行为都会改变 —— 因此如果你唯一的问题只是会话卡顿，还是优先使用
boot 命令。

## 当低 MTU 的那一跳在服务器一侧

受限的一端未必总是 WSL；从服务器上运行同样的 ping 测试就能知道是哪一侧被
钳住了。如果你能控制中间的路由器，把 TCP 的 MSS 钳制到探测出的路径 MTU，
可以一次性修复经过它的所有连接：

```
iptables -t mangle -A POSTROUTING -p tcp --tcp-flags SYN,RST SYN \
  -j TCPMSS --clamp-mss-to-pmtu
```

## 在问题解决之前

把该主机的[传输方式](https://bambouville.com/docs/zh-hans/connections/#传输方式ssh-或-mosh)改为 mosh 是权宜
之计，而不是真正的修复。mosh 的数据报足够小，能从有问题的 MTU 下溜过去，
而且它是重绘屏幕而不是重放字节流，因此交互式会话能撑下来。文件面板和 tmux
侧信道仍然走 SSH，所以大文件传输仍可能卡住。

如果以上都不符合你看到的情况，请抓取一份日志
（**设置 → 诊断 → 导出日志**，参见[诊断](https://bambouville.com/docs/zh-hans/security/#诊断)），并在
[GitHub Issues](https://github.com/bambouville/tessera/issues) 提交问题。
