QEMU+NetBSDではipv6addrctlで接続ポリシーを設定しよう

[NetBSD Advent Calendar 2021](https://qiita.com/advent-calendar/2021/netbsd) 1日目の記事です。今年もNetBSDの記事を増やして行きましょう！
今日のお話は少し前に私がハマった、IPv6/IPv6の接続優先度の話をしようと思います。

## QEMU+NetBSDではIPv6接続が優先される？

QEMU環境上にNetBSD-9をインストールして動かしていた時の話です。 `pkg_add` でパッケージをインストールしようとすると、妙に時間がかかる現象に遭遇してしまいました。

```sh
# echo $PKG_PATH
http://cdn.NetBSD.org/pub/pkgsrc/packages/NetBSD/x86_64/9.2/All
# pkg_add -v bash
...
Running install with POST-INSTALL for bash-5.1.8nb3.
bash-5.1.8nb3: adding /usr/pkg/bin/bash to /etc/shells
Package bash-5.1.8nb3 registered in /usr/pkg/pkgdb/bash-5.1.8nb3
 1877.82s real     3.01s user     4.52s system
```

約14分くらいかかっているようです…。

```sh
# # 1秒だとrealは2.14sくらい。
# time sleep 1
    2.14s real     0.08s user     0.00s system
#
# bc
1877.82 / 2.14
877
877 / 60
14
```

`tcpdump` で確認してみると、どうやらIPv6での接続を試したのち、IPv4接続にフォールバックされるという動作になっていました。

```sh
# tcpdump -i wm0 port 80
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on wm0, link-type EN10MB (Ethernet), capture size 262144 bytes
02:16:55.924838 IP6 fec0::1a6b:e5d8:a4b4:2bb7.65532 > 2a04:4e42::262.http: Flags [S], seq 2490812735, win 32768, options [mss 1440,nop,wscale 3,sackOK,TS val 1 ecr 0], length 0
...
```

## IPv4での接続を優先するよう設定する

VirtualBox等の他の仮想化環境ではIPv6が優先される現象に遭遇していないため、慌てて設定方法を調査する感じになりました。
Linuxでは[getaddrinfo(3)](https://linuxjm.osdn.jp/html/LDP_man-pages/man3/getaddrinfo.3.html)でIPv4/IPv6のどちらを優先して接続を試すかを設定できるため、NetBSDでも同等の方法だろうと予想していました。が、BSD系だと[ip6addrctl(8)](https://man.netbsd.org/NetBSD-8.0/ip6addrctl.8)でIPv4/IPv6の優先ポリシーを設定する手順となっていました。
私が今まで設定していなかっただけで、かなり前から提供されているツールのようです。

設定内容はシンプルで、以下の内容を `/etc/rc.conf` に追加＋再起動でOKです。

```sh
ip6addrctl=YES
ip6addrctl_policy=ipv4_prefer
```

## IPv4が優先して利用される

`ip6addrctl` の設定投入後、改めて `pkg_add` を試してみます。
今度はサクッとパッケージがインストールされています。

```sh
# time pkg_add -v bash
...
Running install with POST-INSTALL for bash-5.1.8nb3.
bash-5.1.8nb3: adding /usr/pkg/bin/bash to /etc/shells
Package bash-5.1.8nb3 registered in /usr/pkg/pkgdb/bash-5.1.8nb3
   12.39s real     3.16s user     4.32s system
```

念のため `tcpdump` で確認すると、IPv4で接続されていることが分かります。

```sh
# tcpdump port 80
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on wm0, link-type EN10MB (Ethernet), capture size 262144 bytes
04:20:41.475156 IP 10.0.2.15.65535 > 151.101.1.6.http: Flags [S], seq 1157240960, win 32768, options [mss 1460,nop,wscale 3,sackOK,TS val 1 ecr 0], le
ngth 0
```

## NetBSD/FreeBSDでip6addrctlのデフォルト設定を比較してみる

ここまでで一連の対応手順を調べてみたのですが、どうやらBSD系では `ip6addrctl` というツールでIPv4/IPv6の接続優先ポリシーを設定するようです。
(Linux(というか `glibc` )では `/etc/gai.conf` で設定するようですね)

ふと「これQEMU+FreeBSDでも同じ挙動になるのだろうか？」と思い確認してみました。
すると、FreeBSDでは特に何もしなくてもIPv4での接続が優先されているような挙動になっています。

これは何が違うのだろう？と思い、 `ip6addrctl show` の結果を比較すると以下のようになっていました。

NetBSD,FreeBSD共に、 `fecd::/64` というサイトローカルアドレス(※)が振られています。
※： `/10` ではないけどサイトローカルアドレスなはず…。

```sh
$ # FreeBSD環境での確認結果。
$ uname -a
FreeBSD fbsdvm01 13.0-RELEASE FreeBSD 13.0-RELEASE #0 releng/13.0-n244733-ea31abc261f: Fri Apr  9 04:24:09 UTC 2021     root@releng1.nyi.freebsd.org:/usr/obj/usr/src/amd64.amd64/sys/GENERIC  amd64
$
$ ifconfig em0 | egrep 'em0|inet'
em0: flags=8863<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
        inet6 fe80::5054:ff:fe12:3456%em0 prefixlen 64 scopeid 0x1
        inet6 fec0::5054:ff:fe12:3456 prefixlen 64 autoconf
        inet 10.0.2.15 netmask 0xffffff00 broadcast 10.0.2.255
$
$ ip6addrctl show
Prefix                          Prec Label      Use
::1/128                           50     0        0
::/0                              40     1        3
::ffff:0.0.0.0/96                 35     4        0
2002::/16                         30     2        0
2001::/32                          5     5        0
fc00::/7                           3    13        0
::/96                              1     3        0
fec0::/10                          1    11        2
3ffe::/16                          1    12        0
```

```sh
$ # NetBSDでの確認結果。
$ uname -a
NetBSD nbsdvm01 9.2 NetBSD 9.2 (GENERIC) #0: Wed May 12 13:15:55 UTC 2021  mkrepro@mkrepro.NetBSD.org:/usr/src/sys/arch/amd64/compile/GENERIC amd64
$
$ ifconfig wm0 | egrep 'wm0|inet'
wm0: flags=0x8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500
        inet 10.0.2.15/24 broadcast 10.0.2.255 flags 0x0
        inet6 fe80::88e4:be83:7b9e:c844%wm0/64 flags 0x0 scopeid 0x1
        inet6 fec0::f911:3fa9:73e:3cf7/64 flags 0x0
$
$ ip6addrctl show
no source-address-selection policy is installed
```

以下のように、FreeBSDでの `ip6addrctl show` ではデフォルトでIPv4/IPv6優先ポリシーが設定されています。
[ほっといたらIPv4運用に影響するIPv6の話](https://www.janog.gr.jp/meeting/janog18/files/IPv6_Kunitake.pdf)というPDFのスライド20枚目の解説を基に設定内容を読み解くと、 `::ffff:0.0.0.0/96 35 4 0` でIPv4接続のポリシーが追加されています。
この設定ポリシーでは、IPv4の接続がIPv6( `fec0::/10` )よりも優先されていることが分かります。

```sh
$ ip6addrctl show
Prefix                          Prec Label      Use
::1/128                           50     0        0
::/0                              40     1        3
::ffff:0.0.0.0/96                 35     4        0
2002::/16                         30     2        0
2001::/32                          5     5        0
fc00::/7                           3    13        0
::/96                              1     3        0
fec0::/10                          1    11        2
3ffe::/16                          1    12        0
```

これに対し、NetBSDでは `ipv6addrctl show` では特に接続優先ポリシーは設定されていないため、この部分の差異が振る舞いの違いとして現れるということですね。

```sh
$ ip6addrctl show
no source-address-selection policy is installed
```

## QEMU側で設定する方法

もう一つの対応方法として、QEMU側でIPv4を優先する(というか、IPv6を無効化しておく)という方法もあります。
QEMUの起動オプションで `-net user,ipv6=off` とすることで、QEMU側でIPv6を無効化することができます。
(でもできれば `ip6addrctl` で接続ポリシーを設定する方法を採りたいところではありますね…)

```sh
$ qemu-system-x86_64 \
      -m 256M \
      -boot order=c \
      -net nic \
      -net user,hostfwd=tcp::2222-:22 \
      -hdd disk/nbsd.img
　│
　│-net userオプションを以下のように変更する。
　↓
$ qemu-system-x86_64 \
      -m 256M \
      -boot order=c \
      -net nic \
      -net user,ipv6=off,hostfwd=tcp::2222-:22 \
      -hdd disk/nbsd.img
```

## まとめ

NetBSDで[ip6addrctl(8)](https://man.netbsd.org/NetBSD-8.0/ip6addrctl.8)によりIPv4/IPv6の優先設定を変更する方法を紹介しました。Linuxの `/etc/gai.conf` と設定方法が異なることもあり、備忘録も兼ねて記事を作成してみました。