# WSL の MTU による停止

WSL2 のホストは、セッションが普通につながったあと、どちらの端にもエラーを
出さずにただ止まる、唯一の構成です。原因が SSH や Tessera であることは
ほとんどありません。原因は、WSL2 がその背後に置かれている仮想アダプタの
MTU です。接続時にこの特徴的な停止を観測すると、Tessera はここへリンクする
アンバーの *WSL2 / Tailscale の MTU の問題の可能性があります* カードを表示
します。

## 症状

- セッションはつながり、プロンプトが現れ、短いコマンドは問題なく動きます。
- 大きなまとまりを送るものはすべて固まります。長いファイルへの `cat`、幅の
  広い `ls`、tmux の全画面再描画、[ファイルパネル](https://bambouville.com/docs/ja/files/)での転送。
- 何も失敗を報告しません。セッションがただ動かなくなるだけです。接続し直せば
  動きますが、同じ種類の出力でまた止まります。
- 同じホストでも、ssh より mosh のほうが調子よく動きます。

接続がプロンプトにたどり着きさえしない場合は、同じ不具合が、それ自体が
大きい SSH の鍵交換を飲み込んでいる可能性があります。以下のテストはそのまま
当てはまります。

## なぜ起きるのか

WSL2 は Hyper-V の仮想スイッチの背後で動き、その `eth0` は MTU 1500 で
立ち上がります。Windows ホストが実際にはもっと小さい経路 — VPN、PPPoE、
あるいは何らかのトンネル — でネットワークに出ている場合、その実際の経路 MTU
を超えるパケットは途中のどこかで捨てられます。

経路 MTU 探索は、まさにこれを扱うためのものです。パケットを転送できない
ホップが ICMP の *fragmentation needed* を返し、送信側が下げる、という
仕組みです。ところが多くの VPN やファイアウォールはその ICMP を捨てるため、
返事は届きません。小さいパケットは流れ続け、大きいものは黙って消えます —
PMTU のブラックホールです。SSH からは、理由もなく止まった接続に見えます。

## 確かめる

WSL の中から、インターフェイスが何と言っているかを確認します:

```
ip link show eth0
```

次に、実際に経路を通り抜けられるサイズを探します。以下の ping は
don't-fragment を立てており、ペイロードにヘッダの 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
```

2 つ目は各アダプタの 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` で確認してください。

1400 をそのまま写すのではなく、ping のテストで出た数値を使ってください。
1400 はほとんどの VPN を通せる安全な落としどころですが、自分の経路に
合わせた値のほうが、1 パケットあたりの無駄が少なくなります。

## あるいは WSL にホストをミラーさせる

Windows 11 22H2 以降で WSL 2.0 以上なら、ミラーモードのネットワークが
ホスト自身のインターフェイスを MTU ごとディストリビューションに渡すため、
そもそも不一致が起きません。`%UserProfile%\.wslconfig` に:

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

`wsl --shutdown` で適用します。これは MTU よりずっと多くのものを変え、
localhost と LAN の到達性の挙動もどちらも変わります。停止するセッション
だけが問題なのであれば、起動コマンドのほうを選んでください。

## 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/ja/connections/#トランスポート-ssh-か-mosh-か)を
mosh に切り替えるのは、直すことではなく回避することです。mosh のデータグラム
は壊れた MTU の下をすり抜けられるほど小さく、バイトストリームを再生する
代わりに画面を描き直すため、対話的なセッションは生き延びます。ファイル
パネルと tmux のサイドチャネルは今も SSH に乗るので、大きな転送は止まり
続ける可能性があります。

ここに書いたどれもが当てはまらない場合は、ログを取得し
（**設定 → 診断 → ログを書き出す**、[診断](https://bambouville.com/docs/ja/security/#診断)を参照）、
[GitHub Issues](https://github.com/bambouville/tessera/issues)に issue を
立ててください。
