Chamadas de função para Aplicativos Winsock IPv6

Novas funções foram introduzidas na interface do Windows Sockets especificamente projetadas para facilitar a programação. Um dos benefícios dessas novas funções do Windows Sockets é o suporte integrado para IPv6 e IPv4.

Essas novas funções do Windows Sockets incluem o seguinte:

Além disso, novas funções auxiliares de IP com suporte para IPv6 e IPv4 foram adicionadas para simplificar a programação. Essas novas funções auxiliares de IP incluem o seguinte:

Ao adicionar suporte IPv6 a um aplicativo, as diretrizes a seguir devem ser usadas:

  • Use WSAConnectByName para estabelecer uma conexão com um terminal com um nome de host e uma porta. A função WSAConnectByName está disponível no Windows Vista e posterior.
  • Use para estabelecer uma conexão com um dentre possíveis terminais representados por um conjunto de endereços de destino (nomes de host e portas) WSAConnectByList. A função está disponível no Windows Vista e posterior WSAConnectByList.
  • Substitua chamadas de função gethostbyname por chamadas para uma das novas funções do Windows Sockets getaddrinfo. A função com suporte para o protocolo IPv6 está disponível no Windows XP e posterior getaddrinfo. O protocolo IPv6 também é compatível com Windows 2000 quando está instalada a Visualização da Tecnologia IPv6 para Windows 2000.
  • Substitua chamadas de função por chamadas para uma das novas funções do Windows Sockets gethostbyaddr getnameinfo. A função com suporte para o protocolo IPv6 está disponível no Windows XP e posterior getnameinfo. O protocolo IPv6 também é compatível com Windows 2000 quando está instalada a Visualização de Tecnologia IPv6 para Windows 2000.

WSAConnectByName

A função simplifica a conexão a um terminal usando um soquete baseado em fluxo dado o nome do host ou endereço IP do destino (IPv4 ou IPv6) WSAConnectByName. Essa função reduz o código-fonte necessário para criar um aplicativo IP independente da versão do protocolo IP utilizado. WSAConnectByName substitui as seguintes etapas, em um aplicativo TCP típico, para uma única chamada de função:

  • Resolver um nome de host para um conjunto de endereços IP.
  • Para cada endereço IP:
    • Crie um soquete da família de endereços apropriada.
    • Tente se conectar ao endereço IP remoto. Se a conexão for bem-sucedida, ela retornará. Caso contrário, será tentado o próximo endereço IP remoto para o host.

A função é mais do que apenas resolver o nome e tentar se conectar WSAConnectByName. A função utiliza todos os endereços remotos retornados pela resolução de nomes e todos os endereços IP de origem do computador local. Primeiro tenta se conectar usando pares de endereços com maior chance de sucesso. Portanto, não apenas garante que uma conexão possível seja estabelecida, como também minimiza o tempo para estabelecer a conexão WSAConnectByName.

Para habilitar as comunicações IPv6 e IPv4, use o seguinte método:

  • A função deve ser chamada em um soquete criado para a família de endereços AF_INET6 para desabilitar a opção de soquete setsockopt d IPV6_V6ONLY antes de chamar WSAConnectByName. Isso é feito chamando a função setsockopt no soquete com o parâmetro level definido como IPPROTO_IPV6 (consulte IPPROTO_IPV6 Socket Options), o parâmetro optname definido como IPV6_V6ONLY, e o valor do parâmetro optvalue definido como zero.

Se um aplicativo precisar se associar a um endereço ou porta local específicos então não poderá ser usado, pois o parâmetro de soquete deve ser um soquete não associado WSAConnectByName WSAConnectByName.

O exemplo de código abaixo demonstra que apenas algumas linhas de código são necessárias para a função para implementar um aplicativo que seja independente para a versão do IP.

Estabelecer uma conexão usando 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

A função estabelece uma conexão com um host dado um conjunto de hosts possíveis (representado por um conjunto de portas e endereços IP de destino) WSAConnectByList. A função usa todos os endereços IP e portas para o terminal, todos os endereços IP do computador local e tenta uma conexão usando todas as combinações possíveis de endereço.

WSAConnectByList está relacionado à função WSAConnectByName. Em vez de usar um único nome de host, aceita uma lista de hosts (endereços de destino e pares de porta) e se conecta a um dos endereços e portas na lista fornecida WSAConnectByList. Essa função foi projetada para dar suporte em cenários nos quais um aplicativo precisa se conectar a qualquer host disponível de uma lista potencial de hosts.

Semelhante a função reduz significativamente o código-fonte necessário para criar, associar e conectar um soquete WSAConnectByName, WSAConnectByList. Essa função facilita muito a implementação de um aplicativo independente para a versão do IP. A lista de endereços de um host aceita por essa função pode ser de endereços IPv6 ou IPv4.

Para habilitar que ambos os endereços IPv6 e IPv4 sejam passados na lista única aceita pela função, as seguintes etapas devem ser executadas antes de chamar a função:

  • A função deve ser chamada em um soquete criado para a família de endereços AF_INET6 para desabilitar a opção de soquete antes de chamar setsockopt IPV6_V6ONLY WSAConnectByList. Isso é feito chamando a função definido no soquete com o parâmetro nível ssetsockopt IPPROTO_IPV6 (consulte Opções de Soquete IPPROTO_IPV6 Opções), o parâmetro definido como optname IPV6_V6ONLY e o valor do parâmetro definido como zero optvalue.
  • Todos os endereços IPv4 devem ser representados no formato IPv6 mapeado por IPv4, que permite que um aplicativo IPv6 somente se comunique com um IPv4. O formato de endereço IPv6 mapeado para IPv4 permite que o endereço IPv4 de um nó IPv4 seja representado como um endereço IPv6. O endereço IPv4 é codificado nos 32 bits de ordem baixa do endereço IPv6 e os 96 bits de ordem alta contêm o prefixo fixo 0:0:0:0:0:FFFF. O formato de endereço mapeado IPv6 por IPv4 é especificado no RFC 4291. Para obter mais informações, consulte www.ietf.org/rfc/rfc4291.txt. A macro IN6ADDR_SETV4MAPPED pode ser usada para converter um endereço IPv4 no formato necessário de endereço IPv6 mapeado IPv4 Mstcpip.h.

Estabelecer uma conexão usando 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

A função também executa o trabalho de processamento de várias funções getaddrinfo. Antesas chamadas do Windows Sockets para várias funções eram necessárias para criar, abrir e associar um endereço a um soquete. Com a função as linhas do código-fonte necessárias para executar esse trabalho podem ser significativamente reduzidas getaddrinfo. Os dois exemplos a seguir mostram o código-fonte necessário para executar essas tarefas, com e sem a função getaddrinfo.

Executar um Abrir, Conectar e Associar usando 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;
}

Executar um Abrir, Conectar e Associar sem usar 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;
}

Observe que esses dois exemplos de código-fonte executam as mesmas tarefas, mas o primeiro exemplo, usando a função requer menos linhas de código-fonte e pode manipular endereços IPv6 ou IPv4 getaddrinfo. O número de linhas de código-fonte eliminadas usando a função varia getaddrinfo.

Observação:

No código-fonte de produção, seu aplicativo iteraria por meio do conjunto de endereços retornados pela função gethostbyname ou getaddrinfo function. Esses exemplos ocultam essa etapa em favor da simplicidade.

 

Outro problema que você deve resolver ao modificar um aplicativo IPv4 existente para dar suporte ao IPv6 é associar a ordem na qual as funções são chamadas. Tanto quanto exigem que uma sequência de chamadas de função seja feita em uma ordem específica getaddrinfo gethostbyname.

Em plataformas em que iPv4 e IPv6 são usados, não é conhecida com antecedência a família de endereços do nome do host remoto. Portanto, a resolução de endereços usando a função deve ser executada primeiro para determinar o endereço IP e a família de endereços do host remoto getaddrinfo. Depois, a função socket pode ser chamada para abrir um soquete da família de endereços retornada por getaddrinfo. Essa é uma alteração importante na forma como os aplicativos do Windows Sockets são gravados, já que muitos aplicativos IPv4 tendem a usar uma ordem diferente de chamadas de função.

A maioria dos aplicativos IPv4 primeiro cria um soquete para a família de endereços AF_INET, faz a resolução de nomes e, em seguida, usa o soquete para se conectar ao endereço IP resolvido. Ao tornar esses aplicativos compatíveis com IPv6, a chamada de função deve ser atrasada até depois da resolução de nomes, quando a família de endereços tiver sido desativada. socket. Observe que, se a resolução de nomes retornar endereços IPv4 e IPv6, os soquetes IPv4 e IPv6 separados deverão ser usados para se conectar a esses endereços de destino. Todas essas complexidades podem ser evitadas usando a função no Windows Vista e posterior, por isso os desenvolvedores de aplicativos são incentivados a usá-la WSAConnectByName .

O exemplo de código a seguir mostra a sequência adequada para executar a resolução de nomes primeiro (executada na quarta linha no exemplo de código-fonte a seguir) e, em seguida, abrir um soquete (executado na linha 19 como no exemplo de código a seguir)ª. Este exemplo é um trecho do arquivo Client.c encontrado no Apêndice B. A função PrintError chamada no exemplo de código a seguir está listada no exemplo Client.c Código do Cliente Habilitado para IPv6.

#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;
}

Funções auxiliares de IP

Por fim, os aplicativos que usam a função Auxiliar de IP e sua estrutura associada devem reconhecer que essa função e estrutura estão limitadas a endereços IPv4 GetAdaptersInfo IP_ADAPTER_INFO. As substituições habilitadas para IPv6 para essa função e estrutura são a função e a estrutura GetAdaptersAddresses IP_ADAPTER_ADDRESSES. Os aplicativos habilitados para IPv6 que usam a API auxiliar de IP devem usar a função e a estrutura habilitada para IPv6 correspondente, ambas definidas no Software Development Kit do Windows (SDK) GetAdaptersAddresses IP_ADAPTER_ADDRESSES.

Recomendações

A melhor abordagem para garantir que seu aplicativo esteja usando chamadas de função compatíveis com IPv6 é usar a função para obter a tradução de host para endereço getaddrinfo. A partir do Windows XP, a função getaddrinfo torna a função desnecessária e, portanto, seu aplicativo deve usar a função para projetos de programação futuros gethostbyname getaddrinfo. Enquanto a Microsoft continuar compatível, essa função não será estendida para dar suporte ao IPv6 gethostbyname. Para um suporte transparente para obter informações de host IPv6 e IPv4, você deve usar getaddrinfo.

O exemplo a seguir mostra como usar, da melhor forma melhor, a função getaddrinfo. Observe que a função, quando usada corretamente como este exemplificado, trata corretamente a tradução de host para endereço IPv6 e IPv4 e obtém outras informações úteis sobre o host, como o tipo de soquete compatível. Esse exemplo é um trecho da amostra Client.c, encontrada no 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;
}

Observação

A versão da função que dá suporte ao IPv6 é nova para a versão do Windows XP do Windows getaddrinfo.

 

Código a ser evitado

Tradicionalmente, a tradução de endereço do host é obtida usando a função gethostbyname. começando com o Windows XP:

  • A função torna a função obsoleta getaddrinfo fgethostbyname.
  • Seus aplicativos devem usar a função em vez da função getaddrinfo gethostbyname.

Tarefas de Codificação

Para modificar um aplicativo IPv4 existente para adicionar suporte ao IPv6

  1. Adquira o utilitário Checkv4.exe. Esse utilitário é instalado como parte do SDK do Windows. Uma versão mais antiga da ferramenta também foi incluída como parte da Visualização de Tecnologia do Microsoft IPv6 para Windows 2000 Checkv4.exe.
  2. Execute o utilitário seu código Checkv4.exe. Consulte para saber mais sobre como executar o utilitário de versão em seus arquivos Como usar o Utilitário Checkv4.exe.
  3. O utilitário alerta você sobre o uso do gethostbyname, gethostbyaddr, bem como outras funções somente IPv4 e faz recomendações sobre como substituí-las pela função compatível com IPv6, como getaddrinfo e getnameinfo.
  4. Substitua todas as instâncias da função e o código associado conforme apropriado, pela função gethostbyname getaddrinfo. No Windows Vista, use a função quando apropriado WSAConnectByName ou WSAConnectByList.
  5. Substitua todas as instâncias da função gethostbyaddr, bem como o código associado conforme apropriado, com a função getnameinfo.

Como alternativa, você pode pesquisar em sua base de código por instâncias das funções e alterar todo esse uso (e outros códigos associados, conforme apropriado) para as funções gethostbyname e gethostbyaddr getaddrinfo e getnameinfo.

Guia do IPv6 para aplicativos Windows Sockets

Alteração das estruturas de dados para aplicativos Winsock IPv6

Soquetes de pilha dupla para aplicativos Winsock IPv6

Uso de endereços IPv4 codificados

Problemas de interface do usuário em aplicativos Winsock IPv6

Protocolos subjacentes para aplicativos Winsock IPv6