Raspberry Pi 2 で NAT64 箱をつくってみた
最近の VirtualBox の記事の人気っぷりに嫉妬している @pandax381 です。
OSX 10.11(El Capitan)がリリースされ、NAT64 の話題をチラホラ見かけるようになってきましたね。弊社内でも例に漏れず話題に上がってきまして、なぜかデスクの上にラズパイが大量に転がっていたので、そこから1台使って NAT64 箱を作ってみました。
準備するもの
とりあえず 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.64 を eth1 などに読みかえてください。
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_net と joolns_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_device は linux/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_t は net/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_net は possible_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 *skb が skb->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 として正式リリースされています。みなさんは何も気にせず、最新バージョンを使ってください。おしまい。