pslaboが試したことの記録

はてなダイヤリーからはてなブログに引っ越してきました

この日記は現在実行中の減量記録を含む個人的なメモとして始めましたが、最近はコンピュータやガジェット、ハック、セキュリティネタのほうがメインになっております。

はてなダイヤリー時代はカテゴリ分けが適当だったのですが、これはそのうち直します。


iOS向けIPv6なNAT64/DNS64の試験ネットワークをRaspberry Pi 3で作る

はじめに

Raspberry Pi 3 は WiFi と LAN が標準搭載なので、WiFi で試験ネットワークを作るのにはうってつけです。

さて、iOS向けのアプリ審査ではIPv6対応が必須となりましたが、その試験ネットワークを作るための標準的な方法は「MacOSXでNAT64が有効なインターネット共有を作る」という方法です。

しかし Mac1台をNAT64のためにあてがうのはどうにも非効率な気がします。また、そもそも今時の Macbook などは有線LANインタフェースを持っていないので、このためにUSB or Thunderbold LANアダプタを用意せねばなりません。あるいは、PC or MacIPv6 側にぶら下げて挙動を比較する、などというケースもあるかもしれません。また、自分の手元でMac OSXのNAT64を試したのちにインターネット共有をオフにしても
WiFi APへの接続が行えず、やむをえずMac OSXを再起動しなければならない状況も起きたりしました。

このようにいろいろ考えたり、実際に発生する状況から考えると、IPv6 試験ネットワークを作るのは Raspberry Pi3 を使うのが最も効率的な気がしてきました。

さて、こういうネタにはたぶん先人がいるはずなのですが、調べてみると、やはりこういう事例がが出てきます。これらはRaspberry Pi3が出る前に書かれているので、当然ながらRaspberry Pi2をベースにしています。

dsas.blog.klab.org
alpha.mixi.co.jp

しかし Raspberry Pi3 でこれを行っている事例は見当たらないようです。不思議に思って自分で試してみると、どうやら上記の方法をそのまま Raspberry Pi3 で行っても環境構築できないことがわかりました。その一つの要因は RasPi3 で動く Raspbian のバージョンが上記記事よりも進んでいることにあります。

そこで試行錯誤を繰り返してみた結果、最終的に Raspberry Pi3 でNAT64/DNS64 な環境を作るための手順がおおむね見えてきました。その内容をここにメモとしてまとめておきます。

なお、以下の手順の中で案内する設定ファイルの内容は、大筋において Klab様や mixi 様の事例の内容を踏襲させていただきました。その上で以下に該当する箇所を変更しています。

  • そのままでは RasPi3 では動かない内容
  • 設定を記述する箇所を変更したほうが、Linux の設定として適切と考えられる箇所

そもそも Raspberry Pi 3 で DNS64/NAT64 を実装するための選択肢にはどのようなものがあるか?

ざっと調べると、今回の方法を含めて2つの方法があるようです。

Linux kernel のカーネルモジュールでやる方法

先に挙げた事例や今回の事例では Jool というカーネルモジュールで NAT64 を行っています。他にも幾つかのカーネルモジュールがあるっぽいです。

OpenWRT でやる方法

OpenWRT に TAYGA + TOTD で NAT64/DNS64 を行う方法があるようです。
tech.sybreon.com

そして、TAYGA は stageful な NAT64だそうです。ステートフルな NAT64 は ISP 側の設備との組み合わせが必要と私は認識しております。今回の要件には合わないように思います。

また、OpenWRTでの事例はほとんどないので、まずはKLab様やmixi様が実装したケースに倣った環境を作って挙動を正しく理解すべきと考えました。ですので、今回はこれはパスします。本当は OpenWRT で環境構築できるほうがいろいろとありがたいのですけどねー。基本的な設定変更は Web UI から行えるし。

なお、OpenWRTというと、市販のルータのファームウェアをごにょごにょする方法がよく知られていますけれど、実はRasPi向けのパッケージングもあります。
wiki.openwrt.org
RasPiなどのシングルボードコンピュータにOpenWRTをインストールすれば、日本国内で技適のことを考えずにOpenWRTでWiFiをを利用できます。OpenWRTをインストールするハードウェアとしてWiFiとLANが両方利用できるRasPi3は試験環境の構築に妥当な選択肢の一つと言えるかもしれません。

準備するもの

Raspberry Pi を以前から使っている方にとっては既知の話ですが、初めて使う方を対象に必要な物品をリストアップしておきます。

最低限必要なもの

  • Raspberry Pi 3本体 + ケース
  • microSDHCカード (Class10、容量 4GB 以上)
  • USB 電源アダプタ(2.5A出力対応品)

とりあえず RasPi だけは Amazon へのリンクを貼っておきます。

電源アダプタはNAT64/DNS64だけに使うなら1Aでも大丈夫だと思いますが、RasPi3を汎用的に使う場合は2.5A対応品を用意したほうがよさげです。

初期設定時だけ必要なもの

  • HDMI接続が可能なディスプレイ
  • USB接続キーボード
  • microSDHC の reader / writer

作業の流れ

  • microSDHC に OS (Raspbian jessie) をインストールする。
  • Raspbian jessie の初期設定を行う。
  • 環境構築に必要なパッケージをまとめてインストールしておく。
  • NAT64 に必要なカーネルモジュールをコンパイルする環境を作るために kernel のリビルドを行う
  • WiFi で NAT64/DNS64 を実装するために必要なパッケージをインストールする。

ここに挙げた手順の中で、kernel のリビルドが最も時間がかかる作業です。先に挙げた事例ではコンパイル済みのカーネルヘッダを入手する、なんて話もありますが、現時点で入手可能な Raspbian jessie の最新版に正しく対応したカーネルヘッダはアップロードされていませんでした。インストールを試みると gcc のバージョンが違う、などのエラーがでてしまいます。

したがって自前でカーネルのリビルドを行うことは必須です。Raspberry pi 単体だと随分な時間がかかりますから、それなりに覚悟しておいたほうがよいです。遅い理由の一つはストレージがmicroSDHCだということです。カーネルのリビルドはストレージへの読み書きが多く発生します。クロスコンパイル環境を用意するか、またはカーネルのリビルドはHDDを接続して行うのがよさげです。(フラッシュストレージの書き込み回数の問題もあります)

microSDHC に OS (Raspbian jessie) をインストールする。

「Raspbian jessie lite」を以下のページからダウンロードします。
https://www.raspberrypi.org/downloads/raspbian/

ここからは jessie と jessie lite の2つがダウンロード可能ですが、違いは GUI の有無です。今回の作業では GUI は不要ですから jessie lite で環境構築します。

ダウンロードした zip ファイルを展開して得られる img を dd で microSDHC に書き込めばインストールは完了です。

Mac な方は以下の過去記事に書いたスクリプトを使うとmicroSDHCへの書き込みがとてもラクになります。
pslabo.hatenablog.com

Windows な方は下記のページから確認したり、あるいは Windows 版の dd を入手してご利用くださいまし。
https://www.raspberrypi.org/documentation/installation/installing-images/README.md

microSDHC の準備ができたら Raspberry Pi に装着して起動します。ただし Raspberry Pi には電源スイッチはありませんので、microUSB ケーブルを差すだけで起動します。HDMI ケーブルや USB キーボードは先に接続しておいたほうがよいでしょう。

起動したら、お約束どおり、ID=pi, password=raspberry でログインします。

環境構築に必要なパッケージをまとめてインストールしておく。

LANケーブルを RasPi に接続していれば、Raspbian が起動した時点で RasPi はインターネット側と通信可能なはずです。この時点で必要なパッケージのインストールはすませておきましょう。

まずは sources.list に設定を1行追記します。これは unbound を jessie-backports からインストールするための設定追加です。通常のリポジトリからインストールできるバージョンは 1.4 なのですが、DNS64でIPv6のホストに対してもNAT64されたIPアドレスを通知するには1.5が必要だそうです。1.5はjessie-backportsで配布されているので、これをありがたく使わせていただくことにします。

sources.list を書き換えたら sudo apt-get update しておきます。(下記の手順には書いてます)

$ sudo vi /etc/apt/sources.list 
deb http://ftp.jp.debian.org/debian/ jessie-backports main 
$ sudo apt-get update

そして以下のように実行すれば、環境構築に必要なソフトウェア一式がリポジトリからインストールされます。

sudo apt-get install hostapd radvd isc-dhcp-server bc pkg-config libnl-3-dev iftop 
sudo apt-get -t jessie-backports install unbound

上記以外で必要となるのは下記2点です。

カーネルソースは GitHub から以下のように git clone しておきます。

git clone --depth=1 https://github.com/raspberrypi/linux

Jool はここらへんから最新版を落としておきましょう。
https://nicmx.github.io/Jool/en/download.html

なお、最も時間を要するのは kernel のビルドです。これから先の手順については kernel のリビルドを行いつつ、並行で作業するほうがよいでしょう。

kernel をリビルドする。

カーネルのリビルドというと若干引いてしまう方もいるかもしれませんが、Linux kernel 2.0 の時代から今に至るまで基本的な手順は変わっていません。本質的には make menuconfig して make zImage modules して make modules_install するだけです。


こういう基本を押さえた上で、実際の RasPi 向けのビルドは以下の手順で行います。

cd linux
KERNEL=kernel7
make bcm2709_defconfig
make -j4 zImage modules dtbs
sudo make modules_install
sudo cp arch/arm/boot/dts/*.dtb /boot/
sudo cp arch/arm/boot/dts/overlays/*.dtb* /boot/overlays/
sudo cp arch/arm/boot/dts/overlays/README /boot/overlays/
sudo scripts/mkknlimg arch/arm/boot/zImage /boot/$KERNEL.img

この手順は以下のページに出ている手順です。
Kernel building - Raspberry Pi Documentation


なお、リビルドしたkernelを使うにはOSを再起動する必要があります。ただしここから先の作業で新しいkernelが必要な作業はJool をコンパイルするときです。そこまでは再起動せずに作業を進めることができます。

NICを設定する。

今回の作業では、NICを以下のように設定します。

  • eth0 は DHCP でのIPv4アドレス割り当てを受ける(dhcp client)
  • wlan0 は IPv6 固定アドレスを割り当てる。

これにより、LAN側はDHCPが有効なネットワークならば、どこに持ち込んでも利用できます。


さて、DebianUbuntu では /etc/network/interfaces にて設定を行うのが基本なのですが、jessie では /etc/dhcpcd.conf に対しても設定を記述するほうが確実に動作するようです。今回の環境構築で最もハマった点はこれです。

まず、/etc/network/interfaces には以下のように設定します。

iface eth0 inet manual
        offload-tso off
        offload-ufo off
        offload-gso off
        offload-gro off
        offload-lro off

allow-hotplug wlan0
iface wlan0 inet6 static
        address 2001:2:0:aab1::1
        netmask 64
        offload-tso off
        offload-ufo off
        offload-gso off
        offload-gro off
        offload-lro off

/etc/dhcpcd.conf では、RasPiのNICへのDHCP割り当てを制限するために下記のように設定します。これにより、eth0 の IP アドレスは IPv4 だけが動的に割り当てられます。wlan0 は IPv4/IPv4 ともに動的割り当ては行われません。

interface eth0
        noipv6

interface wlan0
        noipv4
        noipv6

つぎにIPパケットの転送に関するカーネルパラメータの設定を行います。カーネルパラメータは /etc/sysctl.conf で設定します。
今回はIPv4IPv6の転送をすべて有効にします。さらに念のために eth0 の IPv6 は完全に殺しておきます。

net.ipv4.conf.all.forwarding=1
net.ipv6.conf.all.forwarding=1 
net.ipv6.conf.eth0.disable_ipv6=1

なお、上記のパケットの転送設定が行われていな状態では、のちに設定する radvd が正常に起動せずにハマります。

WiFi AP の設定を行う。

RasPiのwlan0をWiFi APとするための設定は以下2つのファイルで行います。

まず、/etc/default/hostapd は以下のように設定します。

# Defaults for hostapd initscript
#
# See /usr/share/doc/hostapd/README.Debian for information about alternative
# methods of managing hostapd.
#
# Uncomment and set DAEMON_CONF to the absolute path of a hostapd configuration
# file and hostapd will be started during system boot. An example configuration
# file can be found at /usr/share/doc/hostapd/examples/hostapd.conf.gz
#
DAEMON_CONF="/etc/hostapd/hostapd.conf"

# Additional daemon options to be appended to hostapd command:-
# 	-d   show more debug messages (-dd for even more)
# 	-K   include key data in debug messages
# 	-t   include timestamps in some debug messages
#
# Note that -B (daemon mode) and -P (pidfile) options are automatically
# configured by the init.d script and must not be added to DAEMON_OPTS.
#
#DAEMON_OPTS=""

そして /etc/hostapd/hostapd.conf では以下のように設定します。このときに channel, ssid, wpa_passphrase は必ず変更してください。

# This is the name of the WiFi interface we configured above
interface=wlan0

# Use the nl80211 driver with the brcmfmac driver
driver=nl80211

# This is the name of the network
ssid=NAT64DNS64

# Use the 2.4GHz band
hw_mode=g

# Use channel 6
channel=6

## Enable 802.11n
#ieee80211n=1

## Enable WMM
#wmm_enabled=1

# Enable 40MHz channels with 20ns guard interval
ht_capab=[HT40][SHORT-GI-20][DSSS_CCK-40]

# Accept all MAC addresses
macaddr_acl=0

# Use WPA authentication
auth_algs=1

# Require clients to know the network name
ignore_broadcast_ssid=0

# Use WPA2
wpa=2

# Use a pre-shared key
wpa_key_mgmt=WPA-PSK

# The network passphrase
wpa_passphrase=[WPA-PSKのパスフレーズ]

# Use AES, instead of TKIP
rsn_pairwise=CCMP

# set country_code to JP
country_code=JP

DHCPv6関連の設定を行う。

IPv6 向けの DHCP は RA (Router Advertisement) と isc-dhcp-server の組み合わせで実現します。基本的には RA だけで良いのですが、これだと DNS サーバの情報が配布できないので、isc-dhcp-server と組み合わせます。

/etc/radvd.conf

interface wlan0 {
    AdvSendAdvert on;
    MinRtrAdvInterval 3;
    MaxRtrAdvInterval 10;
    AdvOtherConfigFlag on;
    prefix 2001:2:0:aab1::/64 {
        AdvOnLink on;
        AdvAutonomous on;
        AdvRouterAddr on;
    };
};

/etc/default/isc-dhcp-server

# Defaults for isc-dhcp-server initscript
# sourced by /etc/init.d/isc-dhcp-server
# installed at /etc/default/isc-dhcp-server by the maintainer scripts

#
# This is a POSIX shell fragment
#

# Path to dhcpd's config file (default: /etc/dhcp/dhcpd.conf).
DHCPD_CONF=/etc/dhcp/dhcpd6.conf

# Path to dhcpd's PID file (default: /var/run/dhcpd.pid).
DHCPD_PID=/var/run/dhcpd6.pid

# Additional options to start dhcpd with.
#	Don't use options -cf or -pf here; use DHCPD_CONF/ DHCPD_PID instead
OPTIONS="-6"

# On what interfaces should the DHCP server (dhcpd) serve DHCP requests?
#	Separate multiple interfaces with spaces, e.g. "eth0 eth1".
INTERFACES="wlan0"

/etc/dhcp/dhcpd6.conf

default-lease-time 600;
max-lease-time 7200; 
#log-facility local7;

subnet6 2001:2:0:aab1::/64 {
        option dhcp6.name-servers 2001:2:0:aab1::1;
}


これらの設定ファイルの設定調整が終わったら、dhcp6.leases の空ファイルを作っておきます。これを行わないと isc-dhcp-server が起動しません。

sudo touch /var/lib/dhcp/dhcpd6.leases

DNS64 の設定を行う。

今回の試験ネットワークでは、すべてのホスト名に対する AAAA の問い合わせに対して NAT64 のIPアドレスを渡す必要があります。bind で試してみると IPv6 に対して NAT64 の AAAA を渡すことができなかったので、先例に倣って unbound で設定します。


/etc/unbound/unbound.conf

# Unbound configuration file for Debian.
#
# See the unbound.conf(5) man page.
#
# See /usr/share/doc/unbound/examples/unbound.conf for a commented
# reference config file.
#
# The following line includes additional configuration files from the
# /etc/unbound/unbound.conf.d directory.
#include: "/etc/unbound/unbound.conf.d/*.conf"

server:
    verbosity: 1
    pidfile: "/var/run/unbound.pid"
    module-config: "dns64 iterator"
    dns64-prefix: 64:ff9b::/96
    dns64-synthall: yes
    interface: ::0
    access-control: ::0/0 allow

forward-zone:
        name: "."
        forward-addr: 8.8.8.8

再起動して WiFi AP への接続と DHCPv6 の動作を確認する。

ここまでの作業が完了したらRasPi3を再起動して、RasPi3が提供するWiFi APへの接続およびDHCPv6の動作を確認します。

確認事項は下記4点です。

  • WiFi APに接続できること。

これはつながるかどうかだけ確認すればOKです。

  • RA + DHCPv6 で IPv6 アドレスが割り振られること。

これはWindows/Mac/Linuxを接続して、それぞれの環境の標準的な方法で確認してください。

  • IPv4なホストに対してDNS64での名前解決が正しく行えること。

たとえばこういうやつですね。普通に AAAA を問い合わせても応答がないホスト名に対して、unbound が AAAA を返すことを確かめます。一般的には dig でテストするのでしょうけど、今回のインストール構成では dig を含んでいません。代わりに host コマンドで試します。

pi@raspberrypi:/etc/unbound $ host -t AAAA www.yahoo.co.jp 8.8.8.8
Using domain server:
Name: 8.8.8.8
Address: 8.8.8.8#53
Aliases: 

www.yahoo.co.jp is an alias for www.g.yahoo.co.jp.


pi@raspberrypi:/etc/unbound $ host -t AAAA www.yahoo.co.jp localhost
Using domain server:
Name: localhost
Address: ::1#53
Aliases: 

www.yahoo.co.jp is an alias for www.g.yahoo.co.jp.
www.g.yahoo.co.jp has IPv6 address 64:ff9b::7697:e7e7
www.g.yahoo.co.jp has IPv6 address 64:ff9b::b616:27f2
www.g.yahoo.co.jp has IPv6 address 64:ff9b::b616:28f0
www.g.yahoo.co.jp has IPv6 address 64:ff9b::b74f:e7b6
  • IPv6なホストに対して本来のAAAAとは異なるアドレスがDNS64で返されること。

IPv6といえば、やはり kame ですよねー。だから kame.net の名前解決を比較します。

pi@raspberrypi:/etc/unbound $ host -t AAAA www.kame.net 8.8.8.8
Using domain server:
Name: 8.8.8.8
Address: 8.8.8.8#53
Aliases: 

www.kame.net is an alias for orange.kame.net.
orange.kame.net has IPv6 address 2001:200:dff:fff1:216:3eff:feb1:44d7


pi@raspberrypi:/etc/unbound $ host -t AAAA www.kame.net
www.kame.net is an alias for orange.kame.net.
orange.kame.net has IPv6 address 64:ff9b::cbb2:8dc2

このように www.kame.net の AAAA の応答結果が違っていることが確認できました。


これらが正常に動作していたら、あとはNAT64で仕上げです。

Jool で NAT64 を有効にする。

ソースパッケージを展開してコンパイル、インストールするだけです。

unzip Jool-3.4.4.zip
cd Jool-3.4.4/mod
make
sudo make modules_install
sudo depmod -a
sudo modprobe jool pool6=64:ff9b::/96

ここまでの作業でNAT64が有効になっているはずです。WiFi側のクライアントから普通にインターネット側に出られることを確認してください。

なお、今回の作業手順では iftop をインストールしていますので、以下のように実行してみるとNAT64/DNS64の動作が可視化できるかもです。フィルタでポート22を除外しているので ssh 接続時でもバッチリです。

sudo iftop -i eth0 -f "not port 22"
sudo iftop -i wlan0 -f "not port 22"

さて、NAT64/DNS64が動作することが確認できたら、NAT64を固定化するために以下の設定を行っておきます。

sudo vim /etc/modules

# /etc/modules: kernel modules to load at boot time.
#
# This file contains the names of kernel modules that should be loaded
# at boot time, one per line. Lines beginning with "#" are ignored.
jool

sudo vim /etc/modprobe.d/jool.conf

options jool pool6=64:ff9b::/96

最後に、もう一度再起動した上で、NAT64が有効に機能することを確認してください。

今後の課題?

NAT64/DNS64を使えば、クライアント側からは常にIPv6で通信が行えます。しかし、NAT64箱の外側はIPv4のままです。www.kame.net に接続しても kame が動いている状況を見ることはできません。

できれば、NAT64箱の外ともIPv6で通信できるほうがおもしろい。

これを実現するには、NAT64箱の外側にIPv6なアドレスを割り当てつつ、unbound で "dns64-synthall: yes" を無効化すればよさそうな気がします。

さて、NAT64箱の外側にIPv6 なアドレスを割り当てるには、どのような方法があるだろうか。ざっと思いつくのは3つの方法です。

まず、フレッツを使うのはもっともスタンダードな方法です。だけどこれは特定の場所に紐付きとなってしまうのでつまらない。

3G/LTEでやるのは場所に縛られずに使えるので悪くない方法です。月額費用が発生するけど仕事で使うならこれはやむなし。キャリアはIIJmioでいいんじゃないかな。唯一の懸念は、Linuxイーサネットに見える3G/LTEモデムって選択肢があまり多くなさげ、という点です。

ソフトイーサの実験サービスを使うのは、追加費用がかからないという点ではもっともすぐれているけど、これはバックボーンがWIDEプロジェクトなので商用利用は原則NGです。

ま、こんなことを考えつつ、IPv6のインターネットにも接続できるNAT64箱が作れればよいのですけどねー。