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의 경우 :: 등 와일드카드 주소에 대한 바인딩을 나타냅니다. 특정은 어댑터에 할당된 IP 주소 바인딩과 같은 특정 인터페이스에 대한 바인딩을 나타냅니다. Specific2는 특정 사례에서 바인딩된 주소 이외의 특정 주소에 바인딩을 나타냅니다.

참고

Specific2 사례는 첫 번째 바인딩이 특정 주소로 수행되는 경우에만 적용됩니다. 첫 번째 소켓이 와일드카드에 바인딩된 경우 특정 항목은 모든 특정 주소 사례를 다룹니다.

 

예를 들어 10.0.0.1 및 10.99.99.99의 두 IP 인터페이스가 있는 컴퓨터를 생각해 보세요. 첫 번째 바인딩이 SO_EXCLUSIVEADDRUSE 옵션이 설정된 10.0.0.1 및 포트 5150이면 두 번째 바인딩이 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

해당 없음

Success

해당 없음

Success

${ROWSPAN3}$옵션 없음${REMOVE}$

와일드카드

실패(10048)

실패(10048)

실패(10048)

실패(10048)

특정

실패(10048)

실패(10048)

실패(10048)

실패(10048)

Specific2

해당 없음

Success

해당 없음

Success

${ROWSPAN3}$SO_REUSEADDR${REMOVE}$

와일드카드

실패(10013)

실패(10013)

성공*

Success

특정

실패(10013)

실패(10013)

Success

성공*

Specific2

해당 없음

Success

해당 없음

Success

* 동작은 패킷을 수신할 소켓에 대해 정의되지 않습니다.

 

첫 번째 바인딩이 옵션이나 SO_REUSEADDR 설정하지 않고 두 번째 바인딩이 SO_REUSEADDR 수행하는 경우 두 번째 소켓은 패킷을 수신할 소켓에 대한 포트 및 동작을 추월했습니다. 이 상황을 해결하기 위해 SO_EXCLUSIVEADDRUSE 도입되었습니다.

SO_EXCLUSIVEADDRUSE 설정된 소켓은 소켓 닫기 직후에 항상 다시 사용할 수 없습니다. 예를 들어 배타적 플래그가 설정된 수신 대기 소켓이 수신 대기 소켓이 닫힌 후 연결을 수락하는 경우 허용된 연결이 더 이상 활성화되지 않을 때까지 다른 소켓은 배타적 플래그를 사용하여 첫 번째 수신 소켓과 동일한 포트에 바인딩할 수 없습니다.

이 상황은 매우 복잡할 수 있습니다. 소켓이 닫혀 있더라도 기본 전송이 연결을 종료하지 않을 수 있습니다. 소켓이 닫힌 후에도 시스템은 버퍼링된 모든 데이터를 보내고, 피어에 정상적인 연결 끊기를 전송하고, 피어에서 정상적인 연결 끊기를 기다려야 합니다. 따라서 피어가 0 크기의 창을 보급하거나 다른 공격을 하는 경우와 같이 기본 전송이 연결을 해제하지 않을 수 있습니다. 이전 예제에서는 클라이언트 연결이 수락된 후 수신 대기 소켓이 닫혔습니다. 이제 클라이언트 연결이 닫혀 있더라도 승인되지 않은 데이터 등으로 인해 클라이언트 연결이 활성 상태로 유지되는 경우에도 포트가 계속 재사용되지 않을 수 있습니다.

이러한 상황을 방지하려면 애플리케이션이 정상 종료를 보장해야 합니다. SD_SEND 플래그를 사용하여 종료 를 호출한 다음, 0바이트가 반환될 때까지 recv 루프에서 대기합니다. 이렇게 하면 포트 재사용과 관련된 문제를 방지하고, 피어에서 모든 데이터를 수신하고, 피어에 모든 데이터가 성공적으로 수신되었음을 보장합니다.

포트가 활성 대기 상태 중 하나로 전환되지 않도록 소켓에 SO_LINGER 옵션을 설정할 수 있습니다. 그러나 이렇게 하면 연결이 다시 설정될 수 있으므로 원치 않는 효과가 발생할 수 있으므로 권장되지 않습니다. 예를 들어 데이터가 수신되었지만 아직 피어에서 승인되지 않은 경우 로컬 컴퓨터가 SO_LINGER 설정된 소켓을 닫으면 연결이 다시 설정되고 피어는 승인되지 않은 데이터를 삭제합니다. 또한, 남아있는 적절한 시간을 선택하는 것은 어렵다; 값이 너무 작을수록 연결이 중단되는 반면, 시간 제한이 크면 많은 연결을 설정하여 시스템 서비스 거부 공격에 취약해질 수 있으며 이로 인해 수많은 애플리케이션 스레드가 지연될 수 있습니다.

참고

SO_EXCLUSIVEADDRUSE 옵션을 사용하는 소켓은 닫기 전에 제대로 종료해야 합니다. 이렇게 하지 않으면 연결된 서비스를 다시 시작해야 하는 경우 서비스 거부 공격이 발생할 수 있습니다.

 

요구 사항

요구 사항
지원되는 최소 클라이언트
Windows 2000 Professional[데스크톱 앱만]
지원되는 최소 서버
Windows 2000 Server[데스크톱 앱만]
헤더
Winsock2.h