WireGuard lento: descubra a causa real
WireGuard lento costuma ser problema de MTU. Encontre a MTU do caminho por bisseção, ajuste o TCP MSS, verifique steal time e meça antes de culpar o túnel.
De onde vêm realmente as velocidades baixas no WireGuard
As velocidades baixas no WireGuard têm uma de quatro causas, e essas quatro causas não são igualmente prováveis. A primeira é a MTU (unidade máxima de transmissão): o túnel cria pacotes demasiado grandes para uma ligação no caminho, fazendo com que as transferências volumosas parem, enquanto as pequenas parecem normais. A segunda é o próprio caminho, que já era o limite antes de o túnel existir. A terceira é a CPU num VPS partilhado de pequena dimensão, onde a encriptação compete com todos os outros guests no mesmo host. A quarta é a própria ligação do peer.
Verifique-as por esta ordem. A MTU vem primeiro porque é a única causa da lista introduzida pelo próprio WireGuard e porque os respetivos sintomas não parecem lentidão. Uma MTU incorreta normalmente manifesta-se como um túnel que estabelece ligação instantaneamente, responde a ping, aceita um início de sessão SSH e depois bloqueia na primeira vez que copia um ficheiro.
Vale a pena excluir um sintoma antes de tudo isto. Se todos os sites novos demoram vários segundos a começar a carregar e depois transferem à velocidade máxima, o problema está na resolução de nomes, não no débito. DNS através do WireGuard tem os seus próprios modos de falha, e nenhuma alteração à MTU os corrige.
Por que o MTU do WireGuard é 1420?
Cada pacote enviado pelo túnel é cifrado e encapsulado num novo pacote. O encapsulamento consome bytes, que deixam de estar disponíveis para a carga útil.
O cabeçalho de dados do WireGuard tem 32 bytes: um campo de tipo de 4 bytes, um índice do recetor de 4 bytes, um contador de 8 bytes e uma tag de autenticação Poly1305 de 16 bytes. À volta dele existe um cabeçalho UDP de 8 bytes. À volta desse cabeçalho existe o cabeçalho IP externo, que tem 20 bytes para IPv4 e 40 bytes para IPv6. O encapsulamento total é, portanto, de 60 bytes quando o seu Endpoint é um endereço IPv4 e de 80 bytes quando é IPv6. A página do protocolo WireGuard documenta a estrutura das mensagens da qual estes valores são obtidos.
wg-quick não faz uma estimativa. Lê o MTU da interface que encaminha o tráfego para o seu Endpoint e subtrai 80. Numa ligação Ethernet normal com 1500 bytes, o resultado é 1420, que é o valor apresentado por ip link show wg0. A subtração é de 80, e não de 60, para que o mesmo valor continue seguro se esse endpoint for algum dia alcançado através de IPv6, cujo cabeçalho externo é 20 bytes maior.
The data behind this chart
[
{
"label": "Ethernet, IPv4 endpoint",
"path_mtu": 1500,
"encap_overhead": 60,
"usable_wg0_mtu": 1440
},
{
"label": "Ethernet, IPv6 endpoint",
"path_mtu": 1500,
"encap_overhead": 80,
"usable_wg0_mtu": 1420
},
{
"label": "PPPoE DSL, IPv4 endpoint",
"path_mtu": 1492,
"encap_overhead": 60,
"usable_wg0_mtu": 1432
},
{
"label": "Extra tunnel in the path",
"path_mtu": 1400,
"encap_overhead": 80,
"usable_wg0_mtu": 1320
}
]Essas 4 linhas são cálculos aritméticos, não medições. Numa ligação limpa de 1500 bytes com um endpoint IPv4, 1440 caberia, portanto o valor predefinido de 1420 deixa 20 bytes sem utilização. Essa margem é intencional e não é um problema seu.
A última linha é a que causa problemas. Quando algum enlace no caminho suporta apenas 1400 bytes, um túnel ainda configurado com 1420 produz um pacote externo de 1500 bytes em cada segmento de tamanho máximo, ou seja, 100 bytes a mais do que esse enlace aceita. O valor que cabe é 1320.
Também não copie 1320. O MTU do seu caminho é uma propriedade desse caminho, e a única forma de o conhecer é medi-lo.
Como se manifesta um MTU incorreto
A falha não é gradual. Existe uma separação clara entre pacotes pequenos e grandes.
pingatravés do túnel funciona com qualquer tamanho normal.- Uma sessão SSH é estabelecida e a introdução de comandos continua a responder.
curl -I https://example.comdevolve os cabeçalhos imediatamente.curl https://example.comnuma página grande fica bloqueado depois dos primeiros kilobytes.scpde um ficheiro grande começa e depois para numa determinada percentagem.- Uma sessão SSH bloqueia no momento em que executa um comando que produz muito conteúdo.
Isto acontece porque uma ligação TCP só cria segmentos de tamanho completo quando tem dados suficientes para transmitir. O handshake e o primeiro pedido cabem em qualquer MTU do caminho. O bloqueio começa no primeiro segmento de tamanho completo. Por isso, a ligação parece saudável até ao momento em que deixa de ser útil.
Um pacote externo maior do que o MTU da ligação seguinte tem um de dois destinos.
É fragmentado. Um router divide-o e o destino remoto volta a montar as partes. A transferência funciona, mas fica mais lenta, porque passa a utilizar dois pacotes em vez de um e o recetor mantém estado até ambos chegarem. Se um fragmento se perder, perde-se todo o pacote original. Assim, um caminho com 1% de perda comporta-se como um caminho muito pior. Muitos firewalls também bloqueiam fragmentos IP por política, o que transforma este resultado no seguinte.
É descartado e pode ou não haver uma indicação desse facto. Um router que não pode fragmentar envia ao remetente uma mensagem ICMP (internet control message protocol) "fragmentation needed", com o MTU que consegue aceitar. Se essa mensagem chegar, a descoberta do MTU do caminho funciona e o remetente reduz automaticamente o tamanho dos segmentos. Muitas redes filtram ICMP, por isso a mensagem frequentemente não chega. Nenhum outro mecanismo comunica a perda. Esse é o black hole: o pacote sai, nada regressa, não aparece nenhum erro nos logs de nenhuma das extremidades e a transferência fica bloqueada até ocorrer um timeout.
Como encontro o MTU correto?
Meça o caminho e faça a subtração. Teste a rede subjacente, não o túnel. Para isso, faça ping ao endereço público do servidor a partir do cliente e impeça a fragmentação.
ping -M do -s 1472 -c 3 203.0.113.10-M do define o bit DF (don't fragment), por isso nenhum router no caminho pode dividir o pacote. -s é o tamanho do payload ICMP. Um pacote IPv4 completo tem esse payload mais 8 bytes do cabeçalho ICMP e 20 bytes do cabeçalho IP. Assim, -s 1472 coloca exatamente 1500 bytes na rede.
Há três resultados importantes. Respostas sem erros significam que 1500 bytes cabem e que o MTU não é o problema. Um erro local significa que a sua própria interface já é menor do que o tamanho solicitado:
ping: local error: message too long, mtu=1500Uma resposta de um router no caminho fornece diretamente a resposta, e pode parar aí:
From 203.0.113.1 icmp_seq=1 Frag needed and DF set (mtu = 1492)Uma perda de pacotes de 100% com 1472, acompanhada por respostas sem erros com um tamanho menor, indica um black hole. Nenhum router está a fornecer informação. Encontre o limite dividindo o intervalo ao meio. Mantenha um tamanho que sabe que funciona e outro que sabe que falha, teste o ponto intermédio e mova o limite correspondente ao resultado. Cada ronda reduz o intervalo restante para metade, por isso cinco ou seis rondas são suficientes.
Uma bisseção completa, uma ronda de cada vez
Cada linha é um comando executado no cliente contra o endereço público do servidor. O comentário regista o resultado obtido.
ping -M do -s 1472 -c 3 203.0.113.10 # 100% loss, so 1500 is too big
ping -M do -s 1272 -c 3 203.0.113.10 # replies, so 1300 fits
ping -M do -s 1372 -c 3 203.0.113.10 # replies, so 1400 fits
ping -M do -s 1422 -c 3 203.0.113.10 # 100% loss, so 1450 is too big
ping -M do -s 1397 -c 3 203.0.113.10 # 100% loss, so 1425 is too big
ping -M do -s 1384 -c 3 203.0.113.10 # 100% loss, so 1412 is too bigO maior payload que funcionou foi 1372. Portanto, este caminho suporta pelo menos 1400 bytes e menos de 1412. Use o limite seguro. Um MTU de caminho de 1400, menos 80 bytes de encapsulamento, resulta num MTU de 1320 para wg0.
tracepath executa a mesma pesquisa automaticamente. Vale a pena executá-la uma vez antes de começar a bisseção:
tracepath -n 203.0.113.10A última linha indica o valor encontrado:
Resume: pmtu 1492 hops 12 back 12Use ambas as ferramentas como ponto de partida, não como prova. Alguns hosts limitam a taxa ou descartam ICMP completamente. Por isso, uma bisseção pode indicar um MTU menor do que o MTU real do caminho. A transferência que estava a falhar é o teste definitivo.
Aplique primeiro o valor em tempo de execução. Assim, se estiver errado, pode revertê-lo com um único comando:
sudo ip link set mtu 1320 dev wg0Repita a transferência que estava bloqueada. Se for concluída, torne o valor permanente no bloco [Interface] do cliente:
[Interface]
PrivateKey = <client private key>
Address = 10.8.0.2/32
MTU = 1320sudo wg-quick down wg0 && sudo wg-quick up wg0
ip link show wg0ip link show wg0 deve agora apresentar mtu 1320. Se continuar a apresentar o valor antigo, wg-quick não leu o ficheiro que editou. Confirme que editou /etc/wireguard/wg0.conf e que MTU está dentro de [Interface], e não dentro de [Peer], onde é ignorado.
O MTU é uma propriedade de uma interface e nunca é negociado entre peers. Defini-lo apenas no cliente reduz os pacotes enviados pelo cliente. O servidor continua a criar pacotes com o seu próprio MTU de wg0. Por isso, os downloads ainda podem ficar bloqueados por um black hole depois de os uploads começarem a funcionar. Defina o valor nos dois lados ou limite o MSS no servidor.
Por que o ajuste de MSS corrige TCP e nada mais
Se o servidor encaminha tráfego dos seus peers, como acontece em qualquer configuração padrão de WireGuard num VPS que usa NAT (tradução de endereços de rede), uma regra corrige TCP para todos os peers e evita procurar um valor em cada cliente que não controla.
MSS (tamanho máximo do segmento) é uma opção de TCP que cada lado inclui no seu pacote SYN para indicar o tamanho máximo do segmento que aceita receber. O ajuste reescreve essa opção em trânsito para corresponder ao MTU real da rota. Assim, os dois extremos concordam com um segmento menor antes de qualquer transferência de dados. Isto funciona porque ocorre durante o handshake e porque não depende de uma mensagem ICMP que o caminho provavelmente está a filtrar.
Com nftables, adicione esta tabela a /etc/nftables.conf abaixo das tabelas existentes:
table inet mangle {
chain forward {
type filter hook forward priority mangle; policy accept;
tcp flags syn tcp option maxseg size set rt mtu
}
}Recarregue com sudo systemctl reload nftables. Num servidor com iptables, o equivalente é uma única linha:
sudo iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtuConfirme que a regra está no caminho percorrido pelos pacotes. Execute sudo nft list table inet mangle ou sudo iptables -t mangle -L FORWARD -n -v enquanto um cliente abre novas ligações e monitorize o contador. Um contador que permanece em zero significa que os pacotes não estão a passar por esse hook. Nesse caso, a regra não faz nada.
Agora, os limites reais. O ajuste cobre TCP e nada mais. Também cobre apenas tráfego encaminhado. Portanto, um serviço executado no próprio servidor WireGuard nunca passa pelo hook forward e nunca tem o MSS ajustado. A regra também afeta apenas ligações abertas depois de ser carregada. As sessões existentes mantêm o MSS que já tinham acordado.
UDP não é alterado, porque UDP não tem um handshake para reescrever. Mesmo assim, a maior parte do tráfego UDP continua a funcionar. QUIC, o transporte usado pelo HTTP/3, testa o próprio tamanho de pacote utilizável e começa deliberadamente com pacotes pequenos. O que não funciona é o UDP que envia um datagrama grande e espera que ele atravesse o caminho, como uma resposta DNSSEC (extensões de segurança do DNS) com mais de 1400 bytes. Essas consultas expiram e são repetidas através de TCP. Para o utilizador, isto aparece como um site lento, e não como um site indisponível.
O CPU da minha VPS é o limite?
O WireGuard cifra com ChaCha20-Poly1305 e troca chaves com Curve25519. Não existe AES em nenhuma parte do caminho de dados, e isso tem uma consequência que é frequentemente interpretada de forma errada: as instruções AES-NI do seu CPU não fazem nada pelo WireGuard. Um host que anuncia AES-NI não está a oferecer uma funcionalidade de desempenho do WireGuard. O ChaCha20 foi escolhido porque é rápido em software puro, incluindo em CPUs sem qualquer aceleração criptográfica.
Isso não significa que o WireGuard não consuma recursos. Numa VPS com 1 vCPU, um núcleo trata da cifra e das interrupções de rede, além do trabalho da sua aplicação.
Faça a medição enquanto uma transferência está a decorrer:
sudo apt install -y sysstat
mpstat -P ALL 1Leia três colunas. %soft é o tempo de softirq, onde ocorre o processamento de pacotes pelo kernel. %steal é o tempo que o hypervisor atribuiu a outra tarefa. %idle é o tempo restante.
%soft próximo de 100 no seu único núcleo significa que a máquina atingiu o limite de processamento de pacotes, e esse é um limite real que mais núcleos conseguem aumentar. top mostra ksoftirqd/0 no topo da lista de processos no mesmo momento, o que confirma a mesma conclusão por outro ângulo.
%steal acima de alguns por cento significa que o limite não pode ser corrigido por si, porque o host está sobrecarregado e a sua vCPU está à espera de um núcleo físico. Isto é comum nos planos partilhados mais baratos e varia ao longo do dia. O tempo de CPU roubado por um vizinho ruidoso exige uma investigação própria, e nenhum valor de MTU o resolverá.
Outro fator é a implementação usada pelo cliente. O módulo do kernel Linux é o caminho rápido e distribui a cifra de um peer por vários núcleos. wireguard-go, a implementação em userspace, é mais lenta e é usada pelos clientes macOS e iOS, porque essas plataformas não permitem que uma aplicação carregue um módulo do kernel.
É o caminho ou o próprio enlace do peer?
Antes de ajustar qualquer coisa, obtenha duas medições do mesmo cliente com alguns minutos de intervalo: o throughput sem o túnel e com o túnel. Sem esse par, estará a fazer suposições.
Execute iperf3 -s no servidor. O teste direto precisa de ter o TCP 5201 acessível no endereço público. Abra essa porta durante o teste e remova a regra depois. Confirme que a porta voltou a estar fechada em vez de assumir que sim.
# on the client, outside the tunnel
iperf3 -c 203.0.113.10
# then through the tunnel
iperf3 -c 10.8.0.1Se os dois valores forem próximos, o WireGuard está a introduzir um custo muito baixo e o limite está no caminho. Se o valor através do túnel ficar muito abaixo do valor direto enquanto %soft se manteve baixo, volte a verificar a MTU. A fragmentação reduz o throughput sem causar falhas, pelo que aparece aqui como uma percentagem de perda e não como um bloqueio.
Teste as duas direções, porque as ligações domésticas normalmente são assimétricas. iperf3 -c 10.8.0.1 -R inverte o fluxo para que o servidor envie. Um cliente numa ligação de 500/20 nunca conseguirá enviar mais de 20 Mbit de upload para o túnel, e nenhuma alteração no servidor altera esse limite.
Depois, teste com fluxos paralelos:
iperf3 -c 10.8.0.1 -P 4Se quatro fluxos em conjunto transferirem muito mais do que um único fluxo, uma única ligação TCP não está a conseguir preencher o caminho. O throughput de um fluxo é limitado pela janela de receção dividida pelo tempo de ida e volta. Por isso, um caminho com 150 ms precisa de uma janela grande para transportar muitos dados. Consulte os seus próprios limites:
sysctl net.ipv4.tcp_rmem net.ipv4.tcp_wmemO terceiro valor de cada comando é o máximo até ao qual o Linux fará o ajuste automático. A perda de pacotes também limita bastante um único fluxo, porque é à perda que o controlo de congestionamento do TCP reage, e um caminho longo torna a recuperação dispendiosa. Execute mtr -rwc 100 203.0.113.10 a partir do cliente durante cem ciclos para ver onde ocorre a perda ao longo da rota. A perda que começa num salto e continua até ao salto final é real. A perda num salto intermédio que desaparece depois desse salto indica que o router está a dar prioridade baixa ao ICMP e não tem significado.
Para obter uma visão repetível do próprio servidor, separada da rede, faça um benchmark do VPS com um método documentado para poder executar o mesmo teste novamente depois de uma alteração e comparar resultados equivalentes.
O que o WireGuard não consegue corrigir
O WireGuard é um túnel. Não pode ser mais rápido do que a ligação mais lenta no caminho que utiliza, e a sua adição torna sempre esse caminho ligeiramente mais lento.
Não faz compressão. Não existe um equivalente de comp-lzo do OpenVPN, nem está previsto um, porque comprimir antes de cifrar expõe informações sobre o texto simples. A maior parte dos dados volumosos já está comprimida, por isso, na prática, isto não lhe custa nada. Esta é uma das diferenças reais a considerar ao comparar o WireGuard com o OpenVPN e resulta de uma decisão de design deliberada.
Um túnel completo altera a rota seguida por cada pacote. O tráfego que antes ia de si para uma CDN (content delivery network) próxima passa a ir de si para o seu VPS e depois para a CDN. Se o VPS estiver noutro continente, cada pedido faz esse desvio e o tempo de ida e volta aumenta de forma correspondente. Nenhum valor de configuração o reduz. Mova o VPS para mais perto ou use um túnel dividido, para que apenas o tráfego que precisa da VPN faça o percurso mais longo. O tráfego que segue cada rota é decidido inteiramente por AllowedIPs, e o encaminhamento por chaves criptográficas explica como essa decisão é tomada.
Os limites do fornecedor ficam fora do túnel e são fáceis de esquecer. Um plano com um limite mensal de largura de banda costuma limitar a porta a uma velocidade muito inferior quando o limite é atingido, e o túnel parece então avariado. Consulte o painel antes de passar uma noite a investigar o MTU.
PersistentKeepalive não afeta o débito. Serve para manter aberta uma associação NAT, para que o servidor possa continuar a alcançar um cliente atrás de um router doméstico. Reduzi-lo para menos de 25 segundos acrescenta pacotes e não corrige nada.
Meça nesta ordem
- Reproduza o problema e anote se é um bloqueio ou uma lentidão uniforme. Um bloqueio aponta para o MTU. Uma lentidão uniforme não.
- Faça uma análise com
ping -M dodo cliente para o endereço público do servidor e anote o MTU do caminho. - Subtraia 80, defina esse MTU em wg0 nas duas extremidades e repita o teste da transferência que estava a falhar.
- Adicione o ajuste de MSS no servidor se ele encaminhar tráfego para os peers.
- Execute
mpstat -P ALL 1durante uma transferência e leia%softe%steal. - Execute
iperf3fora do túnel e dentro dele, nas duas direções, com um único fluxo e com-P 4. - Execute
mtr -rwc 100para o servidor e procure perdas que persistam até ao último salto.
Altere o plano do servidor apenas depois de o passo 5 indicar que o CPU é o limite. Os passos 1 a 4 não têm custo e resolvem a maioria dos relatos de lentidão num túnel.
FAQ
Por que o meu túnel WireGuard é rápido para ping, mas lento para downloads?
Essa diferença indica um problema de MTU. Os pacotes pequenos cabem em todas as ligações do caminho, por isso ping e um login SSH funcionam. Uma transferência em massa envia segmentos no tamanho máximo. A versão encapsulada desses segmentos é maior do que o que uma das ligações aceita. Se esse router os descartar sem devolver uma mensagem ICMP, a perda não é comunicada e a transferência fica bloqueada. Encontre a MTU do caminho com uma bisseção de ping -M do até ao endereço público do servidor, subtraia 80 bytes pelo encapsulamento e defina o resultado como a MTU de wg0 nas duas extremidades.
Que MTU devo definir para o WireGuard?
Não existe um valor universal. É precisamente por isso que o valor predefinido de 1420 falha para algumas pessoas. 1420 corresponde a 1500 menos 80 bytes pelo cabeçalho do WireGuard, pelo cabeçalho UDP e pelo cabeçalho IPv6 externo. Se o caminho transportar menos de 1500 bytes, o que é normal em DSL com PPPoE e quando o tráfego atravessa outro túnel, precisa de um valor menor. Meça primeiro a MTU do caminho e subtraia 80.
O ajuste de MSS substitui a definição da MTU?
Não. O ajuste altera a opção MSS durante o handshake TCP para que as duas extremidades enviem segmentos menores. Isto corrige o TCP sem alterar a interface. O UDP não tem um handshake que possa ser alterado, por isso não é afetado. O ajuste também se aplica apenas ao tráfego encaminhado pelo servidor. Um serviço executado no próprio servidor WireGuard não beneficia dele. Use ambos: uma MTU correta na interface e o ajuste de MSS para abranger peers cuja configuração não controla.
Um plano VPS mais rápido tornará o WireGuard mais rápido?
Apenas se o limite for a CPU, e um comando permite confirmar isso. Execute mpstat -P ALL 1 enquanto uma transferência estiver em curso. %soft próximo de 100 no seu único core significa que o processamento de pacotes é o limite. Nesse caso, mais cores aumentarão o desempenho. Um valor elevado de %steal significa que o host está sobrecarregado, pelo que a solução é outro plano ou outro host. Se os dois valores forem baixos enquanto o túnel continuar lento, a CPU está ociosa e um plano maior não mudará nada.
Por que o meu Mac é mais lento do que o meu cliente Linux na mesma rede?
O cliente Linux usa o módulo WireGuard integrado no kernel. Este processa os pacotes no espaço do kernel e distribui a encriptação de um peer pelos cores da CPU. As aplicações para macOS e iOS usam wireguard-go, uma implementação no espaço do utilizador, porque essas plataformas não permitem que uma aplicação carregue um módulo do kernel. O espaço do utilizador copia cada pacote entre o kernel e a aplicação, e essa cópia reduz o débito. A diferença é esperada e nenhuma definição do cliente a elimina.