параметр сокета SO_EXCLUSIVEADDRUSE

Параметр SO_EXCLUSIVEADDRUSE сокета предотвращает принудительное привязку других сокетов к одному и тому же адресу и порту.

Синтаксис

Параметр SO_EXCLUSIVEADDRUSE предотвращает принудительное привязку других сокетов к одному и тому же адресу и порту, что поддерживается параметром сокета SO_REUSEADDR. Такое повторное использование может быть выполнено вредоносными приложениями, чтобы нарушить работу приложения. Параметр SO_EXCLUSIVEADDRUSE очень полезен для системных служб, требующих высокого уровня доступности.

В следующем примере кода показано задание этого параметра.

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

Для получения какого-либо эффекта необходимо задать параметр SO_EXCLUSIVADDRUSE перед вызовом функции bind (это также относится к параметру SO_REUSEADDR). В таблице 1 перечислены последствия установки параметра SO_EXCLUSIVEADDRUSE. Подстановочный знак указывает привязку к адресу с подстановочными знаками, например 0.0.0.0 для IPv4 и :: для IPv6. Специфичный указывает на привязку к определенному интерфейсу, например привязку IP-адреса, назначенного адаптеру. Specific2 указывает на привязку к определенному адресу, отличному от адреса, привязанного к в конкретном случае.

Примечание

Случай Specific2 применим только в том случае, если первая привязка выполняется с определенным адресом; Для случая, когда первый сокет привязан к подстановочным знакам, запись Для Конкретного охватывает все конкретные варианты адресов.

 

Например, рассмотрим компьютер с двумя интерфейсами IP: 10.0.0.1 и 10.99.99.99. Если первая привязка к 10.0.0.1 и порт 5150 с набором параметров SO_EXCLUSIVEADDRUSE, то вторая привязка к 10.99.99.99 и порту 5150 с любым параметром или без них будет выполнена успешно. Однако если первый сокет привязан к адресу с подстановочными знаками (0.0.0.0) и порту 5150 с заданным SO_EXCLUSIVEADDRUSE, любая последующая привязка к тому же порту (независимо от IP-адреса) завершится ошибкой WSAEADDRINUSE (10048) или WSAEACCESS (10013) в зависимости от того, какие параметры были заданы для второго сокета привязки.

Поведение привязки с помощью различных параметров

Вторая привязка

Первая привязка

SO_EXCLUSIVEADDRUSE

Нет параметров или SO_REUSEADDR

Подстановочный знак

Конкретных

Подстановочный знак

Конкретных

${ROWSPAN3}$SO_EXCLUSIVEADDRUSE${REMOVE}$

Подстановочный знак

Fail (10048)

Fail (10048)

Fail (10048)

Fail (10048)

Конкретных

Fail (10048)

Fail (10048)

Fail (10048)

Fail (10048)

Конкретный2

Недоступно

Успешное завершение

Недоступно

Успешное завершение

${ROWSPAN3}$Нет параметров${REMOVE}$

Подстановочный знак

Fail (10048)

Fail (10048)

Fail (10048)

Fail (10048)

Конкретных

Fail (10048)

Fail (10048)

Fail (10048)

Fail (10048)

Конкретный2

Недоступно

Успешное завершение

Недоступно

Успешное завершение

${ROWSPAN3}$SO_REUSEADDR${REMOVE}$

Подстановочный знак

Сбой (10013)

Сбой (10013)

Успех*

Успешное завершение

Конкретных

Сбой (10013)

Сбой (10013)

Успешное завершение

Успех*

Конкретный2

Недоступно

Успешное завершение

Недоступно

Успешное завершение

* Поведение не определено в том, какой сокет будет получать пакеты.

 

В случае, когда первая привязка не задает параметров или SO_REUSEADDR, а вторая привязка выполняет SO_REUSEADDR, второй сокет обогнал порт и поведение в отношении того, какой сокет будет получать пакеты, не определено. SO_EXCLUSIVEADDRUSE была введена для решения этой ситуации.

Сокет с заданным SO_EXCLUSIVEADDRUSE нельзя всегда повторно использовать сразу после закрытия сокета. Например, если прослушивающий сокет с установленным монопольным флагом принимает соединение, после которого прослушивающий сокет закрывается, другой сокет не может привязаться к тому же порту, что и первый сокет прослушивания с монопольным флагом, пока принятое соединение не будет активно.

Эта ситуация может быть довольно сложной; Несмотря на то, что сокет был закрыт, базовый транспорт не может завершить подключение. Даже после закрытия сокета система должна отправить все буферизированные данные, передать корректное отключение однорангового узла и дождаться корректного отключения от однорангового узла. Поэтому возможно, что базовый транспорт никогда не разблокирует подключение, например, когда одноранговый узел объявляет окно нулевого размера или другие подобные атаки. В предыдущем примере прослушивающий сокет был закрыт после принятия клиентского подключения. Теперь, даже если клиентское подключение закрыто, порт по-прежнему может не использоваться повторно, если клиентское подключение остается в активном состоянии из-за непризнанных данных и т. д.

Чтобы избежать этой ситуации, приложения должны обеспечить корректное завершение работы: вызовите завершение работы с флагом SD_SEND, а затем подождите в цикле recv , пока не будут возвращены ноль байтов. Это позволяет избежать проблем, связанных с повторным использованием портов, гарантирует, что все данные были получены одноранговым элементом, а также гарантирует, что все его данные были успешно получены.

Параметр SO_LINGER может быть установлен для сокета, чтобы предотвратить переход порта в одно из активных состояний ожидания. однако делать это не рекомендуется, так как это может привести к нежелательным последствиям, так как это может привести к сбросу подключения. Например, если данные получены, но еще не подтверждены одноранговым элементом, а локальный компьютер закрывает сокет с заданным SO_LINGER, соединение сбрасывается, а одноранговый узел удаляет непризнанные данные. Кроме того, выбрать подходящее время, чтобы задержиться трудно; Слишком малое значение приводит к большому числу прерванных подключений, в то время как большое время ожидания может сделать систему уязвимой для атак типа "отказ в обслуживании", установив много подключений и, таким образом, застопорив многочисленные потоки приложений.

Примечание

Сокет, использующий параметр SO_EXCLUSIVEADDRUSE, должен быть должным образом выключен перед закрытием. Если связанную службу потребуется перезапустить, это может привести к атаке типа "отказ в обслуживании".

 

Требования

Требование Значение
Минимальная версия клиента
Windows 2000 Professional [только классические приложения]
Минимальная версия сервера
Windows 2000 Server [только классические приложения]
Заголовок
Winsock2.h