2022.02.23追記
一部環境でこの通りには通信できないケースがあったので補足記事を書きました。
補足記事) WSL2に固定のIPv6アドレスをつける記事に問題を発見したので修正してみる
WSL2との連携の基本はlocalhost
WSL2インスタンス内でサービスを起動するとWindowsホストのlocalhostにマッピングされます。具体的には127.0.0.1と::1です。この仕組みを使えばWSL2でnginxを起動して、WindowsホストのEdgeからアクセスすることができます。通常はこれで問題ないでしょう。ところが、複数の環境を共通で配布したり使い分ける場合、独立した環境ごとにアクセスしたいという需要が(ほんの少し)あります。実は最近流行りの脱Docker Desktopでこの問題に遭遇したのですが、Docker Desktopのport proxyは127.0.0.1だけでなく127.0.0.2等127.0.0.0/8のどのアドレスでもWindows側にマッピングすることが出来ました。個人的には非常に便利だったのですが、WSL2上のUbuntuにdocker-ceをインストールした環境だとこれが使えないのでその代替手段を考えてみました。
127.0.0.0/8 -> fd00::/8
127.0.0.0/8が使えないとなるならどうすればいい?
WSL2は172.16.0.0/12や192.168.0.0/16のアチコチを自在に利用するのでこのあたりを使うとぶつかることが多いです。それ以前に組織内のプライベートネットワークもあってIPv4のプライベートアドレスに安全な領域はもはや残っていないかもしれません。であれば、IPv6を使いましょう。IPv6には組織内で自由に使えるユニークローカルアドレスという広大な空間があります。自由といってもアドレスの計算方法に指定があったりするのですが、細かいことは気にせずひとまずくみ上げてみましょう。
※この記事ではWSL2 + Ubuntu + シェルスクリプトを想定していますが、他のディストロでも同じような感じだと思います。
WSL2にIPアドレスを割り振る
ここではfdff:ffff:ffff:ffff::/64
を使う前提で進めます。実際にはprefix
に適当なものを割り当ててください。
#! /bin/sh
prefix=fdff:ffff:ffff:ffff
for i in `seq 1 8`
do
ip a add $prefix::$i/64 dev eth0
done
割り当てを決めた/64
のprefix
の末尾が1~8のアドレスを生成してeth0
に割り当てています。
新たに生成したIPアドレスのことをWindowsホストは知りませんから、これらのアドレスへのアクセス経路をつけないといけません。まずは、Windowsホストから見たゲートウェイ = WSL2のIPv6アドレスを調べます。
linklocal=$(ip a show dev eth0 | sed -nr 's/^.*inet6 *(fe[89ab][0-9a-f:]+).*$/\1/pi')
こういう感じですね。WSL2は外界のIPv6と接続されていませんが、リンクローカルアドレスは設定されていて、Windowsホストと通信可能です。リンクローカルはfe80::/10
ですから、fe8
~feb
で始まるアドレスです。eth0
から当該のアドレスを調べて変数に格納します。これをWindowsホストに渡してルーティングしてもらえば完了です。
Windowsホストのネットワーク設定はPowerShellを使うと設定できます。
Get-NetAdapter -Name 'vEthernet (WSL)'|New-Netroute -DestinationPrefix $prefix::/64 -NextHop $linklocal
Get-NetAdapter
でネットワークアダプタを取得します。ここではvEthernet (WSL)
、つまりWSL2インスタンスが接続されている仮想ネットワークスイッチです。この取得したオブジェクトを引き継いでNew-Netroute
を実行すると、当該のインタフェースに対して経路を設定できます。
設定する経路は最初に決めたfdff:ffff:ffff:ffff::/64
です。このネットワークの宛先は先ほど調べたWSL2のリンクローカルアドレスです。この操作はWindowsホスト側で行うのですが、WSL2はWindowsホストのコマンドを直接呼び出せる連携機能があるので、シェルスクリプト内から呼び出します。
但し、ネットワークの設定を変更するので、PowerShellを管理者権限で動かさないといけません。
これはStart-Process <コマンド> -Verb Runas
と書くことでsudoのように権限昇格が要求できるようです。
まとめてこのように書きます。
powershell=/mnt/c/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe
c=$(cat <<EOF
Remove-NetRoute -DestinationPrefix $prefix::/64 -Confirm:\`\$false
Get-NetAdapter -Name 'vEthernet (WSL)'|New-Netroute -DestinationPrefix $prefix::/64 -NextHop $linklocal
EOF
)
$powershell Start-Process -Windowstyle Hidden powershell -Verb Runas "\"$c\""
普通にWSL2インスタンスを起動するとpowershell.exe
へのパスは通っているのですが、sudoでrootになるとパスが引き継がれないのでフルパス指定しています。
次に先ほど書いたPowerShellスクリプトを変数に格納するのですが、念のため古い経路を削除しています。これはWSL2インスタンスの再起動などでIPアドレスが変わってしまった場合への対応です。
そして、最後に記述した文字列をpowershell.exe
に引き渡します。これで管理者権限でコマンド実行を行えます。
これらの操作で、fdff:ffff:ffff:ffff::1
~fdff:ffff:ffff:ffff::8
の8つのアドレスに対してWindowsホスト側からアクセスが可能になります。複数の環境を配布する場合、入口アドレスを分けることで共存が可能になって自由度が高まります。
まとめると、このようになるのですが最後にもうひとひねり。
先頭にこの一文を入れておけば、root以外のユーザーでこのスクリプトを実行しても、sudo
して呼びなおしてくれます。
#! /bin/sh
sudouser="root"
if [ $USER != $sudouser ]; then
sudo -u $sudouser sh $0
exit
fi
prefix=fdff:ffff:ffff:ffff
for i in `seq 1 8`
do
ip a add $prefix::$i/64 dev eth0
done
linklocal=$(ip a show dev eth0 | sed -nr 's/^.*inet6 *(fe[89ab][0-9a-f:]+).*$/\1/pi')
powershell=/mnt/c/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe
c=$(cat <<EOF
Remove-NetRoute -DestinationPrefix $prefix::/64 -Confirm:\`\$false
Get-NetAdapter -Name 'vEthernet (WSL)'|New-Netroute -DestinationPrefix $prefix::/64 -NextHop $linklocal
EOF
)
$powershell Start-Process -Windowstyle Hidden powershell -Verb Runas "\"$c\""
おわりに
IPv6アドレスをつけるというタイトルで、WSL2で外界とのIPv6通信を期待された方もおられるかと思いますが、今回はインスタンスとWindowsホストの2者間の通信です。Microsoftの公式見解ではWSL2はIPv6通信を行えないのですが、方法はいくつかありますので今後紹介していきたいと思います。