IPv6とIPv4の併用問題
プログラミングの視点で
現在、IPv6 と IPv4 とを同時に利用できるデュアルスタック状態が一般的である。
IPv6 と IPv4 とはアプリケーション層からはただのアドレスの違いにしか見えないように設計されているが、実のところまったく異なるプロトコルである。
現状、このデュアルスタック状態に適応するため、名前からアドレスに解決する時に、IPv6 アドレスと IPv4 アドレスの2つのアドレスが取れてしまうことがある。
(リゾルバにより名前から複数のアドレスが返されることがある。名前から1つのアドレスだけが返ってくるというのは誤解である。)
IPv6のみあるいはIPv4のみの場合は、listen するアドレスは1つでよいが、IPv6とIPv4とのデュアルスタック状態では listen する(すべき)アドレスはIPv6 アドレスと IPv4 アドレスの2つになってしまう。
同様に接続する側からも、接続先が IPv6 アドレスと IPv4 アドレスの2つになってしまう。
よく勘違いされるが、IPv4アドレスをIPv6アドレスに自動的に変換してくれるわけではない。
IPv4アドレスとIPv6アドレスはそもそも互換性がないので単純に自動変換はできない。
以下の2つがあるが、あくまで、単純なアドレスの変換で、「名前とアドレス」の解決の問題とは異なる。
IPv4-compatible IPv6 address (IPv4互換 IPv6アドレス。廃止)
IPv4-mapped IPv6 address (IPv4射影 IPv6アドレス)
例えば、localhost のアドレスは IPv4 では 127.0.0.1、IPv6 では ::1 になるが、IPv4-mapped IPv6 addressではない。
::1 を listen した時に、127.0.0.1 が自動的にlisten してくれるわけでもないし、
127.0.0.1 に connect しようとした時に、自動的に ::1 に connect してくれるわけでもない。
また、どちらを優先すべきかが明示的に決まっているわけでもない。(IPv6 の方を優先すべきと思うが)
このため、何も考えずにプログラムを作ると、サーバーは IPv6 アドレスを使い、クライアントは IPv4 アドレスを使ってしまい、接続できない、というような現象が起きてしまう。
さらに、IPv6 アドレスで接続できないことが IPv4 アドレスで接続できないことではなかったりする。このため、IPv6 で接続できない場合、IPv4 で接続し直すような処理が必要となってくる。
原則として、リゾルバは試すことができるすべてのアドレスを優先度順で返すべきであり、(名前を元にアドレス解決する)サーバーはそのすべてで listen し、(名前を元にアドレス解決する)クライアントは優先度順に connect を試行すべきである。
問題は、APIによってリゾルバが隠蔽されていることがあること。名前とアドレスのどちらでも受け取れるように設計されているAPIがよくあるが、前出した原則に従っていないことがある。
この場合、リゾルバを別に用意して、自前で原則通りに呼び出すようにするしかない。
この時、connect でのタイムアウトは、「全試行時間」と見なすべきである。
(よって各試行に対して、タイムアウト時間を残り試行可能なアドレスの数で割る必要がある。)
listen の場合、IP6ADDR_ANY_INIT (::) でlistenすると、IPv4 (INADDR_ANY (0.0.0.0)) まで listen 状態になって、接続元のアドレスを IPv4-mapped IPv6 address に変換してくれることはあるようだ。
(あくまで、TCPスタックかライブラリあたりが便宜的にやってくれているだけであることに注意)