2020年10月31日土曜日

Javaでデュアルスタック環境のときIPv6させるには

最近、Java(JDK11)環境でHTTP通信しているときに気が付いたのだが、Javaアプリケーションの通信の多くはIPv4で通信している。IPv6対応のサービスに接続してもIPv4通信している。ドキュメントを見ると、IPv6対応をうたっており、通信できないわけではない。
実際、IPv6のアドレスを直接打って通信させると問題なくIPv6で通信する。
IPv6が使われない理由は、Javaのデフォルト設定だとIPv4とIPv6の両方対応している場合、IPv4を優先して利用するようになっているためである。

IPv4とIPv6の両方対応しているとはどういうことなのか?
ここからはJava固有の話ではなく、インターネットの通信をするときの仕組みについて考える。

そもそも、IPv6とIPv4は全く互換性がなく相互通信できない。つまり、IPv4のネットワークとIPv6のネットワークは完全に分離されており別物である。
したがって、IPレイヤーで両方対応するのではなく、DNSのレイヤーで両方対応するデュアルスタックという技術が一般的に利用されている。

DNSの仕様で、IPv4のアドレス解決はAレコード、IPv6のアドレス解決はAAAAレコードを登録することになっている。つまり、IPv4/IPv6デュアルスタックの場合、両方のレコードを同じホスト名に登録することである。
IPv4ネットワークに接続している端末からは、DNSでAのクエリを発行し、IPv4アドレスに変換する。IPv4アドレスが判明するので、IPv4ネットワークでサーバーに接続しに行く。
IPv6の場合も同様で、IPv6ネットワークに接続している端末から、DNSでAAAAのクエリを発行し、IPv6アドレスに変換する。IPv6アドレスが判明するので、IPv6ネットワークでサーバーに接続しに行く。
つまり、IPv6とIPv4の両方に接続している端末が、最初にAレコードとAAAAレコードのどっちを投げるのかで、IPv4/IPv6のどっちのネットワークに接続するかが決まる。
サーバー側は、IPv4とIPv6で別のサーバーを用意してもいいし、1台で両方のネットワークに接続してもよい。両方のネットワークにサーバーを用意して、DNS設定さえすればいい。

やっと、本題。JavaでIPv4とIPv6どっちを優先するかの設定は、以下のJVMプロパティによって切り替えることになっている。
java.net.preferIPv6Addresses
JVMプロパティなのでこのリクエストはIPv6といったことはできず、アプリケーション単位での切り替えとなる。

JVMプロパティの切り替えは、起動オプションで「-Djava.net.preferIPv6Addresses=true」をつけるか、mainメソッドの最初で以下のコードを実行する。

System.setProperty("java.net.preferIPv6Addresses", "true");

近年はIPv6の普及も進んで切るので、基本的に上記の設定を置こうなうことっをお勧めする。