ECN (notificação de congestionamento explícito) winsock

Introdução

Alguns aplicativos e/ou protocolos baseados no Protocolo de Datagrama do Usuário (UDP) (por exemplo, QUIC) buscam aproveitar o uso de pontos de código ECN (notificação de congestionamento explícito) para melhorar a latência e a tremulação em redes congestionadas.

As APIs do Winsock ECN estendem a interface de mensagem de controle getsockopt/setsockopt , bem como a interface de mensagem de controle WSASendMsg/LPFN_WSARECVMSG (WSARecvMsg), com suporte para modificar e receber pontos de código ECN em cabeçalhos IP. A funcionalidade fornecida permite que você obtenha e defina pontos de código ECN por pacote.

Para obter mais informações sobre o ECN, consulte A adição de ECN (notificação de congestionamento explícito) ao IP.

Seu aplicativo não tem permissão para especificar o ponto de código CE (Congestionamento Encontrado) ao enviar datagramas. O envio retornará com o erro WSAEINVAL.

Consultar o ECN com WSAGetRecvIPEcn

WSAGetRecvIPEcn é uma função embutida, definida em ws2tcpip.h.

Chame WSAGetRecvIPEcn para consultar a habilitação atual de receber a mensagem de controle IP_ECN (ou IPV6_ECN) por meio de LPFN_WSARECVMSG (WSARecvMsg).

Consulte também a estrutura WSAMSG .

  • Protocolo: IPv4

  • Cmsg_level: IPPROTO_IP

  • Cmsg_type: IP_ECN (50 decimais)

  • Descrição: especifica/recebe o ponto de código ECN no campo de cabeçalho IPv4 do Tipo de Serviço (TOS).

  • Protocolo: IPv6

  • Cmsg_level: IPPROTO_IPV6

  • Cmsg_type: IPV6_ECN (50 decimais)

  • Descrição: especifica/recebe o ponto de código ECN no campo de cabeçalho IPv6 da Classe de Tráfego.

Especificar ECN com WSASetRecvIPEcn

WSASetRecvIPEcn é uma função embutida, definida em ws2tcpip.h.

Chame WSASetRecvIPEcn para especificar se a pilha de IP deve preencher o buffer de controle com uma mensagem contendo o ponto de código ECN do campo de cabeçalho Tipo de Serviço IPv4 (ou campo de cabeçalho da Classe de Tráfego IPv6) em um datagrama recebido. Quando definida como TRUE, a função LPFN_WSARECVMSG (WSARecvMsg) retorna dados de controle opcionais que contêm o ponto de código ECN do datagrama recebido. O tipo de mensagem de controle retornado será IP_ECN (ou IPV6_ECN) com IPPROTO_IP de nível (ou IPPROTO_IPV6). Os dados da mensagem de controle são retornados como um INT. Essa opção é válida somente em soquetes de datagrama (o tipo de soquete deve ser SOCK_DGRAM).

Exemplo de código 1 – suporte ao ECN de publicidade de aplicativos

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

Exemplo de código 2 – aplicativo detectando congestionamento

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

Confira também