SO_EXCLUSIVEADDRUSE通訊端選項

SO_EXCLUSIVEADDRUSE通訊端選項可防止其他通訊端強制系結至相同的位址和埠。

Syntax

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選項的效果。 萬用字元表示系結至萬用字元位址,例如 IPv4 的 0.0.0.0 和 :: 適用于 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, 任何後續系結至相同的埠,不論 IP 位址為何,都會因為 WSAEADDRINUSE (10048) 或 WSAEACCESS (10013) 而失敗,視第二個系結通訊端上設定的選項而定。

具有各種選項組的系結行為

第二個系結

第一個系結

SO_EXCLUSIVEADDRUSE

沒有選項SO_REUSEADDR

通 配 符

特定

通 配 符

特定

${ROWSPAN3}$SO_EXCLUSIVEADDRUSE${REMOVE}$

通 配 符

失敗 (10048)

失敗 (10048)

失敗 (10048)

失敗 (10048)

特定

失敗 (10048)

失敗 (10048)

失敗 (10048)

失敗 (10048)

特定2

n/a

Success

n/a

Success

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

通 配 符

失敗 (10048)

失敗 (10048)

失敗 (10048)

失敗 (10048)

特定

失敗 (10048)

失敗 (10048)

失敗 (10048)

失敗 (10048)

特定2

n/a

Success

n/a

Success

${ROWSPAN3}$SO_REUSEADDR${REMOVE}$

通 配 符

失敗 (10013)

失敗 (10013)

成功*

Success

特定

失敗 (10013)

失敗 (10013)

Success

成功*

特定2

n/a

Success

n/a

Success

* 行為未定義為將接收封包的通訊端。

 

如果第一個系結未設定任何選項或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