SO_EXCLUSIVEADDRUSE opción de socket

La opción de socket SO_EXCLUSIVEADDRUSE impide que otros sockets se enlacen forzosamente a la misma dirección y puerto.

Sintaxis

La opción SO_EXCLUSIVEADDRUSE impide que otros sockets se enlacen forzosamente a la misma dirección y puerto, una práctica habilitada por la opción de socket SO_REUSEADDR. Esta reutilización se puede ejecutar mediante aplicaciones malintencionadas para interrumpir la aplicación. La opción SO_EXCLUSIVEADDRUSE es muy útil para los servicios del sistema que requieren alta disponibilidad.

En el ejemplo de código siguiente se muestra cómo establecer esta opción.

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

Para tener cualquier efecto, la opción SO_EXCLUSIVADDRUSE debe establecerse antes de llamar a la función de enlace (esto también se aplica a la opción SO_REUSEADDR). En la tabla 1 se enumeran los efectos de establecer la opción SO_EXCLUSIVEADDRUSE. El carácter comodín indica el enlace a la dirección comodín, como 0.0.0.0 para IPv4 y :: para IPv6. Específico indica el enlace a una interfaz específica, como enlazar una dirección IP asignada a un adaptador. Specific2 indica el enlace a una dirección específica distinta de la dirección enlazada a en el caso específico.

Nota

El caso Specific2 solo se aplica cuando se realiza el primer enlace con una dirección específica; para el caso en el que el primer socket está enlazado al carácter comodín, la entrada para Specific cubre todos los casos de direcciones específicos.

 

Por ejemplo, considere un equipo con dos interfaces IP: 10.0.0.1 y 10.99.99.99. Si el primer enlace es a 10.0.0.1 y el puerto 5150 con la opción SO_EXCLUSIVEADDRUSE establecido, el segundo enlace a 10.99.99.99 y el puerto 5150 con cualquiera o ninguna opción establecida correctamente. Sin embargo, si el primer socket está enlazado a la dirección comodín (0.0.0.0) y al puerto 5150 con SO_EXCLUSIVEADDRUSE establecido, cualquier enlace posterior al mismo puerto (independientemente de la dirección IP), producirá un error con WSAEADDRINUSE (10048) o WSAEACCESS (10013), dependiendo de las opciones establecidas en el segundo socket de enlace.

Enlazar el comportamiento con varios conjuntos de opciones

Segundo enlace

Primer enlace

SO_EXCLUSIVEADDRUSE

No hay opciones ni SO_REUSEADDR

Wildcard (Carácter comodín)

Específico

Wildcard (Carácter comodín)

Específico

${ROWSPAN3}$SO_EXCLUSIVEADDRUSE${REMOVE}$

Wildcard (Carácter comodín)

Error (10048)

Error (10048)

Error (10048)

Error (10048)

Específico

Error (10048)

Error (10048)

Error (10048)

Error (10048)

Specific2

N/D

Correcto

N/D

Correcto

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

Wildcard (Carácter comodín)

Error (10048)

Error (10048)

Error (10048)

Error (10048)

Específico

Error (10048)

Error (10048)

Error (10048)

Error (10048)

Specific2

N/D

Correcto

N/D

Correcto

${ROWSPAN3}$SO_REUSEADDR${REMOVE}$

Wildcard (Carácter comodín)

Error (10013)

Error (10013)

Éxito*

Correcto

Específico

Error (10013)

Error (10013)

Correcto

Éxito*

Specific2

N/D

Correcto

N/D

Correcto

* El comportamiento no está definido en cuanto a qué socket recibirá paquetes.

 

En el caso de que el primer enlace no establezca ninguna opción o SO_REUSEADDR, y el segundo enlace realiza una SO_REUSEADDR, el segundo socket ha superado el puerto y el comportamiento con respecto a qué socket recibirá paquetes no está definido. SO_EXCLUSIVEADDRUSE se introdujo para abordar esta situación.

Un socket con SO_EXCLUSIVEADDRUSE establecido no siempre se puede reutilizar inmediatamente después del cierre del socket. Por ejemplo, si un socket de escucha con el conjunto de marcas exclusivo acepta una conexión después de la cual se cierra el socket de escucha, otro socket no se puede enlazar al mismo puerto que el primer socket de escucha con la marca exclusiva hasta que la conexión aceptada ya no esté activa.

Esta situación puede ser bastante complicada; aunque se haya cerrado el socket, es posible que el transporte subyacente no finalice su conexión. Incluso después de cerrar el socket, el sistema debe enviar todos los datos almacenados en búfer, transmitir una desconexión correcta al mismo nivel y esperar una desconexión correcta del mismo nivel. Por lo tanto, es posible que el transporte subyacente nunca libere la conexión, como cuando el elemento del mismo nivel anuncia una ventana de tamaño cero u otros ataques de este tipo. En el ejemplo anterior, el socket de escucha se cerró después de aceptar una conexión de cliente. Ahora incluso si se cierra la conexión de cliente, es posible que el puerto todavía no se reutilice si la conexión de cliente permanece en un estado activo debido a datos no reconocidos, etc.

Para evitar esta situación, las aplicaciones deben garantizar un apagado correcto: llamar al apagado con la marca de SD_SEND y, a continuación, esperar en un bucle recv hasta que se devuelvan cero bytes. Al hacerlo, se evita el problema asociado a la reutilización del puerto, se garantiza que el mismo nivel recibió todos los datos y garantiza que todos sus datos se han recibido correctamente.

La opción SO_LINGER puede establecerse en un socket para evitar que el puerto entre en uno de los estados de espera activos; sin embargo, no se recomienda hacerlo porque puede provocar efectos no deseados, ya que puede hacer que la conexión se restablezca. Por ejemplo, si los datos se han recibido pero aún no los reconoce el mismo nivel y el equipo local cierra el socket con SO_LINGER establecido, la conexión se restablece y el mismo nivel descarta los datos no reconocidos. Además, elegir un tiempo adecuado para la persistencia es difícil; un valor demasiado pequeño da como resultado muchas conexiones anuladas, mientras que un tiempo de espera grande puede dejar al sistema vulnerable a ataques de denegación de servicio mediante el establecimiento de muchas conexiones y, por tanto, detiene numerosos subprocesos de aplicación.

Nota

Un socket que use la opción SO_EXCLUSIVEADDRUSE debe apagarse correctamente antes de cerrarlo. Si el servicio asociado necesita reiniciarse, puede provocar un ataque de denegación de servicio.

 

Requisitos

Requisito Value
Cliente mínimo compatible
Windows 2000 Professional [solo aplicaciones de escritorio]
Servidor mínimo compatible
Windows 2000 Server [solo aplicaciones de escritorio]
Encabezado
Winsock2.h