以前、WSL2のUbuntuに固定のIP(IPv6)アドレスを複数つけてみるという記事を書いたのですが、うまく動作しないケースがあったので修正してみます。
きっかけ
この記事を書いた時点ではWSL2はvEthernet (WSL)
に接続される仕様一択でした。しかし、ブリッジ接続にする場合はWindows側から見る接点はデフォルトインタフェースになるでの、必ずしもvEthernet (WSL)ではなくなってしまいます。そこで、スクリプトの修正をしていたのですが、設定したULAにWindows側からアクセスできない個体がありました。調べると、普段使っている環境が普通じゃなかったようです。ただ、他の環境に適用させるために修正するには分量が多いので別記事としました。
この記事はあくまで補足だけなので本編はWSL2ネットワークを新機能でブリッジモードに変更する(IPv6も利用可)を参照してください。
問題点リスト
- Windowsのソースアドレスに対して経路がない場合がある
- vEthernet (WSL)がルーティングできない場合がある
- vEthernet (WSL)以外のスイッチだと経路設定できない(これはおまけ)
そもそもどんな環境だと大丈夫だったの?
筆者が利用していた環境の場合
- WindowsはIPv6利用可能
- WSL2はIPv6利用可能
基本的にこの状態だったので問題は発覚しませんでした。
そしてもう一つ、ネットワーク(ルーター)側が対応していなくてWindowsがIPv6利用できない場合(無効にしてはいけません)も問題ありません。
まずは、この二つのパターンの状況の確認をします。ともにWindowsから設定したアドレス(fdff:ffff:ffff:ffff::1)に通信した場合の経路です。
WindowsでIPv6を利用可能な場合
|---------- vEthernet(WSL) ----------|--------------- LAN ---------------|
WSL2 ----------------------------- Windows ------------------------------- ルーター
LLA---------------------------LLA LLA
| \
ULA GUA
LLA: リンクローカルアドレス
ULA: ユニークローカルアドレス(先の記事で設定するIPv6アドレス)
GUA: グローバルユニキャストアドレス(インターネットと通信するアドレス)
A. ソースアドレスはGUAが選択される
B. 経路指定されたインタフェースを選択
C. 経路指定されたWSL2のリンクローカルへ中継
D. WSL2リクローカルからユニークローカルへ到着
そして逆の経路を通って戻っていきます。この通信にはWSL2側のGUAは登場しませんから、WSL2にIPv6を利用可能にしていなくても同じ経路をたどります。ただこの場合、A -> B -> C -> Dの順路は問題ないのですが、逆の経路に大きな違いがあります。
まず、D -> Cの順路ですが、
IPv6が利用可能な場合にはデフォルトゲートウェイとしてWindows側のリンクローカルが指示されています。このおかげで逆向きの順路をたどれたのですが、IPv6が設定されていない状況ではデフォルトゲートウェイも設定されておらず、WindowsのGUAへの経路がわかりません。詰みました。
そして、もう一つの問題は B -> A の順路です。
Bは vEthernet(WSL)のリンクローカルアドレスです。対してソースアドレスとなるGUAはイーサネット
やWiFi
という物理インタフェースです。インタフェースまたぎが発生するのでパケットフォワーディングが有効でないといけないのですが、vEthernetはデフォルトではこの機能が無効にされているため中継できません。WSL2でIPv6を利用している場合にはそもそもWindowsをまたいで通信しないといけないのでその時に有効にしていました。
Windows自体がIPv6を利用できない状態の場合
|---------- vEthernet(WSL) ----------|--------------- LAN ---------------|
WSL2 ----------------------------- Windows ----------------------------- ルーター
LLA---------------------------LLA LLA
|
ULA
Windowsにはグローバルアドレスが割り当てられませんからこんな感じになります。Windowsにはリンクローカルアドレスしかないので、選択した経路のアドレスを利用することになるため、WSL2とWindows(vEthernet(WSL))お互いのリンクローカルアドレスでやりとりすることで経路の問題もフォワーディングの問題も発生しません。
修正1 Windowsのソースアドレスに対して経路を設定する
まず、追加コードはこんな感じです。
setV6route () {
address=$(execPowerShell "Get-NetRoute -DestinationPrefix '0.0.0.0/0'|Get-NetAdapter|Get-Netipaddress -AddressFamily Ipv6 -PrefixOrigin @('RouterAdvertisement','Manual','DHCP')|%{\$_.Ipaddress}")
gate=$( execPowerShell "ping -n 1 -w 100 $1|Out-Null;Get-NetNeighbor -IPAddress $1|Get-NetAdapter|Get-NetIpaddress -AddressFamily Ipv6 -IPAddress 'fe80::*'|%{\$_.Ipaddress}" | sed -ne 's/%.*$//' -e 1p)
echo "$address" | sed -r "s/^(.*)$/ip r replace \1 via $gate dev eth0/g" | wslSudo
}
ipv4addr=$( ip a show dev eth0 | sed -nr 's/^.*inet\s+([0-9.]+).*$/\1/pi')
ip -6 r | grep -q default; test $? -ne 0 && setV6route $ipv4addr
Windows側のソースになりそうなアドレスと宛先をPowerShellで取得しています。
細かく見ていくと、
addressの方は
Get-NetRoute ... | (IPv4の)デフォルトルートを抽出 |
Get-NetAdapter | 抽出した経路が属するインタフェースを抽出 |
Get-Netipaddress ... | 抽出したインタフェースに属するIPv6アドレスの内RA,DHCP,手動で付けたものを抽出 |
%{$_.Ipaddress} | 抽出したオブジェクトからIPアドレスを抽出 |
これでソースとなりそうなアドレスを抽出できます。
次にgateは
ping -n 1 -w 100 $1... | Windows側からWSL2のLLAへpingを打つ |
Get-NetNeighbor ... | WSL2のLLAを探す |
Get-NetAdapter | WSL2と隣接するアダプターを抽出 |
Get-NetIpaddress ... | 抽出したアダプターに属するLLAを抽出 |
%{$_.Ipaddress} | オブジェクトからIPアドレスを抽出 |
sed -ne 's/%.*$//' -e 1p | アドレス(複数ある場合には最初のもの)からインタフェース番号を除外 |
最初にpingを打っているのは、タイミングによってはWSL2のLLAがまだ隣人として認識されていないからです。
そして、これらの情報を使って、WSL2側に経路を付けておけば、復路の経路設定は完了です。あらかじめ経路を見てから設定しているのは、デフォルト経路がある場合にはこの処理は必要ないからです。
修正2 vEthernet (WSL)がルーティングできるよう設定する
あとは、Windowsが持つ複数のインタフェース間でルーティングできるようにしておくだけです。
こちらはWSL2のUbuntuに固定のIP(IPv6)アドレスを複数つけてみるでPowerShellに渡していた変数に以下の1文を追加するだけでインタフェースのForwardingを有効にできます。
Get-NetIPInterface|?{\`\$_.InterfaceAlias -match 'vEther.*(WSL)'}|Set-NetIPInterface -Forwarding Enabled;
おまけ 経路設定の拡張
そして、この問題を発見するきっかけとなった修正ですが、
- Remove-NetRoute -DestinationPrefix $prefix::/64 -Confirm:\`\$false
- Get-NetAdapter -Name 'vEthernet (WSL)'|New-Netroute -DestinationPrefix $prefix::/64 -NextHop $linklocal
+ ping -n 1 -w 100 $ipv4addr|Out-Null;
+ Remove-NetRoute -DestinationPrefix $prefix::/64 -PassThru -Confirm:\`\$False|Out-Null;
+ Get-NetNeighbor -IPAddress $ipv4addr|Get-NetAdapter|New-Netroute -DestinationPrefix $prefix::/64 -NextHop + $linklocal -Policystore ActiveStore;
このような差分になります。
先頭のpingは先ほどと同じ理由なので、どちらか一方にあれば大丈夫でしょう。Remove-NetRouteは、タイミングによって設定した後で削除されるケースがあったので、パイプで終了を待つようにしています。ポリシーストア指定も細かな調整です。
違うのは最後の行ですが、以前は vEhternet(WSL)決め打ちでアダプターを取得していた部分を隣接するインタフェースをIPv4アドレスで調べて取得しています。経路設定の時も同様にIPv4アドレスを使っていますが、その方が使いやすいからです。
おしまい
これらの修正でいままで動かなかった環境でもこのギミックが使えるようになるのと、この修正をご自身で対応されることでHyper-Vスイッチのルーティング有効無効の制御や、Windows,Linuxでの経路指定への理解が深まると思います。