Partager via


Appels de fonction pour les applications Winsock IPv6

De nouvelles fonctions ont été introduites dans l’interface des sockets Windows pour faciliter la programmation relative aux sockets Windows. L’un des avantages de ces nouvelles fonctions des sockets Windows est la prise en charge intégrée d’IPv6 et d’IPv4.

Ces nouvelles fonctions de sockets Windows sont les suivantes :

De plus, de nouvelles fonctions d’assistance IP avec prise en charge d’IPv6 et d’IPv4 ont été ajoutées pour simplifier la programmation des sockets Windows. Ces nouvelles fonctions d’assistance IP sont les suivantes :

Quand vous ajoutez la prise en charge d’IPv6 à une application, suivez les recommandations suivantes :

  • Utilisez WSAConnectByName pour établir une connexion à un point de terminaison en fonction d’un nom d’hôte et d’un port. La fonction WSAConnectByName est disponible sur Windows Vista et les versions ultérieures.
  • Utilisez WSAConnectByList pour établir une connexion à l’une des collections de points de terminaison possibles représentées par un ensemble d’adresses de destination (noms d’hôtes et ports). La fonction WSAConnectByList est disponible sur Windows Vista et les versions ultérieures.
  • Remplacez les appels de fonction gethostbyname par des appels à l’une des nouvelles fonctions de la famille getaddrinfo des sockets Windows. La fonction getaddrinfo avec prise en charge du protocole IPv6 est disponible sur Windows XP et les versions ultérieures. Le protocole IPv6 est également pris en charge sur Windows 2000, quand la préversion IPv6 Technology Preview pour Windows 2000 est installée.
  • Remplacez les appels de fonction gethostbyaddr par des appels à l’une des nouvelles fonctions de la famille getnameinfo des sockets Windows. La fonction getnameinfo avec prise en charge du protocole IPv6 est disponible sur Windows XP et les versions ultérieures. Le protocole IPv6 est également pris en charge sur Windows 2000, quand la préversion IPv6 Technology Preview pour Windows 2000 est installée.

WSAConnectByName

La fonction WSAConnectByName simplifie la connexion à un point de terminaison à l’aide d’un socket basé sur un flux en fonction du nom d’hôte ou de l’adresse IP (IPv4 ou IPv6) de destination. Cette fonction réduit le code source nécessaire à la création d’une application IP indépendante de la version du protocole IP utilisé. WSAConnectByName remplace les étapes suivantes dans une application TCP classique par un seul appel de fonction :

  • Résous un nom d’hôte en un ensemble d’adresses IP.
  • Pour chaque adresse IP :
    • Crée un socket de la famille d’adresses appropriée.
    • Tente de se connecter à l’adresse IP distante. Si la connexion a réussi, la valeur est retournée. Sinon, l’adresse IP distante suivante de l’hôte est tentée.

La fonction WSAConnectByName va au-delà de la simple résolution de nom et tentative de connexion. La fonction utilise toutes les adresses distantes retournées par la résolution de noms ainsi que toutes les adresses IP sources de la machine locale. Elle tente d’abord de se connecter à l’aide des paires d’adresses ayant les probabilités de réussite les plus élevées. Ainsi, WSAConnectByName permet non seulement de garantir l’établissement d’une connexion (si cela est possible), mais également de réduire le délai d’établissement de la connexion.

Pour activer à la fois les communications IPv6 et IPv4, utilisez la méthode suivante :

  • La fonction setsockopt doit être appelée sur un socket créé pour la famille d’adresses AF_INET6 afin de désactiver l’option de socket IPV6_V6ONLY avant d’appeler WSAConnectByName. Pour ce faire, appelez la fonction setsockopt sur le socket avec le paramètre level défini à IPPROTO_IPV6 (consultez Options de socket IPPROTO_IPV6), le paramètre optname défini à IPV6_V6ONLY, et le paramètre optvalue défini à la valeur zéro.

Si une application doit se lier à une adresse locale ou un port spécifique, WSAConnectByName ne peut pas être utilisé, car le paramètre de socket de WSAConnectByName doit être un socket indépendant.

L’exemple de code ci-dessous montre que quelques lignes de code suffisent pour utiliser cette fonction afin d’implémenter une application indépendante de la version IP.

Établir une connexion à l’aide de 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 fonction WSAConnectByList établit une connexion à un hôte en fonction d’un ensemble d’hôtes possibles (représentés par un ensemble d’adresses IP et de ports de destination). La fonction accepte toutes les adresses IP et tous les ports pour le point de terminaison ainsi que toutes les adresses IP de la machine locale, puis tente d’établir une connexion à l’aide de toutes les combinaisons d’adresses possibles.

WSAConnectByList est lié à la fonction WSAConnectByName. Au lieu d’accepter un seul nom d’hôte, WSAConnectByList accepte une liste d’hôtes (paires d’adresses de destination et de ports), puis se connecte à l’une des adresses et à l’un des ports de la liste fournie. Cette fonction est conçue pour prendre en charge les scénarios dans lesquels une application doit se connecter à un hôte disponible parmi une liste d’hôtes potentiels.

À l’image de WSAConnectByName, la fonction WSAConnectByList réduit considérablement le code source nécessaire pour créer, lier et connecter un socket. Cette fonction facilite considérablement l’implémentation d’une application indépendante de la version IP. La liste des adresses d’hôtes acceptées par cette fonction peut être constituée d’adresses IPv6 ou IPv4.

Pour permettre le passage des adresses IPv6 et IPv4 dans la liste d’adresses unique acceptée par la fonction, vous devez suivre les étapes ci-dessous avant d’appeler la fonction :

  • La fonction setsockopt doit être appelée sur un socket créé pour la famille d’adresses AF_INET6 afin de désactiver l’option de socket IPV6_V6ONLY avant d’appeler WSAConnectByList. Pour ce faire, appelez la fonction setsockopt sur le socket avec le paramètre level défini à IPPROTO_IPV6 (consultez Options de socket IPPROTO_IPV6), le paramètre optname défini à IPV6_V6ONLY, et le paramètre optvalue défini à la valeur zéro.
  • Toutes les adresses IPv4 doivent être représentées au format d’adresse IPv6 mappée à IPv4, qui permet à une application exclusivement IPv6 de communiquer avec un nœud IPv4. Le format d’adresse IPv6 mappée à IPv4 permet à l’adresse IPv4 d’un nœud IPv4 d’être représentée sous la forme d’une adresse IPv6. L’adresse IPv4 est codée dans les 32 bits de poids faible de l’adresse IPv6, et les 96 bits de poids fort contiennent le préfixe fixe 0:0:0:0:0:FFFF. Le format d’adresse IPv6 mappée à IPv4 est spécifié dans la RFC 4291. Pour plus d’informations, consultez www.ietf.org/rfc/rfc4291.txt. La macro IN6ADDR_SETV4MAPPED dans Mstcpip.h peut être utilisée pour convertir une adresse IPv4 au format d’adresse IPv6 mappée à IPv4 nécessaire.

Établir une connexion à l’aide de 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 fonction getaddrinfo effectue également le travail de traitement de nombreuses fonctions. Jusqu’ici, les appels à un certain nombre de fonctions des sockets Windows étaient nécessaires pour créer, ouvrir, puis lier une adresse à un socket. Avec la fonction getaddrinfo, les lignes de code source nécessaires à l’exécution de ce travail peuvent être considérablement réduites. Les deux exemples suivants illustrent le code source nécessaire pour effectuer ces tâches avec et sans la fonction getaddrinfo.

Effectuer une opération d’ouverture, de connexion et de liaison à l’aide de 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;
}

Effectuer une opération d’ouverture, de connexion et de liaison sans utiliser 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;
}

Notez que ces deux exemples de code source effectuent les mêmes tâches, mais que le premier exemple, qui utilise la fonction getaddrinfo, nécessite moins de lignes de code source et peut gérer les adresses IPv6 ou IPv4. Le nombre de lignes de code source éliminées à l’aide de la fonction getaddrinfo varie.

Remarque

Dans le code source de production, votre application itère sur l’ensemble d’adresses retourné par la fonction gethostbyname ou getaddrinfo. Ces exemples omettent cette étape pour des raisons de simplicité.

 

Vous devez résoudre un autre problème quand vous modifiez une application IPv4 existante pour qu’elle prenne en charge IPv6 : il s’agit de l’ordre dans lequel les fonctions sont appelées. getaddrinfo et gethostbyname nécessitent l’exécution d’une séquence d’appels de fonctions dans un ordre spécifique.

Sur les plateformes où IPv4 et IPv6 sont utilisées, la famille d’adresses du nom d’hôte distant n’est pas connue à l’avance. Ainsi, la résolution d’adresses à l’aide de la fonction getaddrinfo doit d’abord être exécutée pour déterminer l’adresse IP et la famille d’adresses de l’hôte distant. Vous pouvez ensuite appeler la fonction socket pour ouvrir un socket de la famille d’adresses retournée par getaddrinfo. Il s’agit d’un changement important dans la façon dont les applications de sockets Windows sont écrites, car de nombreuses applications IPv4 ont tendance à utiliser un autre ordre pour les appels de fonctions.

La plupart des applications IPv4 créent d’abord un socket pour la famille d’adresses AF_INET, puis effectuent une résolution de noms, et utilisent ensuite le socket pour se connecter à l’adresse IP résolue. Quand de telles applications sont compatibles avec IPv6, l’appel de la fonction socket doit être retardé jusqu’à la fin de la résolution de noms, quand la famille d’adresses a été déterminée. Notez que si la résolution de noms retourne à la fois des adresses IPv4 et IPv6, des sockets IPv4 et IPv6 distincts doivent être utilisés pour la connexion à ces adresses de destination. Vous pouvez éviter toutes ces complexités en utilisant la fonction WSAConnectByName sur Windows Vista et les versions ultérieures. Les développeurs d’applications sont donc invités à utiliser cette nouvelle fonction.

L’exemple de code suivant montre la séquence appropriée pour effectuer d’abord la résolution de noms (à la quatrième ligne de l’exemple de code source suivant), puis ouvrir un socket (à la 19e ligne de l’exemple de code suivant). Cet exemple est un extrait du fichier Client.c présent dans le code client compatible IPv6, au sein de l’Annexe B. La fonction PrintError appelée dans l’exemple de code suivant est listée dans l’exemple 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;
}

Fonctions de l’assistance IP

Enfin, les applications qui utilisent la fonction d’assistance IP GetAdaptersInfo et sa structure associée IP_ADAPTER_INFO doivent reconnaître que cette fonction et cette structure sont limitées aux adresses IPv4. Les remplacements au format IPv6 de cette fonction et de cette structure sont la fonction GetAdaptersAddresses et la structure IP_ADAPTER_ADDRESSES. Les applications IPv6 qui utilisent l’API d’assistance IP doivent utiliser la fonction GetAdaptersAddresses et la structure IP_ADAPTER_ADDRESSES correspondante pour IPv6, toutes deux définies dans le kit de développement logiciel Microsoft Windows (kit SDK Microsoft Windows).

Recommandations

La meilleure approche pour avoir la certitude que votre application utilise des appels de fonctions compatibles avec IPv6 consiste à utiliser la fonction getaddrinfo pour obtenir la traduction de l’hôte en adresse. À partir de Windows XP, la fonction getaddrinfo rend la fonction gethostbyname inutile. Votre application doit donc utiliser la fonction getaddrinfo à la place pour les futurs projets de programmation. Bien que Microsoft continue de prendre en charge gethostbyname, cette fonction ne sera pas étendue pour prendre en charge IPv6. Pour une prise en charge transparente de l’obtention d’informations d’hôte IPv6 et IPv4, vous devez utiliser getaddrinfo.

L’exemple suivant montre comment utiliser au mieux la fonction getaddrinfo. Notez que la fonction, quand elle est utilisée de manière appropriée, comme le montre cet exemple, gère correctement la traduction de l’hôte en adresse pour IPv6 et IPv4. Toutefois, elle obtient également d’autres informations utiles sur l’hôte, par exemple les types de sockets pris en charge. Cet exemple est un extrait de l’exemple Client.c présent dans l’Annexe 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;
}

Remarque

La version de la fonction getaddrinfo qui prend en charge IPv6 est une nouveauté de la version Windows XP de Windows.

 

Code à éviter

La traduction des adresses d’hôtes est généralement effectuée à l’aide de la fonction gethostbyname. À partir de Windows XP :

  • La fonction getaddrinfo rend la fonction gethostbyname obsolète.
  • Vos applications doivent utiliser la fonction getaddrinfo à la place de la fonction gethostbyname.

Tâches de programmation

Pour modifier une application IPv4 existante afin d’ajouter la prise en charge d’IPv6

  1. Récupérez l’utilitaire Checkv4.exe. Cet utilitaire est installé dans le cadre du kit SDK Windows. Le kit SDK Windows est disponible via un abonnement MSDN et peut également être téléchargé à partir du site web de Microsoft (https://msdn.microsoft.com). Une ancienne version de l’outil Checkv4.exe a été également incluse dans le cadre de la préversion Microsoft IPv6 Technology Preview pour Windows 2000.
  2. Exécutez l’utilitaire Checkv4.exe sur votre code. Consultez Utilisation de l’utilitaire Checkv4.exe pour en savoir plus sur l’exécution de l’utilitaire de version sur vos fichiers.
  3. L’utilitaire vous alerte en cas d’utilisation des fonctions gethostbyname, gethostbyaddr et d’autres fonctions IPv4 uniquement. De plus, il fournit des recommandations sur la façon de remplacer ces fonctions par une fonction compatible IPv6, par exemple getaddrinfo et getnameinfo.
  4. Remplacez toutes les instances de la fonction gethostbyname ainsi que le code associé, le cas échéant, par la fonction getaddrinfo. Sur Windows Vista, utilisez la fonction WSAConnectByName ou WSAConnectByList, le cas échéant.
  5. Remplacez toutes les instances de la fonction gethostbyaddr ainsi que le code associé, le cas échéant, par la fonction getnameinfo.

Vous pouvez également rechercher dans votre codebase des instances des fonctions gethostbyname et gethostbyaddr, et les remplacer (ainsi que le code associé, le cas échéant) par les fonctions getaddrinfo et getnameinfo.

Guide IPv6 pour les applications de sockets Windows

Changement des structures de données pour les applications Winsock IPv6

Sockets double pile pour les applications Winsock IPv6

Utilisation d’adresses IPv4 codées en dur

Problèmes d’interface utilisateur pour les applications Winsock IPv6

Protocoles sous-jacents pour les applications Winsock IPv6