sysctl とカーネル起動オプションによる IPv6 無効化方法の違い

# はじめに

Linux 上で IPv6 を無効化する方法として、よく以下の2つが紹介されています。

1. sysctl による ```net.ipv6.conf.*.disable_ipv6 = 1```の設定
1. カーネル起動オプションに ```ipv6.disable=1``` を設定

昔は 1. の紹介をよく見ていたので私もこちらをよく設定していたのですが、最近になって 1. と 2. の挙動の違いについて知ることになったので共有したいと思います。

ちなみに、

- Network Managerで ipv6.method は ignore に設定されている前提です。
- Network Managerで ipv4.dns は 192.168.1.1 に設定されており、ローカルで dnsmasq などは動かしていないです。
- 今回の挙動は環境依存の部分が大きいかもしれないので、どの環境でも再現するとは限らないです。

# sysctl による設定時の挙動

まずは、以下のように sysctl を設定します。sysctl -p でロードすればカーネル再起動は不要ですが、一応再起動はしておきます。

```/etc/sysctl.d/99-ipv6disable.conf
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
```

再起動後、ip addr の結果から ```inet6``` の記述が消えていることを確認します。

```
[root@centos01 ~]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:a3:7d:51 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.201/24 brd 192.168.1.255 scope global noprefixroute enp0s3
       valid_lft forever preferred_lft forever
```

この状態で、curl によるファイルダウンロードを行います。その際にシステムコールをトレースするために strace コマンドを挟みます。

```
[root@centos01 ~]# strace -o strace.dat curl http://ftp.jaist.ac.jp > /dev/null
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  4514  100  4514    0     0  16521      0 --:--:-- --:--:-- --:--:-- 16534
[root@centos01 ~]#
```

strace によるシステムコールトレースの結果を以下に抜粋します。```socket``` の第１引数が ```AF_INET6``` になっており、UDP ソケットのドメインに IPv6 インターネット・プロトコルが指定されていることが分かります。ソケットのディスクリプターも正常に返ってきているようです。

``` strace.dat（抜粋） 
open("/root/.curlrc", O_RDONLY)         = -1 ENOENT (そのようなファイルやディレクトリはありません)
ioctl(1, TCGETS, 0x7ffcef491ea0)        = -1 ENOTTY (デバイスに対する不適切なioctlです)
brk(NULL)                               = 0x167a000
brk(0x169c000)                          = 0x169c000
socket(AF_INET6, SOCK_DGRAM, IPPROTO_IP) = 3
close(3)                                = 0
mmap(NULL, 8392704, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7fa54cc200
00
mprotect(0x7fa54cc20000, 4096, PROT_NONE) = 0
```

とはいえ、tcpdump で UDP の通信を確認すると別に IPv6 で通信しているわけではなさそうです。というか、AAAA レコード（IPv6 アドレス）を問い合わせているんですね。

```tcpdumpの結果
12:25:30.426393 IP 192.168.1.201.41974 > 192.168.1.1.domain: 55084+ A? ftp.jaist.ac.jp. (33)
12:25:30.426404 IP 192.168.1.201.41974 > 192.168.1.1.domain: 55609+ AAAA? ftp.jaist.ac.jp. (33)
12:25:30.427310 IP 192.168.1.1.domain > 192.168.1.201.41974: 55084 1/0/0 A 150.65.7.130 (49)
12:25:30.427366 IP 192.168.1.1.domain > 192.168.1.201.41974: 55609 1/0/0 AAAA 2001:df0:2ed:feed::feed (61)
```

# カーネル起動オプションによる設定時の挙動

次にカーネル起動オプションで IPv6 無効化を設定します。今回は sysctl による設定を外さずに設定します。

まず、GRUB の設定ファイルを修正します。```GRUB_CMDLINE_LINUX``` に```ipv6.disable=1```というオプションを追加します。

```/etc/default/grub
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="ipv6.disable=1 crashkernel=auto rhgb quiet"
GRUB_DISABLE_RECOVERY="true"
```

その後、grub.cfg を再生成し、カーネルを再起動します。

```
[root@centos01 ~]# grub2-mkconfig -o /boot/grub2/grub.cfg
```

この状態で、sysctl の時と同様に curl コマンドを実行し、そのシステムコールを strace で追います。すると、socket システムコールの呼び出しは ```EAFNOSUPPORT``` でエラーになりました。

```
brk(NULL)                               = 0xba9000
brk(0xbcb000)                           = 0xbcb000
socket(AF_INET6, SOCK_DGRAM, IPPROTO_IP) = -1 EAFNOSUPPORT (アドレスファミリはプロトコルによってサポ
ートされていません)
mmap(NULL, 8392704, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7ff4e189d0
00
mprotect(0x7ff4e189d000, 4096, PROT_NONE) = 0
```

ちなみに、その時の tcpdump による通信は以下になります。sysctl 設定時と異なり、AAAA レコードは問い合わせていないですね。

```
12:57:14.070559 IP 192.168.1.201.43179 > 192.168.1.1.domain: 30876+ A? ftp.jaist.ac.jp. (33)
12:57:14.071303 IP 192.168.1.1.domain > 192.168.1.201.43179: 30876 1/0/0 A 150.65.7.130 (49)
```

# おわりに

今回、この違いに気付いたのは、sysctl の設定のみの際に名前解決が5秒程度待たされるという事象（poll システムコールで待たされる）を調査したのがきっかけでした。この問題は環境によって発生有無が異なっていてまだ原因がよく分かっていないのですが、カーネル起動オプションを設定すると回避できることは分かっています。

調査してみたのはよいものの、

- sysctl の設定時にも問題なく名前解決できるのはなぜか？
    - AF_INET6 指定で作成したソケットディスクリプターに対して IPv4 アドレス構造体を渡してデータ送信しても OK なのか？
- カーネル起動オプションの設定時はエラーが発生しているのに名前解決できているのはなぜか？
    - strace の結果からは、エラーになっている箇所以外で SOCK_DGRAM パラメータを渡してソケット作成しているところが見当たらない。

など、結局分からないことは多くあり、また wget だと挙動が違ったりもします。

ひとまず、カーネル起動オプションによる設定（実際には両方）を採用したいと思いますが、IPv6 含めてネットワークの知識が足りてないなぁ。