Chiamate di funzione per le applicazioni Winsock IPv6

Sono state introdotte nuove funzioni nell'interfaccia Windows Sockets appositamente progettata per semplificare la programmazione di Windows Sockets. Uno dei vantaggi di queste nuove funzioni di Windows Sockets è il supporto integrato sia per IPv6 che per IPv4.

Queste nuove funzioni di Windows Sockets includono quanto segue:

Inoltre, sono state aggiunte nuove funzioni helper IP con supporto per IPv6 e IPv4 per semplificare la programmazione di Windows Sockets. Queste nuove funzioni helper IP includono quanto segue:

Quando si aggiunge il supporto IPv6 a un'applicazione, è consigliabile usare le linee guida seguenti:

  • Usare WSAConnectByName per stabilire una connessione a un endpoint in base a un nome host e a una porta. La funzione WSAConnectByName è disponibile in Windows Vista e versioni successive.
  • Usare WSAConnectByList per stabilire una connessione a una raccolta di possibili endpoint rappresentati da un set di indirizzi di destinazione (nomi host e porte). La funzione WSAConnectByList è disponibile in Windows Vista e versioni successive.
  • Sostituire le chiamate di funzione gethostbyname con chiamate a una delle nuove funzioni getaddrinfo di Windows Sockets. La funzione getaddrinfo con supporto per il protocollo IPv6 è disponibile in Windows XP e versioni successive. Il protocollo IPv6 è supportato anche in Windows 2000 quando è installata l'anteprima della tecnologia IPv6 per Windows 2000.
  • Sostituire le chiamate di funzione gethostbyaddr con chiamate a una delle nuove funzioni getnameinfo di Windows Sockets. La funzione getnameinfo con supporto per il protocollo IPv6 è disponibile in Windows XP e versioni successive. Il protocollo IPv6 è supportato anche in Windows 2000 quando è installata l'anteprima della tecnologia IPv6 per Windows 2000.

WSAConnectByName

La funzione WSAConnectByName semplifica la connessione a un endpoint usando un socket basato su flusso in base al nome host o all'indirizzo IP della destinazione (IPv4 o IPv6). Questa funzione riduce il codice sorgente necessario per creare un'applicazione IP indipendente dalla versione del protocollo IP usata. WSAConnectByName sostituisce i passaggi seguenti in una tipica applicazione TCP a una singola chiamata di funzione:

  • Risolvere un nome host in un set di indirizzi IP.
  • Per ogni indirizzo IP:
    • Creare un socket della famiglia di indirizzi appropriata.
    • Tenta di connettersi all'indirizzo IP remoto. Se la connessione ha avuto esito positivo, viene restituita; in caso contrario, viene tentato l'indirizzo IP remoto successivo per l'host.

La funzione WSAConnectByName va oltre la semplice risoluzione del nome e quindi il tentativo di connessione. La funzione usa tutti gli indirizzi remoti restituiti dalla risoluzione dei nomi e tutti gli indirizzi IP di origine del computer locale. Tenta prima di tutto di connettersi usando coppie di indirizzi con la più alta probabilità di successo. Pertanto, WSAConnectByName non solo garantisce che una connessione venga stabilita, se possibile, ma riduce anche al minimo il tempo necessario per stabilire la connessione.

Per abilitare le comunicazioni IPv6 e IPv4, usare il metodo seguente:

  • La funzione setsockopt deve essere chiamata su un socket creato per la famiglia di indirizzi AF_INET6 per disabilitare l'opzione socket IPV6_V6ONLY prima di chiamare WSAConnectByName. A tale scopo, chiamare la funzione setsockopt sul socket con il parametro level impostato su IPPROTO_IPV6 (vedere IPPROTO_IPV6 Socket Options), il parametro optname impostato su IPV6_V6ONLY e il valore del parametro optvalue impostato su zero.

Se un'applicazione deve eseguire l'associazione a un indirizzo locale o a una porta specifica, non è possibile usare WSAConnectByName perché il parametro socket a WSAConnectByName deve essere un socket non associato.

L'esempio di codice seguente mostra solo alcune righe di codice necessarie per usare questa funzione per implementare un'applicazione indipendente dalla versione IP.

Stabilire una connessione con 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 funzione WSAConnectByList stabilisce una connessione a un host dato un set di possibili host (rappresentato da un set di indirizzi IP e porte di destinazione). La funzione accetta tutti gli indirizzi IP e le porte per l'endpoint e tutti gli indirizzi IP del computer locale e tenta una connessione usando tutte le possibili combinazioni di indirizzi.

WSAConnectByList è correlato alla funzione WSAConnectByName. Anziché accettare un singolo nome host, WSAConnectByList accetta un elenco di host (indirizzi di destinazione e coppie di porte) e si connette a uno degli indirizzi e delle porte nell'elenco fornito. Questa funzione è progettata per supportare scenari in cui un'applicazione deve connettersi a qualsiasi host disponibile da un elenco di potenziali host.

Analogamente a WSAConnectByName, la funzione WSAConnectByList riduce significativamente il codice sorgente necessario per creare, associare e connettere un socket. Questa funzione semplifica notevolmente l'implementazione di un'applicazione indipendente dalla versione IP. L'elenco di indirizzi per un host accettato da questa funzione può essere indirizzi IPv6 o IPv4.

Per consentire il passaggio di indirizzi IPv6 e IPv4 nell'elenco di indirizzi singolo accettato dalla funzione, è necessario eseguire i passaggi seguenti prima di chiamare la funzione:

  • La funzione setsockopt deve essere chiamata su un socket creato per la famiglia di indirizzi AF_INET6 per disabilitare l'opzione socket IPV6_V6ONLY prima di chiamare WSAConnectByList. A tale scopo, chiamare la funzione setsockopt sul socket con il parametro level impostato su IPPROTO_IPV6 (vedere IPPROTO_IPV6 Socket Options), il parametro optname impostato su IPV6_V6ONLY e il valore del parametro optvalue impostato su zero.
  • Tutti gli indirizzi IPv4 devono essere rappresentati nel formato di indirizzi IPv4 mappato a IPv6, che consente a un'applicazione solo IPv6 di comunicare con un nodo IPv4. Il formato di indirizzo IPv4 mappato a IPv6 consente di rappresentare l'indirizzo IPv4 di un nodo IPv4 come indirizzo IPv6. L'indirizzo IPv4 viene codificato nei 32 bit di 32 bit dell'indirizzo IPv6 e i 96 bit di ordine elevato contengono il prefisso fisso 0:0:0:0:0:0:0:FFFF. Il formato di indirizzo IPv6 mappato per IPv4 è specificato in RFC 4291. Per altre informazioni, vedere www.ietf.org/rfc/rfc4291.txt. La macro IN6ADDR_SETV4MAPPED in Mstcpip.h può essere usata per convertire un indirizzo IPv4 nel formato di indirizzo IPv4 con mapping IPv6 richiesto.

Stabilire una connessione 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

La funzione getaddrinfo esegue anche il lavoro di elaborazione di molte funzioni. In precedenza, le chiamate a una serie di funzioni di Windows Sockets erano necessarie per creare, aprire e quindi associare un indirizzo a un socket. Con la funzione getaddrinfo , le righe del codice sorgente necessarie per eseguire tali operazioni possono essere notevolmente ridotte. I due esempi seguenti illustrano il codice sorgente necessario per eseguire queste attività con e senza la funzione getaddrinfo .

Eseguire un'operazione Open, Connect e Bind tramite 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;
}

Eseguire un'operazione Open, Connect e Bind senza usare 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;
}

Si noti che entrambi questi esempi di codice sorgente eseguono le stesse attività, ma il primo esempio, usando la funzione getaddrinfo , richiede meno righe di codice sorgente e può gestire indirizzi IPv6 o IPv4. Il numero di righe di codice sorgente eliminato tramite la funzione getaddrinfo varia.

Nota

Nel codice sorgente di produzione l'applicazione scorre il set di indirizzi restituiti dalla funzione gethostbyname o getaddrinfo. Questi esempi omettono tale passaggio a favore della semplicità.

 

Un altro problema da risolvere quando si modifica un'applicazione IPv4 esistente per supportare IPv6 è associato all'ordine in cui vengono chiamate le funzioni. Sia getaddrinfo che gethostbyname richiedono che venga eseguita una sequenza di chiamate di funzione in un ordine specifico.

Nelle piattaforme in cui vengono usati sia IPv4 che IPv6, la famiglia di indirizzi del nome host remoto non è nota in anticipo. Pertanto, la risoluzione degli indirizzi tramite la funzione getaddrinfo deve essere eseguita per prima cosa per determinare l'indirizzo IP e la famiglia di indirizzi dell'host remoto. È quindi possibile chiamare la funzione socket per aprire un socket della famiglia di indirizzi restituita da getaddrinfo. Si tratta di una modifica importante del modo in cui vengono scritte le applicazioni Windows Sockets, poiché molte applicazioni IPv4 tendono a usare un ordine diverso di chiamate di funzione.

La maggior parte delle applicazioni IPv4 crea prima un socket per la famiglia di indirizzi AF_INET, quindi esegue la risoluzione dei nomi e quindi usa il socket per connettersi all'indirizzo IP risolto. Quando tali applicazioni supportano IPv6, la chiamata alla funzione socket deve essere ritardata fino alla risoluzione dei nomi quando la famiglia di indirizzi è stata detereminata.When making such applications IPv6-capable, the socket function call must be delayed until after name resolution when the address family has been deteremined. Si noti che se la risoluzione dei nomi restituisce indirizzi IPv4 e IPv6, è necessario usare socket IPv4 e IPv6 separati per connettersi a questi indirizzi di destinazione. Tutte queste complessità possono essere evitate usando la funzione WSAConnectByName in Windows Vista e versioni successive, quindi gli sviluppatori di applicazioni sono invitati a usare questa nuova funzione.

Nell'esempio di codice seguente viene illustrata la sequenza corretta per eseguire prima la risoluzione dei nomi (eseguita nella quarta riga nell'esempio di codice sorgente seguente), quindi aprire un socket (eseguito nella 19a riga nell'esempio di codice seguente). Questo esempio è un estratto del file Client.c presente nel codice client abilitato per IPv6 nell'Appendice B. La funzione PrintError chiamata nell'esempio di codice seguente è elencata nell'esempio 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;
}

Funzioni dell'helper IP

Infine, le applicazioni che usano la funzione Helper IP GetAdaptersInfo e la struttura associata IP_ADAPTER_INFO, devono riconoscere che sia questa funzione che la struttura sono limitate agli indirizzi IPv4. Le sostituzioni abilitate per IPv6 per questa funzione e la struttura sono la funzione GetAdaptersAddresses e la struttura IP_ADAPTER_ADDRESSES. Le applicazioni abilitate per IPv6 che usano l'API dell'helper IP devono usare la funzione GetAdaptersAddresses e la struttura di IP_ADAPTER_ADDRESSES abilitata per IPv6 corrispondente, entrambe definite in Microsoft Windows Software Development Kit (SDK).

Consigli

L'approccio migliore per garantire che l'applicazione usi chiamate di funzione compatibili con IPv6 consiste nell'usare la funzione getaddrinfo per ottenere la conversione da host a indirizzo. A partire da Windows XP, la funzione getaddrinfo rende la funzione gethostbyname non necessaria e l'applicazione deve quindi usare la funzione getaddrinfo per i progetti di programmazione futuri. Anche se Microsoft continuerà a supportare gethostbyname, questa funzione non verrà estesa per supportare IPv6. Per il supporto trasparente per ottenere informazioni sull'host IPv6 e IPv4, è necessario usare getaddrinfo.

Nell'esempio seguente viene illustrato come usare al meglio la funzione getaddrinfo . Si noti che la funzione, se usata correttamente come illustrato in questo esempio, gestisce correttamente la conversione da host a indirizzo IPv6 e IPv4, ma ottiene anche altre informazioni utili sull'host, ad esempio il tipo di socket supportati. Questo esempio è un estratto dell'esempio Client.c disponibile nell'Appendice 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 versione della funzione getaddrinfo che supporta IPv6 è una novità per la versione di Windows XP di Windows.

 

Codice da evitare

La conversione degli indirizzi host è stata tradizionalmente ottenuta usando la funzione gethostbyname. A partire da Windows XP:

  • La funzione getaddrinfo rende obsoleta la funzione gethostbyname .
  • Le applicazioni devono usare la funzione getaddrinfo anziché la funzione gethostbyname .

Attività di codifica

Per modificare un'applicazione IPv4 esistente per aggiungere il supporto per IPv6

  1. Acquisire l'utilità Checkv4.exe. Questa utilità viene installata come parte di Windows SDK. Una versione precedente dello strumento di Checkv4.exe è stata inclusa anche come parte di Microsoft IPv6 Technology Preview per Windows 2000.
  2. Eseguire l'utilità Checkv4.exe sul codice. Vedere Uso dell'utilità Checkv4.exe per informazioni sull'esecuzione dell'utilità di versione sui file.
  3. L'utilità avvisa l'utente dell'utilizzo di gethostbyname, gethostbyaddr e altre funzioni solo IPv4 e fornisce consigli su come sostituirli con la funzione compatibile con IPv6, ad esempio getaddrinfo e getnameinfo.
  4. Sostituire qualsiasi istanza della funzione gethostbyname e il codice associato in base alle esigenze, con la funzione getaddrinfo . In Windows Vista usare la funzione WSAConnectByName o WSAConnectByList quando appropriato.
  5. Sostituire qualsiasi istanza della funzione gethostbyaddr e il codice associato in base alle esigenze, con la funzione getnameinfo.

In alternativa, è possibile cercare nella code base le istanze delle funzioni gethostbyname e gethostbyaddr e modificare tutto questo utilizzo (e altro codice associato, in base alle esigenze) alle funzioni getaddrinfo e getnameinfo .

Guida IPv6 per le applicazioni Windows Sockets

Modifica delle strutture di dati per le applicazioni Winsock IPv6

Socket dual-stack per applicazioni Winsock IPv6

Uso di indirizzi IPv4 hardcoded

Problemi dell'interfaccia utente per le applicazioni Winsock IPv6

Protocolli sottostanti per le applicazioni Winsock IPv6