Поделиться через


Явное уведомление о перегрузке Winsock (ECN)

Введение

Некоторые приложения и (или) протоколы, основанные на протоколе UDP (например, QUIC), стремятся использовать явные кодовые точки уведомления о перегрузке (ECN), чтобы повысить задержку и дрожание в перегруженных сетях.

API-интерфейсы ECN Winsock расширяют интерфейс getsockopt/setsockopt , а также интерфейс сообщений управления WSASendMsg/LPFN_WSARECVMSG (WSARecvMsg) с поддержкой изменения и получения кодовых точек ECN в заголовках IP-адресов. Предоставляемые функции позволяют получать и задавать кодовые точки ECN для каждого пакета.

Дополнительные сведения о ECN см. в разделе Добавление явного уведомления о перегрузке (ECN) в IP-адрес.

Приложению не разрешено указывать кодовую точку "Обнаруженная перегрузка" (CE) при отправке датаграмм. Отправка возвращается с ошибкой WSAEINVAL.

Запрос ECN с помощью WSAGetRecvIPEcn

WSAGetRecvIPEcn — это встроенная функция, определенная в ws2tcpip.h.

Вызовите WSAGetRecvIPEcn, чтобы запросить текущее включение получения сообщения управления IP_ECN (или IPV6_ECN) через LPFN_WSARECVMSG (WSARecvMsg).

См. также структуру WSAMSG .

  • Протокол: IPv4

  • Cmsg_level: IPPROTO_IP

  • Cmsg_type: IP_ECN (50 десятичных разделов)

  • Описание: указывает или получает кодовую точку ECN в поле заголовка IPv4 типа службы (TOS).

  • Протокол: IPv6

  • Cmsg_level: IPPROTO_IPV6

  • Cmsg_type: IPV6_ECN (50 десятичных разделителя)

  • Описание: указывает или получает кодовую точку ECN в поле заголовка IPv6 класса трафика.

Указание ECN с помощью WSASetRecvIPEcn

WSASetRecvIPEcn — это встроенная функция, определенная в ws2tcpip.h.

Вызовите WSASetRecvIPEcn , чтобы указать, должен ли стек IP заполнять буфер управления сообщением, содержащим кодовую точку ECN поля заголовка Type of Service IPv4 (или Traffic Class IPv6 header) на полученной датаграмме. Если задано значение TRUE, функция LPFN_WSARECVMSG (WSARecvMsg) возвращает необязательные данные управления, содержащие кодовую точку ECN полученной датаграммы. Возвращаемое сообщение элемента управления будет IP_ECN (или IPV6_ECN) с уровнем IPPROTO_IP (или IPPROTO_IPV6). Данные сообщения элемента управления возвращаются в виде 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);
}

См. также раздел