概要
『IPv4 over IPv6接続その1』に引続き、DS-Lite対応ルーター無しでIPv4 over IPv6接続を実現します。
今回は、DHCPv6による自動設定について説明します。
使用するソフトウェア
以下は2020年7月時点の最新のソースコードです。
-
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の説明に沿っていますが、少し変更しています。
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
)は、初回と変更時で同じ処理で問題ありません。
半年以上、問題なく動作しています。
2022/4/12追記
今までIPv4とIPv6の性能比較をしていなかったのですが、Encapsulation limitオプション(encaplimit)がとても重要なことがわかりました。
Linuxではデフォルトは4になっていますが、デフォルトのままだと、IPv4はIPv6の半分にも満たない性能になります。
制限なしの「none」にすることで、ほぼ性能劣化は発生しなくなりました。