Share via


opzione socket SO_EXCLUSIVEADDRUSE

L'opzione socket SO_EXCLUSIVEADDRUSE impedisce ad altri socket di essere associati in modo forcibly allo stesso indirizzo e alla stessa porta.

Sintassi

L'opzione SO_EXCLUSIVEADDRUSE impedisce ad altri socket di essere associati forcibmente allo stesso indirizzo e alla stessa porta, una pratica abilitata dall'opzione socket SO_REUSEADDR. Tale riutilizzo può essere eseguito da applicazioni dannose per interrompere l'applicazione. L'opzione SO_EXCLUSIVEADDRUSE è molto utile per i servizi di sistema che richiedono disponibilità elevata.

Nell'esempio di codice seguente viene illustrata l'impostazione di questa opzione.

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

Per avere alcun effetto, l'opzione SO_EXCLUSIVADDRUSE deve essere impostata prima che venga chiamata la funzione di associazione (questa operazione si applica anche all'opzione SO_REUSEADDR). La tabella 1 elenca gli effetti dell'impostazione dell'opzione SO_EXCLUSIVEADDRUSE. Il carattere jolly indica l'associazione all'indirizzo jolly, ad esempio 0.0.0.0 per IPv4 e :: per IPv6. Specifica indica l'associazione a un'interfaccia specifica, ad esempio l'associazione di un indirizzo IP assegnato a una scheda. Specifica2 indica l'associazione a un indirizzo specifico diverso dall'indirizzo associato nel caso specifico.

Nota

Il caso Specifico2 è applicabile solo quando il primo binding viene eseguito con un indirizzo specifico; per il caso in cui il primo socket è associato al carattere jolly, la voce per Specifica copre tutti i casi di indirizzo specifici.

 

Si consideri ad esempio un computer con due interfacce IP: 10.0.0.1 e 10.99.99.99. Se il primo binding è 10.0.0.1 e la porta 5150 con l'opzione SO_EXCLUSIVEADDRUSE impostata, il secondo associa a 10.99.99.99 e porta 5150 con qualsiasi o nessun set di opzioni ha esito positivo. Tuttavia, se il primo socket è associato all'indirizzo jolly (0.0.0.0.0) e alla porta 5150 con SO_EXCLUSIVEADDRUSE impostata, qualsiasi associazione successiva alla stessa porta, indipendentemente dall'indirizzo IP, avrà esito negativo con WSAEADDRINUSE (10048) o WSAEACCESS (10013), a seconda delle opzioni impostate sul secondo socket di associazione.

Associare il comportamento con vari set di opzioni

Secondo binding

Prima associazione

SO_EXCLUSIVEADDRUSE

Nessuna opzione o SO_REUSEADDR

Carattere jolly

Specifico

Carattere jolly

Specifico

${ROWSPAN3}$SO_EXCLUSIVEADDRUSE${REMOVE}$

Carattere jolly

Fail (10048)

Fail (10048)

Fail (10048)

Fail (10048)

Specifico

Fail (10048)

Fail (10048)

Fail (10048)

Fail (10048)

Specifico2

n/d

Operazione riuscita

n/d

Operazione riuscita

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

Carattere jolly

Fail (10048)

Fail (10048)

Fail (10048)

Fail (10048)

Specifico

Fail (10048)

Fail (10048)

Fail (10048)

Fail (10048)

Specifico2

n/d

Operazione riuscita

n/d

Operazione riuscita

${ROWSPAN3}$SO_REUSEADDR${REMOVE}$

Carattere jolly

Fail (10013)

Fail (10013)

Successo*

Operazione riuscita

Specifico

Fail (10013)

Fail (10013)

Operazione riuscita

Successo*

Specifico2

n/d

Operazione riuscita

n/d

Operazione riuscita

* Il comportamento non è definito in base al quale socket riceverà pacchetti.

 

Nel caso in cui il primo binding non imposta opzioni o SO_REUSEADDR e il secondo binding esegue una SO_REUSEADDR, il secondo socket ha superato la porta e il comportamento relativo al socket che riceverà pacchetti non è indeterminato. SO_EXCLUSIVEADDRUSE è stato introdotto per risolvere questa situazione.

Non è sempre possibile riutilizzare un socket con SO_EXCLUSIVEADDRUSE set immediatamente dopo la chiusura del socket. Ad esempio, se un socket in ascolto con il set di flag esclusivo accetta una connessione dopo la quale il socket in ascolto è chiuso, un altro socket non può associare alla stessa porta del primo socket in ascolto con il flag esclusivo fino a quando la connessione accettata non è più attiva.

Questa situazione può essere piuttosto complicata; anche se il socket è stato chiuso, il trasporto sottostante potrebbe non terminare la connessione. Anche dopo la chiusura del socket, il sistema deve inviare tutti i dati memorizzati nel buffer, trasmettere una disconnessione graziata al peer e attendere una disconnessione graziata dal peer. È quindi possibile che il trasporto sottostante non rilasci mai la connessione, ad esempio quando il peer annuncia una finestra di dimensioni zero o altri attacchi. Nell'esempio precedente il socket di ascolto è stato chiuso dopo l'accettazione di una connessione client. Anche se la connessione client è chiusa, la porta potrebbe comunque non essere riutilizzata se la connessione client rimane in uno stato attivo a causa di dati non riconosciuti e così via.

Per evitare questa situazione, le applicazioni devono garantire un arresto normale: chiamare l'arresto con il flag SD_SEND, quindi attendere in un ciclo recv fino a quando non vengono restituiti zero byte. In questo modo, evita il problema associato al riutilizzo delle porte, garantisce che tutti i dati siano stati ricevuti dal peer e assicurino che tutti i dati siano stati ricevuti correttamente.

L'opzione SO_LINGER può essere impostata su un socket per impedire che la porta entri in uno degli stati di attesa attivi; tuttavia, questa operazione è sconsigliata perché può causare effetti indesiderati, perché può causare la reimpostazione della connessione. Ad esempio, se i dati sono stati ricevuti ma non ancora riconosciuti dal peer e il computer locale chiude il socket con SO_LINGER impostato, la connessione viene reimpostata e il peer ignora i dati non riconosciuti. Inoltre, la selezione di un tempo adatto per il ritardo è difficile; un valore troppo piccolo genera molte connessioni interrotte, mentre un timeout elevato può lasciare il sistema vulnerabile agli attacchi denial of service stabilendo molte connessioni e quindi bloccando numerosi thread dell'applicazione.

Nota

Un socket che usa l'opzione SO_EXCLUSIVEADDRUSE deve essere arrestato correttamente prima di chiuderlo. Se il servizio associato deve riavviare, può causare un attacco denial of service.

 

Requisiti

Requisito Valore
Client minimo supportato
Windows 2000 Professional [solo app desktop]
Server minimo supportato
Windows 2000 Server [solo app desktop]
Intestazione
Winsock2.h