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选项, (这也适用于) SO_REUSEADDR选项。 表 1 列出了设置 SO_EXCLUSIVEADDRUSE 选项的效果。 通配符指示绑定到通配符地址,例如 0.0.0.0(对于 IPv4)和 ::(对于 IPv6)。 Specific 指示绑定到特定接口,例如绑定分配给适配器的 IP 地址。 Specific2 指示绑定到特定地址,而不是特定情况下绑定到的地址。

注意

仅当使用特定地址执行第一个绑定时,Specific2 才适用;对于第一个套接字绑定到通配符的情况,则 Specific 的条目涵盖所有特定的地址事例。

 

例如,假设有两个 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, 与 WSAEADDRINUSE (10048) 或 WSAEACCESS (10013) (无论 IP 地址如何)的任何后续绑定都将失败,具体取决于第二个绑定套接字上设置的选项。

具有各种选项集的绑定行为

第二个绑定

第一个绑定

SO_EXCLUSIVEADDRUSE

无选项SO_REUSEADDR

通配符

特定

通配符

特定

${ROWSPAN3}$SO_EXCLUSIVEADDRUSE${REMOVE}$

通配符

(10048) 失败

(10048) 失败

(10048) 失败

(10048) 失败

特定

(10048) 失败

(10048) 失败

(10048) 失败

(10048) 失败

Specific2

不适用

成功

不适用

成功

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

通配符

(10048) 失败

(10048) 失败

(10048) 失败

(10048) 失败

特定

(10048) 失败

(10048) 失败

(10048) 失败

(10048) 失败

Specific2

不适用

成功

不适用

成功

${ROWSPAN3}$SO_REUSEADDR${REMOVE}$

通配符

(10013) 失败

(10013) 失败

成功*

成功

特定

(10013) 失败

(10013) 失败

成功

成功*

Specific2

不适用

成功

不适用

成功

* 未定义将接收数据包的套接字的行为。

 

如果第一个绑定未设置任何选项或SO_REUSEADDR,并且第二个绑定执行SO_REUSEADDR,则第二个套接字已超过端口,并且有关哪个套接字将接收数据包的行为不确定。 引入了SO_EXCLUSIVEADDRUSE来解决此问题。

设置SO_EXCLUSIVEADDRUSE的套接字在套接字关闭后不能始终立即重复使用。 例如,如果设置了独占标志的侦听套接字接受连接,在该连接之后将关闭侦听套接字,则另一个套接字无法绑定到与具有独占标志的第一个侦听套接字相同的端口,直到接受的连接不再处于活动状态。

这种情况可能相当复杂:即使套接字已关闭,基础传输也可能不会终止其连接。 即使在套接字关闭后,系统也必须发送所有缓冲数据,将正常断开连接传输到对等方,并等待与对等方正常断开连接。 因此,基础传输可能永远不会释放连接,例如当对等播发零大小的窗口或其他此类攻击时。 在前面的示例中,侦听套接字在接受客户端连接后关闭。 现在,即使客户端连接已关闭,如果客户端连接由于未确认的数据等而保持活动状态,则仍可能不重复使用该端口。

若要避免这种情况,应用程序应确保正常关闭:使用 SD_SEND 标志调用 shutdown ,然后在 recv 循环中等待,直到返回零个字节。 这样做可避免与端口重用相关的问题,保证对等方接收所有数据,并保证对等方成功接收其所有数据。

可以在套接字上设置SO_LINGER选项,以防止端口进入活动等待状态之一;但是,不建议这样做,因为这可能会导致意外的影响,因为它可能会导致连接重置。 例如,如果已收到数据但尚未被对等方确认,并且本地计算机关闭了SO_LINGER设置的套接字,则会重置连接,并且对等方会丢弃未确认的数据。 此外,挑选合适的时间挥之不去是困难的:值过小会导致许多连接中止,而长时间超时可能会使系统容易受到拒绝服务攻击,方法是建立许多连接,从而停止大量应用程序线程。

注意

使用 SO_EXCLUSIVEADDRUSE 选项的套接字在关闭之前必须正确关闭。 如果关联服务需要重启,否则可能会导致拒绝服务攻击。

 

要求

要求
最低受支持的客户端
Windows 2000 Professional [仅限桌面应用]
最低受支持的服务器
Windows 2000 Server [仅限桌面应用]
标头
Winsock2.h