2015年11月13日

Raspberry Pi 2 で NAT64 箱をつくってみた

はてなブックマークに登録

最近の VirtualBox の記事の人気っぷりに嫉妬している @pandax381 です。

OSX 10.11(El Capitan)がリリースされ、NAT64 の話題をチラホラ見かけるようになってきましたね。弊社内でも例に漏れず話題に上がってきまして、なぜかデスクの上にラズパイが大量に転がっていたので、そこから1台使って NAT64 箱を作ってみました。

COR0iyKUcAAIqRV-1

準備するもの

とりあえず Raspbian か 自前で Debian をインストールした Rapberry Pi 2 が1台あれば大丈夫です。ここに登場するラズパイは、ブートローダとカーネル(とカーネルモジュール)だけ Raspbian のものを使って、rootfs は debootstrap で wheezy を構築したものですが、純粋な Raspbian でも変わらないと思います。

NAT64とは?DNS64とは?

ぼくがヘタクソな説明しなくても、わかりやすい情報がたくさんあると思うのでそちらにお任せします。

http://qiita.com/shao1555/items/4433803419dfc72bf80b

DNS64 Server

NAT64 環境を構築するためには DNS64 が必ずセットになります。DNS64の説明は(ry。メジャーどころだと BIND と Unbound が DNS64 をサポートしています。ここでは、使い慣れている Unbound を使うことにします(どの宗派にも属してなければ、El Capitan も DNS64 のために Unbound を使っているので、とりえず Unbound 使っておいたらいいと思います)。

Unbound のインストール

Unbound の場合、1.5.0 から DNS64 をサポートしていますが、Debian だと sid 以外は 1.4 系のパッケージしかないので、最新版のソースコードをダウンロードしてビルドします。

$ wget https://www.unbound.net/downloads/unbound-1.5.6.tar.gz
$ tar zxvf unbound-1.5.6.tar.gz
$ cd unbound-1.5.6
$ ./configure --with-conf-file=/etc/unbound/unbound.conf
$ make
$ sudo make install

unbound.conf を以下のように記述します。

# /etc/unbound/unbound.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

付属の起動スクリプトをコピーして Unbound のデーモンを起動させます。

$ sudo cp contrib/unbound.init /etc/init.d/unbound 
$ sudo chmod +x /etc/init.d/unbound
$ sudo insserv unbound
$ sudo /etc/init.d/unbound start

AAAA を持たないホストに対する問い合わせ

まずは、通常のリゾルバ(8.8.8.8)経由で AAAA を持たないホスト(例えば www.klab.com)の AAAA を問い合わせてみます。当然ですが、AAAA を持たないホストの AAAA を問い合わせても、ANSWER SECTION が空の応答が返ってきます。

$ dig www.klab.com AAAA

; <<>> DiG 9.8.4-rpz2+rl005.12-P1 <<>> www.klab.com AAAA
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 1953
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0

;; QUESTION SECTION:
;www.klab.com.            IN    AAAA

;; AUTHORITY SECTION:
klab.com.        1799    IN    SOA    ns10.klab.org. postmaster.klab.org. 1446444958 16384 2048 1048576 2560

;; Query time: 128 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Thu Nov 12 15:45:40 2015
;; MSG SIZE  rcvd: 90

続いてローカルで動いている Unbound に対して、先ほどのホスト(www.klab.com)の AAAA を問い合わせてみます。こちらは Unbound の DNS64 機能によって生成された AAAA が得られます。

$ dig @localhost www.klab.com AAAA

; <<>> DiG 9.8.4-rpz2+rl005.12-P1 <<>> @localhost www.klab.com AAAA
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 57883
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;www.klab.com.            IN    AAAA

;; ANSWER SECTION:
www.klab.com.        3553    IN    AAAA    64:ff9b::85f2:574

;; Query time: 39 msec
;; SERVER: ::1#53(::1)
;; WHEN: Thu Nov 12 15:45:44 2015
;; MSG SIZE  rcvd: 58

なお、この AAAA は unbound.conf の「dns64-prefix」に設定したプレフィックス(64:ff9b::/96)と、問い合わせ対象ホストの A レコードを合成した結果になります。この例だと、64:ff9b::85f2:574 の下位32ビットを8ビット毎にドットで区切ると「133.242.5.116」となり、これは www.klab.com の A レコードと一致します。

AAAA を持っているホストに対する問い合わせ

もともと AAAA を持っているホストの場合にはどうなるのかも試してみます。通常のリゾルバ経由で AAAA も持っているホスト(例えば www.debian.org)の AAAA を問い合わせます。ごくごく普通に AAAA が得られます。

$ dig www.debian.org AAAA

; <<>> DiG 9.8.4-rpz2+rl005.12-P1 <<>> www.debian.org AAAA
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 4534
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;www.debian.org.            IN    AAAA

;; ANSWER SECTION:
www.debian.org.        299    IN    AAAA    2001:41c8:1000:21::21:4
www.debian.org.        299    IN    AAAA    2001:610:1908:b000::148:14

;; Query time: 322 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Thu Nov 12 16:40:22 2015
;; MSG SIZE  rcvd: 88

続いてローカルの Unbound 経由で問い合わせてみます。こちらも AAAA を得られましたが、さきほどの問い合わせ結果とは AAAA の内容が異なります。IPv6アドレスのプレフィクスからわかるように、これは DNS64 された結果です。

$ dig @localhost www.debian.org AAAA

; <<>> DiG 9.8.4-rpz2+rl005.12-P1 <<>> @localhost www.debian.org AAAA
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 46630
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;www.debian.org.            IN    AAAA

;; ANSWER SECTION:
www.debian.org.        223    IN    AAAA    64:ff9b::801f:3e
www.debian.org.        223    IN    AAAA    64:ff9b::599:e704
www.debian.org.        223    IN    AAAA    64:ff9b::8259:940e

;; Query time: 1 msec
;; SERVER: ::1#53(::1)
;; WHEN: Thu Nov 12 16:40:26 2015
;; MSG SIZE  rcvd: 116

本来、Unbound の DNS64 のデフォルト動作は、AAAA を持つホストへの問い合わせは DNS64 の対象にせず、オリジナルの AAAA をパススルーで応答します。IPv6 のアップリンクがある環境ならなにも問題ないのですが、アップリンクが IPv4 のみの場合、ここで得られた IPv6 グローバルアドレスに到達することができません。その場合、もともと AAAA を持っているホストであっても DNS64 の対象にして、NAT64 経由で IPv4 のアップリンクを通じて通信する必要があります。

幸い、Unbound にはそのための機能があります。上記にて AAAA を持つホストも DNS64 の対象になっているのは、unbound.conf に「dns64-synthall: yes」と設定してあるためです。dns64-synthall を yes に設定すると、問い合わせ対象のホストが AAAA を持っているかどうかに関わらず、すべての問い合わせが DNS64 の対象になります(Unbound 自身が AAAA の問い合わせを行わずに、A レコードしか問い合わせなくなります)。

OSX 10.11(El Capitan)の unbound.conf でもこの機能が有効になっていますので、動作を合わせるという意味でも有効にしておくのがいいと思います。

IPv6 ネットワークの構築

ラズパイに NIC を追加して IPv6 Only のネットワークを構築します。

ネットワークインタフェースの設定

NAT64 箱を作るためには NIC が最低でも2つ必要ですが、ラズパイには NIC が1つしかありません。USB Ethernet アダプタで物理的に NIC を増設してもいいと思いますが、ここでは VLAN NIC を使うことにします。物理 NIC を増設している場合には、eth0.64eth1 などに読みかえてください。

IPv6 ネットワークのために eth0.64 という VLAN NIC を追加します。この NIC で使う IPv6 プレフィックスは、グローバルアドレスでなくて構いません。ユニークローカルアドレス(ULA)を生成して使うのがいいと思いますが、ここでは El Capitan と同じように 2001:2:0:aab1::/64 を使っています。また、eth0 には IPv6 アドレスはいらないので無効にしています。

NICのオフロード機能を無効にしていますが、これは後述する NAT64 の設定で必要になるためです。

# /etc/network/interfaces

auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
    address 192.168.0.100
    netmask 255.255.255.0
    gateway 192.168.0.1
    offload-tso off
    offload-ufo off
    offload-gso off
    offload-gro off
    offload-lro off
    up sysctl -w net.ipv6.conf.$IFACE.disable_ipv6=1 >/dev/null 2>&1

auto eth0.64
iface eth0.64 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

interfaces ファイルが書けたら eth0.64 を起動させます。

$ sudo ifup eth0.64

Router Advertisement

IPv6 ネットワークに接続したクライアントに IPv6 アドレスの自動生成をさせるために RA(Router Advertisement)を投げてあげます。RA を投げるために radvd をインストールします。

$ sudo apt-get install radvd

RA では DNS サーバを通知できないので(できるけど対応してるクライアントが少ない)、AdvOtherConfigFlag を ON にして DHCPv6 サーバへと追加情報を問い合わせるよう通知します。

# /etc/radvd.conf

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

radvd.conf が書けたら radvd を再起動させます。

$ /etc/init.d/radvd restart

DHCPv6

IPv6 クライアントに対して DNS サーバを通知するために DHCPv6 サーバをインストールします。

$ sudo apt-get install isc-dhcp-server

/etc/default/isc-dhcp-server で DHCPv6 サーバの起動オプションなどを指定します。

# /etc/default/isc-dhcp-server

DHCPD_CONF=/etc/dhcp/dhcpd6.conf
DHCPD_PID=/var/run/dhcpd6.pid
OPTIONS="-6"
INTERFACES="eth0.64"

DHCPv6 用の設定ファイル /etc/dhcp/dhcpd6.conf を新しく作成します。

# /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;
}

DNS(DNS64)サーバは自分自身なので、option dhcp6.name-servers には自分自身の IPv6 アドレスを指定します。

接続テスト

IPv6 ネットワークにクライアントを接続して、RA による IPv6 アドレスの自動生成と、DHCPv6 による DNS サーバの設定がなされていることを確認します。

clinet:~$ ifconfig en0
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
    ether 5c:f9:38:xx:xx:xx 
    inet6 fe80::xxxx:xxxx:xxxx:ad7e%en0 prefixlen 64 scopeid 0x4 
    inet6 2001:2::aab1:xxxx:xxxx:xxxx:ad7e prefixlen 64 autoconf 
    inet6 2001:2::aab1:xxxx:xxxx:xxxx:ae10 prefixlen 64 autoconf temporary 
    nd6 options=1<PERFORMNUD>
    media: autoselect
    status: active
clinet:~$ cat /etc/resolv.conf 
#
# Mac OS X Notice
#
# This file is not used by the host name and address resolution
# or the DNS query routing mechanisms used by most processes on
# this Mac OS X system.
#
# This file is automatically generated.
#
nameserver 2001:2:0:aab1::1

正常に動いていれば、この状態で IPv6 クライアントから DNS64 の問い合わせができるようになっています。

clinet:~$ dig +short www.klab.com AAAA
64:ff9b::85f2:574

NAT64

いよいよ本題の NAT64 です。OpenBSD なら 標準機能の PF が NAT64 に対応していますが、Linux の場合にはサードパーティのソフトウェアをインストールする必要あります。ざっと調べた限り Linux なら「Jool」を使うのが一番良さそうです。Jool は Netfilter と連携して NAT64 を実現するカーネルモジュールで、NIC Mexico(メキシコの国別インターネットレジストリ)が開発しています。他の選択肢として Ecdysis というプロダクトもありますが、こちらは 2014年から更新されておらず、対応している Kernel が 2.6 までのようです。

カーネルヘッダのインストール

Jool はカーネルモジュールなので、これをビルドすためには Linux のカーネルヘッダが必要です。通常であれば $ sudo apt-get install linux-headers-$(uname -r) といった感じでコマンド一発でインストールできるのですが、rpi-update で最新のカーネル(4.1.y)を入れている場合、それに対応するカーネルヘッダのパッケージがラズパイのリポジトリで提供されていません。https://github.com/raspberrypi/linux/ にラズパイ用カーネルのソースがあるのでそれを持って来るのが正しいと思いますが、ちょっと面倒なので、ここでは親切な人が公開してくれているオレオレパッケージを使うことにします。

http://www.niksula.hut.fi/~mhiienka/Rpi/linux-headers-rpi/ にラズパイ用のカーネルヘッダの deb パッケージが公開されているので、カーネルバージョンに対応するパッケージをダウンロードしてインストールします。

$ wget http://www.niksula.hut.fi/~mhiienka/Rpi/linux-headers-rpi/linux-headers-4.1.7-v7+_4.1.7-v7+-2_armhf.deb
$ sudo dpkg -i linux-headers-4.1.7-v7+_4.1.7-v7+-2_armhf.deb

Jool のインストール

カーネルヘッダがあれば Jool のインストールは簡単です。

$ wget https://nicmx.github.io/jool-doc/download/Jool-3.3.5.zip
$ unzip Jool-3.3.5.zip
$ cd Jool-3.3.5/mod
$ sudo make
$ sudo make modules_install

何故だかモジュールが /lib/modules/4.1.7-v7+/ ではなく /lib/modules/4.1.7-v7/ に作られてしまうため、ダサいですが移動させます..

$ sudo mkdir /lib/modules/4.1.7-v7+/extra
$ sudo mv /lib/modules/4.1.7-v7/extra/jool* /lib/modules/4.1.7-v7+/extra/
$ sudo depmod -a

Jool は、NAT64 のためにホストに設定されている IPv4 アドレス以外に、サービス用の IPv4 アドレスを必要とします(ホストの IPv4 アドレスをサービス用に使うこともできますが、そうするとホスト自身が外部と通信できなくなってしまいます)。そこで、あらかじめ eth0 に NAT64 のサービス用 IPv4 アドレスを追加してから jool のカーネルモジュールをロードします。また、IPv4 と IPv6 のパケットフォワードも有効にしておく必要があります。sysctl で設定するだけだと再起動すると消えてしまうので、良い子は /etc/sysctl.conf にも記述しておきましょう。

$ sudo ip addr add 192.168.0.101/24 dev eth0
$ sudo modprobe jool pool6=64:ff9b::/96 pool4=192.168.0.101
$ sudo sysctl -w net.ipv4.conf.all.forwarding=1
$ sudo sysctl -w net.ipv6.conf.all.forwarding=1

接続テスト

IPv6 ネットワークに接続したクライアントから、本来は AAAA を持たないホスト宛に ping6 を投げてみます。正常に稼働していれば HTTP や SSH などの TCP 通信も問題なく行えるはずです。

clinet:~$ ping6 www.klab.com
PING6(56=40+8+8 bytes) 2001:2::aab1:xxxx:xxxx:xxxx:ae10 --> 64:ff9b::85f2:574
16 bytes from 64:ff9b::85f2:574, icmp_seq=0 hlim=50 time=17.319 ms
16 bytes from 64:ff9b::85f2:574, icmp_seq=1 hlim=50 time=17.983 ms
16 bytes from 64:ff9b::85f2:574, icmp_seq=2 hlim=50 time=16.872 ms

Jool のユーザコマンド

Jool には、カーネルモジュール以外にユーザコマンドも付属しています。usr ディレクトリに入って Jool のユーザコマンドをビルド&インストールします。

$ cd Jool-3.3.5/usr
$ ./autogen.sh
$ ./configure
$ make
$ sudo make install

jool コマンドで、NAT64 のアクティブなセッションを確認したり、他にもいろいろできます。詳しくは Jool のマニュアルを読んでください。

$ sudo jool --session
TCP:
---------------------------------
  (empty)

UDP:
---------------------------------
  (empty)

ICMP:
---------------------------------
  (empty)

おまけ

前置きが長くなりましたがここからが本題です。

最新の Jool-3.4.0 を使ってみる

Jool-3.3.5 では、ホストに設定されている IPv4 アドレス以外に、NAT64 サービス用に IPv4 アドレスが必要でしたが、最新の 3.4.0 のリリースメモには「サービス用の(セカンド)IPv4 アドレスが不要になった」と書かれています。サービス用の IPv4 アドレスが不要なら、NAT64 箱が DHCPv4 環境下でも動作するということになるので、設置のハードルがさらに下がることが期待できます。

先ほどと同じように Jool-3.4.0 のソースコードをダウンロードしてビルドします。

$ wget https://nicmx.github.io/jool-doc/download/Jool-3.4.0.zip
$ unzip Jool-3.4.0.zip
$ cd Jool-3.4.0/mod
$ make
make -C stateless
make[1]: Entering directory `/home/pandax381/LOCAL/Jool-3.4.0/mod/stateless'
make -C /lib/modules/4.1.7-v7+/build M=$PWD JOOL_FLAGS=""
make[2]: Entering directory `/usr/src/linux-headers-4.1.7-v7+'
  CC [M]  /home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/../common/rfc6145/4to6.o
  CC [M]  /home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/../common/rfc6145/6to4.o
  CC [M]  /home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/../common/rfc6145/common.o
  CC [M]  /home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/../common/rfc6145/core.o
  CC [M]  /home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/../common/address.o
  CC [M]  /home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/../common/types.o
  CC [M]  /home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/../common/str_utils.o
  CC [M]  /home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/../common/packet.o
  CC [M]  /home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/../common/stats.o
  CC [M]  /home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/../common/log_time.o
  CC [M]  /home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/../common/icmp_wrapper.o
  CC [M]  /home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/../common/ipv6_hdr_iterator.o
  CC [M]  /home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/../common/pool6.o
  CC [M]  /home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/../common/rfc6052.o
  CC [M]  /home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/../common/nl_buffer.o
  CC [M]  /home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/../common/rbtree.o
  CC [M]  /home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/../common/config.o
  CC [M]  /home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/../common/nl_handler.o
  CC [M]  /home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/../common/route.o
  CC [M]  /home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/../common/send_packet.o
  CC [M]  /home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/../common/core.o
/home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/../common/core.c: In function 'check_namespace':
/home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/../common/core.c:70:25: error: invalid operands to binary != (have 'possible_net_t' and 'struct net *')
make[3]: *** [/home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/../common/core.o] Error 1
make[2]: *** [_module_/home/pandax381/LOCAL/Jool-3.4.0/mod/stateless] Error 2
make[2]: Leaving directory `/usr/src/linux-headers-4.1.7-v7+'
make[1]: *** [all] Error 2
make[1]: Leaving directory `/home/pandax381/LOCAL/Jool-3.4.0/mod/stateless'
make: *** [stateless] Error 2

が、なんか make がエラー吐いて fail します...ここで諦めずに該当コードを確認します。

/*
 *  Jool-3.4.0/mod/stateless/common/core.c
 */
static bool check_namespace(const struct net_device *dev)
{
#ifdef CONFIG_NET_NS
        if (dev && dev->nd_net != joolns_get())
                return false;
#endif
        return true;
}

どうやら network namespace のチェックをしているようですね。dev->nd_netjoolns_get() の型が一致しないと怒られているので、先に joolns_get() の戻り値の型を確認してみます。

/*
 *  Jool-3.4.0/mod/stateless/common/namespace.c
 */
struct net *jool_net;

...

struct net *joolns_get(void)
{
        return jool_net;
}

joolns_get() の戻り値は struct net * だと判明したので、次は dev->nd_net です。struct net_devicelinux/netdevice.h に定義されています。

/*
 *  /usr/src/linux-headers-4.1.7-v7+/include/linux/netdevice.h
 */
struct net_device {
        char                    name[IFNAMSIZ];
        struct hlist_node       name_hlist;
        char                    *ifalias;
        ...
        possible_net_t                  nd_net;
        ...
}

dev->nd_net の型は struct net * ではなく、possible_net_t のようですね、単に typedef しているだけかどうか確認します。possible_net_tnet/net_namespace.h に定義されていました。

/*
 *  /usr/src/linux-headers-4.1.7-v7+/include/net/net_namespace.h
 */
typedef struct {
#ifdef CONFIG_NET_NS
        struct net *net;
#endif
} possible_net_t;

dev->nd_netpossible_net_t の実体で struct net のポインタではないので比較できなくて当然です。CONFIG_NET_NS が有効な場合なので、エラーとなっている箇所を dev->nd_net.net != joolns_get() と書けばよさそうです。

Jool のコードを修正したら、気を取り直してもう一度 make します。

$ make
make -C stateless
make[1]: Entering directory `/home/pandax381/LOCAL/Jool-3.4.0/mod/stateless'
make -C /lib/modules/4.1.7-v7+/build M=$PWD JOOL_FLAGS=""
make[2]: Entering directory `/usr/src/linux-headers-4.1.7-v7+'
...
/home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/nf_hook.c:59:3: warning: initialization from incompatible pointer type [enabled by default]
/home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/nf_hook.c:59:3: warning: (near initialization for 'nfho[0].hook') [enabled by default]
/home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/nf_hook.c:66:3: warning: initialization from incompatible pointer type [enabled by default]
/home/pandax381/LOCAL/Jool-3.4.0/mod/stateless/nf_hook.c:66:3: warning: (near initialization for 'nfho[1].hook') [enabled by default]
...
/home/pandax381/LOCAL/Jool-3.4.0/mod/stateful/nf_hook.c:82:3: warning: initialization from incompatible pointer type [enabled by default]
/home/pandax381/LOCAL/Jool-3.4.0/mod/stateful/nf_hook.c:82:3: warning: (near initialization for 'nfho[0].hook') [enabled by default]
/home/pandax381/LOCAL/Jool-3.4.0/mod/stateful/nf_hook.c:89:3: warning: initialization from incompatible pointer type [enabled by default]
/home/pandax381/LOCAL/Jool-3.4.0/mod/stateful/nf_hook.c:89:3: warning: (near initialization for 'nfho[1].hook') [enabled by default]
...
make[2]: Leaving directory `/usr/src/linux-headers-4.1.7-v7+'
make[1]: Leaving directory `/home/pandax381/LOCAL/Jool-3.4.0/mod/stateful'
# Running the dependencies is enough.

make は完了したけど、なにやらポインタの型が一致しないという不穏な warning が出ているので念のため確認してみます。

/*
 *  Jool-3.4.0/mod/stateless/nf_hook.c
 */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0)
#define HOOK_ARG_TYPE const struct nf_hook_ops *
#else
#define HOOK_ARG_TYPE unsigned int
#endif

static unsigned int hook_ipv4(HOOK_ARG_TYPE hook, struct sk_buff *skb,
                const struct net_device *in, const struct net_device *out,
                int (*okfn)(struct sk_buff *))
{
        return core_4to6(skb, in);
}

static unsigned int hook_ipv6(HOOK_ARG_TYPE hook, struct sk_buff *skb,
                const struct net_device *in, const struct net_device *out,
                int (*okfn)(struct sk_buff *))
{
        return core_6to4(skb, in);
}

static struct nf_hook_ops nfho[] = {
        {
                .hook = hook_ipv6,
                .owner = NULL,
                .pf = PF_INET6,
                .hooknum = NF_INET_PRE_ROUTING,
                .priority = NF_IP6_PRI_JOOL,
        },
        {
                .hook = hook_ipv4,
                .owner = NULL,
                .pf = PF_INET,
                .hooknum = NF_INET_PRE_ROUTING,
                .priority = NF_IP_PRI_JOOL,
        },
};

struct nf_hook_ops は Netfilter にフック関数を登録する際に使うもので、.hook はフック関数への関数ポインタですね。これらは linux/netfilter.h に定義されているので確認してみます。

/*
 *  /usr/src/linux-headers-4.1.7-v7+/include/linux/netfilter.h
 */
typedef unsigned int nf_hookfn(const struct nf_hook_ops *ops,
                               struct sk_buff *skb,
                               const struct nf_hook_state *state);

struct nf_hook_ops {
        struct list_head list;

        /* User fills in from here down. */
        nf_hookfn       *hook;
        struct module   *owner;
        void            *priv;
        u_int8_t        pf;
        unsigned int    hooknum;
        /* Hooks are ordered in ascending priority. */
        int             priority;
};

なんと、struct nf_hook_ops は同じですが、nf_hookfn のプロトタイプが違いますね...なんか書き間違えたってわけじゃなさそうなくらい違いますし、そもそもこれで動かしてもカーネルパニック起きるような気がするので、もしかしてAPIが変わったのかな?と当たりをつけてもう少し深追いしてみます...

こんな時は、http://lxr.free-electrons.com/ を使うと Linux カーネルの各バージョンを横断しながらコード検索できるのでとても便利です。

調べた結果、kernel 4.0 以前と kernel 4.1 以降で Netfilter のフック関数のプロトタイプが変更になっていることがわかりました。以下が kernel 4.0 のもので、Jool 側が期待しているプロトタイプと一致しています。

/*
 *  linux/netfilter.h
 */
typedef unsigned int nf_hookfn(const struct nf_hook_ops *ops,
                               struct sk_buff *skb,
                               const struct net_device *in,
                               const struct net_device *out,
                               int (*okfn)(struct sk_buff *));

struct nf_hook_ops {
        struct list_head list;

        /* User fills in from here down. */
        nf_hookfn       *hook;
        struct module   *owner;
        void            *priv;
        u_int8_t        pf;
        unsigned int    hooknum;
        /* Hooks are ordered in ascending priority. */
        int             priority;
};

ということで、Jool のコードが kernel 4.1 以降の Netfilter の変更に対応していなかったのが原因でした。原因がわかったので、新しい Netfilter のフック関数のプロトタイプに合わせるよう修正を試みます。幸いほとんどの引数を使っておらず、新しいプロトタイプで消えてしまったパケットを受信したデバイスを示す struct net_device *in も、struct sk_buf *skbskb->dev として持っているのでそれを使えば大丈夫そうです。

そんなわけで、こんなパッチを書きました。

https://gist.github.com/pandax381/04a718b9ccc0e0a94d7f

これを適用してもう一度ビルドしてみます。

$ wget https://gist.githubusercontent.com/pandax381/04a718b9ccc0e0a94d7f/raw/a797a3e1148d11dcf09ae024c88e1a6c8dafd881/Jool-3.4.0.patch
$ patch -u -p1 -d Jool-3.4.0 < Jool-3.4.0.patch
$ cd Jool-3.4.0/mod
$ sudo make
$ sudo make modules_install
$ sudo mv /lib/modules/4.1.7-v7/extra/jool* /lib/modules/4.1.7-v7+/extra/
$ sudo depmod -a

無事にビルドできたので、カーネルモジュールの jool をリロードします。3.4.0 からは、pool4 オプションでサービス用の IPv4 アドレスが指定されない場合、ホストに設定されている IPv4 アドレスを共有して動作するようになっているはずなので、eth0 に設定したサービス用のIPv4アドレスを削除し、modprobe で jool をロードする際の pool4 オプションも取り除いて実行します。

$ sudo modprobe -r jool
$ sudo ip addr del 192.168.0.101/24 dev eth0
$ sudo modprobe jool pool6=64:ff9b::/96

IPv6 ネットワークに接続しているクライアントから ping6 を投げて確認してみます。

clinet:~$ ping6 www.klab.com
PING6(56=40+8+8 bytes) 2001:2::aab1:xxxx:xxxx:xxxx:ae10 --> 64:ff9b::85f2:574
16 bytes from 64:ff9b::85f2:574, icmp_seq=0 hlim=50 time=17.319 ms
16 bytes from 64:ff9b::85f2:574, icmp_seq=1 hlim=50 time=17.983 ms
16 bytes from 64:ff9b::85f2:574, icmp_seq=2 hlim=50 time=16.872 ms

無事に疎通が確認できました!もちろん TCP や UDP の通信もちゃんと動いているので、このまま Webブラウジングしていると IPv6 Only なネットワークにいるということを忘れてしまいそうなくらいです。

まとめ

お約束ですが、このパッチを元に Jool の開発元にプルリク投げました。そして無事にマージされ、既に Jool-3.4.1 として正式リリースされています。みなさんは何も気にせず、最新バージョンを使ってください。おしまい。

klab_gijutsu2 at 12:48│Comments(0)TrackBack(0)network | kernel

トラックバックURL

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔   
 
 
 
Blog内検索
Archives
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
最新コメント