IPv6 Winsock アプリケーションの関数呼び出し

Windows ソケットのプログラミングを容易にするように特別に設計された新しい関数が Windows ソケット インターフェイスに導入されました。 これらの新しい Windows ソケット関数の利点の 1 つは、IPv6 と IPv4 の両方に対する統合サポートです。

これらの新しい Windows ソケット関数には、次のものが含まれます。

さらに、Windows ソケットのプログラミングを簡略化するために、IPv6 と IPv4 の両方をサポートする新しい IP ヘルパー関数が追加されました。 これらの新しい IP ヘルパー関数には、次のものが含まれます。

アプリケーションに IPv6 のサポートを追加する場合は、次のガイドラインを使用する必要があります。

  • ホスト名とポートを指定してエンドポイントへの接続を確立するには、WSAConnectByName を使用します。 WSAConnectByName 関数は、Windows Vista 以降で使用できます。
  • 一連の宛先アドレス (ホスト名とポート) で表される、可能性のあるエンドポイントのコレクションのうちの 1 つへの接続を確立するには、WSAConnectByList を使用します。 WSAConnectByList 関数は、Windows Vista 以降で使用できます。
  • gethostbyname 関数呼び出しを、いずれかの新しい getaddrinfo Windows ソケット関数の呼び出しに置き換えます。 Windows XP 以降では、IPv6 プロトコルをサポートする getaddrinfo 関数を使用できます。 また、Windows 2000 の IPv6 テクノロジ プレビューがインストールされていると、IPv6 プロトコルは Windows 2000 でもサポートされます。
  • gethostbyaddr 関数呼び出しを、いずれかの新しい getnameinfo Windows ソケット関数の呼び出しに置き換えます。 Windows XP 以降では、IPv6 プロトコルをサポートする getnameinfo 関数を使用できます。 また、Windows 2000 の IPv6 テクノロジ プレビューがインストールされていると、IPv6 プロトコルは Windows 2000 でもサポートされます。

WSAConnectByName

WSAConnectByName 関数は、宛先のホスト名または IP アドレス (IPv4 または IPv6) を指定して、ストリーム ベースのソケットを使用してエンドポイントへの接続を簡略化します。 この関数では、使用される IP プロトコルのバージョンに依存しない IP アプリケーションを作成するために必要なソース コードが削減されます。 WSAConnectByName によって、一般的な TCP アプリケーションでの次の手順が 1 つの関数呼び出しに置き換えられます。

  • ホスト名を一連の IP アドレスに解決します。
  • 各 IP アドレスについて:
    • 適切なアドレス ファミリのソケットを作成します。
    • リモート IP アドレスへの接続を試みます。 接続が成功した場合は、戻ります。そうでない場合は、ホストへの次のリモート IP アドレスが試行されます。

WSAConnectByName 関数は、単に、名前を解決してから接続を試みるわけではありません。 この関数は、名前解決によって返されたすべてのリモート アドレスと、ローカル コンピューターのすべてのソース IP アドレスを使用します。 まず、成功する確率が最も高いアドレス ペアを使用して接続を試みます。 そのため、WSAConnectByName では、可能であれば接続が確実に確立されるだけでなく、接続を確立するための時間も最小限に抑えられます。

IPv6 と IPv4 の両方の通信を有効にするには、次の方法を使用します。

  • WSAConnectByName を呼び出す前に IPV6_V6ONLY ソケット オプションを無効にするには、AF_INET6 アドレス ファミリ用に作成されたソケットで setsockopt 関数を呼び出す必要があります。 これは、IPPROTO_IPV6 に設定された level パラメーター (「IPPROTO_IPV6 ソケット オプション」を参照)、IPV6_V6ONLY に設定された optname パラメーター、0 に設定された optvalue パラメーター値を使用して、そのソケットで setsockopt 関数を呼び出すことによって実現されます。

WSAConnectByName へのソケット パラメーターはバインドされていないソケットである必要があるため、アプリケーションを特定のローカル アドレスまたはポートにバインドする必要がある場合は WSAConnectByName を使用できません。

次のコード例は、この関数を使用して IP バージョンに依存しないアプリケーションを実装するために必要なコードの数行だけを示しています。

WSAConnectByName を使用して接続を確立する

#ifndef UNICODE
#define UNICODE
#endif

#define WIN32_LEAN_AND_MEAN

#include <winsock2.h>
#include <Ws2tcpip.h>
#include <stdio.h>

// Link with ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")

SOCKET OpenAndConnect(LPWSTR NodeName, LPWSTR PortName) 
{
    SOCKET ConnSocket;
    DWORD ipv6only = 0;
    int iResult;
    BOOL bSuccess;
    SOCKADDR_STORAGE LocalAddr = {0};
    SOCKADDR_STORAGE RemoteAddr = {0};
    DWORD dwLocalAddr = sizeof(LocalAddr);
    DWORD dwRemoteAddr = sizeof(RemoteAddr);
  
    ConnSocket = socket(AF_INET6, SOCK_STREAM, 0);
    if (ConnSocket == INVALID_SOCKET){
        return INVALID_SOCKET;
    }

    iResult = setsockopt(ConnSocket, IPPROTO_IPV6,
        IPV6_V6ONLY, (char*)&ipv6only, sizeof(ipv6only) );
    if (iResult == SOCKET_ERROR){
        closesocket(ConnSocket);
        return INVALID_SOCKET;       
    }

    bSuccess = WSAConnectByName(ConnSocket, NodeName, 
            PortName, &dwLocalAddr,
            (SOCKADDR*)&LocalAddr,
            &dwRemoteAddr,
            (SOCKADDR*)&RemoteAddr,
            NULL,
            NULL);
    if (bSuccess){
        return ConnSocket;
    } else {
        return INVALID_SOCKET;
    }
}

WSAConnectByList

WSAConnectByList 関数は、可能性のある (一連の宛先 IP アドレスとポートで表される) 一連のホストを指定して、ホストへの接続を確立します。 この関数は、エンドポイントのすべての IP アドレスおよびポートとローカル コンピューターのすべての IP アドレスを取得し、可能性のあるすべてのアドレスの組み合わせを使用して接続を試みます。

WSAConnectByList は、WSAConnectByName 関数に関連しています。 1 つのホスト名を取得するのではなく、WSAConnectByList はホストの一覧 (宛先アドレスとポートのペア) を受け付け、指定された一覧にあるいずれかのアドレスとポートに接続します。 この関数は、アプリケーションが、潜在的なホストの一覧のうちのいずれかの使用可能なホストに接続する必要があるシナリオをサポートするように設計されています。

WSAConnectByName と同様に、WSAConnectByList 関数では、ソケットを作成、バインド、接続するために必要なソース コードが大幅に削減されます。 この関数により、IP バージョンに依存しないアプリケーションの実装がきわめて容易になります。 この関数で受け付けられるホストのアドレスの一覧には IPv6 または IPv4 アドレスを指定できます。

この関数で受け付けられる 1 つのアドレス一覧で IPv6 アドレスと IPv4 アドレスの両方を渡せるようにするには、この関数を呼び出す前に次の手順を実行する必要があります。

  • WSAConnectByList を呼び出す前に IPV6_V6ONLY ソケット オプションを無効にするには、AF_INET6 アドレス ファミリ用に作成されたソケットで setsockopt 関数を呼び出す必要があります。 これは、IPPROTO_IPV6 に設定された level パラメーター (「IPPROTO_IPV6 ソケット オプション」を参照)、IPV6_V6ONLY に設定された optname パラメーター、0 に設定された optvalue パラメーター値を使用して、そのソケットで setsockopt 関数を呼び出すことによって実現されます。
  • IPv4 アドレスはすべて、IPv6 のみのアプリケーションが IPv4 ノードと通信できるようにする IPv4 マップ IPv6 アドレス形式で表す必要があります。 IPv4 マップ IPv6 アドレス形式を使用すると、IPv4 ノードの IPv4 アドレスを IPv6 アドレスとして表すことができます。 IPv4 アドレスは IPv6 アドレスの下位 32 ビットにエンコードされ、上位 96 ビットには固定のプレフィックス 0:0:0:0:0:FFFF が保持されます。 IPv4 マップ IPv6 アドレス形式は、RFC 4291 で指定されています。 詳細については、www.ietf.org/rfc/rfc4291.txt を参照してください。 Mstcpip.h 内の IN6ADDR_SETV4MAPPED マクロを使用すると、IPv4 アドレスを必要な IPv4 マップ IPv6 アドレス形式に変換できます。

WSAConnectByList を使用して接続を確立する

#ifndef UNICODE
#define UNICODE
#endif

#define WIN32_LEAN_AND_MEAN

#include <winsock2.h>
#include <Ws2tcpip.h>
#include <stdio.h>

// Link with ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")

SOCKET OpenAndConnect(SOCKET_ADDRESS_LIST *AddressList) 
{
    SOCKET ConnSocket;
    DWORD ipv6only = 0;
    int iResult;
    BOOL bSuccess;
    SOCKADDR_STORAGE LocalAddr = {0};
    SOCKADDR_STORAGE RemoteAddr = {0};
    DWORD dwLocalAddr = sizeof(LocalAddr);
    DWORD dwRemoteAddr = sizeof(RemoteAddr);

    ConnSocket = socket(AF_INET6, SOCK_STREAM, 0);
    if (ConnSocket == INVALID_SOCKET){
        return INVALID_SOCKET;
    }

    iResult = setsockopt(ConnSocket, IPPROTO_IPV6,
        IPV6_V6ONLY, (char*)&ipv6only, sizeof(ipv6only) );
    if (iResult == SOCKET_ERROR){
        closesocket(ConnSocket);
        return INVALID_SOCKET;       
    }

    // AddressList may contain IPv6 and/or IPv4Mapped addresses
    bSuccess = WSAConnectByList(ConnSocket,
            AddressList,
            &dwLocalAddr,
            (SOCKADDR*)&LocalAddr,
            &dwRemoteAddr,
            (SOCKADDR*)&RemoteAddr,
            NULL,
            NULL);
    if (bSuccess){
        return ConnSocket;
    } else {
        return INVALID_SOCKET;
    }
}

getaddrinfo

getaddrinfo 関数はまた、多くの関数の処理作業も実行します。 以前は、ソケットを作成して開き、そこにアドレスをバインドするには、いくつかの Windows ソケット関数の呼び出しが必要でした。 getaddrinfo 関数を使用すると、このような作業を実行するために必要なソース コードの行数を大幅に削減できます。 次の 2 つの例は、getaddrinfo 関数を使用した場合と使用しない場合のそれぞれで、これらのタスクを実行するために必要なソース コードを示しています。

getaddrinfo を使用してオープン、接続、バインドを実行する

#ifndef UNICODE
#define UNICODE
#endif

#define WIN32_LEAN_AND_MEAN

#include <winsock2.h>
#include <Ws2tcpip.h>
#include <stdio.h>

// Link with ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")

SOCKET OpenAndConnect(char *ServerName, char *PortName, int SocketType)
{
    SOCKET ConnSocket;
    ADDRINFO *AI;

    if (getaddrinfo(ServerName, PortName, NULL, &AI) != 0) {
        return INVALID_SOCKET;
    }

    ConnSocket = socket(AI->ai_family, SocketType, 0);
    if (ConnSocket == INVALID_SOCKET) {
        freeaddrinfo(AI);
        return INVALID_SOCKET;
    }

    if (connect(ConnSocket, AI->ai_addr, (int) AI->ai_addrlen) == SOCKET_ERROR) {
        closesocket(ConnSocket);
        freeaddrinfo(AI);
        return INVALID_SOCKET;
    }

    return ConnSocket;
}

getaddrinfo を使用せずにオープン、接続、バインドを実行する

#ifndef UNICODE
#define UNICODE
#endif

#define WIN32_LEAN_AND_MEAN

#include <winsock2.h>
#include <Ws2tcpip.h>
#include <stdio.h>

// Link with ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")

SOCKET OpenAndConnect(char *ServerName, unsigned short Port, int SocketType) 
{
    SOCKET ConnSocket;
    LPHOSTENT hp;
    SOCKADDR_IN ServerAddr;
    
    ConnSocket = socket(AF_INET, SocketType, 0); /* Open a socket */
    if (ConnSocket < 0 ) {
        return INVALID_SOCKET;
    }

    memset(&ServerAddr, 0, sizeof(ServerAddr));
    ServerAddr.sin_family = AF_INET;
    ServerAddr.sin_port = htons(Port);

    if (isalpha(ServerName[0])) {   /* server address is a name */
        hp = gethostbyname(ServerName);
        if (hp == NULL) {
            return INVALID_SOCKET;
        }
        ServerAddr.sin_addr.s_addr = (ULONG) hp->h_addr;
    } else { /* Convert nnn.nnn address to a usable one */
        ServerAddr.sin_addr.s_addr = inet_addr(ServerName);
    } 

    if (connect(ConnSocket, (LPSOCKADDR)&ServerAddr, 
        sizeof(ServerAddr)) == SOCKET_ERROR)
    {
        closesocket(ConnSocket);
        return INVALID_SOCKET;
    }

    return ConnSocket;
}

これらのソース コード例はどちらも同じタスクを実行しますが、getaddrinfo 関数を使用する最初の例は必要なソース コードの行数が少なく、IPv6 アドレスと IPv4 アドレスの両方を処理できることに注意してください。 getaddrinfo 関数を使用して削減されるソース コードの行数はさまざまです。

Note

運用ソース コードでは、アプリケーションは gethostbyname または getaddrinfo 関数によって返された一連のアドレスを反復処理します。 これらの例では、簡潔さを優先して、その手順が省略されています。

 

IPv6 をサポートするように既存の IPv4 アプリケーションを変更するときに対処する必要のある別の問題は、関数が呼び出される順序に関することです。 getaddrinfogethostbyname では、どちらも、一連の関数呼び出しが特定の順序で行われることが必要です。

IPv4 と IPv6 の両方が使用されるプラットフォームでは、リモート ホスト名のアドレス ファミリが事前に認識されていません。 そのため、リモート ホストの IP アドレスとアドレス ファミリを特定するために、まず getaddrinfo 関数を使用したアドレス解決を実行する必要があります。 その後、getaddrinfo によって返されたアドレス ファミリのソケットを開くために、socket 関数を呼び出すことができます。 多くの IPv4 アプリケーションは関数呼び出しの別の順序を使用する傾向があるため、これは Windows ソケット アプリケーションの記述方法における重要な変更です。

ほとんどの IPv4 アプリケーションでは、まず AF_INET アドレス ファミリ用のソケットを作成してから名前解決を行い、次にそのソケットを使用して、解決された IP アドレスに接続します。 このようなアプリケーションを IPv6 対応にする場合は、socket 関数呼び出しを、アドレス ファミリが特定されたときの名前解決の後まで遅らせる必要があります。 名前解決で IPv4 アドレスと IPv6 アドレスの両方が返された場合は、これらの宛先アドレスに接続するために IPv4 と IPv6 の個別のソケットを使用する必要があることに注意してください。 これらの複雑さはすべて、Windows Vista 以降では WSAConnectByName 関数を使用して回避できるため、アプリケーション開発者はこの新しい関数を使用することが推奨されます。

次のコード例は、まず名前解決を実行し (次のソース コード例の 4 行目で実行されています)、次にソケットを開く (次のコード例の 19 行目で実行されています) ための適切なシーケンスを示しています。 この例は、付録 B の「IPv6 対応クライアント コード」にある Client.c ファイルからの抜粋です。次のコード例で呼び出されている PrintError 関数は、Client.c サンプルに記載されています。

#ifndef UNICODE
#define UNICODE
#endif

#define WIN32_LEAN_AND_MEAN

#include <winsock2.h>
#include <Ws2tcpip.h>
#include <stdio.h>

// Link with ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")

SOCKET OpenAndConnect(char *Server, char *PortName, int Family, int SocketType)
{

    int iResult = 0;
    SOCKET ConnSocket = INVALID_SOCKET;

    ADDRINFO *AddrInfo = NULL;
    ADDRINFO *AI = NULL;
    ADDRINFO Hints;

    char *AddrName = NULL;

    memset(&Hints, 0, sizeof (Hints));
    Hints.ai_family = Family;
    Hints.ai_socktype = SocketType;

    iResult = getaddrinfo(Server, PortName, &Hints, &AddrInfo);
    if (iResult != 0) {
        printf("Cannot resolve address [%s] and port [%s], error %d: %s\n",
               Server, PortName, WSAGetLastError(), gai_strerror(iResult));
        return INVALID_SOCKET;
    }
    //
    // Try each address getaddrinfo returned, until we find one to which
    // we can successfully connect.
    //
    for (AI = AddrInfo; AI != NULL; AI = AI->ai_next) {

        // Open a socket with the correct address family for this address.
        ConnSocket = socket(AI->ai_family, AI->ai_socktype, AI->ai_protocol);
        if (ConnSocket == INVALID_SOCKET) {
            printf("Error Opening socket, error %d\n", WSAGetLastError());
            continue;
        }
        //
        // Notice that nothing in this code is specific to whether we 
        // are using UDP or TCP.
        //
        // When connect() is called on a datagram socket, it does not 
        // actually establish the connection as a stream (TCP) socket
        // would. Instead, TCP/IP establishes the remote half of the
        // (LocalIPAddress, LocalPort, RemoteIP, RemotePort) mapping.
        // This enables us to use send() and recv() on datagram sockets,
        // instead of recvfrom() and sendto().
        //

        printf("Attempting to connect to: %s\n", Server ? Server : "localhost");
        if (connect(ConnSocket, AI->ai_addr, (int) AI->ai_addrlen) != SOCKET_ERROR)
            break;

        if (getnameinfo(AI->ai_addr, (socklen_t) AI->ai_addrlen, AddrName,
                        sizeof (AddrName), NULL, 0, NI_NUMERICHOST) != 0) {
            strcpy_s(AddrName, sizeof (AddrName), "<unknown>");
            printf("connect() to %s failed with error %d\n", AddrName, WSAGetLastError());
            closesocket(ConnSocket);
            ConnSocket = INVALID_SOCKET;
        }    
    }
    return ConnSocket;
}

IP ヘルパー関数

最後に、IP ヘルパー関数 GetAdaptersInfo とそれに関連付けられた構造体 IP_ADAPTER_INFO を使用するアプリケーションは、この関数と構造体の両方が IPv4 アドレスに制限されていることを認識する必要があります。 この関数と構造体の IPv6 対応の置き換えは、GetAdaptersAddresses 関数と IP_ADAPTER_ADDRESSES 構造体です。 IP ヘルパー API を使用する IPv6 対応アプリケーションは、GetAdaptersAddresses 関数とそれに対応する IPv6 対応 IP_ADAPTER_ADDRESSES 構造体 (どちらも、Microsoft Windows ソフトウェア開発キット (SDK) で定義されています) を使用する必要があります。

推奨事項

アプリケーションで IPv6 互換の関数呼び出しが確実に使用されるようにするための最適なアプローチは、getaddrinfo 関数を使用してホストからアドレスへの変換を取得することです。 Windows XP 以降では、getaddrinfo 関数によって gethostbyname 関数は不必要になったため、将来のプログラミング プロジェクトでは、アプリケーションは代わりに getaddrinfo 関数を使用する必要があります。 Microsoft では gethostbyname を引き続きサポートしますが、この関数が IPv6 をサポートするように拡張されることはありません。 IPv6 と IPv4 のホスト情報を取得するための透過的なサポートを受けるには、getaddrinfo を使用する必要があります。

次の例は、getaddrinfo 関数を最適に使用する方法を示しています。 この関数は、この例で示すように正しく使用されると、IPv6 と IPv4 の両方のホストからアドレスへの変換を正しく処理しますが、ホストに関するその他の役立つ情報 (サポートされているソケットの種類など) も取得することに注意してください。 このサンプルは、付録 B にある Client.c サンプルからの抜粋です。

#ifndef UNICODE
#define UNICODE
#endif

#define WIN32_LEAN_AND_MEAN

#include <winsock2.h>
#include <Ws2tcpip.h>
#include <stdio.h>

// Link with ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")

int ResolveName(char *Server, char *PortName, int Family, int SocketType)
{

    int iResult = 0;

    ADDRINFO *AddrInfo = NULL;
    ADDRINFO *AI = NULL;
    ADDRINFO Hints;

   //
    // By not setting the AI_PASSIVE flag in the hints to getaddrinfo, we're
    // indicating that we intend to use the resulting address(es) to connect
    // to a service.  This means that when the Server parameter is NULL,
    // getaddrinfo will return one entry per allowed protocol family
    // containing the loopback address for that family.
    //
    
    memset(&Hints, 0, sizeof(Hints));
    Hints.ai_family = Family;
    Hints.ai_socktype = SocketType;
    iResult = getaddrinfo(Server, PortName, &Hints, &AddrInfo);
    if (iResult != 0) {
        printf("Cannot resolve address [%s] and port [%s], error %d: %s\n",
               Server, PortName, WSAGetLastError(), gai_strerror(iResult));
        return SOCKET_ERROR;
    }
     return 0;
}

Note

IPv6 をサポートする getaddrinfo 関数のバージョンは、Windows の Windows XP リリースの新機能です。

 

回避するコード

ホスト アドレス変換は、従来から gethostbyname 関数を使用して実現されきました。 Windows XP 以降では、次のとおりです。

  • getaddrinfo 関数により gethostbyname 関数は廃止されます。
  • アプリケーションでは、gethostbyname 関数の代わりに getaddrinfo 関数を使用する必要があります。

コーディング タスク

IPv6 のサポートを追加するように既存の IPv4 アプリケーションを変更するには

  1. Checkv4.exe ユーティリティを取得します。 このユーティリティは、Windows SDK の一部としてインストールされます。 以前のバージョンの Checkv4.exe ツールは、Windows 2000 の Microsoft IPv6 テクノロジ プレビューの一部としても含まれていました。
  2. コードに対して Checkv4.exe ユーティリティを実行します。 ファイルに対してバージョン ユーティリティを実行する方法については、「Checkv4.exe ユーティリティの使用」を参照してください。
  3. このユーティリティは、gethostbynamegethostbyaddr、その他の IPv4 専用関数の使用について警告し、それらを getaddrinfogetnameinfo などの IPv6 互換関数に置き換える方法に関する推奨事項を提供します。
  4. gethostbyname 関数のすべてのインスタンスと、必要に応じてそれに関連するコードを getaddrinfo 関数に置き換えます。 Windows Vista では、必要に応じて WSAConnectByName または WSAConnectByList 関数を使用します。
  5. gethostbyaddr 関数のすべてのインスタンスと、必要に応じてそれに関連するコードを getnameinfo 関数に置き換えます。

あるいは、コード ベースに gethostbyname および gethostbyaddr 関数のインスタンスがないかどうかを検索し、このようなすべての使用を (必要に応じて、その他の関連するコードも) getaddrinfo および getnameinfo 関数に変更することもできます。

Windows ソケット アプリケーションの IPv6 ガイド

IPv6 Winsock アプリケーションのデータ構造の変更

IPv6 Winsock アプリケーション用のデュアル スタック ソケット

ハードコードされた IPv4 アドレスの使用

IPv6 Winsock アプリケーションのユーザー インターフェイスに関する問題

IPv6 Winsock アプリケーションの基になるプロトコル