WSL2
個人的に最近,WSLからWSL2に移行しつつあります.
- Gooleドライブ系ツールの疑似ドライブ(/mnt/gとかになるやつ)が普通にアクセスできるようになった
- DockerもWSLよりWSL2の方がやりやすい
など,割といい感じかなと思っています.
X-WindowのDISPLAY環境変数
一方で,ネットワーク周りでは,WSLがWindows側と同じアドレスをデフォルト(Ubuntuとかならeth0)にしていたのに対し,WSL2では仮想NAT配下のアドレスが毎回動的にアサインされるように変更されています.
このため,X-WindowのサーバとしてVcXsrvなど,外部で起動させるサービスを使う場合には,環境変数DISPLAYを仮想NATルータに設定する必要があります.仮想NATのセグメントは毎回動的にアサインされるので,そこら辺の情報をとってくるようにしなければいけません.
この設定は,他の記事を参考にすると/etc/resolv.confのnameserver の情報を使っています.よくみかけたものはgrepとawkを組み合わせるものですが,awkだけでも以下のようにできます.
# (other settings may be above)
export DISPLAY=$(awk '{ if($1=="nameserver") {printf("%s:0.0",$2); exit} }' /etc/resolv.conf)
まぁどちら(awk単体 or w/grep)で書いてもone linerなので大差ないですが.
外部接続の受付(Port Forwarding)
もう一つ,Windows側からWSL2のUbuntu環境に対しての接続についても,Windows側と連携して設定を行うと便利です.
一般論として言えば,動的に変わるWSL2側のアドレスを毎回調べることを厭わなければ,Djangoのデバッグサーバや,他にもsshなどの接続先として,WSL2側のアドレスを直接指定することで,必要とするサービスにアクセスすることはできます(もともとWSL2へのルーティングは設定されていて,WSL2側も初期では特にホストFW的な設定は入っていないため).
とはいえ,毎回WSL2に割当たったアドレスを調べて使うのも面倒なので,WSLの時と同じく,Windows側のブラウザではlocalhost:8000 へのアクセスで済むようにした方が楽かと思われます.
そこで,ここでは例として,Django等,Web開発環境をWSL2で使っていて,デバッグサーバをポート8000で立ち上げたものにWindows側のブラウザから接続したい,という状況を考えます.8000以外でも同様ですが,とりあえずここでは8000を例として話を進めます.
この場合,Windows側で,localhost:8000へのアクセスを(WSL2のアドレス):8000に転送する設定を行う必要があります.その設定には,毎回動的に変わるWSL2側のアドレスを把握しなくてはなりませんが,これをWindows側からやると面倒なので,wsl
コマンドを使い,Windows側から呼び出すWSL2側のシェルスクリプトでnetsh.exe
を起動する,という,ちょっとねじくれた方法を使います.
なお,以下の例ではついでにssh(tcp/22)も書いています.Django,sshに限らず,外部(といっても主にWindows側でしょうが)からWSL2内に接続したい場合,以下の手順で同様にできます.
用意するファイルは,
- Ubuntu側で,Windows側からリモートで呼び出され,Windowsのnetsh.exe を実行するスクリプト
- Windows側で,1. のスクリプトをwslコマンドで呼び出すバッチファイル
という2つになります.
Ubuntu側からnetsh.exeで設定を行わせるスクリプト
まず,1.は,以下のようなBashスクリプトを作っておきます.場所は/usr/binでなくともかまいません(2.で作成するバッチファイル側で合わせればよい).また,筆者の環境でのテストでは特にroot権限(オーナーrootで+s)は必要ありませんでしたが,うまくいかないようであればrootでsetUIDしないといけないかもしれません(たぶん要らないと思いますが…).実行権限は必要なので,chmod 755
もしくは chmod a+x
はしておきます.
# !/bin/bash
ADDR4=$(hostname -I)
for PORT in 8000 9000 22
do
netsh.exe interface portproxy delete v4tov4 listenport=${PORT}
netsh.exe interface portproxy add v4tov4 listenport=${PORT} connectaddress=${ADDR4}
done
sc.exe stop iphlpsvc
sc.exe config iphlpsvc start=auto
sc.exe start iphlpsvc
上の例では,8000,9000,22をポートフォワードの対象として指定しています.
netsh.exe
のlistenportの指定に,listenport=8000,9000
とか指定できるかと思って試しましたが,コマンドはすんなり通るものの転送はされず,たぶん本気で8000,9000
ポートに転送しようとしている気がしました :-p.で,とりあえず愚直にループを回しています.
Windows側から,Ubutun上の設定スクリプトを呼び出すバッチファイル
次に,Windows側から上記の設定スクリプトを呼び出すバッチファイルです.これは,管理者権限で起動したPowerShell
か,cmd.exe
から呼び出す必要があります(要は管理者権限が必要なのはWindows側であってUbuntu側ではない).で,管理者権限でPSを起動するとデフォルトでC:\WINDOWS\System32
がcwdになるので,筆者はそこに置いています.
wsl -d Ubuntu-20.04 --exec bash /usr/bin/pfw.sh
で,これを
.\pfw.bat
で起動すると,フォワーディングが設定されます.
PS C:\WINDOWS\system32> netsh interface portproxy show all
ipv4 をリッスンする: ipv4 に接続する:
Address Port Address Port
--------------- ---------- --------------- ----------
* 8000 172.23.99.186 8000
* 9000 172.23.99.186 9000
* 22 172.23.99.186 22
PS C:\WINDOWS\system32>
ちなみに,管理者以外でPowerShellを開いている場合は,
Start-Process -Verb runas .\pfw.bat
で起動することもできます.昇格確認ダイアログが出るのは当然として,この場合一瞬CUIウィンドウが開いて終了と同時に消えるので,結果を確認したい場合はpfw.batの最後にpause
を追加します.
Django側のrunserver
以上でポートフォワーディングはできていますが,Django側でrunserverする場合,
python3 manage.py runserver 0.0.0.0:8000
がお手軽です.
なぜなら,Windows側のブラウザでlocalhost:8000
を指定しても,実際は転送されてWSL2側のプライベートアドレス(loopbackではない)に転送されているからです.もちろん,他にも方法はあり,
- settingy.pyとrunserverで対応
- 毎回,WSL2についたアドレスを調べてsettings.pyのALLOWED_HOSTSを書き換え(sed -i でインライン編集する,等),runserverにもそのアドレスを指定する
- WSL2に付与されたアドレスの特定のポートへのアクセスをlocalhostに転送
- iptablesで,設定を入れるスクリプトを用意するか,WSL2ローカルのsshサーバを上げて自分自身にsshしつつポートフォワードをする
といったあたりが思いつきますが,面倒そうですね…
まとめ
なにかと便利になったWSL2ですが,ネットワーク周りは(ある意味まっとうともいえるが)独立した仮想NWに収容されるようになったことで,若干手間は増えました.とりあえず,今のところ必要になったものについて,いくつか調べて入手した情報からまとめてみました.