Llamadas de función para aplicaciones Winsock IPv6

Se han introducido nuevas funciones en la interfaz de Windows Sockets diseñadas específicamente para facilitar la programación de Windows Sockets. Una de las ventajas de estas nuevas funciones de Windows Sockets es la compatibilidad integrada con IPv6 e IPv4.

Estas nuevas funciones de Windows Sockets incluyen lo siguiente:

Además, se han agregado nuevas funciones del asistente de IP compatibles con IPv6 e IPv4 para simplificar la programación de Windows Sockets. Estas nuevas funciones del asistente de IP incluyen lo siguiente:

Al agregar compatibilidad con IPv6 a una aplicación, se deben usar las siguientes instrucciones:

  • Utilice la función WSAConnectByName para establecer una conexión a un punto de conexión dado un nombre de host y un puerto. La función WSAConnectByName está disponible en Windows Vista y versiones posteriores.
  • Utilice la función WSAConnectByList para establecer una conexión a una de una colección de posibles puntos de conexión representados por un conjunto de direcciones de destino (nombres de host y puertos). La función WSAConnectByList está disponible en Windows Vista y versiones posteriores.
  • Reemplace las llamadas de función gethostbyname por llamadas a una de las nuevas funciones getaddrinfo de Windows Sockets. La función getaddrinfo con compatibilidad con el protocolo IPv6 está disponible en Windows XP y versiones posteriores. El protocolo IPv6 también se admite en Windows 2000 cuando está instalado IPv6 Technology Preview para Windows 2000.
  • Reemplace las llamadas de función gethostbyaddr por llamadas a una de las nuevas funciones getnameinfo de Windows Sockets. La función getnameinfo con compatibilidad con el protocolo IPv6 está disponible en Windows XP y versiones posteriores. El protocolo IPv6 también se admite en Windows 2000 cuando está instalado IPv6 Technology Preview para Windows 2000.

WSAConnectByName

La función WSAConnectByName simplifica la conexión a un punto de conexión mediante un socket basado en secuencias según el nombre de host o la dirección IP del destino (IPv4 o IPv6). Esta función reduce el código fuente necesario para crear una aplicación IP independiente de la versión del protocolo IP usado. WSAConnectByName reemplaza los pasos siguientes en una aplicación TCP típica a una sola llamada de función:

  • Resuelva un nombre de host en un conjunto de direcciones IP.
  • Para cada dirección IP:
    • Cree un socket de la familia de direcciones adecuada.
    • Intenta conectarse a la dirección IP remota. Si la conexión ha tenido éxito, vuelve; en caso contrario, se intenta la siguiente dirección IP remota para el host.

La función WSAConnectByName va más allá de simplemente resolver el nombre y, a continuación, intentar conectarse. La función usa todas las direcciones remotas devueltas por resolución de nombres y todas las direcciones IP de origen del equipo local. Primero intenta conectarse mediante pares de direcciones con la mayor probabilidad de éxito. Por lo tanto, WSAConnectByName no solo garantiza que se establezca una conexión si es posible, sino que también minimiza el tiempo para establecer la conexión.

Para habilitar las comunicaciones IPv6 e IPv4, use el método siguiente:

  • Se debe llamar a la función setsockopt en un socket creado para la familia de direcciones de AF_INET6 para deshabilitar la opción de socket de IPV6_V6ONLY antes de llamar a WSAConnectByName. Esto se logra llamando a la función setsockopt en el socket con el parámetro level establecido en IPPROTO_IPV6 (consulte Opciones del socket IPPROTO_IPV6), el parámetro optname establecido en IPV6_V6ONLY y el valor del parámetro optvalue establecido en cero.

Si una aplicación necesita enlazarse a una dirección o puerto local específico, no se puede usar WSAConnectByName, ya que el parámetro de socket para WSAConnectByName debe ser un socket sin enlazar.

En el ejemplo de código siguiente se muestran solo algunas líneas de código necesarias para usar esta función para implementar una aplicación independiente de la versión de IP.

Establecimiento de una conexión mediante 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

La función WSAConnectByList establece una conexión con un host dado un conjunto de posibles hosts (representados por un conjunto de puertos y direcciones IP de destino). La función toma todas las direcciones IP y los puertos para el punto de conexión y todas las direcciones IP del equipo local e intenta una conexión con todas las combinaciones de direcciones posibles.

WSAConnectByList está relacionado con la función WSAConnectByName. En lugar de tomar un único nombre de host, WSAConnectByList acepta una lista de hosts (direcciones de destino y pares de puertos) y se conecta a una de las direcciones y puertos de la lista proporcionada. Esta función está diseñada para admitir escenarios en los que una aplicación debe conectarse a cualquier host disponible de una lista de posibles hosts.

De forma similar a WSAConnectByName, la función WSAConnectByList reduce significativamente el código fuente necesario para crear, enlazar y conectar un socket. Esta función facilita mucho la implementación de una aplicación independiente de la versión de IP. La lista de direcciones de un host aceptado por esta función puede ser direcciones IPv6 o IPv4.

Para permitir que las direcciones IPv6 e IPv4 se pasen en la lista de direcciones única aceptada por la función, se deben realizar los pasos siguientes antes de llamar a la función:

  • Se debe llamar a la función setsockopt en un socket creado para la familia de direcciones AF_INET6 para deshabilitar la opción de socket de IPV6_V6ONLY antes de llamar a WSAConnectByList. Esto se logra llamando a la función setsockopt en el socket con el parámetro level establecido en IPPROTO_IPV6 (consulte Opciones del socket IPPROTO_IPV6), el parámetro optname establecido en IPV6_V6ONLY y el valor del parámetro optvalue establecido en cero.
  • Todas las direcciones IPv4 deben representarse en el formato de dirección IPv4 asignado a IPv6, lo que permite que una aplicación IPv6 solo se comunique con un nodo IPv4. El formato de dirección IPv4 asignado a IPv6 permite representar la dirección IPv4 de un nodo IPv4 como una dirección IPv6. La dirección IPv4 se codifica en los 32 bits de orden inferior de la dirección IPv6 y los 96 bits de orden superior contienen el prefijo fijo 0:0:0:0:0:0:0:FFFF. El formato de dirección IPv4 asignado a IPv6 se especifica en RFC 4291. Para más información, consulte www.ietf.org/rfc/rfc4291.txt. La macro IN6ADDR_SETV4MAPPED en Mstcpip.h se puede usar para convertir una dirección IPv4 al formato de dirección IPv6 asignado a IPv4 necesario.

Establecimiento de una conexión mediante 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

La función getaddrinfo también realiza el trabajo de procesamiento de muchas funciones. Anteriormente, se requerían llamadas a varias funciones de Windows Sockets para crear, abrir y, a continuación, enlazar una dirección a un socket. Con la función getaddrinfo, las líneas de código fuente necesarias para realizar este trabajo se pueden reducir significativamente. En los dos ejemplos siguientes se muestra el código fuente necesario para realizar estas tareas con y sin la función getaddrinfo.

Realización de una operación Open, Connect y Bind mediante 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;
}

Realización de una operación Open, Connect y Bind sin 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;
}

Tenga en cuenta que ambos ejemplos de código fuente realizan las mismas tareas, pero el primer ejemplo, mediante la función getaddrinfo, requiere menos líneas de código fuente y puede controlar las direcciones IPv6 o IPv4. El número de líneas de código fuente eliminados mediante la función getaddrinfo varía.

Nota:

En el código fuente de producción, la aplicación recorrería en iteración el conjunto de direcciones devueltas por la función gethostbyname o getaddrinfo. Estos ejemplos omiten ese paso a favor de la simplicidad.

 

Otro problema que debe solucionar al modificar una aplicación IPv4 existente para admitir IPv6 está asociado al orden en el que se llama a las funciones. Tanto getaddrinfo como gethostbyname requieren que se realice una secuencia de llamadas de función en un orden específico.

En las plataformas en las que se usan IPv4 e IPv6, la familia de direcciones del nombre de host remoto no se conoce con antelación. Por lo tanto, la resolución de direcciones que usa la función getaddrinfo debe ejecutarse primero para determinar la dirección IP y la familia de direcciones del host remoto. A continuación, se puede llamar a la función socket para abrir un socket de la familia de direcciones devuelta por getaddrinfo. Este es un cambio importante en la forma en que se escriben las aplicaciones de Windows Sockets, ya que muchas aplicaciones IPv4 tienden a usar un orden diferente de llamadas de función.

La mayoría de las aplicaciones IPv4 crean primero un socket para la familia de direcciones AF_INET, luego realizan la resolución de nombres y, a continuación, usan el socket para conectarse a la dirección IP resuelta. Al realizar estas aplicaciones compatibles con IPv6, la llamada a la función de socket debe retrasarse hasta después de la resolución de nombres cuando se haya determinado la familia de direcciones. Tenga en cuenta que si la resolución de nombres devuelve direcciones IPv4 e IPv6, se deben usar sockets IPv4 e IPv6 independientes para conectarse a estas direcciones de destino. Todas estas complejidades se pueden evitar mediante el uso de la función WSAConnectByName en Windows Vista y versiones posteriores, por lo que se recomienda a los desarrolladores de aplicaciones que usen esta nueva función.

En el ejemplo de código siguiente se muestra la secuencia adecuada para realizar primero la resolución de nombres (realizada en la cuarta línea del siguiente ejemplo de código fuente), y luego se abre un socket (realizado en la línea 19 del ejemplo de código siguiente). Este ejemplo es un extracto del archivo Client.c que se encuentra en el código de cliente habilitado para IPv6 en el apéndice B. La función PrintError llamada en el ejemplo de código siguiente se muestra en el ejemplo 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;
}

Funciones de aplicación auxiliar IP

Por último, las aplicaciones que usan la función del asistente de IP GetAdaptersInfo y su estructura asociada IP_ADAPTER_INFO, deben reconocer que esta función y estructura están limitadas a direcciones IPv4. Los reemplazos habilitados para IPv6 para esta función y estructura son la función GetAdaptersAddresses y la estructura IP_ADAPTER_ADDRESSES. Las aplicaciones habilitadas para IPv6 que usan la API del asistente de IP deben usar la función GetAdaptersAddresses y la estructura IP_ADAPTER_ADDRESSES habilitada para IPv6 correspondiente, ambas definidas en el Kit de desarrollo de software (SDK) de Microsoft Windows.

Recomendaciones

El mejor enfoque para asegurarse de que la aplicación usa llamadas de función compatibles con IPv6 es usar la función getaddrinfo para obtener la traducción de host a dirección. A partir de Windows XP, la función getaddrinfo hace que la función gethostbyname no sea necesaria y, por tanto, la aplicación debe usar la función getaddrinfo en su lugar para proyectos de programación futuros. Aunque Microsoft seguirá admitiendo gethostbyname, esta función no se extenderá para admitir IPv6. Para obtener información de host IPv6 e IPv4 transparente, debe usar getaddrinfo.

En el ejemplo siguiente se muestra cómo usar mejor la función getaddrinfo. Observe que la función, cuando se usa correctamente como se muestra en este ejemplo, controla correctamente la traducción de host a dirección IPv6 e IPv4, pero también obtiene otra información útil sobre el host, como el tipo de sockets admitidos. Este ejemplo es un extracto del ejemplo Client.c que se encuentra en el Apéndice 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;
}

Nota:

La versión de la función getaddrinfo que admite IPv6 es nueva para la versión de Windows XP de Windows.

 

Código que se va a evitar

La traducción de direcciones de host se ha logrado tradicionalmente mediante la función gethostbyname. A partir de Windows XP:

  • La función getaddrinfo hace que la función gethostbyname esté obsoleta.
  • Las aplicaciones deben usar la función getaddrinfo en lugar de la función gethostbyname.

Tareas de codificación

Para modificar una aplicación IPv4 existente para agregar compatibilidad con IPv6

  1. Adquiera la utilidad Checkv4.exe. Esta utilidad se instala como parte de Windows SDK. Una versión anterior de la herramienta Checkv4.exe también se incluyó como parte de Microsoft IPv6 Technology Preview para Windows 2000.
  2. Ejecute la utilidad Checkv4.exe en el código. Consulte Uso de la utilidad Checkv4.exe para obtener información sobre cómo ejecutar la utilidad de versión en los archivos.
  3. La utilidad le avisa del uso de gethostbyname, gethostbyaddr y otras funciones solo IPv4, y proporciona recomendaciones sobre cómo reemplazarlas por la función compatible con IPv6, como getaddrinfo y getnameinfo.
  4. Reemplace las instancias de la función gethostbyname y el código asociado según corresponda por la función getaddrinfo. En Windows Vista, use la función WSAConnectByName o WSAConnectByList cuando corresponda.
  5. Reemplace las instancias de la función gethostbyaddr y el código asociado según corresponda por la función getnameinfo.

Como alternativa, puede buscar en la base de código las instancias de las funciones gethostbyname y gethostbyaddr, y cambiar todo este uso (y otro código asociado, según corresponda) a las funciones getaddrinfo y getnameinfo.

Guía de IPv6 para aplicaciones de Windows Sockets

Cambio de estructuras de datos para aplicaciones Winsock IPv6

Sockets de doble pila para aplicaciones Winsock IPv6

Uso de direcciones IPv4 codificadas de forma rígida

Problemas de la interfaz de usuario para aplicaciones Winsock IPv6

Protocolos subyacentes para aplicaciones Winsock IPv6