このドキュメントでは、次のトピックについて説明します。
このわずか数年で、IPv6 は業界で (特に、ヨーロッパとアジア太平洋地域の業界で)、幅広く受け入れられるようになりました。拡張性、モビリティ、サービス品質、広大なアドレス空間、自動 構成、セキュリティ、マルチホーム、エニーキャストとマルチキャスト、リナンバリングなどは、IPv6 で実現できる機能の一部です。
J2SE 1.4 が 2002 年 2 月にリリースされてから、Java では Solaris と Linux での IPv6 サポートを開始しました。Windows での IPv6 サポートは、J2SE 5.0 で追加されました。C や C++ などのその他の言語でも IPv6 をサポートしていますが、 Java には大きな利点がいくつかあります。
後述するコード例で以上の点を証明し、IPv6 サポートについて追加の情報を説明します。
J2SE リファレンス実装では、次のオペレーティングシステムがサポートされています。
IPv6 を Java で使用するのは簡単かつ透過的で自動的に行われます。その他の多くの言語とは異なり、移植は必要ありません。実際、ソースファイルの再コンパイルさえ必要 ありません。
「The Java Tutorial」からの例を示します。
Socket echoSocket = null;
PrintWriter out = null;
BufferedReader in = null;
try {
echoSocket = new Socket("taranis", 7);
out = new PrintWriter(echoSocket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(
echoSocket.getInputStream()));
} catch (UnknownHostException e) {
System.err.println("Don't know about host: taranis.");
System.exit(1);
} catch (IOException e) {
System.err.println("Couldn't get I/O for "
+ "the connection to: taranis.");
System.exit(1);
}
// ... code omitted here
communicateWithEchoServer(out, in);
out.close();
in.close();
stdIn.close();
echoSocket.close();
ローカルホストマシンと宛先マシン (taranis) が IPv6 に対応していれば、この例と同じバイトコードを IPv6 モードで実行することができます。
対照的に、対応する C プログラムを IPv6 モードで実行しようとすると、最初に移植しなければなりません。次に、何をしなければならないかを示します。
struct sockaddr_in sin;
struct hostent *hp;
int sock;
/* Open socket.*/
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1) {
perror("socket");
return (-1);
}
/* Get host address */
hp = gethostbyname(hostname);
if (hp == NULL || hp->h_addrtype != AF_INET || hp->h_length != 4) {
(void) fprintf(stderr, "Unknown host '%s'\n", hostname);
(void) close(sock);
return (-1);
}
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
(void) memcpy((void *) &sin.sin_addr, (void *)hp->h_addr, hp->h_length);
/* Connect to the host */
if (connect(sock, (struct sockaddr *)&sin, sizeof(sin)) == -1) {
perror("connect");
(void) close(sock);
return (-1);
}
struct addrinfo *res, *aip;
struct addrinfo hints;
int sock = -1;
int error;
/* Get host address.Any type of address will do. */
bzero(&hints, sizeof(hints));
hints.ai_flags = AI_ALL|AI_ADDRCONFIG;
hints.ai_socktype = SOCK_STREAM;
error = getaddrinfo(hostname, servicename, &hints, &res);
if (error != 0) {
(void) fprintf(stderr,
"getaddrinfo: %s for host %s service %s\n",
gai_strerror(error), hostname, servicename);
return (-1);
}
/* Try all returned addresses until one works */
for (aip = res; aip != NULL; aip = aip->ai_next) {
/*
* Open socket.The address type depends on what
* getaddrinfo() gave us.
*/
sock = socket(aip->ai_family, aip->ai_socktype, aip->ai_protocol);
if (sock == -1) {
perror("socket");
freeaddrinfo(res);
return (-1);
}
/* Connect to the host.*/
if (connect(sock, aip->ai_addr, aip->ai_addrlen) == -1) {
perror("connect");
(void) close(sock);
sock = -1;
continue;
}
break;
}
freeaddrinfo(res);
新しいアプリケーションの場合、アドレスファミリを許容するデータ構造体を作成した場合、移植は必要ありません。
しかし、C または C++ によるサーバサイドプログラミングでは、さらに欠点があります。アプリケーションが Solaris や Linux などのデュアルスタックプラットホーム用に作成されたものか、Windows などのシングルスタックプラットホーム用に記述されたものなのかによって、異なるコード構造にしなければなりません。サーバサイドプログラミングの場合、 Java には大きな利点があります。これまでどおり、同じコードを記述します。
ServerSocket server = new ServerSocket(port);
Socket s;
while (true) {
s = server.accept();
doClientStuff(s);
}
そして、このコードを IPv6 対応のマシンで実行する場合は、IPv6 対応サービスをすぐに提供できます。
デュアルスタックプラットホームで、C による対応するサーバコードは次のとおりです。
int ServSock, csock;
struct sockaddr addr, from;
...
ServSock = socket(AF_INET6, SOCK_STREAM, PF_INET6);
bind(ServSock, &addr, sizeof(addr));
do {
csock = accept(ServSocket, &from, sizeof(from));
doClientStuff(csock);
} while (!finished);
デュアルスタックマシンでは、IPv6 ソケット 1 つが IPv4 と IPv6 プロトコルスタックの両方にアクセスするため、1 ソケットだけ作成する必要があります。そのため、このサーバは潜在的に IPv4 と IPv6 クライアントの両方をサポートできます。
シングルスタックプラットホーム用の、同じサーバの C コードは次のようになります。
SOCKET ServSock[FD_SETSIZE];
ADDRINFO AI0, AI1;
ServSock[0] = socket(AF_INET6, SOCK_STREAM, PF_INET6);
ServSock[1] = socket(AF_INET, SOCK_STREAM, PF_INET);
...
bind(ServSock[0], AI0->ai_addr, AI0->ai_addrlen);
bind(ServSock[1], AI1->ai_addr, AI1->ai_addrlen);
...
select(2, &SockSet, 0, 0, 0);
if (FD_ISSET(ServSocket[0], &SockSet)) {
// IPv6 connection csock = accept(ServSocket[0], (LPSOCKADDR)&From, FromLen);
...
}
if (FD_ISSET(ServSocket[1], &SockSet))
// IPv4 connection csock = accept(ServSocket[1], (LPSOCKADDR)&From, FromLen);
...
}
ここでは、IPv6 スタック用に 1 つと IPv4 スタック用に 1 つ、合計 2 つのサーバソケットを作成する必要があります。また、IPv4 または IPv6 クライアントからの接続を待機するために、2 つのソケット上で多重化する必要があります。
Java を使用すれば、J2SE 1.4 以降を使用して、あらゆる Java アプリケーション、クライアント、またはサーバを IPv6 対応プラットホームで実行できます。また、アプリケーションは魔法のように自動的に IPv6 対応となります。
対照的に、レガシーなネイティブ言語アプリケーションでは、C または C++ アプリケーションを IPv6 対応にしようとすると、移植と再コンパイルが必要になります。
Java ネットワーキングスタックはまず、IPv6 を基本となる OS でサポートしているかどうかを確認します。IPv6 がサポートされている場合は、IPv6 スタックを使用しようとします。具体的には、デュアルスタックシステムの場合、IPv6 ソケットを作成します。個別スタックシステムの場合は、もっと複雑です。IPv4 通信と IPv6 通信用に 1 つずつ、合計 2 つのソケットを作成します。クライアント側の TCP アプリケーションでは、ソケットが接続されると、インターネットプロトコルファミリータイプが固定され、もう 1 つのソケットが閉じられます。サーバ側の TCP アプリケーションでは、次のクライアント要求がどの IP ファミリータイプから送信されるかを知る方法がないため、2 つのソケットを維持しておく必要があります。UDP アプリケーションでは、通信の継続期間中、両方のソケットが必要です。
Java では、ネームサービスから IP アドレスを取得します。
IPv6 を Java で使用するには、次の内容を知っておく必要はありません。しかし、さまざまな状況で疑問を感じたり何が行われているのかを知るために、このドキュメントの 残りの部分で説明しておきます。
これは「エニーローカル (anylocal)」または「ワイルドカード」アドレスとも呼ばれます。ソケットがデュアルスタックのマシン上の IPv6 エニーローカルアドレスにバインドされている場合、そのソケットは、IPv6 トラフィックと IPv4 トラフィックの両方を受け入れることができます。ソケットが IPv4 (IPv4 マップ) エニーローカルアドレスにバインドされている場合、そのソケットは IPv4 トラフィックだけを受け入れることができます。デュアルスタックのマシン上では、関連するシステムプロパティで IPv4 スタックの指定が設定されていないかぎり、IPv6 エニーローカルアドレスにバインドすることが試みられます。
::
にバインドされているとき、メソッド ServerSocket.accept
は、IPv6 ホストと IPv4 ホストの両方からの接続を受け入れます。現在の Java プラットフォーム API では、IPv6
ホストからの接続だけを受け入れるように指定する方法はありません。
アプリケーションでは、NetworkInterface を使用してインタフェースを列挙し、各 IPv6 アドレスに ServerSocketChannel をバインドして、New I/O API からのセレクタを使用してそれらのソケットからの接続を受け入れることができます。
注: このあと説明するオプションは、Draft-ietf-ipngwg-rfc2553bis-03.txt で導入されています。それが標準規格になった時点で、そのオプションが Java 2 プラットフォームでサポートされる予定です。 |
ただし、前述の動作を変更する、新しいソケットオプションがあります。Draft-ietf-ipngwg-rfc2553bis-03.txt では、新しい IP レベルのソケットオプションとして、IPV6_V6ONLY が導入されました。このソケットオプションは、AF_INET6 ソケットを IPv6 通信だけに限定します。通常、AF_INET6 ソケットは、IPv4 および IPv6 の両方の通信のために使用できます。アプリケーションによっては、AF_INET6 ソケットの使用を IPv6 通信だけに限定したい場合があります。そのようなアプリケーションでは、IPV6_V6ONLY ソケットオプションを定義します。このオプションをオンに設定すると、IPv6 パケットだけを送受信するためにそのソケットを使用できます。デフォルトでは、このオプションはオフになっています。
ループバックアドレスを指定されたパケットは、決してリンク上に送信したり、IPv6 ルータによって転送したりしないでください。IPv4 と IPv6 には 2 つの異なるループバックアドレスがあり、それぞれ別個に処理されます。
IPv4 と IPv6 のアドレスは、「::」の場合を除いて、別々のアドレス空間を占めます。
このアドレスは、ホストおよびルータが、IPv6 パケットを IPv4 ルーティングインフラストラクチャ上を動的にトンネルするために使用されます。このアドレスは、OS カーネルとルータに対して意味を持ちます。Java では、このアドレスをテストするためのユーティリティメソッドが提供されています。
これは、IPv4 アドレスを表現するために使用される IPv6 アドレスです。このアドレスを利用すると、ネイティブプログラムでは、IPv4 ノードと IPv6 ノードの両方と通信する際に、同じアドレスデータ構造と、さらには同じソケットを使用できます。このため、IPv4 マップアドレスをサポートするデュアルスタックノード上では、IPv6 アプリケーションは IPv4 ピアと IPv6 ピアの両方と通信できます。IPv4 のデータグラムを送受信するのに必要な基本となる諸処理は OS が実行して、その結果を IPv6 の宛先ソケットに渡します。また、OS は、必要なときに IPv4 マップ IPv6 アドレスを合成します。
Java では、このアドレスは内部の表現に使用され、機能上の役割はありません。Java から IPv4 マップアドレスが返されることはありません。Java では、IPv4 マップアドレスの構文が、バイト配列とテキスト表現の両方で理解されます。ただし、そのアドレスは IPv4 アドレスに変換されます。
デュアルスタックマシンでは、優先されるプロトコルスタック (IPv4 または IPv6) と、優先されるアドレスファミリの型 (inet4 または inet6) を設定するためのシステムプロパティが提供されます。
デフォルトでは、IPv6 スタックが優先されます。デュアルスタックマシンでは、IPv6 ソケットが IPv4 ピアと IPv6 ピアの両方と通信できるためです。
この設定を変更するには、java.net.preferIPv4Stack=<true|false>
システムプロパティを使用します。
アドレスのデフォルトについては、IPv6 アドレスよりも IPv4 アドレスをお勧めします。ネームサービス (DNS サービスなど) に照会すると、IPv6 アドレスではなく IPv4 アドレスが返されます。この選択には、次の 2 つの理由があります。
この設定を変更するには、java.net.preferIPv6Addresses=<true|false>
システムプロパティを使用します。
今後何年もの間 (永久にではないとしても)、インターネットは IPv6 ノードと IPv4 ノードが混在した状態になるでしょう。このため、IPv4 ノードの大規模なインストールベースとの互換性が、IPv4 から IPv6 への移行を成功させるために重要です。デュアルスタック (RFC 1933 で定義されている) は、円滑な移行を保証するための主要な機構の 1 つです。もう 1 つの機構は IPv6 パケットのトンネリングですが、この機構と J2SDK との関連性は IPv4 互換アドレスに関係するものだけです。前者の機構は、J2SDK ともっとも関連性があります。デュアルスタックには、IPv4 と IPv6 の両方のバージョンのインターネットプロトコルを実装することが含まれます。
デュアルスタックノードの一般的な特性は、IPv6 ソケットが IPv4 ピアおよび IPv6 ピアの両方とトランスポート層 (TCP または UDP) で通信できるということです。ネイティブレベルでは、IPv6 ソケットは、IPv4 マップ IPv6 アドレスを使って IPv4 と通信します。ただし、ソケットがピアのアドレス型を調べないかぎり、IPv4 ピアまたは IPv6 ピアのどちらと通信しているかをソケットが認識することはありません。アドレス型に関係した内部の処理と変換はすべて、デュアルプロトコルスタックによっ て実行されます。
注: IPv4 マップアドレスは、デュアルプロトコルスタックの実装においてだけ意味を持ちます。IPv4 マップアドレスは、IPv6 アドレスを「模倣」して (IPv6 と同じ形式になるようにして)、アドレスを IPv6 ソケットに渡せるようにするために使用されます。概念上のレベルでは特に役割がなく、その役割は Java API のレベルに限定されています。IPv4 マップアドレスの解析はサポートされていますが、IPv4 マップアドレスが返されることはありません。
(ノード) V4 のみ V4/V6 V6 のみ V4 のみ x x V4/V6 x x x V6 のみ x x
1 番上の行と左端の列は、通信しようとするノードの種類を表します。「x」は、それらのノードが互いに通信できることを示します。
host1 または host2 のどちらかは、ネイティブアプリケーションにできます。
host2 から host1 に通信しようとするとき、host2 は V6 ソケットを作成します。次に、host2 は host1 の IP アドレスを検索します。host1 は V4 プロトコルスタックだけしか持っていないため、名前検索サービスにも IPv4 のレコードしかありません。そのため、hots2 は IPv4 マップアドレスを使って host1 に到達しようとします。host2 からは IPv4 パケットが送信されてくるので、host1 は、自身が V4 クライアントと通信しているものと認識します。
host2 がサーバの場合、host2 はまず V6 型のソケット (これがデフォルト) を作成して、接続を待機します。host1 は V4 だけしかサポートしていないため、host1 は V4 型のソケットを作成します。両者は、host2 の名前を解決します。host1 は、IPv6 アドレスを認識しないため、host2 の V4 アドレスだけを取得します。そのため、host1 は V4 アドレスを使って host2 に接続します。そして、V4 パケットが回線上に送信されます。host2 では、デュアルスタックによって V4 パケットが V6 パケット (V4 マップアドレスを持つ) に変換され、V6 ソケットに渡されます。サーバアプリケーションは、それが V6 ノードからの接続であるかのようにしてパケットを処理します。
InetAddress
このクラスは、IP
アドレスを表します。そして、アドレスの格納場所、名前とアドレスを変換するメソッド、アドレスを変換するメソッド、およびアドレスをテストするメソッド
を提供します。J2SE 1.4 では、IPv4 アドレスと IPv6
アドレスの両方をサポートするようにこのクラスが拡張されました。また、アドレス型とスコープをチェックするユーティリティメソッドが追加されました。
IPv4 と IPv6 という 2 つの型のアドレスは、Java 型の Inet4Address
および Inet6Address
によって区別できます。
InetAddress
の新しいサブクラスとして、Inet4Address
および Inet6Address
の 2 つのクラスが作成されました。この 2 つのサブクラスには、V4 および V6 に固有の状態と動作が実装されています。Java
のオブジェクト指向の性質のため、アプリケーションは通常、InetAddress
クラスを取り扱うだけで済みます。多相性によって、正しい動作を得ることができるためです。各プロトコルファミリに固有の動作にアクセスする必要がある場
合 (たとえば、IPv6 専用のメソッドを呼び出す場合や、IP アドレスのクラス型を知る必要がある場合) にかぎり、アプリケーションは Inet4Address
と Inet6Address
を区別することになります。
導入された新しいメソッドは次のとおりです。
InetAddress:
isAnyLocalAddress
isLoopbackAddress
isLinkLocalAddress
isSiteLocalAddress
isMCGlobal
isMCNodeLocal
isMCLinkLocal
isMCSiteLocal
isMCOrgLocal
getCanonicalHostName
getByAddr
Inet6Address:
isIPv4CompatibleAddress
InetAddress
および異なるネームサービス1.4 より前では、InetAddress
は、システムに構成されたネームサービスを利用してホスト名を解決していました。1.4 では、代わりになる名前検索手段として、JNDI を介した
Java DNS プロバイダが追加されました。いくつかのシステムプロパティを設定すれば、このプロバイダを使用するように J2SDK
に命令することができます。これらのシステムプロパティについては、Java
システムプロパティの項に説明があります。将来的には、開発者が独自のネームサービスプロバイダを記述できるようにするため、汎用的なサービスプロバイダ
のフレームワークを提供する予定です。
すべての IPv4 アドレスは、Java では Inet4Address
オブジェクトとして表現されます。そのオブジェクトは InetAddress
オブジェクトとして直列化され、下位互換性を保つために、InetAddress
からは Inet4Address
に直列化復元されます。IPv6 アドレスは Inet6Address
として表現され、同様に直列化されます。
Socket
、ServerSocket
、および DatagramSocket
Java のオブジェクト指向の性質のため、アドレスの種類と情報を格納している構造体は、ソケット API のレベルでは公開されません。そのため、新しい API は不要です。既存のソケット API により、IPv4 トラフィックと IPv6 トラフィックの両方が扱われます。
どちらのスタックが使用されるかの選択は、次の基準によります。
サポートされるすべての IPv6 ソケットオプションには、対応する IPv4 オプションがあります。したがって、IPv6 ソケットオプションをサポートするための新しい API は追加されていません。その代わりに、古い API が、V4 と V6 の両方のソケットオプションをサポートするようにオーバーロードされました。
MulticastSocket
このソケットの場合も、ソケットオプションのすべての API が、IPv6 のマルチキャストソケットオプションをサポートするようにオーバーロードされました。
ネットワークインタフェースを設定および取得するための新しい 2 つの API が追加されました。これは、InetAddress
インスタンスを設定および取得する既存の MulticastSocket.setInterface
および MulticastSocket.getInterface
とは別に追加されたものです。2 つの既存のメソッドは、マルチキャストパケットを送信するための現在の MulticastSocket
で使用されているネットワークインタフェースを設定または取得するために使用されます。IPv4 の場合、インタフェースは IP
アドレスによって示されていました。そのため、Java では、それと等価の InetAddress
を使用できます。この 2 つのメソッドは、IPv6 のマルチキャストソケットに対しても引き続き機能します。ただし、IPv6 では、RFC
2553
の仕様によると、インタフェースはインタフェースインデックスを使って示す必要があります。ネットワークインタフェースの概念をより適切にサポートするた
め、新しいクラスとして NetworkInterface
が導入されました。このクラスは、ネットワークインタフェースの状態を表すデータをカプセル化します。名前や IP
アドレスなどのデータと、いくつかの基本操作を実行するメソッドが組み込まれています。これに伴い、マルチキャストソケットの送出インタフェースを設定す
るための新しい 2 つのメソッドとして、setNetworkInterface
と getNetworkInterface
が導入されました。この 2 つのメソッドは、NetworkInterface
オブジェクトを引数にとり、戻り値として返します。新しいメソッドは、V4 と V6 の両方のマルチキャストで使用できます。
さらに、ネットワークインタフェース上のマルチキャストグループに参加したり、そこから抜けたりするためのメソッドも追加されました。これは、以前 の Java API では利用できませんでした。
MulticastSocket:
NetworkInterface getNetworkInterface()
setNetworkInterface(NetworkInterface netIf)
joinGroup(SocketAddress mcastaddr,NetworkInterface netIf)
leaveGroup(SocketAddress mcastaddr,NetworkInterface netIf)
URL
、URI
パーサURL や URI では、リテラル IP アドレスを使用できます。ただし、既存の URL および URI
の仕様では、ホストとポートを区切るためにコロン (:
) が使用されているため、現状のままで URL または URI
にリテラルの IPv6 表現を使用すると、解析に失敗します。このため、URL や URI にリテラルの IPv6
アドレスを指定するために、RFC 2732 が作成されました。URL と URI の解析処理は、RFC 2732
に準拠するように更新されました。
SocketPermission
は URL を利用するので、RFC 2732
に準拠するように実装が更新されました。
アクセス権を定義するのに使用される codebase
は、URL を変形したものです。したがって、codebase
も、URL の形式と規定に従う必要があります。URL と codebase
の場合は RFC 2732
形式が使用され、その他の場合には RFC 2373 形式が使用されます。
java.net.preferIPv4Stack (デフォルト: false)
IPv6 を利用可能なオペレーティングシステムでは、基本となるネイティブソケットは IPv6 ソケットです。このため、Java(TM) アプリケーションは、IPv4 ホストと IPv6 ホストの両方に接続したり、その両方のホストからの接続を受け入れたりできます。
IPv4 ソケットだけを使用するよう設定されているアプリケーションでは、このプロパティを true に設定できます。つまり、そのアプリケーションは IPv6 ホストと通信できないということです。
java.net.preferIPv6Addresses (デフォルト: false)
IPv6 を利用可能なオペレーティングシステムでは、デフォルトの設定として、IPv4 マップのアドレスを IPv6
アドレスよりも優先するようになっています。これは、下位互換性の理由によります。たとえば、IPv4
だけのサービスへのアクセスに依存するアプリケーションや、%d.%d.%d.%d の形式で表現された IP
アドレスに依存するアプリケーションとの互換性のためです。
IPv6 アドレスを IPv4 アドレスよりも優先する設定を試してみるには、このプロパティを true
に設定します。そうすれば、アプリケーションが IPv6
サービスに接続されると予想される環境でアプリケーションをテストし、そのような環境にアプリケーションを配置することができます。
sun.net.spi.nameservice.provider.<n>=<default|dns,sun|...>
使用できるネームサービスプロバイダを指定します。デフォルトでは、Java は、システムに構成された名前検索機構を使用します
(file、nis など)。このオプションを設定すると、独自の名前サービスプロバイダを指定できます。<n>
には、正の数値を指定します。この数値は優先順位を示しており、小さい値ほど優先順位が高くなります。1.4 では、JNDI を使用して 1 つの
DNS ネームサービスプロバイダが提供されており、その名前は dns,sun
です。
sun.net.spi.nameservice.nameservers=<server1_ipaddr,server2_ipaddr
...>
使用する DNS サーバを指す IP アドレスを、コンマで区切ったリストとして指定できます。
sun.net.spi.nameservice.domain=<domainname>
このプロパティでは、デフォルトの DNS ドメイン名 (たとえば、eng.sun.com
) を指定します。