다음을 통해 공유


Winsock ECN(명시적 정체 알림)

소개

UDP(사용자 데이터그램 프로토콜)(예: QUIC)를 기반으로 하는 일부 애플리케이션 및/또는 프로토콜은 혼잡한 네트워크의 대기 시간과 지터를 개선하기 위해 ECN(명시적 정체 알림) 코드포인트를 활용하려고 합니다.

Winsock ECN API는 IP 헤더에서 ECN 코드포인트 수정 및 수신을 지원하여 WSASendMsg/LPFN_WSARECVMSG(WSARecvMsg) 제어 메시지 인터페이스뿐만 아니라 getsockopt/setsockopt 인터페이스를 확장합니다. 제공된 기능을 사용하면 패킷별로 ECN 코드포인트를 가져와서 설정할 수 있습니다.

ECN에 대한 자세한 내용은 IP에 ECN(명시적 정체 알림) 추가를 참조하세요.

애플리케이션은 데이터그램을 보낼 때 CE(정체 발생) 코드 포인트를 지정할 수 없습니다. 송신은 WSAEINVAL 오류와 함께 반환됩니다.

WSAGetRecvIPEcn을 사용하여 ECN 쿼리

WSAGetRecvIPEcn 은 에 정의된 ws2tcpip.h인라인 함수입니다.

WSAGetRecvIPEcn을 호출하여 LPFN_WSARECVMSG(WSARecvMsg)을 통해 IP_ECN(또는 IPV6_ECN) 제어 메시지를 수신하는 현재 활성화를 쿼리합니다.

WSAMSG 구조도 참조하세요.

  • 프로토콜: IPv4

  • Cmsg_level: IPPROTO_IP

  • Cmsg_type: IP_ECN(50진수)

  • 설명: TOS(서비스 유형) IPv4 헤더 필드에서 ECN 코드포인트를 지정/받습니다.

  • 프로토콜: IPv6

  • Cmsg_level: IPPROTO_IPV6

  • Cmsg_type: IPV6_ECN(50진수)

  • 설명: 트래픽 클래스 IPv6 헤더 필드에서 ECN 코드포인트를 지정/받습니다.

WSASetRecvIPEcn을 사용하여 ECN 지정

WSASetRecvIPEcn 은 에 정의된 ws2tcpip.h인라인 함수입니다.

WSASetRecvIPEcn을 호출하여 IP 스택이 수신된 데이터그램에서 서비스 IPv4 헤더 필드(또는 트래픽 클래스 IPv6 헤더 필드)의 ECN 코드포인트가 포함된 메시지로 컨트롤 버퍼를 채울지 여부를 지정합니다. 로 TRUE설정하면 LPFN_WSARECVMSG(WSARecvMsg) 함수는 받은 데이터그램의 ECN 코드포인트를 포함하는 선택적 제어 데이터를 반환합니다. 반환된 컨트롤 메시지 형식은 수준 IPPROTO_IP (또는 IPPROTO_IPV6)을 사용하여 IP_ECN (또는 IPV6_ECN)됩니다. 제어 메시지 데이터는 INT로 반환됩니다. 이 옵션은 데이터그램 소켓에서만 유효합니다(소켓 유형은 SOCK_DGRAM 있어야 합니다).

코드 예제 1 - 애플리케이션 광고 ECN 지원

#define ECN_ECT_0 2

void sendEcn(SOCKET sock, PSOCKADDR_STORAGE addr, LPFN_WSASENDMSG sendmsg, PCHAR data, INT datalen)
{
    DWORD numBytes;
    INT error;

    CHAR control[WSA_CMSG_SPACE(sizeof(INT))] = { 0 };
    WSABUF dataBuf;
    WSABUF controlBuf;
    WSAMSG wsaMsg;
    PCMSGHDR cmsg;

    dataBuf.buf = data;
    dataBuf.len = datalen;
    controlBuf.buf = control;
    controlBuf.len = sizeof(control);
    wsaMsg.name = (PSOCKADDR)addr;
    wsaMsg.namelen = (INT)INET_SOCKADDR_LENGTH(addr->ss_family);
    wsaMsg.lpBuffers = &dataBuf;
    wsaMsg.dwBufferCount = 1;
    wsaMsg.Control = controlBuf;
    wsaMsg.dwFlags = 0;

    cmsg = WSA_CMSG_FIRSTHDR(&wsaMsg);
    cmsg->cmsg_len = WSA_CMSG_LEN(sizeof(INT));
    cmsg->cmsg_level = (addr->ss_family == AF_INET) ? IPPROTO_IP : IPPROTO_IPV6;
    cmsg->cmsg_type = (addr->ss_family == AF_INET) ? IP_ECN : IPV6_ECN;
    *(PINT)WSA_CMSG_DATA(cmsg) = ECN_ECT_0;

    error =
        sendmsg(
            sock,
            &wsaMsg,
            0,
            &numBytes,
            NULL,
            NULL);
    if (error == SOCKET_ERROR) {
        printf("sendmsg failed %d\n", WSAGetLastError());
    }
}

코드 예제 2 - 혼잡을 감지하는 애플리케이션

#define ECN_ECT_CE 3

int recvEcn(SOCKET sock, PSOCKADDR_STORAGE addr, LPFN_WSARECVMSG recvmsg, PCHAR data, INT datalen, PBOOLEAN congestionEncountered)
{
    DWORD numBytes;
    INT error;
    INT ecnVal;
    SOCKADDR_STORAGE remoteAddr = { 0 };

    CHAR control[WSA_CMSG_SPACE(sizeof(INT))] = { 0 };
    WSABUF dataBuf;
    WSABUF controlBuf;
    WSAMSG wsaMsg;
    PCMSGHDR cmsg;

    dataBuf.buf = data;
    dataBuf.len = datalen;
    controlBuf.buf = control;
    controlBuf.len = sizeof(control);
    wsaMsg.name = (PSOCKADDR)&remoteAddr;
    wsaMsg.namelen = sizeof(remoteAddr);
    wsaMsg.lpBuffers = &dataBuf;
    wsaMsg.dwBufferCount = 1;
    wsaMsg.Control = controlBuf;
    wsaMsg.dwFlags = 0;

    *congestionEncountered = FALSE;

    error =
        recvmsg(
            sock,
            &wsaMsg,
            &numBytes,
            NULL,
            NULL);
    if (error == SOCKET_ERROR) {
        printf("recvmsg failed %d\n", WSAGetLastError());
        return -1;
    }

    cmsg = WSA_CMSG_FIRSTHDR(&wsaMsg);
    while (cmsg != NULL) {
        if ((cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_ECN) ||
            (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_ECN)) {
            ecnVal = *(PINT)WSA_CMSG_DATA(cmsg);
            if (ecnVal == ECN_ECT_CE) {
                *congestionEncountered = TRUE;
            }
            break;
        }
        cmsg = WSA_CMSG_NXTHDR(&wsaMsg, cmsg);
    }

    return numBytes;
}

void receiver(SOCKET sock, PSOCKADDR_STORAGE addr, LPFN_WSARECVMSG recvmsg)
{
    DWORD numBytes;
    INT error;
    DWORD enabled;
    CHAR data[512];
    BOOLEAN congestionEncountered;

    error = bind(sock, (PSOCKADDR)addr, sizeof(*addr));
    if (error == SOCKET_ERROR) {
        printf("bind failed %d\n", WSAGetLastError());
        return;
    }

    enabled = TRUE;
    error = WSASetRecvIPEcn(sock, enabled);
    if (error == SOCKET_ERROR) {
        printf(" WSASetRecvIPEcn failed %d\n", WSAGetLastError());
        return;
    }

    do {
        numBytes = recvEcn(sock, addr, recvmsg, data, sizeof(data), &congestionEncountered);
        if (congestionEncountered) {
            // Tell sender to slow down
        }
    } while (numBytes > 0);
}

추가 정보