rcmdnk's blog
Last update

20210301_property_200_200

WindowsでLinuxなどを扱えるWSL2ですが、 WSL1とは結構構成が変わっています。

ネットワーク周りも変わっていて改めて設定を行ったのでそれについて。

WSL1でのネットワーク設定

WSL1ではWindowsのネットワークをそのまま使う形で、 例えばWSL内でsshサーバーを建てるとWindowsホストの22番ポートなどを 直接使う形になっていました。

Windows側でファイアウォールなどの設定を外部からアクセスできるようにすれば アクセスできる状態でした。

WSL2でのネットワーク設定(Windows内)

昨年リリースされたWSL2。 より普通のLinuxぽい使い方が出来るようになりました。

WSL2では立ち上がるUbuntuなどが仮想サーバーの様な 振る舞いをするようになり、 独自のIPアドレスを持っています。

Windows内ではこのアドレスを知っていればアクセスできるわけですが、 このIPアドレスが起動するたびに変わるため結構面倒。

これを回避するために、WSL2へはlocalhostでアクセス出来る仕様があります。

これはWSL2の開発途中で追加されたものですが、現在は デフォルトで有効になっています。

WSL2ではWindowsのC:\Users<ユーザー名>に、 .wslconfigという名前のファイルを作って設定を書いておくとWSLの起動時に有効になります。

ここへ、

.wslconfig
1
2
[wsl2]
localhostForwarding=False

としておくとこれが無効になります。

これをTrueにすると有効になる、という記事がありますが、 これは当初この機能が導入された当時はデフォルト無効な状態で 有効にする必要があったからで、今は以下にあるようにデフォルトがTrueで有効になっています。

Linux ディストリビューションの管理 Microsoft Docs

この機能によってWSL2の中でhttpdサーバーをたてて、http://localhostとかに アクセスするとWSL2で動かしているhttpdサーバーにアクセス出来るようになっています。

勿論、WSL2のIPアドレスを調べてそのアドレスを指定してもアクセスできます。

$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: bond0: <BROADCAST,MULTICAST,MASTER> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 2a:8f:ed:30:01:5b brd ff:ff:ff:ff:ff:ff
3: dummy0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether ee:62:ad:a6:3e:0a brd ff:ff:ff:ff:ff:ff
4: [email protected]: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/sit 0.0.0.0 brd 0.0.0.0
5: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:15:5d:30:e9:41 brd ff:ff:ff:ff:ff:ff
    inet 172.18.229.192/20 brd 172.18.239.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::215:5dff:fe30:e941/64 scope link
       valid_lft forever preferred_lft forever

もしくは

$ ip r
default via 172.18.224.1 dev eth0
172.18.224.0/20 dev eth0 proto kernel scope link src 172.18.229.192

eth0で見えている172.18.229.192がWSL2のアドレス。 (172で始まるクラスBのプライベートアドレスが割り振られている。)

これを使ってhttp://172.18.229.192でアクセスできます。

これであれば、localhostForwarding=Falseでもアクセスできます。

ちなみに、WSL2の中でWindows自体のIPアドレスを知りたい場合は、 Windows側のコマンドである ipconfig.exe(/mnt/c/Windows/system32/ipconfig.exe) が使えるのでこれを使うと

$ ipconfig.exe

Windows IP 構成


イーサネット アダプター vEthernet (WSL):

   接続固有の DNS サフィックス . . . . .:
   リンクローカル IPv6 アドレス. . . . .: fe80::b5af:1841:475a:8a27%50
   IPv4 アドレス . . . . . . . . . . . .: 172.18.224.1
   サブネット マスク . . . . . . . . . .: 255.255.240.0
   デフォルト ゲートウェイ . . . . . . .:

イーサネット アダプター イーサネット:
   接続固有の DNS サフィックス . . . . .:
   リンクローカル IPv6 アドレス. . . . .: fe80::2d90:1387:f61b:2f48%5
   IPv4 アドレス . . . . . . . . . . . .: 192.168.11.2
   サブネット マスク . . . . . . . . . .: 255.255.255.0
   デフォルト ゲートウェイ . . . . . . .: 192.168.11.1

Wireless LAN adapter ローカル エリア接続* 9:
...

な感じでWindowsのIPアドレス(192.168.130.1)とは別にWSLがvEthernetとして認識されて仮想ネットワークが作られていることが分かります。

WSL2でのネットワーク設定(Windows外部から)

localhostForwarding=Trueの設定の時点で localhostでアクセスできる様になるのでWindowsのIPアドレスにも直結している様な感じもしますが 実際にはそうはなっていません。

Windows内からでもhttp://192.168.11.2としてもWSLで動かしているサーバーにはアクセス出来ない状態です。

sshサーバーを立ててWSL内からssh 192.168.11.2としてもアクセスできません。

これはhttpdやsshで使っている80番や22番のポートが192.168.11.2とは結びついていないからです。

この辺なんとなくアドレスとポートはまとめて1つで、 アドレスの呼び方(localhost:80なのか192.168.11.2:80なのか)が違っても 同じものと思っていましたが、 実際にはこれらはすべて別のものとして管理されている、ということです。

localhostForwarding=True とすることで、WSL側のlocalhostとWindows側のlocalhostで ポートが統一される、という状態にはなっていますが、 外部向けのWindowsのIPアドレスのポートには結びついてない状態です。

これを結びつけるためにはWSLの外から作業をしてやる必要があります。

具体的には以下のようなPowerShellのスクリプトを実行します。

wsl_port.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole("Administrators")) { Start-Process powershell.exe "-File `"$PSCommandPath`"" -Verb RunAs; exit }

$ip = bash.exe -c "ip r |tail -n1|cut -d ' ' -f9"
if( ! $ip ){
  echo "The Script Exited, the ip address of WSL 2 cannot be found";
  exit;
}

# All the ports you want to forward separated by comma
$ports[email protected](22,3000,8080);
$ports_a = $ports -join ",";

# Remove Firewall Exception Rules
iex "Remove-NetFireWallRule -DisplayName 'WSL 2 Firewall Unlock' ";

# Adding Exception Rules for inbound and outbound Rules
iex "New-NetFireWallRule -DisplayName 'WSL 2 Firewall Unlock' -Direction Outbound -LocalPort $ports_a -Action Allow -Protocol TCP";
iex "New-NetFireWallRule -DisplayName 'WSL 2 Firewall Unlock' -Direction Inbound -LocalPort $ports_a -Action Allow -Protocol TCP";

for( $i = 0; $i -lt $ports.length; $i++ ){
  $port = $ports[$i];
  iex "netsh interface portproxy add v4tov4 listenport=$port listenaddress=* connectport=$port connectaddress=$ip";
}

# Show proxies
iex "netsh interface portproxy show v4tov4";

以下のものを参考にしてちょっとアップデートしたスクリプトです。

[WSL 2] NIC Bridge mode 🖧 (Has TCP Workaround🔨) · Issue #4150 · microsoft/WSL

WSL側のIPアドレスは変わるので固定では出来ず、毎回上の様に何らかの方法で取得する必要があります。

追記: 2021/05/16

3行目のところで、 Windowsが複数のネットワークインターフェースを持っている場合などはtail -n1だと別のものを選んでしまう可能性もあるので、 状況によって

bash -c "ip route |grep 'eth0 proto'|cut -d ' ' -f9"

などインターフェースを指定するなどする必要があります。

(Thanks to jx @ comment)

追記ここまで

netsh interface portproxy addというところでポートをつないでいます。 古い接続がある場合にdeleteしてから、という記述も見かけましたが、 少なくとも手元の環境ではdeleteしなくても新しいaddをすれば上書きされていたので 特にdeleteをする必要はありません。

これをwsl_port.ps1とかで保存して実行すれば 22, 3000, 8080番のポートをWSLとWindowsの外部向けIPアドレスとでつないでいます。 (最初の行にあるのが管理者として実行するようにするおまじないなので、ファイルを右クリックで出てくるメニューでPowerShellで実行を実施すればOK。)

また、同時にファイアウォールの設定もこれらのポートについて外すようにしています。

これで、外部からもhttp://192.168.11.2:8080とかでWSLのサーバーにアクセスすることができます。 (この例だとクラスCのプライベートアドレスなので同じプライベートネットワーク内にあるものからであれば、ですが。)

Windowsの中からも自分のhttp://192.168.11.2:8080でWSLのサーバーにアクセス出来るようになります。

Windows起動時につなげる

外部からの接続を常時する必要がなければ必要な時に上のスクリプトを実行すれば良いわけですが、 常時使いたい、という場合はタスクスケジューラーに仕込んで ログイン時に上のスクリプトを実行すると楽できます。

Windowsの検索でtaskとかしてタスクスケジューラーを見つけて立ち上げます。

20210301_taskscheduler.jpg

操作基本タスクの作成からタスクを作ります。

  • 名前、説明: 適当にwslなど。
  • トリガー: ログオン時
  • 操作: プログラムの開始
    • プログラム/スクリプト: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
      • PowerShellの実態へのパス。
    • 引数の追加: -ExecutionPolicy RemoteSigned C:\Users\User\wsl_port.ps1
      • -ExecutionPolicy RemoteSignedを追加することで自作スクリプトの実行を許可。その後にスクリプトへのパスを追記。
  • 完了: [完了]をクリックしたときに、このタスクの[プロパティ]ダイアログを開くにチェックして完了を押す。
  • 出てきたプロパティの全般タブにある最上位の特権で実行するにチェックしてOK

20210301_property.jpg

これでログイン時にWSLの特定のポートをWindows側の外部IPアドレスにつなげることが出来ます。

この方法だと起動中にWSLを再起動した場合にはIPアドレスが変更されるため、 再びこのスクリプトを実行する必要があります。

必要であれば、再起動コマンドを上のスクリプトに追加して 再起動と同時にポートを繋げる作業をするスクリプトにしても良いかもしれません。

WSLの再起動

WSLの再起動の話をしたのでそれについて少し。

PowerShell(管理者権限)上で

PS C:\WINDOWS\system32> wsl -l -v
  NAME                   STATE           VERSION
* Ubuntu                 Running         2
  docker-desktop         Stopped         2
  docker-desktop-data    Stopped         2

みたいにすると今動いているディストリビューションが分かります。 ここではUbuntuが動いています。

ここで

PS C:\WINDOWS\system32> wsl --terminate Ubuntu

とすると、Ubuntuを停止しますが、即座に再起動を行います。

--terminateの代わりに-tでも可。

これをスクリプトの先頭に書いて、少しスリープさせてポートを付けなおす様な ことをすれば良いとは思うのですが、 使っているLinuxのディストリビューションが変わる可能性があり、そのたびに 書き換えないといけません。

現在はUbuntuというバージョンなしの名前になっているので、Ubuntuを使い続けるのであれば そこま気にしなくても良いのですが、 バージョン名のついている状態や、他のLinuxを使ったりする場合には使ってるものをちゃんと把握して やってもらいたいところ。

そこで、wsl -l -vの出力を見て使っているものを把握することが考えられますが、 この出力が現状、とくに日本語環境だとうまく使えない状態になっています。

wsl.exe outputting unicode to stdout · Issue #4607 · microsoft/WSL

上のIssueにありますが、例えばPowerShell版grepのSelect-Stringを使って

PS C:\WINDOWS\system32> wsl -l |Select-String Ubuntu

とかやっても何も出ません。

PS C:\WINDOWS\system32> wsl -l
Linux 用 Windows サブシステム ディストリビューション:
Ubuntu (既定)
docker-desktop
docker-desktop-data

とちゃんと出てそうなのに。

試しに出力をパイプに渡してmoreとかで見てみると

PS C:\WINDOWS\system32> wsl -l |more
L
i
n
u
x

(u
W
i
n
d
o
w
s

オ0ヨ0キ0ケ0ニ0・
ヌ0」0ケ0ネ0・モ0・・キ0・・:






U
b
u
n
t
u

(
稙喙)

こんな感じになっていて、日本語の文字化けもそうなんですが、 そもそも各文字が1行ずつ出力されている状態なので、 Select-Stringでは捕まらない状態になっています。

なので現状日本語環境でwsl -lなどを使って使っているディストリビューションを確認して どうこうするのは出来ません。

必要なら英語環境にしてしまう、とかですね。

Sponsored Links
Sponsored Links

« MH-Z19BとRaspberry Pi Zeroで二酸化炭素濃度を測定する mitmproxyを使ってアプリの通信内容を確認する »