IPv4 over IPv6接続その2

# 概要

『[IPv4 over IPv6接続その1](https://qiita.com/bamboo23/items/23ae59e0a16df5132695)』に引続き、DS-Lite対応ルーター無しでIPv4 over IPv6接続を実現します。
今回は、DHCPv6による自動設定について説明します。

# 使用するソフトウェア

以下は2020年7月時点の最新のソースコードです。

- [dhcpcdプロジェクトサイト](https://roy.marples.name/projects/dhcpcd)
  - dhcpcd-9.1.4

# ビルド

気を付ける点は、デーモンの起動ユーザーの設定です。
デフォルトでは`dhcpd`という名前のユーザーが使用されます。
予め`dhcpd`ユーザーを作成しておくか、以下のように`--privsepuser`オプションで存在するユーザーを指定します。

```
$ ./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --privsepuser=daemon
$ make
$ sudo make install
```

インストールされるファイルは以下の通りです。

```
/etc/dhcpcd.conf
/usr/lib/dhcpcd/dev/udev.so
/usr/libexec/dhcpcd-hooks/01-test
/usr/libexec/dhcpcd-hooks/20-resolv.conf
/usr/libexec/dhcpcd-hooks/30-hostname
/usr/libexec/dhcpcd-hooks/50-ntp.conf
/usr/libexec/dhcpcd-run-hooks
/usr/sbin/dhcpcd
/usr/share/dhcpcd/hooks/10-wpa_supplicant
/usr/share/dhcpcd/hooks/15-timezone
/usr/share/dhcpcd/hooks/29-lookup-hostname
/usr/share/man/man5/dhcpcd.conf.5
/usr/share/man/man8/dhcpcd-run-hooks.8
/usr/share/man/man8/dhcpcd.8
```

# dhcpcd.conf設定

dhcpcd.confは[dhcpcdプロジェクトのgithub](https://github.com/rsmarples/dhcpcd)の説明に沿っていますが、少し変更しています。

```
noarp
nodev
option domain_name_servers, domain_name, domain_search, host_name
option ntp_servers
option dhcp6_name_servers, dhcp6_domain_search
option classless_static_routes
option interface_mtu
require dhcp_server_identifier
slaac private
```

- `domain_name_servers`等はIPv4用ですので必須ではありません。
- `dhcp6_name_servers`がIPv6用のDNSサーバーアドレスを取得する指定です。
- `slaac private`の設定によって、MACアドレスベースではないIPv6アドレスが設定されます。

> ## DNSサーバーアドレス
>
> IPトンネリング設定でgw.transix.jpのIPv6アドレスを設定するため、IPv6のDNSサーバーアドレスが必要です。しかし、NTTから提供されているルーターによっては、必要なDNSサーバアドレスが得られない場合があります。
> 実施した環境で使用しているルーターはRV-S340NEです。ルーター自身のWAN側のインターフェースはNTTのDNSサーバーアドレスを受け取っていますが、ルーターがLAN側にDHCPv6で払い出すDNSサーバーアドレスはルーター自身のアドレスになっていました。この場合、ルーターがDNS Proxyとして振舞うべきと思いますが、当該ルーターはそのような動作が行われませんでした。
> そのため本記事では、ルーターの設定画面から確認したDNSサーバーアドレスを/etc/resolv.confに静的設定することとします。
> なお、GoolgeのIPv6 DNSサーバーではgw.transix.jpのアドレスを引くことができません

# フックスクリプト

次に、IPアドレス設定のタイミングでIPトンネリングを設定するために、フックスクリプトを使用します。
dhdpcd標準で用意されているフックスクリプトは使用しません。

以下のようなスクリプトを用意します。

```
#!/bin/bash

umask 022
export LANG=C
export PATH=/sbin:/bin

TUNDEV=dslite
REMOTE_NAME=gw.transix.jp
ENABLE_DEFAULT_GATEWAY=true

TAG=${TUNDEV}[$$]

debug() { logger -p daemon.debug   -t "${TAG}" "$*"; }
info()  { logger -p daemon.info    -t "${TAG}" "$*"; }
warn()  { logger -p daemon.warning -t "${TAG}" "$*"; }
error() { logger -p daemon.err     -t "${TAG}" "$*"; }

run()
{
        debug "$*"
        { "$@" 3>&1 >&2 2>&3 | error --; } 2>&1
        return "${PIPESTATUS[0]}"
}

lookup_remote()
{
        while true
        do
                for interval in 3 3 0
                do
                        remote=$(run ping6 -c1 "${REMOTE_NAME}")
                        remote=${remote##*from }
                        remote=${remote%%: *}

                        [ -n "${remote}" ] && return 0

                        sleep "${interval}"
                done

                [ -d "/sys/class/net/${TUNDEV}" ] && return 1

                sleep 3
        done
}

get_current_tundev()
{
        local info=$(run ip link show "${TUNDEV}")

        curr_addr=${info##*tunnel6 }
        curr_addr=${curr_addr%% *}
        curr_remote=${info##*peer }
        curr_remote=${curr_remote%% *}
}

func_ROUTERADVERT()
{
        if_down=${if_down:-false}
        if_down=${if_down,,}

        if "${if_down}"
        then
                [ -d "/sys/class/net/${TUNDEV}" ] || return 0

                if run ip route show default dev "${TUNDEV}"
                then
                        run ip route del default dev "${TUNDEV}"
                fi

                run ip tunnel del "${TUNDEV}"
                info "Unconfiguration done"
                return 0
        fi

        if [ -z "${nd1_addr1}" ]
        then
                error "No local address information"
                return 1
        fi

        local addr=${nd1_addr1%/*}
        local action=add

        local remote=
        lookup_remote "${REMOTE_NAME}"

        if [ -z "${remote}" ]
        then
                error "Unable to lookup for remote(${REMOTE_NAME}) address"
                return 1
        fi

        if [ -d "/sys/class/net/${TUNDEV}" ]
        then
                local curr_addr=
                local curr_remote=

                get_current_tundev

                if [ "${curr_addr}" == "${addr}" ]
                then
                        debug "Already configured"
                        return 0
                else
                        info "Reconfiguration..."
                        action=change
                fi
        else
                info "Configuration..."
        fi

        local result=0

        run ip tunnel "${action}" "${TUNDEV}" \
                mode ipip6 remote "${remote}" local "${addr}" \
                encaplimit none dev "${interface}"
        result=$((result|$?))

        run ip link set "${TUNDEV}" up
        result=$((result|$?))

        if "${ENABLE_DEFAULT_GATEWAY}"
        then
                run ip route replace default dev "${TUNDEV}"
                result=$((result|$?))
        fi

        case "${result}" in
        0)
                case "${action}" in
                add)    info   "Configuration successfully";;
                change) info "Reconfiguration successfully";;
                esac
                ;;
        *)
                run ip route del default dev "${TUNDEV}"
                run ip tunnel del "${TUNDEV}"
                error "Configuration error"
                ;;
        esac

        return "${result}"
}

case "${reason}" in
ROUTERADVERT)
        "func_${reason}"
        ;;
esac
```

- ROUTERADVERTを受け取った時だけ動作します。
- ローカルのIPv6アドレス情報はnd?_addr?に格納されますが、実施した環境ではnd1_addr1だけ定義されていたため、これだけを使用しています。
- IPv6アドレスのインターフェースへの設定はdhcpcdが実施してくれますので、IPトンネルの設定だけを行います。
- `lookup_remote()`でgw.transix.jpのIPv6アドレスを調べています。何らかの事情で引けなかった場合を考慮してリトライしています。
- 初回はトンネルデバイスdsliteが存在しないため、ip tunnel addで新規設定します。
- 何らかの変更が発生して改めてROUTERADVERTを受け取った場合は、IPトンネル設定のremoteとlocalの変化を確認します。変化があった場合、`ip tunnel change`で再設定します。
- トンネルデバイスのリンクアップ（`ip link set dslite up`）とデフォルトルート設定（`ip route replace default dev dslite`）は、初回と変更時で同じ処理で問題ありません。

半年以上、問題なく動作しています。

:::note warn
2022/4/12追記
今までIPv4とIPv6の性能比較をしていなかったのですが、Encapsulation limitオプション（encaplimit）がとても重要なことがわかりました。
Linuxではデフォルトは4になっていますが、デフォルトのままだと、IPv4はIPv6の半分にも満たない性能になります。
制限なしの「none」にすることで、ほぼ性能劣化は発生しなくなりました。
:::