QUIC para o kernel Linux
(lwn.net)- O primeiro patch para integrar oficialmente o protocolo QUIC ao kernel Linux foi enviado
- O objetivo é superar limitações do TCP, como latência, head-of-line blocking e a ossificação do protocolo causada por dispositivos intermediários
- O QUIC é baseado em UDP, oferece suporte a multistream e criptografia ponta a ponta, e sua adoção no kernel pode ampliar o aproveitamento em mais plataformas e hardwares
- O desempenho da implementação inicial no kernel ficou abaixo do TCP tradicional e do kernel TLS, mas há expectativa de melhora com offloading de hardware e otimizações futuras
- Atualmente, há discussões ativas sobre suporte em Samba, SMB/NFS baseado em kernel, curl e outros, mas a incorporação ao mainline ainda deve levar tempo
Contexto do surgimento do protocolo QUIC e limitações do TCP
- O QUIC foi criado para resolver vários problemas do TCP na internet atual
- A latência causada pelo three-way handshake no processo de conexão do TCP, a falta de bom suporte a multistream e o fenômeno de head-of-line blocking devido à perda de pacotes prejudicam a experiência de uso da web
- Os metadados do TCP são transmitidos sem criptografia, o que traz risco de vazamento de informações, e os middleboxes (dispositivos intermediários) filtram o tráfego com base nas informações da conexão, levando à ossificação do protocolo (ossification)
- Mesmo tentativas de melhorar o TCP, como o Multipath TCP, não conseguem funcionar normalmente sem se disfarçar de TCP convencional
Características do QUIC e vantagens técnicas
- O QUIC funciona sobre UDP e permite estabelecer conexões rapidamente, sem um three-way handshake separado
- O protocolo foi projetado para transmissão multistream, de forma que a perda de pacotes não afete todo o fluxo
- Os dados de transporte relacionados ao QUIC são sempre criptografados de ponta a ponta (com base em TLS), impedindo que dispositivos intermediários acessem as mensagens internas
- Em qualquer ambiente de rede onde pacotes UDP possam passar, o QUIC também pode funcionar normalmente
Visão geral do patch de integração do QUIC no kernel Linux
- O patch enviado introduz um novo tipo de protocolo chamado IPPROTO_QUIC, permitindo usar a chamada de sistema socket() existente
- Assim como no TCP, é possível usar chamadas como bind() , connect() , listen() e accept() , mas o processamento posterior tem diferenças
- O gerenciamento de sessão TLS e os processos de autenticação/criptografia são tratados no espaço do usuário, e após a conexão o handshake TLS precisa ser concluído em cada ponta antes que a troca de dados seja possível
- Após a conexão inicial, é possível armazenar em cache o resultado da negociação TLS, acelerando bastante reconexões entre dois sistemas
Desafios de desempenho e perspectivas
- A implementação de QUIC dentro do kernel ainda mostra desempenho inferior ao do kernel TLS e TCP existentes
- Menos de um terço da vazão do in-kernel TLS; mesmo com a criptografia desativada, a vazão pode ser até 4 vezes menor que a do TCP
- Entre os motivos apontados estão a falta de suporte a segmentation offloading, cópias extras de dados no caminho de transmissão e o processo de criptografia de cabeçalhos
- Espera-se que o desempenho melhore com futuro suporte a offloading de hardware e com a otimização da implementação no kernel
Adoção atual e perspectivas futuras
- Há discussões ativas sobre suporte a QUIC no kernel em vários projetos, como servidor/cliente Samba, sistemas de arquivos SMB e NFS do kernel, curl e outros
- O patch tem cerca de 9.000 linhas e, no momento, inclui apenas código de suporte de baixo nível. A implementação completa foi prometida em patches adicionais
- As discussões de revisão de código e incorporação estão apenas começando, então o uso prático ainda deve levar tempo
- Considerando o precedente recente do protocolo Homa, cuja incorporação ao kernel exigiu 11 envios ao longo de 9 meses, também se espera que o QUIC entre no mainline só depois de 2026
1 comentários
Comentários do Hacker News
ssl_preread_server_nameà configuração do NGINX para fazerproxy_passde requisições de certos domínios para outra instância do NGINXA primeira instância apenas encaminha o fluxo TLS bruto (incluindo
proxy_protocol), e a segunda faz de fato a terminação do TLSEssa abordagem é eficaz para implementar failover — se o caminho padrão do servidor cair, atualizo o registro DNS A para o NGINX da máquina de failover, e essa instância roteia as requisições de determinados domínios por um caminho separado até o backend original
É conveniente porque não é preciso duplicar toda a configuração de TLS
Só que esse método não funciona com HTTP/3
HTTP/3 é baseado em QUIC, roda sobre UDP e criptografa o SNI durante o handshake, então não dá para fazer roteamento por domínio com
ssl_preread_server_nameFico me perguntando se existe alguma alternativa que suporte roteamento baseado em SNI no HTTP/3, ou se, caso essa funcionalidade seja necessária, ainda é preciso continuar com HTTP/1.1 ou HTTP/2 sobre TLS
Na prática, o comportamento varia conforme a implementação do cliente (veja o status do suporte a registros HTTPS no Chromium neste issue), mas quando a conexão QUIC falha, o cliente faz fallback de forma transparente para HTTP/1.1/2 e também respeita o cabeçalho Alt-Svc
Se for um failover planejado, você também pode simplesmente deixar de enviar o cabeçalho Alt-Svc e esperar o timeout até a instância alternativa
Se o roteamento de QUIC for realmente necessário, felizmente a informação de SNI está sempre no primeiro pacote, então dá para rotear com inspeção de pacotes
O udpgrm da Cloudflare pode servir de referência, e isso funciona quando não há ECH (Encrypted Client Hello)
Se houver ECH, o roteador precisa ter a chave de descriptografia para poder tomar a decisão de roteamento, e também é possível projetar failover em cascata no próprio protocolo
Uma implementação concreta de código pode ser vista neste exemplo do udpgrm
Se um invasor tiver acesso a esse servidor, também será fácil emitir novos certificados SSL, então, em vez de pensar em um sistema complexo de failover, parece mais sensato simplesmente terminar o TLS diretamente
Pessoalmente, nunca consegui reproduzir na prática os ganhos de desempenho e confiabilidade do QUIC
Faço testes repetidamente há anos, mas na maioria das vezes acabo desativando por questões de desempenho e afins
O failover baseado em DNS também leva minutos para realmente se refletir, e clientes simples como navegadores muitas vezes não fazem o failover direito
Por isso uso diretamente um handler
onerrorpara carregar um segundo caminhoPor exemplo, para rastreamento de anúncios uso código desse tipo, e também forneço uma API
fetchencapsulada da mesma formaEsse método é muito mais eficiente do que qualquer outra tentativa que já fiz
Mesmo que o navegador não consiga estabelecer a conexão QUIC (ainda que ela esteja anunciada no DNS), ele fará fallback automático para HTTP/1 ou HTTP/2 sobre TLS, então dá para usar exatamente o mesmo método de failover de sempre
Uma característica de design do HTTP/3 é justamente não expor informações do endpoint até a camada TLS
Pessoalmente, considero isso uma vantagem
O HAProxy consegue fazer proxy de TLS bruto, mas não roteamento com base no nome do host
O Cloudflare Tunnel tem uma funcionalidade especial que permite roteamento por nome de host sem terminar o TLS, mas para usá-la o DNS também precisa apontar para a Cloudflare
Veja a forma como isso é retratado nesta tirinha do xkcd
Fico curioso se a mesma limitação também existe em ambientes TCP+TLS quando se usa Encrypted Client Hello
Imagino que a resposta seja praticamente a mesma
Sinto que esta discussão caminha para resolver esses problemas aos poucos
Também existe a possibilidade de suporte em hardware nas placas de rede no futuro
Mas hoje em dia a maior parte do tráfego da internet vai entre dispositivos móveis e servidores, e é justamente nesse trecho que QUIC e HTTP/3 mostram seu valor
Para os outros casos, o TCP pode continuar sendo usado
Imagino algo que continue parecendo várias conexões, mas com cache interno
Eu preferiria receber explicitamente um objeto de conexão e abrir streams separados a partir dele, mas por enquanto também aceito a forma atual
Pelo que vi nesta discussão, se isso não for uma extensão, então no lado do servidor também é possível criar novos streams depois que a conexão for estabelecida
No cliente, como na prática é um stream mas é difícil abstraí-lo como algo separado tipo uma “conexão”, parece que será necessária uma abstração de API completamente nova
Imagino algo como receber um descritor de arquivo para cada novo stream via
recvmsgQuero algo tão resistente a problemas de rede quanto o Mosh, mas mantendo todos os recursos do OpenSSH como SFTP, SOCKS, port forwarding, gerenciamento de estado, roaming etc.
Fico me perguntando se o OpenSSH conseguiria aproveitar o suporte no kernel
Veja o Mosh
Provavelmente faria mais sentido criar um protocolo de login separado baseado em QUIC
Há várias abordagens em estágio de protótipo
Porém, agora estão dizendo que a implementação atual de QUIC no kernel é de 3 a 4 vezes mais lenta que no Linux, e que a diferença de desempenho deve diminuir em breve
Se velocidade é a vantagem do QUIC, e na prática ele é mais lento, qual é então a razão para usá-lo?
Até o autor do PR diz que parte da perda de desempenho vem do próprio design do protocolo; então fica a dúvida se ainda há problemas no TCP que poderiam ser corrigidos separadamente
Em grande parte, tudo se resume a “ainda não foi otimizado”
Por exemplo, falta suporte a segment offload, há cópias extras de dados no caminho de transmissão, e existe overhead por causa da criptografia de cabeçalhos — tudo isso tem boa chance de ser resolvido
O benchmarking aqui foi feito em um ambiente muito idealizado
Na prática, ambientes móveis têm grande variabilidade de rede, e é aí que as limitações estruturais do TCP ficam mais evidentes
Na verdade, em muitos casos já se implementam sobre TCP, como no HTTP/2, recursos semelhantes aos do QUIC
No fim, o QUIC é uma pilha de rede abrangente que opera acima da camada 5 do modelo OSI, enquanto o TCP é mais como um motor de camada 3, então é estruturalmente difícil compará-los
Acima de tudo, a vantagem do QUIC está em conexões e reconexões mais rápidas, além de garantir continuidade da sessão mesmo quando o IP muda
Sua estrutura de multiplexação e streams não bloqueantes simplifica drasticamente o desenho de protocolos de nível superior
Se essa estrutura entrar no kernel, o potencial de otimização de desempenho será enorme
Em vez de continuar construindo soluções em várias camadas sobre as limitações do TCP, deveríamos usar com mais frequência bases mais avançadas como o QUIC no dia a dia
Quando há perda de pacotes, tudo o que vem depois fica atrasado até a recuperação completa da transmissão (HOL blocking), o que é uma limitação estrutural importante
Não é simplesmente uma questão de velocidade, e sim de melhorar a latência
Veja esta explicação técnica
A principal limitação está nas trocas de contexto entre kernel e espaço do usuário
Redes em user space (por exemplo, com acesso direto à NIC) eliminam a entrada no kernel
Por outro lado, fornecer funcionalidades no espaço do kernel (como
sendfile, TLS no kernel, offloading na NIC, DMA direto do disco para a NIC) também reduz trocas de contexto e cópias de dados no sistema como um todoAs pilhas QUIC atuais não conseguem aproveitar bem nenhuma das duas vantagens
A entrada e saída de pacotes ainda dependem de syscalls, e não se consegue evitar cópias de dados
Dá para reduzir trocas de contexto com I/O em lote via
io_uringe afins, mas isso não reduz a cópia em siHá dois caminhos: bypass do kernel + DMA, ou excluir o espaço do usuário, como em
sendfile/kTLSA implementação de QUIC no kernel hoje não tem nenhuma das duas vantagens
Se isso puder ser feito escrevendo direto na NIC via DMA ou se tiver que passar por syscall do kernel, a diferença de desempenho é grande
Networking em user space só faz sentido quando existe essa estrutura de transição de privilégio e DMA
É algo usado só por empresas gigantes (MOFAANG e afins)
Em teoria, espera-se que o
io_uringgeneralize esses benefícios, mas isso ainda não chegou de fato ao uso práticoÉ por isso que o TCP/IP continua no kernel nos principais sistemas operacionais
Eu achava que o papel do kernel era gerenciar memória, hardware e tarefas; protocolos acima de IP não deveriam ficar no userland?
Por outro lado, separar essas pilhas para user space também pode trazer ganhos em alguns casos
Como TCP/UDP no kernel arbitra o roteamento de sockets por porta, vários programas conseguem usar TCP/UDP ao mesmo tempo
O QUIC roda sobre UDP, então o ponto da discussão continua válido
O destaque é que apenas protocolos diretamente acima de IP não podem rodar em user space
Espero que a internet fique um pouco mais rápida no futuro
Em ambientes de comunicação como 5G talvez a diferença nem seja tão perceptível, mas ainda assim é um avanço valioso
Acho curioso haver uma estrutura de handshake separada por link
Eu imaginava que o QUIC incorporasse o TLS inteiramente dentro de si, então isso foi diferente do que eu pensava
Ainda assim, acho que esta tecnologia pode reduzir ainda mais a latência em jogos
Quando os recursos computacionais e a eficiência de rede aumentam, a demanda em si também cresce
Em jogos ou computação científica isso não importa porque o objetivo é “resultados melhores”
Mas na web isso muitas vezes tem efeito contrário, com mais anúncios, rastreamento e JavaScript
bind(),connect(),listen()eaccept(), mas depois a estrutura muda para uso de syscallssendmsg()erecvmsg()Eu gostaria que também houvesse uma explicação de por que essa abordagem foi adotada e por que não criaram syscalls separadas específicas para QUIC