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選項,才能呼叫 系結 函式, (這也適用于SO_REUSEADDR選項) 。 表 1 列出設定SO_EXCLUSIVEADDRUSE選項的效果。 萬用字元表示系結至萬用字元位址,例如 IPv4 的 0.0.0.0 和 :: 適用于 IPv6。 Specific 表示系結至特定介面,例如系結指派給介面卡的 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}$

萬用字元

失敗 (10048)

失敗 (10048)

失敗 (10048)

失敗 (10048)

特定

失敗 (10048)

失敗 (10048)

失敗 (10048)

失敗 (10048)

Specific2

n/a

Success

n/a

Success

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

萬用字元

失敗 (10048)

失敗 (10048)

失敗 (10048)

失敗 (10048)

特定

失敗 (10048)

失敗 (10048)

失敗 (10048)

失敗 (10048)

Specific2

n/a

Success

n/a

Success

${ROWSPAN3}$SO_REUSEADDR${REMOVE}$

萬用字元

失敗 (10013)

失敗 (10013)

成功*

Success

特定

失敗 (10013)

失敗 (10013)

Success

成功*

Specific2

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 專業版 [僅限傳統型應用程式]
最低支援的伺服器
Windows 2000 Server [僅限傳統型應用程式]
標頭
Winsock2.h