理系学生日記

おまえはいつまで学生気分なのか

WindowsからWSL2のサーバへlocalhostでアクセスできる仕組みと片方向性の理由

以前の記事で、WSL2は「別マシン」だと書きました。VMとして独立していて、Windowsとは明確な壁がある。

WSL2でWebサーバを起動すると、Windowsのブラウザからlocalhost:3000で普通にアクセスできる。別マシンなのに、なぜlocalhostで繋がるのか? 矛盾してません?

僕も最初混乱しました。「WSL2とWindowsが同じlocalhostを共有しているのか?」と。ところが逆方向だとそうではない。Windows側で起動したサーバに、WSLからlocalhostでアクセスしようとすると、届きません。

この挙動は、WSL2のNATネットワーキングモードと、Microsoftが実装した「片方向のlocalhost転送機構」によるものです。この非対称性を理解すれば、WSL2のネットワーク挙動で混乱することはなくなりそうです。

WSL2のNATネットワーキングモードの基本

WSL2のデフォルトネットワークモードは、NATです。以前の記事で説明した通り、WSL2は「別マシン」。VMとして独立していて、Windowsとは仮想NIC経由で繋がっています。

この構造上、WSL2 VMはプライベートIP(172.x.x.x系のアドレス)を持つ別ホストになります。本来、localhost(127.0.0.1)は各OS内のループバックアドレスに過ぎません。別マシン間では共有されない。Windowsのlocalhostはwindows自身、WSLのlocalhostはWSL自身を指します。ごく普通のネットワークの話です。

だからこそ、WindowsからWSL2のサーバへlocalhostでアクセスできることが不思議なんです。理論上は不可能なはず。でも実際には動く。種明かしをすると、Microsoftが「自動的なlocalhost転送機構」を実装しているからです。NAT境界を越えて、まるでlocalhostが共有されているかのように見せかける、影武者のような仕掛けです。

diagram

この図のように、NAT networking modeでは、Windows-WSL2間に明確な境界があります。だけど、localhost転送機構(緑色)が片方向に存在する。これが、矛盾を生んでいるように見える原因です。

localhost転送の非対称性:主要な2つのケース

ケース1:Windows → WSL方向(自動的に繋がる)

WSL側でWebサーバを起動してみます。

# WSL側(Linux)
python3 -m http.server 8080

サーバが立ち上がりました。0.0.0.0:8080で待ち受けています。

次に、Windowsのブラウザからhttp://localhost:8080にアクセスすると、普通に繋がります。ページが表示される。なぜでしょう? WSL2は別マシンなのに。

答えは、Microsoftが「Windows → WSL方向」に限定して、localhost転送機構を自動的に有効化しているからです。WSL側が0.0.0.0や127.0.0.1でポートにbindすると、Windows側でwslrelay.exeというプロセスが影武者リスナーを立てます。Windowsアプリがlocalhost:8080へアクセスすると、この影武者が受け取って、VM内(WSL側)のサーバへ中継する。vsockという仮想ソケット技術を使って。

wslrelay.exe is a windows executable that is used to relay network and debug console traffic from Linux to Windows.

wslrelay.exe - WSL

ユーザーは何も意識しません。「WSL2でサーバ立てたら、Windowsから普通にlocalhostで見られた」で終わり。魔法みたいですが、公式機能です。

注意点を1つ。WSLサーバを127.0.0.1のみでbindしている場合、転送が効きません。0.0.0.0でbindすれば動きます。

ケース2:WSL → Windows方向(localhostでは届かない)

逆方向を試します。Windows側でサーバを起動します。

# Windows側(PowerShell)
python -m http.server 9000

サーバが立ち上がりました。次に、WSLからlocalhost:9000へアクセスしてみます。

# WSL側(Linux)
curl http://localhost:9000

届きません。なぜか?

WSL内のlocalhost(127.0.0.1)は、WSL自身(VM内Linux)のループバックだからです。Windowsには届かない。NAT構造上、WSLとWindowsは別ホスト。そして、この方向のlocalhost転送機構は、デフォルトでは存在しません。片方向なんです。

じゃあどうするか。Windowsのホスト名またはIPを明示的に指定します。Microsoft公式ドキュメントでは、以下の方法を推奨しています。

ip route show | grep -i default | awk '{ print $3}'

注意点があります。Windows側のサーバが127.0.0.1(localhostのみ)でbindしていると、WSLからホストIPで叩いても拒否されます。Windows側も0.0.0.0でbindします。さらに、Windowsファイアウォールでポートを許可する設定が必要になる場合もあります。

ちなみに、LANの別PCからWSL2のサーバへアクセスしたい場合、さらに一手間、netshでポートフォワード設定が必要です。

localhost転送の実装メカニズム

NAT networking modeにおいて、なぜWindows→WSL方向のみlocalhostで繋がるのでしょうか。

localhostForwardingとは

.wslconfigというファイルに、localhostForwardingという設定があります。デフォルト値はtrueです。

localhostForwarding=true

この設定は、NAT networking modeにおいて「WSL2 VMの0.0.0.0または127.0.0.1へのbindを、Windowsのlocalhostからアクセスできるようにするかどうか」を制御します。trueなら転送機構が有効。falseなら無効。

重要なのは、これは「Windows → WSL方向」の転送を制御する設定だということです。WSL → Windows方向は対象外。片方向専用の設定なんです。

中継を支える2つのプロセス

実は、このlocalhost転送は2つのプロセスが協調して実現しています。見えないところで、地道に働いている影武者たちです。

Linux側ではlocalhostという名前のプロセスがWSL2 VM起動時に生成されます。mini_initというプロセスが親になって、ひっそりと起動する。このプロセスの仕事は、TCPの待ち受けポートを常に監視することです。新しいポート(0.0.0.0や127.0.0.1へのbind)を検知すると、Windows側へ「このポート、転送お願い」と中継設定を依頼します。

一方、Windows側ではwslrelay.exeというプロセスが、127.0.0.1:portに「影武者リスナー」を立てます。Windowsアプリから127.0.0.1:portへの接続を受け取ると、この中継プロセスがLinux側のlocalhostプロセスへデータを送る。Linux側のlocalhostプロセスが、最終的にWSL内の本来のサーバソケットへ接続する。

いろいろ情報を見てると、おそらくパケットは、こんな経路を辿ることになるはずです。 (ref: Blocking I/O behavior in localhost relay hangs with full duplex traffic due to half duplex forwarding logic. · Issue #10688 · microsoft/WSL · GitHub)

  1. Windowsアプリ → 127.0.0.1:8080へ接続要求
  2. wslrelay.exeがリスナーとして受け取る
  3. vsockでLinux側のlocalhostプロセスへ中継
  4. localhostプロセスがWSL内のサーバ(0.0.0.0:8080)へ接続
  5. レスポンスが逆順で返る

ユーザーには見えません。でも、これが舞台裏で起きています。

どのbindが転送対象になるか

localhostForwardingの定義がただしいとすると、基本的にWSL内のサーバが「wildcard(0.0.0.0)またはlocalhost(127.0.0.1)」でbindしたときに転送対象となるようです。

  • 0.0.0.0:8000で待ち受け → Windows側のlocalhost:8000で到達可能
  • 127.0.0.1:8000で待ち受け → Windows側のlocalhost:8000で到達可能
  • WSLの特定IP(例:172.xx.yy.zz:8000)のみでbind → 自動転送の対象外

WSL側が特定IPのみでbindしている場合、Windowsから直接そのIPを指定してアクセスします。転送機構は介在しません。

Mirroredモードによる改善

Windows 11およびWSLの新しいバージョンでは、Mirrored networking modeが試験的に提供されています。これは非対称性を解消する方向への改善っぽくて、これがデフォルトのnetowrking modeになっていくのかもしれません、

networkingMode=mirrored

このモードでは、WSLがWindowsのネットワークIFを「ミラー」するため、NAT境界の問題が軽減されます。特に、WSL → Windows方向でもlocalhost(127.0.0.1)が使えるようになりそうです。

まとめ

WindowsからWSL2のサーバへlocalhostでアクセスできるのは、Microsoftが実装した片方向の転送機構のおかげです。この仕組みは、wslrelay.exe、localhostプロセスという要素で構成されています。

逆方向(WSL → Windows)は転送機構が存在しないため、ホストIPを明示的に指定します。ip route show | grep -i default | awk '{ print $3}'でWindowsホストのIPを取得して、それを使う。この非対称性を理解すれば、WSL2のネットワーク挙動で混乱することはなくなりそうです。