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);
}