• Windows Subsystem for Linuxガイド 第10回 WSLネットワーク編

WSLは、ネットワーク機能を持ち、WSLディストリビューション上でネットワークを利用するアプリケーションを動作させることができる。基本的には、通常のLinuxと同じ環境だが、ディストリビューション上でサーバーを動作させネットワークを介してサービスを提供することは、WSLの用途としては想定されていない。このため、Win32側からのアクセスには問題はないが、LAN内からのWSL2上のサーバーアクセスには制限がある。

WSL1とWSL2のネットワーク機能

WSL1とWSL2は、Linuxカーネルの有無のほか、ネットワークに関しては、Win32側のネットワークスタックを使うか、独自のネットワークスタックを使うのかという違いがある。このため、WSL1は、Win32側と同じIPアドレスを持つが、WSL2は、別のIPアドレスを持つ(表01)。

  • ■表01

簡単にいうと、WSL2のネットワークは、Hyper-Vによる仮想マシンと同じく、「仮想スイッチ」によるネットワーク機能を使う(図01。この仮想スイッチには、“vEternet(WSL)”という名前がつけられている。このとき、仮想スイッチのタイプは「内部ネットワーク」になっているが、WSL2側から仮想スイッチを介して、インターネットアクセスが可能なNAT機能が有効化されている。

  • 図01: WSL2は仮想マシンで動作しているので、ネットワークは仮想スイッチ経由となる。ただし、Win32側からはLocalhostでアクセスできるようにwslhost.exeが同じポート番号で待ち受けを行う

ただし、Hyper-Vの仮想マシンと異なり、ユーザーがWSL2用仮想スイッチをGUIなどで設定変更することができない。特に、仮想スイッチに割り当てられるネットワークアドレスやWSLディストリビューションに割り当てられるIPアドレスは、Windowsを再起動するたびに割り当てが行われ、毎回異なる割り当てになり、固定したIPアドレスを利用できない。ただし、Win32側からは、WSL2側のサーバーに対して“localhost”でアクセスが可能になっている。

なお、複数のWSLディストリビューションをインストールしている場合、すべてのディストリビューションのIPアドレスは同一になる(写真01)。このため、異なるディストリビューションを同時に起動して同一のポート番号で待ち受けを行うと後から開始されたほうはエラーとなる。このため、複数のWSLディストリビューションを同時実行するときには、ポート番号の割り当てを考慮する必要がある。

  • 写真01: WSL2で複数のWSLディストリビューションをインストールした場合、すべてのディストリビューションに割り当てられるIPアドレスは同一になる

WSL1の場合、ネットワークに関しては、Win32ホスト側と同等であり、特に制限などはない。しかし、WSL2の場合、IPアドレスが異なり、さらに仮想スイッチによるネットワークを経由するため、Win32側とは異なる制限が発生する。これをWSL2上のサーバー、クライアントでのそれぞれのアクセスでまとめたのが(表02)だ。簡単にいうと、WSL2とWin32の間のアクセスには、特に設定などは不要だが、LAN側のPCからWSL2上のサーバーをアクセスするためには、いくつかの設定が必要になる。これは、クライアント版Windowsには、ルーティングの機能がなく、LAN側からWSL2宛のパケットを受け取ってもこれを仮想スイッチ側に転送しないからである。

  • ■表02

ネットワークに関連した情報を得る

以後の作業に必要となるLinuxでのIPアドレスの調べ方などを簡単に解説しておこう。WSLでは、ipコマンドを使うのが簡単だ。ipコマンドはサブコマンドで対象を指定し、表示や設定が行える(表03)。サブコマンドには、IPアドレス、ルーティングテーブル、リンクアドレス(MACアドレス)などの対象を指定し、さらにコマンド(show、setなど)を続けることで、表示や設定が行える。詳しくは、ipコマンドのmanページを参照のこと。

  • ■表03

Linuxのipコマンドは、wsl.exeのコマンド実行機能を使って実行させることができる。Win32側で、Ubuntuディストリビューションのipアドレスを調べたい場合、“wsl.exe -d ubuntu -e ip addr”とする。

Win32側では、仮想スイッチの状態表示や接続中の表示には、PowerShellの「Get-NetIPAddress」コマンドを使う(写真02)。cmd.exeでも利用可能なipconfig.exeもある。

  • 写真02: PowerShellのGet-NetIPAddressコマンドを使うことで、Win32側のIPアドレスをすべて表示させることができる

しかし、PowerShellには、(表04)のようなネットワーク関連のコマンドがある。以前から使われていたnetstat.exeやipconfig.exeの代用になるものだが、PowerShellのオブジェクトやプロパティ選択、書式表示機能を使うことで、必要な情報だけをコンパクトに表示できる。

  • ■表04

ネットワークアクセスの実際 Win32とWSL2

ここでは、WSL2、Win32でhttpサーバーを動作させ、他方からアクセスを行い、その挙動を観察してみる。httpサーバーには、node.jsを使い、簡易なhttpサーバーをポート番号3000で動作させる(node.jsのサンプルプログラムを改良。リスト01)。そのアクセスには、curl/curl.exeを利用する。挙動の観察には、netstat/netstat.exeを利用する。

■リスト01

var http = require('http'); var port = 3000; http.createServer(function (req, result) { var now = new Date; console.log('Connect:'+now.toISOString()+ ' From ' + result.connection.remoteAddress + ":" + result.connection.remotePort+" To " + result.connection.localAddress+":"+ result.connection.localPort); result.writeHead(200, {'Content-Type': 'text/plain'}); result.end('This is WSL2.\n'+now.toISOString()+'\n'); }).listen(port,'0.0.0.0'); console.log('Server running at http://localhost:'+port+'/');

まずは、WSL2上でhttpサーバーを動作させる。この場合、Win32側からは、“http://localhost:3000”でアクセスが可能になる。このとき、Win32側では、wslhost.exeがポート3000で待ち受けを行っている(写真03)。wslhost.exeは、WSL2へのアクセスを受け付けるためのWin32側のプログラムだ。curl.exeを使って、“http//localhost:3000”をアクセスすると、WSL2のサーバーは、localhost(127.0.0.1)からアクセスされたと表示する(写真04)。

  • 写真03: WSL2側で簡易なhttpサーバーを動作させポート3000で待ち受けを行う。Win32側で待ち受けポート3000を探すとプロセス番号30968が待ち受けを行っており、調べるとwslhost.exeというプログラムだった。このとき、WSL2側へはlocalhostおよび仮想スイッチ側IPアドレス(172.26.17.100)でアクセスができる

  • 写真04: Win32側からlocalhostでアクセスすると、WSL2側では、127.0.0.1(localhost)からのアクセスになるが、仮想スイッチ側IPアドレスでのアクセスでは、仮想スイッチのWin32側IPアドレス(172.26.16.1)からのアクセスになる

WSL2側のIPアドレス(172.26.17.100)を使って、アクセスを行った場合(curl http://172.26.17.100:3000)、サーバーは、172.26.16.1(仮想スイッチのWin32側IPアドレス)からアクセスがあったことを表示する。

つまり、同じWin32からWSL2へのアクセスだが、localhostを宛先にした場合と、WSL2のIPアドレスを宛先にした場合では、パケットの送り元のIPアドレスが異なる。

逆にWin32側でhttpサーバーを動作させた場合、WSL2からアクセスするには、Win32のIPアドレスを指定するしか方法がない。この場合にはlocalhostを宛先ホスト名としては利用できない(写真05)。

  • 写真05: Win32側で簡易httpサーバーを動作させた場合、WSL2側からは、Win32のIPアドレス(192.168.0.4)でアクセスが可能になる

LANからWSL2へのアクセス

WSL2上でサーバーを動作させ、Win32側が接続しているLAN側からアクセスする場合、そのままでは接続が行えない。portproxyを設定し、ファイアウォールにルールを設定することでLAN側からのアクセスが可能になる。

これらの設定は、Windowsの再起動後も維持されるが、WSL2側のIPアドレスが変化するため、portproxyは再作成が必要になる。Windowsの起動時にスクリプトを実行することで、対応は可能だが、サーバーの起動なども自動化する必要があり、簡単な設定作業ではないため、ここでは、手動による方法を解説し、WSL2側でLAN側にサービスを行うためのサーバー起動に関しては、回を改めて解説させていただきたい。

WSL2側で動作しているサーバーにLAN側からアクセスするには、「ポートフォワーディングによるパケットの転送」および「ファイアウォールのルールの追加」が必要になる。ポートフォワーディングは、netsh.exeを使いPortProxyを設定する。ファイアウォールに関しては、コントロールパネルの「Windows Defenderファイアウォール」から手動でルールを追加するか、PowerShellのコマンドを使って行う。

前提条件として、WSL2側サーバープログラムの待ち受けポートを3000とし、WSL2のIPアドレスを172.23.220.194とする。ここで、Win32のポート番号3000をWSL2のポート番号3000に転送するポートフォワーディングを設定するには、管理者権限でコンソールを開き、


netsh.exe interface portproxy add v4tov4 listenport=3000 connectaddress=172.23.220.194 connectport=3000

を実行する。これでWin32側のポート3000に来たパケットがWSL2(172.23.220.194)のポート3000に転送される。

しかし、Win32側のLAN側にはファイアウォールがあるため、このままではポート3000宛のパケットはファイアウォールを通過できない。これを通過させるためには、新規にルールを追加する必要がある。ここでは、PowerShellで設定を行う。この場合、


New-NetFirewallRule -DisplayName 'WSL2Server access' -Protocol TCP  -LocalPort 3000 -Action Allow

とする。

WSL2のネットワーク設定

WSL2ディストリビューションでは、/etc/wsl.confファイルにより、(表05)のようなネットワーク機能を設定することが可能だ。これらは、WSL2が自動的に行うサポート機能に対する設定だ。設定項目は3つ。DNSサーバーを指定する/etc/resolv.confの自動生成を制御する“generateResolvConf”、/etc/hostsの自動生成機能“generateHosts”、そしてLinuxのhostname設定だ。

  • ■表05

“generateResolvConf”と“generateHosts”は、それぞれ/etc/の下にある対応するファイルをWSLディストリビューションの起動時に自動生成したファイルを上書きする。このため、設定が有効な場合、/etc/hostsおよび/etc/resolv.confを手動で書き換えても、WSLの起動時に上書きされてしまう点に注意が必要だ。つまり、DNSサーバーやhostsファイルを手動で変更する場合、wsl.confを同時に変更する必要がある。

ここでは、具体的にDNSサーバーの設定を手動で行えるようにしてみる。

まず、/etc/wsl.confを編集する。sudo コマンドを使ってエディタなどを起動する必要がある。デフォルトでは、WSLの設定はすべてデフォルト値であり、/etc/wsl.confが存在しないので新規に作成する。

■リスト02

[network] generateResolvConf = false

wsl.confに(リスト02)の行を追加する。resolv.confの自動生成が行われているとき、/etc/resolv.confは、/run/resolvconf/resolv.confへのシンボリックリンクになっている。まずはリンクを削除し、その上で/etc/resolv.confファイルを作成する。具体的には、


sudo rm /etc/resolv.conf
sudo vi /etc/resolv.conf

とする。たとえば、DNSサーバーのアドレスが“8.8.8.8”の場合、resolv.confファイルには以下のように記述する(写真06)。


nameserver 8.8.8.8
  • 写真06: /etc/resolv.confの自動生成をやめるには、/etc/wsl.confを変更し、その後/etc/resolv.confを削除して、新規に作り直す

その後、WSLセッションをexitコマンドなどで抜け、WSLのプロセスを“wsl.exe --terminate”で一回終了させる。

WSLは、HTTPサーバーのように広くLAN内にサービスを行うようなサーバーの実行には向いていないが、Win32側からのアクセスは問題なく行える。

>> Windows Subsystem for Linuxガイド 連載バックナンバー
https://news.mynavi.jp/tag/winsubsystem/