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 オプションを設定した場合の効果を示します。 ワイルドカードは、IPv4 の場合は 0.0.0.0、IPv6 の場合は :: など、ワイルドカード アドレスへのバインドを示します。 Specific は、アダプターに割り当てられた IP アドレスのバインドなど、特定のインターフェイスへのバインドを示します。 Specific2 は、Specific ケースの にバインドされているアドレス以外の特定のアドレスへのバインドを示します。

注意

Specific2 のケースは、最初のバインドが特定のアドレスで実行される場合にのみ適用されます。最初のソケットがワイルドカードにバインドされている場合、Specific のエントリは特定のすべてのアドレス ケースを対象とします。

 

たとえば、10.0.0.1 と 10.99.99.99 の 2 つの IP インターフェイスを持つコンピューターを考えてみましょう。 最初のバインドが 10.0.0.1 に設定され、ポート 5150 が SO_EXCLUSIVEADDRUSE オプションが設定されている場合、2 番目のバインドは 10.99.99.99 に、ポート 5150 は成功し、オプションが設定されていない場合は成功します。 ただし、最初のソケットがワイルドカード アドレス (0.0.0.0) にバインドされ、ポート 5150 がSO_EXCLUSIVEADDRUSE設定されている場合、IP アドレスに関係なく、同じポートへの後続のバインドは、2 番目のバインド ソケットで設定されたオプションに応じて、WSAEADDRINUSE (10048) または WSAEACCESS (10013) のいずれかで失敗します。

さまざまなオプションセットを使用したバインド動作

2 番目のバインド

最初のバインド

SO_EXCLUSIVEADDRUSE

オプションまたはSO_REUSEADDRなし

ワイルドカード

特定

ワイルドカード

特定

${ROWSPAN3}$SO_EXCLUSIVEADDRUSE${REMOVE}$

ワイルドカード

Fail (10048)

Fail (10048)

Fail (10048)

Fail (10048)

特定

Fail (10048)

Fail (10048)

Fail (10048)

Fail (10048)

Specific2

N/A

Success

該当なし

Success

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

ワイルドカード

Fail (10048)

Fail (10048)

Fail (10048)

Fail (10048)

特定

Fail (10048)

Fail (10048)

Fail (10048)

Fail (10048)

Specific2

N/A

Success

該当なし

Success

${ROWSPAN3}$SO_REUSEADDR${REMOVE}$

ワイルドカード

Fail (10013)

Fail (10013)

成功*

Success

特定

Fail (10013)

Fail (10013)

Success

成功*

Specific2

N/A

Success

N/A

Success

* この動作は、どのソケットがパケットを受信するかを定義しません。

 

最初のバインドでオプションまたはSO_REUSEADDRが設定されず、2 番目のバインドがSO_REUSEADDRを実行する場合、2 番目のソケットはポートを超過し、パケットを受信するソケットに関する動作は未確定です。 SO_EXCLUSIVEADDRUSEこの状況に対処するために導入されました。

SO_EXCLUSIVEADDRUSEが設定されたソケットは、ソケットのクローズ直後に常に再利用できるとは限りません。 たとえば、排他的フラグが設定されたリッスン ソケットが、リッスンしているソケットが閉じられた後の接続を受け入れる場合、別のソケットは、受け入れられた接続がアクティブでなくなるまで、最初のリッスン ソケットと同じポートにバインドできません。

この状況は非常に複雑な場合があります。ソケットが閉じられている場合でも、基になるトランスポートがその接続を終了しない可能性があります。 ソケットが閉じられた後でも、システムはバッファー内のすべてのデータを送信し、正常な切断をピアに送信し、ピアからの正常な切断を待機する必要があります。 そのため、ピアが 0 サイズのウィンドウをアドバタイズするときや、その他の攻撃など、基になるトランスポートが接続を解放しない可能性があります。 前の例では、クライアント接続が受け入れられた後にリッスン ソケットが閉じられました。 クライアント接続が閉じられている場合でも、未確認のデータが原因でクライアント接続がアクティブな状態のままである場合、ポートは再利用されない可能性があります。

このような状況を回避するには、アプリケーションで正常なシャットダウンを確保する必要があります。SD_SEND フラグを使用して シャットダウン を呼び出し、0 バイトが返されるまで recv ループで待機します。 これにより、ポートの再利用に関連する問題を回避し、すべてのデータがピアによって受信されたことを保証し、そのすべてのデータが正常に受信されたことをピアに保証します。

SO_LINGER オプションをソケットに設定して、ポートがアクティブな待機状態のいずれかに入らないようにすることができます。ただし、接続がリセットされる可能性があるため、望ましくない影響が発生する可能性があるため、これを行うことはお勧めしません。 たとえば、データが受信されたがピアによってまだ確認されていない場合、ローカル コンピューターが SO_LINGER 設定されたソケットを閉じると、接続はリセットされ、ピアは未確認のデータを破棄します。 また、残る適切な時間を選ぶのは難しいです。値が小さすぎると、多くの接続が中止されますが、タイムアウトが大きいと、多数の接続を確立して多数のアプリケーション スレッドを停止することで、システムがサービス拒否攻撃に対して脆弱になる可能性があります。

注意

SO_EXCLUSIVEADDRUSE オプションを使用しているソケットは、閉じる前に正しくシャットダウンする必要があります。 これを行わないと、関連付けられているサービスを再起動する必要がある場合に、サービス拒否攻撃が発生する可能性があります。

 

要件

要件
サポートされている最小のクライアント
Windows 2000 Professional [デスクトップ アプリのみ]
サポートされている最小のサーバー
Windows 2000 Server [デスクトップ アプリのみ]
ヘッダー
Winsock2.h