Winsock の明示的な輻輳通知 (ECN)

はじめに

ユーザー データグラム プロトコル (UDP) (QUIC など) に基づく一部のアプリケーションやプロトコルでは、混雑したネットワークでの待機時間とジッターを改善するために、明示的な輻輳通知 (ECN) コードポイントの使用を利用します。

Winsock ECN API は 、getsockopt/setsockopt インターフェイスと WSASendMsg/LPFN_WSARECVMSG (WSARecvMsg) コントロール メッセージ インターフェイスを拡張し、IP ヘッダーでの ECN コードポイントの変更と受信をサポートします。 提供される機能を使用すると、パケットごとに 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 (10 進数 50)

  • 説明: サービスの種類 (TOS) IPv4 ヘッダー フィールドで ECN コードポイントを指定または受信します。

  • プロトコル: IPv6

  • Cmsg_level: IPPROTO_IPV6

  • Cmsg_type: IPV6_ECN (10 進数 50)

  • 説明: [トラフィック クラス IPv6] ヘッダー フィールドで ECN コードポイントを指定または受信します。

WSASetRecvIPEcn で ECN を指定する

WSASetRecvIPEcn は、 で ws2tcpip.h定義されているインライン関数です。

WSASetRecvIPEcn を呼び出して、IP スタックが制御バッファーに、受信したデータグラムのサービスの種類 IPv4 ヘッダー フィールド (または Traffic Class 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);
}

関連項目