1 pontos por GN⁺ 2025-09-28 | 1 comentários | Compartilhar no WhatsApp
  • Cerca de 20% do tráfego HTTP do Firefox usa HTTP/3, que funciona sobre QUIC e UDP
  • Ao substituir a camada legada de I/O de rede, NSPR, por quinn-udp baseado em Rust, o navegador reforçou desempenho e segurança de memória
  • A otimização aproveita ativamente syscalls modernas específicas de cada sistema operacional (multi-message, segmentation offloading etc.)
  • Em Windows e MacOS, alguns recursos ficaram limitados por compatibilidade e problemas de driver, mas no Linux foi confirmado o melhor desempenho
  • A experiência acumulada com suporte a ECN no QUIC e I/O de UDP em várias plataformas, incluindo tentativas, erros e correções de bugs, deve ajudar projetos futuros e o ecossistema open source

Motivação

  • Cerca de 20% do tráfego HTTP do Firefox usa HTTP/3, que opera por meio de QUIC, implementado sobre UDP
  • Historicamente, o Firefox usava a biblioteca NSPR para I/O de rede, mas os recursos relacionados a I/O de UDP eram antigos e limitados (as principais funções são PR_SendTo e PR_RecvFrom)
  • Recentemente, os sistemas operacionais passaram a oferecer syscalls de múltiplas mensagens (ex.: sendmmsg, recvmmsg) e otimizações de rede como segmentation offload (GSO, GRO)
  • Essas técnicas podem melhorar bastante o desempenho do I/O de UDP
  • O Firefox investigou se poderia obter esses benefícios ao substituir a stack existente de I/O de UDP por syscalls modernas

Visão geral

  • O projeto começou em meados de 2024, com o objetivo de reconstruir a stack de I/O de UDP do QUIC no Firefox com syscalls modernas em todos os sistemas operacionais suportados
  • Além do ganho de desempenho, também buscou melhorar a segurança ao usar Rust, com garantia de segurança de memória, no I/O de UDP
  • Como o próprio QUIC já era implementado em Rust, o desenvolvimento foi feito sobre a biblioteca quinn-udp baseada em Rust
  • As diferenças de syscalls entre sistemas operacionais aumentaram a complexidade do desenvolvimento, mas o quinn-udp acelerou bastante o trabalho
  • Em meados de 2025, a implantação já está em andamento para a maioria dos usuários do Firefox, e os benchmarks mostram um grande salto de desempenho, chegando a 4 Gbit/s

Estrutura de I/O de UDP e formas modernas de otimização

Envio de datagrama único

  • O método anterior usava sendto e recvfrom, enviando e recebendo apenas um único datagrama UDP por vez
  • O custo da transição entre espaço de usuário e espaço de kernel era pago por datagrama, o que é ineficiente em ambientes com tráfego intenso
  • Exemplo: para enviar pacotes menores que 1500 bytes a várias centenas de Mbit/s por segundo, o overhead se torna significativo

Envio em lote de múltiplos datagramas

  • Linux e alguns outros sistemas oferecem suporte a syscalls multi-message, como sendmmsg e recvmmsg
  • Isso permite enviar e receber vários datagramas de uma vez, reduzindo bastante o overhead

Datagrama único grande segmentado

  • Tecnologias de offload como GSO (envio) e GRO (recebimento) permitem dividir automaticamente grandes datagramas UDP no sistema operacional ou na NIC
  • A interface de rede faz a separação por pacote, calcula checksums e adiciona cabeçalhos
  • Com isso, a aplicação consegue processar muitos pacotes reais com apenas uma syscall
  • Com GSO ativado, algumas ferramentas de rede, como o Wireshark, têm suporte limitado para análise de pacotes

Processo de substituição do NSPR no Firefox

  • Primeiro, o Firefox substituiu o NSPR por quinn-udp na estrutura de envio e recebimento de datagrama único
  • Em seguida, o pipeline de processamento de datagramas da implementação de QUIC foi refatorado para suportar envio/recebimento em lote e segmentação
  • Tanto syscalls multi-message quanto chamadas de segmentation offload são usadas conforme o caso
  • Também foram adicionados tratamentos de exceção por plataforma e várias melhorias de I/O

Detalhes por plataforma

Windows

  • O Windows oferece WSASendMsg/WSARecvMsg, com suporte a datagramas no tamanho tradicional de MTU ou a grandes datagramas segmentados
  • Os equivalentes do GSO/GRO no Windows são USO (envio) e URO (recebimento)
  • No início, só eram usadas chamadas de datagrama único e não havia problemas, mas com o URO ativado surgiu um bug em certos ambientes (ex.: Windows on ARM + WSL) em que não era possível determinar o tamanho dos pacotes QUIC, causando falhas no carregamento de páginas
  • O USO no envio também foi usado, mas foram descobertos efeitos colaterais no ambiente de instalação do Firefox no Windows, como aumento de perda de pacotes e crashes de drivers de rede
  • Atualmente, o Firefox mantém URO/USO desativados e continua o trabalho de depuração

MacOS

  • No MacOS, o quinn-udp foi introduzido com sendmsg/recvmsg no lugar de sendto/recvfrom
  • O recurso de segmentation offload não está ativado
  • Em vez disso, sendmsg_x/recvmsg_x, não documentados oficialmente, oferecem suporte a envio em lote, aplicado ao quinn-udp de forma não oficial
  • Como a Apple pode remover essas chamadas no futuro, foram apenas testadas sem ativação por padrão e não entraram na versão final

Linux

  • Há suporte tanto a sendmmsg/recvmmsg quanto a GSO/GRO, e o quinn-udp prioriza GSO por padrão no envio
  • O Firefox usa um socket UDP separado por conexão para reforçar a privacidade (distinção por 4-tuple)
  • Nessa estrutura, as vantagens do segmentation offload são maximizadas, enquanto os benefícios de transmissão cruzada de sendmmsg/recvmmsg ficam limitados
  • Fora pequenas mudanças, como sandbox de rede e checagem de suporte a GSO em tempo de execução, a adoção foi bem-sucedida sem grandes dificuldades

Android

  • Diferentemente do Linux, o Android tem caminhos de tratamento de syscall e filtros de segurança (ex.: seccomp) diferentes
  • Há vários problemas de compatibilidade, como suporte a plataformas muito antigas, como Android 5 em x86, contorno de socketcall e tratamento de erros
  • Em alguns ambientes, chamadas de envio com o bit ECN ativado geravam erro (EINVAL), então o quinn-udp adotou uma estratégia de repetir a tentativa e desativar a opção
  • Graças a várias melhorias vindas da comunidade Quinn, o Firefox também pode se beneficiar automaticamente dessas evoluções

Suporte a ECN (Notificação Explícita de Congestionamento)

  • A introdução de syscalls modernas permitiu o envio e recebimento de ancillary data (dados auxiliares), viabilizando o suporte a ECN no QUIC
  • Houve pequenos bugs, mas no Firefox Nightly mais da metade das conexões QUIC operam no caminho de saída com ECN
  • Com o destaque crescente de novas tecnologias como L4S, a importância e a utilidade do suporte a ECN estão aumentando

Resumo da conclusão

  • A camada de I/O de UDP do QUIC no Firefox foi substituída por uma implementação em Rust baseada em quinn-udp, garantindo ao mesmo tempo desempenho e segurança
  • Em vez de syscalls defasadas, agora são usadas syscalls de I/O modernas específicas de cada sistema operacional, permitindo maior throughput e suporte a ECN
  • Alguns recursos de otimização, especialmente no Windows, ainda exigem melhorias por questões de compatibilidade
  • À medida que o uso de QUIC continua crescendo, o suporte no nível de sistema operacional e driver também deve continuar evoluindo

1 comentários

 
GN⁺ 2025-09-28
Opiniões no Hacker News
  • O ponto principal do texto está escondido no meio

    Em casos extremos, em benchmarks limitados por CPU, houve melhora de < 1Gbit/s para 4 Gbit/s. No flamegraph da CPU, a maior parte do tempo era consumida em chamadas de sistema de I/O e no código de criptografia.
    O fato de a taxa de transferência da rede ter aumentado 400% significa que o uso de CPU caiu muito no tráfego de rede via UDP.
    Isso é especialmente impressionante também do ponto de vista de eficiência energética em clientes portáteis, como celulares e notebooks.
    Existe um clima de que esse tipo de transição é sempre algo bom, mas este texto pareceu renovador por provar isso com dados reais.

    • Fico curioso se algum dia o message passing que atravessa o contexto entre programas de usuário e do sistema vai passar a rodar com aceleração por hardware.
  • A melhoria mostrada aqui é de fato necessária para velocidades realmente muito altas (100Gb/s ou mais), mas 4Gb/s na verdade não é tão rápido assim.
    Como são 500MB/s, isso indica que existe algum gargalo seriamente lento em algum ponto.
    Dizem que a troca de contexto do kernel fica na casa de 1us, mas isso na verdade é alto para uma system call.
    Ainda assim, com um tamanho médio de pacote de cerca de 500 bytes por pacote, já dá para atingir 500MB/s, isto é, 4Gb/s.
    O antigo 1Gb/s era quando o tamanho dos pacotes era menor, e simplesmente empurrar pacotes UDP para o buffer da NIC é algo plenamente possível até pela velocidade de cópia de memória.
    Mesmo que a criptografia fosse lenta, na prática ela não é.
    Por exemplo, um Intel i5-6500 já chegou a 1729MB/s em AES-128 GCM.
    As CPUs atuais conseguem 3-5GB/s por núcleo, ou seja, 25-40Gb/s, então esses 4Gb/s citados aqui são um número baixo demais.
    (link de referência de desempenho do AES-128 GCM)

    • Foi dito que a latência da system call é alta, mas a causa disso pode ser as medidas de mitigação de spectre & meltdown.
      TCP tem binding de rota, mas UDP não, então há uma diferença grande na configuração do caminho.
      Dizer que a criptografia é lenta faz sentido no caso de PDUs (unidades de dados de protocolo) pequenas.
      Quase tudo é otimizado e benchmarkado com base em frames TCP grandes, então na prática o custo de preparar estado acaba se destacando em pacotes pequenos.
      Se você roda microbenchmarks em tight loop, os números saem bonitos, mas em ambientes reais com aleatoriedade a eficiência de uso de cache cai, e abaixo de 1KB a eficiência despenca.
      Some a isso o overhead adicional de framing, validação de dados fora de banda e afins, e o custo fica bem alto.
      A memória de buffer de UDP também é insuficiente por padrão, o que gera muitos problemas em uso real.
      Enquanto no TCP os tamanhos de buffer foram sendo aumentados continuamente, o UDP ficou preso a valores conservadores dos anos 90 e 2000.
      A API de que realmente precisamos deveria fazer fork do fd, suportar completamente connect(2) e route binding, e depois seguir para um modelo baseado em submission queue (uring, rio etc.).
      Do lado da criptografia, uma abordagem com KDF pode reduzir bastante o custo de estado.
      O método PSP é aceito por alguns vendors, mas foi bastante rejeitado em fóruns como a IETF, então não se disseminou.
      Em testes de alta concorrência em grande escala feitos por vendors, ele mostra escalabilidade muito superior às famílias TLS existentes.

    • Em nenhum momento é mencionado de que nível era a CPU usada no benchmark.
      E o overhead de criptografia é custo de processamento do próprio protocolo QUIC.
      O QUIC ainda é fraco em offload de criptografia (processamento em hardware) em comparação com o TCP, enquanto o TCP consegue descarregar parte disso na NIC com kTLS offload.

  • Fiquei realmente satisfeito com esse conteúdo técnico.
    Queria que todo o material técnico da Mozilla tivesse essa profundidade, escrito corretamente por engenheiros de verdade atuando na prática.
    Sem otimismo vazio (alegria) e com muito valor de leitura.

  • Não entendo por que ainda suportam Android 5.
    Já faz mais de 10 anos que foi lançado, e os usuários desses aparelhos são legados ainda mais antigos.
    A web de hoje é pesada demais, então parece difícil até navegar direito com dispositivos tão velhos; por isso fico me perguntando por que ainda manter esse suporte.
    Talvez sejam só alguns hackers que restauram aparelhos antigos tipo OnePlus, deixam ligados no carregador, não instalam nem uma ROM popular como LineageOS e usam Firefox por uma app store alternativa.
    Na prática, isso só desacelera o ritmo geral de desenvolvimento.

  • A parte em que, depois de muito vai e vem com a pessoa que reportou o problema (também funcionária da Mozilla), acabaram comprando até um notebook do mesmo modelo e da mesma cor para reproduzir o problema.
    Foi engraçado porque mostrou a realidade insana de reproduzir problemas de rede, bem no estilo do XKCD 2259.
    (link da tirinha xkcd)

    • Recomendo também um episódio interessante relacionado a isso no blog de desenvolvimento do Factorio, intitulado "The map download struggle".
      (post relacionado do blog)

    • Quem já lidou de verdade com problemas de rede provavelmente vai se identificar ainda mais por causa dos misteriosos packet runts.
      A maioria dos equipamentos de rede não lida bem com esse tipo de pacote.
      Tráfego baseado em UDP ou QUIC fica facilmente vulnerável a ataques se não estiver em um ambiente de nuvem de grande porte acima de certo nível.
      Por isso, provedores de hospedagem pequenos ou autogeridos estão ficando cada vez mais difíceis de operar, e só restam os que conseguem lidar com tráfego em grande escala.
      Então, na maioria dos ambientes LAN, a maior parte do tráfego UDP está sendo descartada, e só o necessário é tratado com rate limiting.

  • Bug tracker da Mozilla
    Ainda estou enfrentando o mesmo problema ao acessar no Firefox sites hospedados pela Cloudflare no macOS e no Fedora.

  • Só agora descobri que Windows e macOS também têm recursos semelhantes a GSO/GRO (processamento de pacotes de rede em grande volume).
    É uma pena que, na prática, pareça haver muitos bugs.

    • Fico me perguntando por que Microsoft e Apple não prestam um pouco mais de atenção à qualidade das suas pilhas de rede.
      Dá a impressão de que não é só GSO/GRO que deve ter bugs.
  • Pergunta para quem conseguir explicar como UDP GSO/GRO funciona estruturalmente.
    UDP é um protocolo de pacotes sem ordenação; então, quando um pacote QUIC é dividido em vários pacotes UDP, sem nem informação de sequência no cabeçalho, como o lado receptor recompõe isso na ordem correta?

    • Pelo que entendi, no nível da aplicação, mesmo com GRO ativado, na prática você não recebe datagramas UDP realmente unidos.
      A ideia é que o kernel coloque vários datagramas em uma única estrutura e os repasse mantendo as fronteiras entre eles em cada camada (por exemplo, fragments de dados dentro de sk_buff).
      Não sou especialista no assunto, mas, pesquisando como isso funciona, acabei usando este texto como referência.
  • Foi mencionado que “começamos a desenvolver em cima do quinn-udp, a biblioteca de UDP I/O do projeto Quinn, e isso acelerou bastante o desenvolvimento”.
    Então fiquei curioso para saber se por acaso houve algum patrocínio ao projeto Quinn.
    (link para apoiar o Quinn)

    • Perguntei diretamente sobre apoio financeiro, e um Senior Principal Software Engineer da Mozilla respondeu: "Mozilla não tem dinheiro".
      Ainda assim, eles contribuíram enormemente com código, e sou muito grato por isso.
      (Sou o maintainer principal do Quinn.)

    • Sobre a pergunta "patrocinaram?", alguém comentou que o estilo da Mozilla é não precisar apoiar open source, mas gastar mais alguns milhões de dólares no salário do CEO.
      Isso enquanto até o produto principal deles (Firefox) está desmoronando.

    • Fiquei curioso para saber se houve contribuição de outras formas também, como código e afins.

  • É surpreendente ver sendmmsg/recvmmsg serem chamados de “modernos”.
    Na verdade, são system calls que já existem há bastante tempo.
    Também senti falta de menção a io_uring nesse conteúdo sobre Linux.

    • io_uring não tem uma função de batch de verdade para processar vários datagramas UDP de uma vez.
      Na melhor das hipóteses, ele permite solicitar vários sendmsg e recvmsg de uma vez.
      GSO/GRO é a resposta correta.
      sendmmsg/recvmmsg já são tecnologias bem antigas, e entre desenvolvedores do kernel há quem ache que já era hora de aposentar isso.
      (discussão relacionada no GitHub)