Düzenle

Aracılığıyla paylaş


Function Calls for IPv6 Winsock Applications

New functions have been introduced to the Windows Sockets interface specifically designed to make Windows Sockets programming easier. One of the benefits of these new Windows Sockets functions is integrated support for both IPv6 and IPv4.

These new Windows Sockets functions include the following:

In addition, new IP Helper functions with support for both IPv6 and IPv4 have been added to simplify Windows Sockets programming. These new IP Helper functions include the following:

When adding IPv6 support to an application the following guidelines should be used:

  • Use WSAConnectByName to establish a connection to an endpoint given a host name and port. The WSAConnectByName function is available on Windows Vista and later.
  • Use WSAConnectByList to establish a connection to one out of a collection of possible endpoints represented by a set of destination addresses (host names and ports). The WSAConnectByList function is available on Windows Vista and later.
  • Replace gethostbyname function calls with calls to one of the new getaddrinfo Windows Sockets functions. The getaddrinfo function with support for the IPv6 protocol is available on Windows XP and later. The IPv6 protocol is also supported on Windows 2000 when the IPv6 Technology Preview for Windows 2000 is installed.
  • Replace gethostbyaddr function calls with calls to one of the new getnameinfo Windows Sockets functions. The getnameinfo function with support for the IPv6 protocol is available on Windows XP and later. The IPv6 protocol is also supported on Windows 2000 when the IPv6 Technology Preview for Windows 2000 is installed.

WSAConnectByName

The WSAConnectByName function simplifies connecting to an endpoint using a stream-based socket given the destination's hostname or IP address (IPv4 or IPv6). This function reduces the source code required to create an IP application that is agnostic to the version of the IP protocol used. WSAConnectByName replaces the following steps in a typical TCP application to a single function call:

  • Resolve a hostname to a set of IP addresses.
  • For each IP address:
    • Create a socket of the appropriate address family.
    • Attempts to connect to the remote IP address. If the connection was successful, it returns; otherwise the next remote IP address for the host is tried.

The WSAConnectByName function goes beyond just resolving the name and then attempting to connect. The function uses all of the remote addresses returned by name resolution and all of the local machine's source IP addresses. It first attempts to connect using address pairs with the highest chance of success. Therefore, WSAConnectByName not only ensures that a connection will be established if possible, but it also minimizes the time to establish the connection.

To enable both IPv6 and IPv4 communications, use the following method:

  • The setsockopt function must be called on a socket created for the AF_INET6 address family to disable the IPV6_V6ONLY socket option before calling WSAConnectByName. This is accomplished by calling the setsockopt function on the socket with the level parameter set to IPPROTO_IPV6 (see IPPROTO_IPV6 Socket Options), the optname parameter set to IPV6_V6ONLY, and the optvalue parameter value set to zero.

If an application needs to bind to a specific local address or port, then WSAConnectByName cannot be used since the socket parameter to WSAConnectByName must be an unbound socket.

The code example below shows only a few lines of code are needed to use this function to implement an application that is agnostic to the IP version.

Establish a connection using 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

The WSAConnectByList function establishes a connection to a host given a set of possible hosts (represented by a set of destination IP addresses and ports). The function takes all IP addresses and ports for the endpoint and all of the local machine's IP addresses, and attempts a connection using all possible address combinations.

WSAConnectByList is related to the WSAConnectByName function. Instead of taking a single hostname, WSAConnectByList accepts a list of hosts (destination addresses and port pairs) and connects to one of the addresses and ports in the provided list. This function is designed to support scenarios in which an application needs to connect to any available host out of a list of potential hosts.

Similar to WSAConnectByName, the WSAConnectByList function significantly reduces the source code required to create, bind and connect a socket. This function makes it much easier to implement an application that is agnostic to the IP version. The list of addresses for a host accepted by this function may be IPv6 or IPv4 addresses.

To enable both IPv6 and IPv4 addresses to be passed in the single address list accepted by the function, the following steps must be performed prior to calling the function:

  • The setsockopt function must be called on a socket created for the AF_INET6 address family to disable the IPV6_V6ONLY socket option before calling WSAConnectByList. This is accomplished by calling the setsockopt function on the socket with the level parameter set to IPPROTO_IPV6 (see IPPROTO_IPV6 Socket Options), the optname parameter set to IPV6_V6ONLY, and the optvalue parameter value set to zero.
  • Any IPv4 addresses must be represented in the IPv4-mapped IPv6 address format which enables an IPv6 only application to communicate with an IPv4 node. The IPv4-mapped IPv6 address format allows the IPv4 address of an IPv4 node to be represented as an IPv6 address. The IPv4 address is encoded into the low-order 32 bits of the IPv6 address, and the high-order 96 bits hold the fixed prefix 0:0:0:0:0:FFFF. The IPv4-mapped IPv6 address format is specified in RFC 4291. For more information, see www.ietf.org/rfc/rfc4291.txt. The IN6ADDR_SETV4MAPPED macro in Mstcpip.h can be used to convert an IPv4 address to the required IPv4-mapped IPv6 address format.

Establish a Connection Using 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

The getaddrinfo function also performs the processing work of many functions. Previously, calls to a number of Windows Sockets functions were required to create, open, and then bind an address to a socket. With the getaddrinfo function, the lines of source code necessary to perform such work can be significantly reduced. The following two examples illustrate the source code necessary to perform these tasks with and without the getaddrinfo function.

Perform an Open, Connect, and Bind Using 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;
}

Perform an Open, Connect, and Bind Without Using 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;
}

Notice that both of these source code examples perform the same tasks, but the first example, using the getaddrinfo function, requires fewer lines of source code, and can handle either IPv6 or IPv4 addresses. The number of lines of source code eliminated by using the getaddrinfo function varies.

Note

In production source code, your application would iterate through the set of addresses returned by the gethostbyname or getaddrinfo function. These examples omit that step in favor of simplicity.

 

Another issue you must address when modifying an existing IPv4 application to support IPv6 is associated with the order in which functions are called. Both getaddrinfo and gethostbyname require that a sequence of function calls are made in a specific order.

On platforms where both IPv4 and IPv6 are used, the address family of the remote host name is not known ahead of time. So address resolution using the getaddrinfo function must be executed first to determine the IP address and address family of the remote host. Then the socket function can be called to open a socket of the address family returned by getaddrinfo. This is an important change in how Windows Sockets applications are written, since many IPv4 applications tend to use a different order of function calls.

Most IPv4 applications first create a socket for the AF_INET address family, then do name resolution, and then use the socket to connect to the resolved IP address. When making such applications IPv6-capable, the socket function call must be delayed until after name resolution when the address family has been deteremined. Note that if name resolution returns both IPv4 and IPv6 addresses, then separate IPv4 and IPv6 sockets must be used to connect to these destination addresses. All of these complexities can be avoided by using the WSAConnectByName function on Windows Vista and later, so application developers are encouraged to use this new function.

The following code example shows the proper sequence for performing name resolution first (performed in the fourth line in the following source code example), then opening a socket (performed in the 19th line in the following code example). This example is an excerpt from the Client.c file found in the IPv6-Enabled Client Code in Appendix B. The PrintError function called in the following code example is listed in the Client.c sample.

#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 Helper Functions

Finally, applications making use of the IP Helper function GetAdaptersInfo, and its associated structure IP_ADAPTER_INFO, must recognize that both this function and structure are limited to IPv4 addresses. IPv6-enabled replacements for this function and structure are the GetAdaptersAddresses function and the IP_ADAPTER_ADDRESSES structure. IPv6-enabled applications making use of the IP Helper API should use the GetAdaptersAddresses function and the corresponding IPv6-enabled IP_ADAPTER_ADDRESSES structure, both defined in the Microsoft Windows Software Development Kit (SDK).

Recommendations

The best approach to ensure your application is using IPv6-compatible function calls is to use the getaddrinfo function for obtaining host-to-address translation. Beginning with Windows XP, the getaddrinfo function makes the gethostbyname function unnecessary, and your application should therefore use the getaddrinfo function instead for future programming projects. While Microsoft will continue to support gethostbyname, this function will not be extended to support IPv6. For transparent support for obtaining IPv6 and IPv4 host information, you must use getaddrinfo.

The following example shows how to best use the getaddrinfo function. Notice that the function, when used properly as this example demonstrates, handles both IPv6 and IPv4 host-to-address translation properly, but it also obtains other useful information about the host, such as the type of sockets supported. This sample is an excerpt from the Client.c sample found in Appendix B.

#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

The version of the getaddrinfo function that supports IPv6 is new for the Windows XP release of Windows.

 

Code to Avoid

Host address translation has traditionally been achieved using the gethostbyname function. Beginning with Windows XP:

  • The getaddrinfo function makes the gethostbyname function obsolete.
  • Your applications should use the getaddrinfo function instead of the gethostbyname function.

Coding Tasks

To modify an existing IPv4 application to add support for IPv6

  1. Acquire the Checkv4.exe utility. This utility is installed as part of the Windows SDK. An older version of the Checkv4.exe tool was also as included as part of the Microsoft IPv6 Technology Preview for Windows 2000.
  2. Run the Checkv4.exe utility against your code. See Using the Checkv4.exe Utility to learn about running the version utility against your files.
  3. The utility alerts you to usage of the gethostbyname, gethostbyaddr, and other IPv4-only functions, and provides recommendations on how to replace them with the IPv6-compatible function such as getaddrinfo and getnameinfo.
  4. Replace any instances of the gethostbyname function, and associated code as appropriate, with the getaddrinfo function. On Windows Vista, use the WSAConnectByName or WSAConnectByList function when appropriate.
  5. Replace any instances of the gethostbyaddr function, and associated code as appropriate, with the getnameinfo function.

Alternatively, you can search your code base for instances of the gethostbyname and gethostbyaddr functions, and change all such usage (and other associated code, as appropriate) to the getaddrinfo and getnameinfo functions.

IPv6 Guide for Windows Sockets Applications

Changing Data Structures for IPv6 Winsock Applications

Dual-Stack Sockets for IPv6 Winsock Applications

Use of Hardcoded IPv4 Addresses

User Interface Issues for IPv6 Winsock Applications

Underlying Protocols for IPv6 Winsock Applications