Partilhar via


Chamadas de função para aplicativos Winsock IPv6

Novas funções foram introduzidas na interface do Windows Sockets especificamente projetada para facilitar a programação do Windows Sockets. 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 IP Helper com suporte para IPv6 e IPv4 foram adicionadas para simplificar a programação do Windows Sockets. Essas novas funções IP Helper incluem o seguinte:

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

  • Use WSAConnectByName para estabelecer uma conexão com um ponto de extremidade que recebe um nome de host e uma porta. A função WSAConnectByName está disponível no Windows Vista e posterior.
  • Use WSAConnectByList para estabelecer uma conexão com um de uma coleção de possíveis pontos de extremidade representados por um conjunto de endereços de destino (nomes de host e portas). A função WSAConnectByList está disponível no Windows Vista e posterior.
  • Substitua chamadas de função gethostbyname por chamadas para uma das novas funções getaddrinfo Windows Sockets. A função getaddrinfo com suporte para o protocolo IPv6 está disponível no Windows XP e posterior. O protocolo IPv6 também é suportado no Windows 2000 quando o IPv6 Technology Preview para Windows 2000 está instalado.
  • Substitua chamadas de função gethostbyaddr por chamadas para uma das novas funções getnameinfo Windows Sockets. A função getnameinfo com suporte para o protocolo IPv6 está disponível no Windows XP e versões posteriores. O protocolo IPv6 também é suportado no Windows 2000 quando o IPv6 Technology Preview para Windows 2000 está instalado.

WSAConnectByName

A funçãoWSAConnectByNamesimplifica a conexão a um ponto de extremidade usando um soquete baseado em fluxo dado o nome do host ou endereço IP do destino (IPv4 ou IPv6). Esta função reduz o código-fonte necessário para criar um aplicativo IP que é agnóstico à versão do protocolo IP usado. WSAConnectByName substitui as seguintes etapas em um aplicativo TCP típico para uma única chamada de função:

  • Resolva 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.
    • Tenta se conectar ao endereço IP remoto. Se a conexão foi bem-sucedida, ela retorna; caso contrário, o próximo endereço IP remoto para o host é tentado.

A função WSAConnectByName vai além de apenas resolver o nome e, em seguida, tentar se conectar. A função usa todos os endereços remotos retornados pela resolução de nomes e todos os endereços IP de origem da máquina local. Primeiro, ele tenta se conectar usando pares de endereços com maior chance de sucesso. Portanto, WSAConnectByName não só garante que uma conexão será estabelecida, se possível, mas também minimiza o tempo para estabelecer a conexão.

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

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

Se um aplicativo precisar se vincular a um endereço ou porta local específico, WSAConnectByName não pode ser usado, pois o parâmetro socket para WSAConnectByName deve ser um soquete não acoplado.

O exemplo de código abaixo mostra que apenas algumas linhas de código são necessárias para usar essa função para implementar um aplicativo que é agnóstico à versão IP.

Estabeleça 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çãoWSAConnectByList estabelece uma conexão com um host dado um conjunto de possíveis hosts (representados por um conjunto de endereços IP de destino e portas). A função usa todos os endereços IP e portas para o ponto de extremidade e todos os endereços IP da máquina local e tenta uma conexão usando todas as combinações de endereços possíveis.

WSAConnectByList está relacionada à funçãoWSAConnectByName. Em vez de usar um único nome de host, WSAConnectByList aceita uma lista de hosts (endereços de destino e pares de portas) e se conecta a um dos endereços e portas na lista fornecida. Esta função foi projetada para suportar cenários nos quais um aplicativo precisa se conectar a qualquer host disponível de uma lista de hosts potenciais.

Semelhante ao WSAConnectByName, a funçãoWSAConnectByList reduz significativamente o código-fonte necessário para criar, vincular e conectar um soquete. Esta função torna muito mais fácil implementar uma aplicação que é agnóstica para a versão IP. A lista de endereços para um host aceito por esta função pode ser endereços IPv6 ou IPv4.

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

  • A função setsockopt deve ser chamada em um soquete criado para a família de endereços AF_INET6 desativar a opção de soquete IPV6_V6ONLY antes de chamar WSAConnectByList. Isso é feito chamando a função setsockopt no soquete com o parâmetro de de nível definido como IPPROTO_IPV6 (consulte IPPROTO_IPV6 Socket Options), o parâmetro optname definido como IPV6_V6ONLYe o valor do parâmetro optvalue definido como zero.
  • Todos os endereços IPv4 devem ser representados no formato de endereço IPv6 mapeado por IPv4, que permite que um aplicativo somente IPv6 se comunique com um nó IPv4. O formato de endereço IPv6 mapeado por 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 mantêm o prefixo fixo 0:0:0:0:0:FFFF. O formato de endereço IPv6 mapeado para IPv4 é especificado na RFC 4291. Para obter mais informações, consulte www.ietf.org/rfc/rfc4291.txt. A macro IN6ADDR_SETV4MAPPED em Mstcpip.h pode ser usada para converter um endereço IPv4 para o formato de endereço IPv6 mapeado IPv4 necessário.

Estabeleça 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

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

Execute um Open, Connect e Bind 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;
}

Execute um Open, Connect e Bind 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 ambos os exemplos de código-fonte executam as mesmas tarefas, mas o primeiro exemplo, usando a função getaddrinfo, requer menos linhas de código-fonte e pode lidar com endereços IPv6 ou IPv4. O número de linhas de código-fonte eliminadas usando a função getaddrinfo varia.

Observação

No código-fonte de produção, seu aplicativo iteraria através do conjunto de endereços retornados pelo função gethostbyname ou função getaddrinfo. Estes exemplos omitem esse passo em favor da simplicidade.

 

Outro problema que você deve resolver ao modificar um aplicativo IPv4 existente para suportar IPv6 está associado à ordem em que as funções são chamadas. Tanto getaddrinfo quanto gethostbyname exigem que uma sequência de chamadas de função seja feita em uma ordem específica.

Em plataformas onde o IPv4 e o IPv6 são usados, a família de endereços do nome do host remoto não é conhecida com antecedência. Portanto, a resolução de endereços usando a função getaddrinfo deve ser executada primeiro para determinar o endereço IP e a família de endereços do host remoto. Em seguida, a função dede soquetepode ser chamada para abrir um soquete da família de endereços retornada por getaddrinfo. Esta é uma mudança importante na forma como os aplicativos Windows Sockets são escritos, uma vez 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, depois faz a resolução de nomes e, em seguida, usa o soquete para se conectar ao endereço IP resolvido. Ao tornar tais aplicativos compatíveis com IPv6, a chamada da função de soquete deve ser adiada até depois da resolução do nome, quando a família de endereços tiver sido determinada. Observe que, se a resolução de nomes retornar endereços IPv4 e IPv6, 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 WSAConnectByName no Windows Vista e posterior, portanto, os desenvolvedores de aplicativos são incentivados a usar essa nova função.

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 19th no exemplo de código a seguir). Este exemplo é um trecho do arquivo Client.c encontrado no Código do ClienteIPv6-Enabled no Apêndice B. A função PrintError chamada no exemplo de código a seguir está listada no exemplo 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;
}

Funções auxiliares de IP

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

Recomendações

A melhor abordagem para garantir que seu aplicativo esteja usando chamadas de função compatíveis com IPv6 é usar a funçãogetaddrinfopara obter a conversão de host para endereço. A partir do Windows XP, a função getaddrinfo torna a funçãogethostbyname desnecessária, e seu aplicativo deve, portanto, usar a função getaddrinfo em vez disso para projetos de programação futuros. Embora a Microsoft continue a suportar gethostbyname, esta função não será estendida para suportar IPv6. Para obter suporte transparente para obter informações de host IPv6 e IPv4, você deve usar getaddrinfo.

O exemplo a seguir mostra como usar melhor o função getaddrinfo. Observe que a função, quando usada corretamente, como este exemplo demonstra, lida com a conversão de host para endereço IPv6 e IPv4 corretamente, mas também obtém outras informações úteis sobre o host, como o tipo de soquetes suportados. Este exemplo é um trecho do exemplo Client.c encontrado 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 do função getaddrinfo que suporta IPv6 é nova para a versão Windows XP do Windows.

 

Código a evitar

A conversão de endereços de host tem sido tradicionalmente obtida usando a funçãogethostbyname. A começar pelo Windows XP:

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

Tarefas de codificação

Para modificar um aplicativo IPv4 existente para adicionar suporte a IPv6

  1. Adquira o utilitário Checkv4.exe. Este utilitário é instalado como parte do SDK do Windows. Uma versão mais antiga da ferramenta Checkv4.exe também foi incluída como parte do Microsoft IPv6 Technology Preview para Windows 2000.
  2. Execute o utilitário Checkv4.exe em seu código. Consulte Usando o utilitário Checkv4.exe para saber mais sobre como executar o utilitário de versão em seus arquivos.
  3. O utilitário alerta você sobre o uso do gethostbyname , gethostbyaddr e outras funções somente IPv4 e fornece 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 gethostbyname e código associado, conforme apropriado, pela função getaddrinfo. No Windows Vista, use o WSAConnectByName ou função WSAConnectByList quando apropriado.
  5. Substitua todas as instâncias da funçãogethostbyaddre o código associado, conforme apropriado, pela funçãogetnameinfo.

Como alternativa, você pode pesquisar sua base de código por instâncias do gethostbyname e funções de gethostbyaddr e alterar todo esse uso (e outro código associado, conforme apropriado) para o getaddrinfo e funções de getnameinfo.

Guia IPv6 para aplicativos Windows Sockets

Alterando estruturas de dados para aplicativos Winsock IPv6

Soquetes Dual-Stack para aplicativos Winsock IPv6

Uso de endereços IPv4 codificados

Problemas de interface do usuário para aplicativos Winsock IPv6

protocolos subjacentes para aplicativos Winsock IPv6