WSL2をIPv6ネットワークに接続する(後編:DHCPv6-PDを受け取る)

## はじめに
:::note warn
2023.11.12追記
現在の最新WSLではブリッジモードやミラーモードというネットワークモードが追加されているのでIPv6を使いたい場合にはそちらをご利用ください。
この手法は旧来のネットワークモードやWindowsサンドボックスでの活用方法もありますが、基本的にはもはや必要のない方法です。
:::

中編では、PowerShellを使って仮想ネットワークスイッチからRAを広告し、WSL2やSandboxにIPv6アドレスを自動構成して通信できることを確認しました。ただ、プレフィクスをあらかじめ決めて固定的に割り当てるのはそうした機材を保有する組織の構成員かつ、その場所から動かさないデスクトップPCにしか適用できない手段です。今回はもう少しフレキシブルに上流が提供する情報を頂いて自動構成する方法を模索します。

本稿の構成は以下のような構成を考えています。

前編 [*WSL2がIPv6通信できない理由*](https://qiita.com/shigeokamoto/items/ba89516d1448b13f7251)
中編 [*現時点のWindows標準機能だけでRAを広告する*](https://qiita.com/shigeokamoto/items/9c5a631fe3099ed346c2)
後編 *DHCPv6-PDでプレフィクス委譲を受ける* <-- イマココ
番外 [*WSL1主体のスクリプトに書き換えてタスクスケジューラに登録する*](https://qiita.com/shigeokamoto/items/4e5538fcc80aba2dc4fe)

## DHCPv6-PDでプレフィクス委譲を受ける
今回行う方法にはいくつか条件があるのでそれにぴったり合えばよいのですが、条件にずれがある場合にはアレンジが必要、あるいは使えないということになります。
想定するプランでは上流から`DHCPv6-PD`でプレフィクス委譲を受け、中編で行った方法を使って`vEthernet(xxxx)`に分配してRAを流して通信ができるようになるまでを一連の流れにしたいと考えています。

## 想定環境
###### [メイン環境]
WSL2 + Ubuntu

######[接続用WSL1]
[Alpine](https://www.microsoft.com/store/productId/9P804CRF0395) (dhcpcdを使ってプレフィクス委譲を受けるのに使います)

######[上流接続]
フレッツ光ネクスト + HGW + ひかり電話 + v6オプション(IPoE接続していれば大丈夫です)

まず、対象の環境ですが`WSL2 + Ubuntu`を想定しています。他のディストロでも若干のアレンジが必要かもしれませんが特に問題はないと思います。更に、Windows側のネットワークに直接アクセスしたいので`WSL1`インスタンスを使います。別用途があってWSL1環境を用意されている場合に はそれを使ってください。WSL1を使っていない場合にはフットプリントの小さいAlpineが良いと思います。使用する用途が限定的なのでオリジナルのもっと小さいディストロを作ってもよいかもしれません。

そして、ここが一番条件を選ぶところなのですが上流回線はフレッツ光ネクストです。もちろん他の事業者でもかまわないのですが、必要な仕様は

/56プレフィクスを割り当て
下流に対してDHCPv6-PDで /60 プレフィクス委譲

フレッツ光ネクストでも光電話のアリナシでこのあたりの仕様に違いがあるそうなので注意が必要です。また、DHCPv6-PDで委譲が受 けられればプレフィクス長など仕様が異なってもアレンジ可能だと思います。NTTのHGWの場合、上流から /56 で譲り受けたものを /60 x 16本に分割し、1本を HGW直下、残り15本を貸し出し対象としています。下流(今回場合Windowsホスト)は/60を借り受けるので、これを更に16分割して /64 として使用し、WSL が x0、Default Switchがあれば x1 として使います。


## 構成

WSL1/Windowsを使ってネットワーク情報を取得する
dhcpcdを使ってプレフィクス委譲を受ける
委譲されたプレフィクスでRAを広告する

今回のプランでは`WSL1`を使ってdhcpcdを起動し、Windowsホストが接続している上流ルータからプレフィクスの委譲を受けます。具体的には、使って構わないIPv6のアドレスブロックをもらい、そのブロックに対する経路をWindows PCに向けてもらうという作業です。あとは、前回同様にWindows内の仮想ネットワークに経路広告するようにできれば全てがつながるはずです。

では早速組み立てていきましょう

## PowerShellを使ってネットワーク情報を取得する

まず、RAを広告するのに必要なプレフィクス以外の要素を先に収集しておきます。操作する対象はPowerShellですが、1本のスクリプトにまとめていくので、すべてWSL2側で作業します。

```shell:WSL2のshellで実行してみましょう
_ps=$(cat <<-EOF
    Get-NetRoute -DestinationPrefix '0.0.0.0/0' | Get-NetAdapter | format-list;
    Get-NetAdapter -Name 'vEther*' | select Name | Format-List
EOF
)
_d=$(powershell.exe $_ps | tr -d '\r')
```

結果を覗いてみると、このような感じでデフォルトゲートウェイに接続されている物理インタフェースの情報と vEthernet の名前が取得できていると思います。

```shell:実行結果を覗いてみる
$ echo "$_d"

Name                       : イーサネット
InterfaceDescription       : xxxxxxx xxxxxxx xxxxx
InterfaceIndex             : xx
MacAddress                 : xx-xx-xx-xx-xx-xx
　　　　　　　(中略)
Name : vEthernet (WSL)

Name : vEthernet (Default Switch)

Name : vEthernet (イーサネット)
```

このテキストを加工して変数に埋めていきます。

```shell:WSL2のシェルで先ほどの続きを実行してみましょう
defaultindex=$(echo -e "$_d" | sed -nr 's/^InterfaceIndex *: *([0-9]+).*$/\1/p')
defaultname=$( echo -e "$_d" | grep -v vEther | sed -nr 's/^Name *: *(.*)$/\1/p')
defaultswitch=$(echo -e "$_d"|grep vEther|grep Default| sed -nr 's/^Name *: *(.*)$/\1/p')
wslswitch=$(echo -e "$_d"|grep vEther|grep WSL| sed -nr 's/^Name *: *(.*)$/\1/p')
```

こちらも代入した変数を覗くと欲しかった情報が各変数に取り込まれているはずです。

```shell:実行結果を覗いてみる
$ echo "$defaultindex $defaultname $defaultswitch $wslswitch"
15 イーサネット vEthernet (Default Switch) vEthernet (WSL)
```

これで、RAを広告する対象の情報があらかた収集できました。


## dhcpcd.confの生成

dhcpcd を動作させるのに必要な情報をWSL1から収集します。
WSL1インスタンスのネットワークスタックはWindowsホストのものを使っているので、dhcpcdを動作させると、Windowsホストネットワークから上流ルータへのやり取りが可能になります。WSL2だと仮想ネットワークの内側に閉じ込められるので手が出ない領域です。

WSL2側から操作しているので、wsl.exe を使って、別ディストロのコマンドを実行します。

```shell:WSL2のシェルで先ほどの続きを実行してみましょう
distro=Alpine
wsl1if=$(wsl.exe -d $distro sh -c "cat /proc/net/route" 2> /dev/null)
```

結果を加工して変数に納めます。

```shell:WSL2のシェルで先ほどの続きを実行してみましょう
primary=$(echo "$wsl1if" | sed -nr 's/^([^ \t]+)[ \t]*00000000.*$/\1/p')
devs=$(echo $(echo "$wsl1if" | sed -nr 's/^(eth[0-9]+).*$/\1/p' | sort | uniq| grep -v $primary))
```

こちらは、WSL1のネットワークインタフェース名をDHCPv6-PDを受け取るインタフェースと受け取らないインタフェースに分別しています。この情報をdhcpcd.confに埋め込みます。

```shell:WSL2のシェルで先ほどの続きを実行してみましょう
config=$(cat <<EOF
denyinterfaces $devs
interface $primary
script /dev/null
nogateway
noipv6rs
ipv6only
nodhcp
noipv4
noipv4ll
dhcp6
ia_pd 1
EOF
)
```

## dhcpcdを使ってプレフィクス委譲を受ける

作成した設定を使ってdhcpcdを起動することでプライマリインタフェースからDHCPv6-PDを受け取れます。情報を受け取った後のインタフェースの設定には失敗します。WSL1ではAPIが足りない、あるいは権限が与えられていないのでしょう。

```shell:WSL2のシェルで先ほどの続きを実行してみましょう
dhcpcdconf=/tmp/.dhcpcd.conf
_ps=$(cat <<EOF
echo "$config" > $dhcpcdconf;
which dhcpcd
if [ $? -ne 0 ]; then
    apk --no-cache add dhcpcd;
fi
dhcpcd -f $dhcpcdconf -x 2>/dev/null;
dhcpcd -f $dhcpcdconf 2>&1;
EOF
)
prefix=$(echo "$_ps" | wsl.exe -d $distro -u root | grep delegated | sed 's/^.*prefix \([0-9a-f:]*\)::\/60/\1/')
```

先ほど作った設定をWSL1インスタンスの /tmp/.dhcpcd.conf に書き込み dhcpcd を動作させてプレフィクス情報を抜き取ります。dhcpcdがなかった場合インストールしたり、すでに起動していた場合に終了させたりしていますが、必要なことは、

上流ルータから DHCPv6-PDでプレフィクス委譲を受けること
委譲されたプレフィクスをスクリプト側に入手すること

の2点です。

## 仮想ネットワークに広告する

ここまでの作業で、上流ルータから使用してかまわないプレフィクスが入手できてルーティングが受けられるようになっています。あとは、内部にプレフィクスを付けていくのですが、ここは中編で行った作業そのままつなぎ合わせるとこういう感じになります。

```shell:最終形スクリプト
#! /bin/sh

distro=Alpine
dhcpcdconf=/tmp/.dhcpcd.conf

_ps=$(cat <<-EOF
    Get-NetRoute -DestinationPrefix '0.0.0.0/0' | Get-NetAdapter | format-list;
    Get-NetAdapter -Name 'vEther*' | select Name | Format-List
EOF
)
_d=$(powershell.exe $_ps | tr -d '\r')
defaultindex=$(echo -e "$_d" | sed -nr 's/^InterfaceIndex *: *([0-9]+).*$/\1/p')
defaultname=$( echo -e "$_d" | grep -v vEther | sed -nr 's/^Name *: *(.*)$/\1/p')
defaultswitch=$(echo -e "$_d"|grep vEther|grep Default| sed -nr 's/^Name *: *(.*)$/\1/p')
wslswitch=$(echo -e "$_d"|grep vEther|grep WSL| sed -nr 's/^Name *: *(.*)$/\1/p')

wsl1if=$(wsl.exe -d $distro sh -c "cat /proc/net/route" 2> /dev/null)
primary=$(echo "$wsl1if" | sed -nr 's/^([^ \t]+)[ \t]*00000000.*$/\1/p')
devs=$(echo $(echo "$wsl1if" | sed -nr 's/^(eth[0-9]+).*$/\1/p' | sort | uniq| grep -v $primary))

config=$(cat <<EOF
denyinterfaces $devs
interface $primary
script /dev/null
nogateway
noipv6rs
ipv6only
nodhcp
noipv4
noipv4ll
dhcp6
ia_pd 1
EOF
)

_ps=$(cat <<EOF
echo "$config" > $dhcpcdconf;
which dhcpcd
if [ $? -ne 0 ]; then
    apk --no-cache add dhcpcd;
fi
dhcpcd -f $dhcpcdconf -x 2>/dev/null;
dhcpcd -f $dhcpcdconf 2>&1;
EOF
)

prefix=$(echo "$_ps" | wsl.exe -d $distro -u root | grep delegated | sed 's/^.*prefix \([0-9a-f:]*\)::\/60/\1/')
default=$(cat <<- EOF
        Set-NetIPInterface -Forwarding Enabled -InterfaceIndex $defaultindex;
        Set-NetIPInterface -InterfaceAlias 'vEthernet ($defaultname)' -AddressFamily IPv6 -Dhcp Disabled -RouterDiscovery Disabled;
EOF
)
address=$(openssl rand -hex 8 | sed -r 's/^(.{4})(.{4})(.{4})(.{4})/\1:\2:\3:\4/')
i=0

for interface in "$wslswitch" "$defaultswitch"
do
    if [ -n "$interface" ]; then
        virtual=$virtual$(cat <<- EOF
        Get-NetIPAddress -InterfaceAlias '$interface' -AddressFamily IPv6 |
                where {\`\$_.IPAddress.StartsWith('2')} |
                Remove-NetIPAddress -Confirm:\`\$false -PassThru;
        New-NetIPAddress -IPAddress $prefix:$address -PrefixLength 64
                 -InterfaceAlias '$interface' -SkipAsSource \`\$True -PolicyStore ActiveStore;
        Set-NetIPInterface -InterfaceAlias '$interface' -AddressFamily IPv6
                 -Advertising Enabled -AdvertiseDefaultRoute Enabled -Forwarding Enabled -PassThru;
EOF
        )
        route=$route"Set-NetRoute -InterfaceAlias '$interface' -AddressFamily IPv6 -DestinationPrefix '$prefix::/64' -Publish yes;"
        i=`expr $i + 1`
        prefix=$(echo $prefix| sed "s/.\$/$i/")
    fi
done
c="$default${virtual}sleep 5;$route"
powershell.exe start-process -windowstyle hidden powershell -Verb runas \"$c\"

```

条件が整っていれば、このスクリプトを実行することで、WSL2, Hyper-V, SandboxにIPv6アドレスが割り振られ通信可能になるはずです。

## おわりに
WSL1でdhcpcdを動作させることで上流ルータからのプレフィクスの委譲を受け、仮想ネットワークにRAを流して一通りの構築はできました。あくまでサンプル的な組み立てなのでプレフィクスが変更になった場合等々処理は甘いのですが、まずは動かすところまでを目的にしています。実際に試したことはありませんが、nuro等もHGWが/54を分割してDHCPv6-PD配布してくれるようなので多少調整すれば使えるかもしれません。環境依存が強い手法ではあるのですが、筆者環境では非常に快適なIPv6 lifeが送れています。

これでWSL2をIPv6ネットワークに接続するところまで一通り出来上がりなのでシリーズとしては完結なのですが、折角なので次回は番外としてもう少し使い勝手が良いようにブラッシュアップして、Windows起動時に自動で設定してしまいたいと思います。