こんにちは。maginemuです。
ここのところはiOS/Android向けにPIGG PARTYというサービスの開発を行っています。
はじめに
iOS9アプリケーションのIPv6への対応期限が刻々と迫ってきました。
今回IPv6への対応に際してトラブルがあり、原因調査をしたのですが、
もともと全然IPv6の知識が無く、ネットワークに関する知識も大して無く、
かろうじて知っているコマンド群もIPv6でうまく動かなかったりしてハマったので、
知ってる人には当たり前だと思いますが、紹介してみることにします。
その前に少しだけ前提を。
iOS9とIPv6対応
AppleではiOS9以降、IPv6でアプリケーションが正しく動作する必要があるとしています。
そしてその対応期限は2016年初頭とされていて、それ以降は対応していないと審査が通らなくなる…!
対応の仕方についてはこちらを参照します。
※実際にアプリケーションをIPv6に対応させることについては、この記事では触れません。
IPv6 DNS64/NAT64環境を用意しよう
- 上記リンク内: Test for IPv6 DNS64/NAT64 Compatibility Regularly を参照。
早い話が、El Capitanではoptionを押しながらインターネット共有を選択すると、NAT64ネットワークを作成するチェックボックスが出ます。
適当なマシンでこのオプションでインターネット共有を行い、そこにアクセスすればIPv6の環境が試せます。
ひとつ気をつける必要があるのが、インターネットに接続したい場合にはこのマシンがWi-Fi以外でインターネットに接続できる
必要があります(有線LANとか)。
なので最近のMacbook Proなどを使う場合、Ethernetアダプタとかが必要になるかもしれないです。
これで手元ではiMacxxxx...
という感じのネットワークが見つかるようになりました。
これに接続して試していきます。
IPアドレスを確認しよう
何はともあれ、諸々確認しましょう。手元の環境は MacBook Pro (Retina, 15-inch, Mid 2014), OS X 10.11.1 です。
ひとまずifconfig
。
...en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500ether aa:bb:cc:dd:ee:ffinet6 fe80::a8bb:ccff:fedd:eeff%en0 prefixlen 64 scopeid 0x4inet6 2001:xxxx::a8bb:ccff:fedd:eeff prefixlen 64 optimistic autoconfinet6 2001:xxxx::xxxx:xxxx:xxxx:xxxx prefixlen 64 autoconf temporarynd6 options=1<PERFORMNUD>media: autoselectstatus: active...
en0
のインタフェースのところだけ抜粋。inet6
となっているところがIPv6アドレスですね。
IPv6では、ホストひとつに対して複数のアドレスを持つことができます。
fe80
から始まるアドレスはリンクローカルなアドレスで、ルータを越えない同一ネットワーク内のアドレスです。
下位64ビットはイーサネットアドレス48ビットを24ビットずつに区切り、間にfffe
を挟み、
上から7ビット目を反転させたものになっています。
2001
から始まるものはグローバルアドレスです。正確には2000::/3
からE000::/3
がグローバルユニキャストアドレスだそうです。2001
から始まるものが、いまのところIANAによって割り振られているようです。
グローバルアドレスのうち上のほうはステートレス設定というやつで生成されたもののようです。
これはルータから得られたプレフィクスとMACアドレスベースで生成されるものです。
temporary
というのは一時アドレスというやつで、上記ステートレス設定のものだと、
アドレスからマシンが特定できてしまうので、定期的に変わるアドレスも用意されているみたいです。
IPアドレスの体系に関してはとりあえず、3つのタイプがあって、
またそれぞれ到達範囲:スコープでも分類できるという感じです。
IPv6のアドレス(ステートレス)自動設定については下記などに書いてあります(なるべく確実そうなやつ)。
- RFC 4862 - IPv6 Stateless Address Autoconfiguration
- IPv6 Configuration Guide, Cisco IOS Release 15.2M&T - IPv6 Stateless Autoconfiguration [Support] - Cisco
疎通確認(ping)
とりあえず自分にpingしてみようかな!
$ ping 2001:xxxx::a8bb:ccff:fedd:eeffping: cannot resolve 2001:xxxx::a8bb:ccff:fedd:eeff: Unknown host
なん…だと…
IPv6だと何か違うのか…! (ping ipv6とかでググる)
知ってれば何てことないんですが、コマンド違うんですよね。。ping6
というのがあります。
$ ping6 2001:xxxx::a8bb:ccff:fedd:eeffPING6(56=40+8+8 bytes) 2001:xxxx::a8bb:ccff:fedd:eeff --> 2001:xxxx::a8bb:ccff:fedd:eeff16 bytes from 2001:xxxx::a8bb:ccff:fedd:eeff, icmp_seq=0 hlim=64 time=0.084 ms16 bytes from 2001:xxxx::a8bb:ccff:fedd:eeff, icmp_seq=1 hlim=64 time=0.210 ms16 bytes from 2001:xxxx::a8bb:ccff:fedd:eeff, icmp_seq=2 hlim=64 time=0.213 ms16 bytes from 2001:xxxx::a8bb:ccff:fedd:eeff, icmp_seq=3 hlim=64 time=0.235 ms^C--- 2001:xxxx::a8bb:ccff:fedd:eeff ping6 statistics ---4 packets transmitted, 4 packets received, 0.0% packet lossround-trip min/avg/max/std-dev = 0.084/0.185/0.235/0.059 ms
よしよし。
サーバのアドレスが知りたい(nslookup)
知ってますよ。nslookup
ですよね。
$ nslookup google.comServer: 2001:xxxx::1Address: 2001:xxxx::1#53Non-authoritative answer:google.comAddress: 216.58.220.206
あれ…v4のアドレスだけですね。。
あー、さては。
$ man nslookup6No manual entry for nslookup6
違うの。。。
(ググる)
なるほど。-type=AAAA
というオプションか。
$ nslookup -type=AAAA google.comServer: 2001:xxxx::1Address: 2001:xxxx::1#53Non-authoritative answer:has AAAA address 64:ff9b::d83a:dcceAuthoritative answers can be found from:google.com
おお。取れた。Wikipediaによると
In the Domain Name System hostnames are mapped to IPv6 addresses by AAAA resource records, so-called quad-A records.
だそうです。
ページ取得(wget)
wget
してみましょう。
$ wget google.com--2015-12-09 18:46:38-- http://google.com/Resolving google.com... 64:ff9b::d83a:dcce, 216.58.220.206Connecting to google.com|64:ff9b::d83a:dcce|:80... connected.HTTP request sent, awaiting response... 302 FoundLocation: http://www.google.co.jp/?gfe_rd=cr&ei=fvhnVumcLMT98we005DwBg [following]--2015-12-09 18:46:38-- http://www.google.co.jp/?gfe_rd=cr&ei=fvhnVumcLMT98we005DwBgResolving www.google.co.jp... 64:ff9b::adc2:75f7, 64:ff9b::adc2:75f8, 64:ff9b::adc2:75ff, ...Connecting to www.google.co.jp|64:ff9b::adc2:75f7|:80... connected.HTTP request sent, awaiting response... 200 OKLength: unspecified [text/html]Saving to: 'index.html'[ <=> ] 19,287 --.-K/s in 0.009s2015-12-09 18:46:38 (2.10 MB/s) - 'index.html' saved [19287]
おお。普通にいけた。helpを見ると
-6, --inet6-only connect only to IPv6 addresses.
というオプションがあるみたいですね。
IPアドレスだと…
$ wget 64:ff9b::d83a:dcce--2015-12-09 18:49:38-- ftp://64/ff9b::d83a:dcce=> 'ff9b::d83a:dcce'Resolving 64... 0.0.0.64Connecting to 64|0.0.0.64|:21... failed: Network is unreachable.
おお、ftpだと思われている。では。
$ wget http://64:ff9b::d83a:dccehttp://64:ff9b::d83a:dcce: Bad port number.
ぐぬぬ。ですよねーーー
IPv6のアドレスを指定するときは、そのままだとポート番号と区別が付きません。[]
で括る必要がありそうです。
$ wget http://\[64:ff9b::d83a:dcce\]--2015-12-09 19:32:48-- http://[64:ff9b::d83a:dcce]/Connecting to [64:ff9b::d83a:dcce]:80... connected.HTTP request sent, awaiting response... 302 FoundLocation: http://www.google.com/ [following]--2015-12-09 19:32:48-- http://www.google.com/Resolving www.google.com... 64:ff9b::adc2:75f3, 64:ff9b::adc2:75f2, 64:ff9b::adc2:75f1, ...Connecting to www.google.com|64:ff9b::adc2:75f3|:80... connected.HTTP request sent, awaiting response... 302 FoundLocation: http://www.google.co.jp/?gfe_rd=cr&ei=UANoVpuWO6Kg8weR_oCoDQ [following]--2015-12-09 19:32:48-- http://www.google.co.jp/?gfe_rd=cr&ei=UANoVpuWO6Kg8weR_oCoDQResolving www.google.co.jp... 64:ff9b::adc2:75f8, 64:ff9b::adc2:75f7, 64:ff9b::adc2:75ef, ...Connecting to www.google.co.jp|64:ff9b::adc2:75f8|:80... connected.HTTP request sent, awaiting response... 200 OKLength: unspecified [text/html]Saving to: 'index.html'[ <=> ] 19,215 --.-K/s in 0.03s2015-12-09 19:32:49 (553 KB/s) - 'index.html' saved [19215]
無事取れました。
link-localアドレスでの通信
一旦ローカルホストでサーバを立てて、それに接続して検証したかったりしますよね。
上述しましたが、IPv6が有効になっているホストでは
まずリンクローカルアドレスが割り当てられます。
このアドレスはiMacやNAT64の環境を用意しなくても使えそうです。
とりあえず適当なhttpサーバを。。node
とかnpm
とかexpress
が入ってる前提で
($ npm install -g express
)
$ express app && cd app && npm install && DEBUG=app:* npm startcreate : appcreate : app/package.jsoncreate : app/app.jscreate : app/publiccreate : app/public/javascriptscreate : app/public/imagescreate : app/public/stylesheets...node ./bin/wwwapp:server Listening on port 3000 +0ms
ここで、本質的じゃないのですが、nodeでlistenするときに、IPを指定しないとIPv6でうまくlistenされないようなので
ちょっとソースコードを変更しておきます。
diff --git a/bin/www b/bin/wwwindex a8c2d36..e79c926 100755--- a/bin/www+++ b/bin/www@@ -25,7 +25,7 @@ var server = http.createServer(app);Listen on provided port, on all network interfaces.*/-server.listen(port);+server.listen(port, '::0');server.on('error', onError);server.on('listening', onListening);
で
$ wget localhost:3000--2015-12-11 15:24:56-- http://localhost:3000/Resolving localhost... ::1, 127.0.0.1, fe80::1Connecting to localhost|::1|:3000... connected.HTTP request sent, awaiting response... 200 OKLength: 170 [text/html]Saving to: 'index.html'2015-12-11 15:24:56 (40.5 MB/s) - 'index.html' saved [170/170]
よしよし。ではIPアドレスで。
$ wget http://\[fe80::a8bb:ccff:fedd:eeff\]--2015-12-11 15:32:03-- http://[fe80::a8bb:ccff:fedd:eeff]:3000/Connecting to [fe80::a8bb:ccff:fedd:eeff]:3000... failed: No route to host.
あれ。。。
$ ping6 fe80::82e6:50ff:fe14:18beping6: UDP connect: No route to host
あれー。。。
調べてみると、どうやら-I
オプションが必要そうです。man ping6
を見てみると
-I interfaceSource packets with the given interface address. This flag applies if the ping destination is a mul-ticast address, or link-local/site-local unicast address.
なるほど。今は送出先がリンクローカルユニキャストアドレスです。
リンクローカルなアドレスを指定するときには、なにかとインタフェース名とか、scope_id
といった値を
指定する必要がありそうです。
ifconfig
を再度見直してみると
inet6 fe80::a8bb:ccff:fedd:eeff%en0 prefixlen 64 scopeid 0x4
%en0
とか、scopeid 0x4
とか書いてありますね。
ということで、
$ ping6 -I en0 fe80::a8bb:ccff:fedd:eeffPING6(56=40+8+8 bytes) fe80::a8bb:ccff:fedd:eeff%en0 --> fe80::a8bb:ccff:fedd:eeff16 bytes from fe80::a8bb:ccff:fedd:eeff%en0, icmp_seq=0 hlim=64 time=0.100 ms16 bytes from fe80::a8bb:ccff:fedd:eeff%en0, icmp_seq=1 hlim=64 time=0.104 ms16 bytes from fe80::a8bb:ccff:fedd:eeff%en0, icmp_seq=2 hlim=64 time=0.209 ms16 bytes from fe80::a8bb:ccff:fedd:eeff%en0, icmp_seq=3 hlim=64 time=0.232 ms^C--- fe80::a8bb:ccff:fedd:eeff ping6 statistics ---4 packets transmitted, 4 packets received, 0.0% packet lossround-trip min/avg/max/std-dev = 0.100/0.161/0.232/0.060 ms
うまく通りました。
しかし、wgetは未だにうまくできず。。%en0
を付けてみて、
$ wget http://\[fe80::a8bb:ccff:fedd:eeff%en0\]:3000http://[fe80::a8bb:ccff:fedd:eeff%en0]:3000: Invalid IPv6 numeric address.
だめか。。ちなみにcurl
だと…?
$ curl http://\[fe80::82e6:50ff:fe14:18be%en0\]:3000 ⏎<!DOCTYPE html><html><head><title>Express</title><link rel="stylesheet" href="/stylesheets/style.css"></head><body><h1>Express</h1><p>Welcome to Express</p></body></html>%
いけたーーーー
scope_idの指定の仕方に関しては、けっこう実装依存のようです。
上記を見てみると、a numeric zone index can be used in the second 16-bit word
とあります。では試しに
$ wget http://\[fe80:4::a8bb:ccff:fedd:eeff\]:3000--2015-12-11 17:36:13-- http://[fe80:4::a8bb:ccff:fedd:eeff]:3000/Connecting to fe80:4::a8bb:ccff:fedd:eeff|fe80::a8bb:ccff:fedd:eeff|:3000... connected.HTTP request sent, awaiting response... 200 OKLength: 170 [text/html]Saving to: 'index.html'100%[======================================================================================>] 170 --.-K/s in 0s2015-12-11 17:36:13 (20.3 MB/s) - 'index.html' saved [170/170]
おお。いけました。なんだそれ。。。
ちなみに、GoogleChromeのアドレスバーに打ち込んだところ、
http://[fe80:4::a8bb:ccff:fedd:eeff]:3000/
この形式では取得できましたが、
http://[fe80::a8bb:ccff:fedd:eeff%en0]:3000/
の場合検索ワードと見なされてしまいました。
http://[fe80::a8bb:ccff:fedd:eeff%25en]:3000/
とかエンコーディングしてみても変わらずでした。
ローカルホストのIPでアクセスするだけでこれほどハマるとは。。。
まとめ
少しですが、IPv6環境について紹介させて頂きました。
最初はコマンド自体が合ってなかったので、どこに問題があるのかの
切り分け自体できなくてハマりました。。
IPv4からアドレス空間が広くなっただけでなく、
知らない仕様が色々あることを痛感しました。。
調べてみると、200X年とかの記事が結構ヒットしますね。
今後IPv6環境もいよいよ増える流れかと思いますので、
トラブルシューティングのための準備を整えて行きたいところです。
ではでは!