option de socket SO_EXCLUSIVEADDRUSE

L’option de socket SO_EXCLUSIVEADDRUSE empêche d’autres sockets d’être liés de force à la même adresse et au même port.

Syntaxe

L’option SO_EXCLUSIVEADDRUSE empêche d’autres sockets d’être liés de force à la même adresse et au même port, une pratique activée par l’option de socket SO_REUSEADDR. Une telle réutilisation peut être exécutée par des applications malveillantes pour perturber l’application. L’option SO_EXCLUSIVEADDRUSE est très utile pour les services système nécessitant une haute disponibilité.

L’exemple de code suivant illustre la définition de cette option.

#ifndef UNICODE
#define UNICODE
#endif

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>             // Needed for _wtoi

#pragma comment(lib, "Ws2_32.lib")

int __cdecl wmain(int argc, wchar_t ** argv)
{
    WSADATA wsaData;
    int iResult = 0;
    int iError = 0;

    SOCKET s = INVALID_SOCKET;
    SOCKADDR_IN saLocal;
    int iOptval = 0;

    int iFamily = AF_UNSPEC;
    int iType = 0;
    int iProtocol = 0;

    int iPort = 0;

    // Validate the parameters
    if (argc != 5) {
        wprintf(L"usage: %ws <addressfamily> <type> <protocol> <port>\n", argv[0]);
        wprintf(L"    opens a socket for the specified family, type, & protocol\n");
        wprintf(L"    sets the SO_EXCLUSIVEADDRUSE socket option on the socket\n");
        wprintf(L"    then tries to bind the port specified on the command-line\n");
        wprintf(L"%ws example usage\n", argv[0]);
        wprintf(L"   %ws 0 2 17 5150\n", argv[0]);
        wprintf(L"   where AF_UNSPEC=0 SOCK_DGRAM=2 IPPROTO_UDP=17  PORT=5150\n",
                argv[0]);
        wprintf(L"   %ws 2 1 17 5150\n", argv[0]);
        wprintf(L"   where AF_INET=2 SOCK_STREAM=1 IPPROTO_TCP=6  PORT=5150\n", argv[0]);
        wprintf(L"   See the documentation for the socket function for other values\n");
        return 1;
    }

    iFamily = _wtoi(argv[1]);
    iType = _wtoi(argv[2]);
    iProtocol = _wtoi(argv[3]);

    iPort = _wtoi(argv[4]);

    if (iFamily != AF_INET && iFamily != AF_INET6) {
        wprintf(L"Address family must be either AF_INET (2) or AF_INET6 (23)\n");
        return 1;
    }

    if (iPort <= 0 || iPort >= 65535) {
        wprintf(L"Port specified must be greater than 0 and less than 65535\n");
        return 1;
    }
    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0) {
        wprintf(L"WSAStartup failed with error: %d\n", iResult);
        return 1;
    }
    // Create the socket
    s = socket(iFamily, iType, iProtocol);
    if (s == INVALID_SOCKET) {
        wprintf(L"socket failed with error: %ld\n", WSAGetLastError());
        WSACleanup();
        return 1;
    }
    // Set the exclusive address option
    iOptval = 1;
    iResult = setsockopt(s, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
                         (char *) &iOptval, sizeof (iOptval));
    if (iResult == SOCKET_ERROR) {
        wprintf(L"setsockopt for SO_EXCLUSIVEADDRUSE failed with error: %ld\n",
                WSAGetLastError());
    }

    saLocal.sin_family = (ADDRESS_FAMILY) iFamily;
    saLocal.sin_port = htons( (u_short) iPort);
    saLocal.sin_addr.s_addr = htonl(INADDR_ANY);

    // Bind the socket
    iResult = bind(s, (SOCKADDR *) & saLocal, sizeof (saLocal));
    if (iResult == SOCKET_ERROR) {

        // Most errors related to setting SO_EXCLUSIVEADDRUSE
        //    will occur at bind.
        iError = WSAGetLastError();
        if (iError == WSAEACCES)
            wprintf(L"bind failed with WSAEACCES (access denied)\n");
        else
            wprintf(L"bind failed with error: %ld\n", iError);

    } else {
        wprintf(L"bind on socket with SO_EXCLUSIVEADDRUSE succeeded to port: %ld\n",
                iPort);
    }

    // cleanup
    closesocket(s);
    WSACleanup();

    return 0;
}

Pour avoir un effet quelconque, l’option SO_EXCLUSIVADDRUSE doit être définie avant que la fonction de liaison soit appelée (cela s’applique également à l’option SO_REUSEADDR). Le tableau 1 répertorie les effets de la définition de l’option SO_EXCLUSIVEADDRUSE. Le caractère générique indique la liaison à l’adresse générique, par exemple 0.0.0.0 pour IPv4 et :: pour IPv6. Spécifique indique la liaison à une interface spécifique, comme la liaison d’une adresse IP affectée à une carte. Specific2 indique la liaison à une adresse spécifique autre que l’adresse liée à dans le cas spécifique.

Notes

Le cas Specific2 s’applique uniquement lorsque la première liaison est effectuée avec une adresse spécifique ; dans le cas où le premier socket est lié au caractère générique, l’entrée spécifique couvre tous les cas d’adresse spécifiques.

 

Par exemple, considérez un ordinateur avec deux interfaces IP : 10.0.0.1 et 10.99.99.99. Si la première liaison est à 10.0.0.1 et au port 5150 avec le jeu d’options SO_EXCLUSIVEADDRUSE, la deuxième liaison à 10.99.99.99.99 et le port 5150 avec une ou aucune option définie réussit. Toutefois, si le premier socket est lié à l’adresse générique (0.0.0.0) et au port 5150 avec SO_EXCLUSIVEADDRUSE défini, toute liaison ultérieure au même port (quelle que soit l’adresse IP) échoue avec WSAEADDRINUSE (10048) ou WSAEACCESS (10013), selon les options définies sur le deuxième socket de liaison.

Comportement de liaison avec différentes options définies

Deuxième liaison

Première liaison

SO_EXCLUSIVEADDRUSE

Aucune option ou SO_REUSEADDR

Caractère générique

Spécifique

Caractère générique

Spécifique

${ROWSPAN3}$SO_EXCLUSIVEADDRUSE${REMOVE}$

Caractère générique

Échec (10048)

Échec (10048)

Échec (10048)

Échec (10048)

Spécifique

Échec (10048)

Échec (10048)

Échec (10048)

Échec (10048)

Spécifique2

n/a

Succès

n/a

Succès

${ROWSPAN3}$No options${REMOVE}$

Caractère générique

Échec (10048)

Échec (10048)

Échec (10048)

Échec (10048)

Spécifique

Échec (10048)

Échec (10048)

Échec (10048)

Échec (10048)

Spécifique2

n/a

Succès

n/a

Succès

${ROWSPAN3}$SO_REUSEADDR${REMOVE}$

Caractère générique

Échec (10013)

Échec (10013)

Succès*

Succès

Spécifique

Échec (10013)

Échec (10013)

Succès

Succès*

Spécifique2

n/a

Succès

n/a

Succès

* Le comportement n’est pas défini quant au socket qui recevra les paquets.

 

Dans le cas où la première liaison ne définit aucune option ou SO_REUSEADDR, et où la deuxième liaison effectue une SO_REUSEADDR, le deuxième socket a dépassé le port et le comportement du socket qui recevra les paquets est indéterminé. SO_EXCLUSIVEADDRUSE a été introduit pour remédier à cette situation.

Un socket avec SO_EXCLUSIVEADDRUSE défini ne peut pas toujours être réutilisé immédiatement après la fermeture du socket. Par exemple, si un socket d’écoute avec le jeu d’indicateurs exclusifs accepte une connexion après laquelle le socket d’écoute est fermé, un autre socket ne peut pas se lier au même port que le premier socket d’écoute avec l’indicateur exclusif tant que la connexion acceptée n’est plus active.

Cette situation peut être assez compliquée; même si le socket a été fermé, le transport sous-jacent peut ne pas arrêter sa connexion. Même après la fermeture du socket, le système doit envoyer toutes les données mises en mémoire tampon, transmettre une déconnexion normale à l’homologue et attendre une déconnexion avec grâce de l’homologue. Il est donc possible que le transport sous-jacent ne libère jamais la connexion, par exemple lorsque l’homologue publie une fenêtre de taille nulle ou d’autres attaques de ce type. Dans l’exemple précédent, le socket d’écoute a été fermé après l’acceptation d’une connexion cliente. Maintenant, même si la connexion client est fermée, le port peut toujours ne pas être réutilisé si la connexion client reste dans un état actif en raison de données non reconnues, et ainsi de suite.

Pour éviter cette situation, les applications doivent s’assurer d’un arrêt gracieux : appeler l’arrêt avec l’indicateur SD_SEND, puis attendre dans une boucle recv jusqu’à ce que zéro octet soit retourné. Cela évite le problème associé à la réutilisation des ports, garantit que toutes les données ont été reçues par l’homologue et garantit à l’homologue que toutes ses données ont été correctement reçues.

L’option SO_LINGER peut être définie sur un socket pour empêcher le port de passer à l’un des états d’attente actifs ; toutefois, il est déconseillé de le faire, car cela peut entraîner des effets indésirables, car cela peut entraîner la réinitialisation de la connexion. Par exemple, si des données ont été reçues, mais pas encore reconnues par l’homologue, et que l’ordinateur local ferme le socket avec SO_LINGER défini, la connexion est réinitialisée et l’homologue ignore les données non reconnues. En outre, il est difficile de choisir un moment approprié pour s’attarder; une valeur trop faible entraîne l’abandon de nombreuses connexions, tandis qu’un délai d’expiration important peut rendre le système vulnérable aux attaques par déni de service en établissant de nombreuses connexions, ce qui bloque de nombreux threads d’application.

Notes

Un socket qui utilise l’option SO_EXCLUSIVEADDRUSE doit être arrêté correctement avant de le fermer. Si vous ne le faites pas, vous pouvez provoquer une attaque par déni de service si le service associé doit redémarrer.

 

Spécifications

Condition requise Valeur
Client minimal pris en charge
Windows 2000 Professionnel [applications de bureau uniquement]
Serveur minimal pris en charge
Windows 2000 Server [applications de bureau uniquement]
En-tête
Winsock2.h