本日も乙

ただの自己満足な備忘録。

VPN 接続時に WSL2 の SSH がハングアップした場合の解決方法


2020/12/10 11:10 UTC+9 追記
本記事の問題は WSL2 固有であり WSL1 は問題ないとのことで、「WSL」だと混在してしまっているとの指摘をいただいたため、「WSL2」と明記するように記事タイトルおよび内容を修正しました。


本記事は WSL Advent Calendar 2020 の9日目です。

今月から部署異動した際に、心機一転で Mac から Windows に変えました。WSL2 が素晴らしくて開発や端末操作が格段にやりやすくなって感動しています。ただ、所々でハマりどころもあります。

事象

VPN に接続したときに WSL2 上の Ubuntu 20.04 LTS から SSH 接続すると、特定の条件でハングアップ(応答が返ってこない)がありました。

  • ある環境Aのサーバへの接続は問題なく、環境Bのサーバへの接続で発生する(サーバによって異なる)
  • すべてのコマンドで起こるわけではなく、topls -la といったコマンドで起こる

原因

使用した VPN の MTU が 1400 に設定されていたのが原因でした。

PS C:\Users\ohsawa> netsh interface ipv4 show interfaces

Idx     Met         MTU          状態                 名前
---  ----------  ----------  ------------  ---------------------------
 53          25        1400  connected     HOGE-VPN

WSL2(Ubuntu)側の MTU は 1500 になっています。

$ ip addr show dev eth0 | grep mtu
4: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000

接続元(Windows; WSL2)と接続先(VPN ルータ)の MTU が異なる場合、小さい方が採用されます。接続元から VPN ルータにパケットを送る場合に、1500バイトを超えた場合はパケット分割(フラグメント化)が起こります。
フラグメント化はスループットが落ちてしまうのに加え、経路のどこかで分割を禁止するフラグ(DF ビット)が立っていると、送信元に ICMP パケットを送って最適な MTU を教えてもらうのですが、WSL2 がなぜか ICMP パケットを受信できないみたいで、通信がずっと待ち状態になってしまうのだと思います。(ブラックホール問題)。そのためハングアップしたように見えます。
Windows ホストから VPN 経由で通信する場合は、ファイアウォール等で ICMP を明示的に禁止しない限りは応答できるので問題なく通信できるはずです(フラグメント化が起こるのでスループットは落ちます)。

解決方法

以下のようなコマンドで VPN の MTU に合わせて WSL2 側の MTU を調整すれば良いのです。今回は1400 でしたが、VPN ルータによって数値は異なります。

$ sudo ip link set eth0 mtu 1400

自動設定方法

毎回起動する度に ip コマンドを打つのが面倒なので自動設定したいですよね。しかし、WSL2 は netplan などの設定がないし systemd のような自動起動の仕組みもないようです。仕方がないので毎回起動時に上のコマンドを叩くようにする必要があります。

まずは sudo で打つ際に、パスワードなしで実行できるように sudoers を編集します。すべてのコマンドを許可する必要がないため、ip コマンドのみにします。

$ sudo sh -c "echo '%sudo ALL=NOPASSWD: /sbin/ip' > /etc/sudoers.d/change_mtu"
$ sudo chmod 0440 /etc/sudoers.d/change_mtu

あとは ~/.bashrc/etc/bashrc などに上の ip コマンドを仕込めば OK です。私の場合はZsh を使っているので、/etc/zsh/zprofile に入れました。

$ sudo sh -c "echo 'sudo ip link set eth0 mtu 1400' >> /etc/zsh/zprofile"

参考URL