OpenVPN sobre TCP: rto_min não corrige o bloqueio à cabeça da fila
Um fluxo de bytes externo entrega por ordem, por isso um único segmento em falta pode parar todos os fluxos internos. Um temporizador interno mais longo muda um relógio, não essa restrição.
Uma shell interativa sobre o túnel congela durante vários segundos enquanto uma cópia de ficheiros decorre, e depois retoma como se nada tivesse acontecido. A transferência é a única coisa com um problema, mas a shell também espera, e ambas recuperam no mesmo momento. Esse padrão é a assinatura de um fluxo fiável que transporta muitas conversas, e sobrevive a quase todas as afinações que as pessoas tentam primeiro.
Vale a pena ler o sintoma com atenção porque aponta para um mecanismo e não para uma definição: a pausa é partilhada, logo a causa é partilhada, e o objeto partilhado é a ligação externa. Tudo o resto no registo parece normal enquanto acontece.
O sintoma: tudo pára ao mesmo tempo
Duas ligações internas a bloquear ao mesmo tempo são a pista. Uma transferência lenta e uma shell lenta são conversas TCP separadas, com números de sequência e estado de congestionamento separados, pelo que uma falha numa não deveria tocar na outra. Quando param em uníssono, algo acima delas está a ordenar os seus bytes.
Esse algo é o transporte do túnel. No modo TCP, o OpenVPN escreve os pacotes encapsulados numa única ligação TCP para o par, e o contrato do TCP é a entrega por ordem de um único fluxo de bytes. Os bytes posteriores não são entregues à aplicação antes dos que faltam, independentemente da conversa interna a que pertencem.
Note o que o sintoma não diz. Não diz que o caminho está quebrado, que o servidor está sobrecarregado ou que a cifra é lenta. Esses produzem formas diferentes: débito uniformemente baixo, tempos limite ou falhas que afetam uma direção. Uma pausa partilhada que termina quando um segmento perdido é retransmitido é especificamente um efeito de ordenação.
Um fluxo de bytes externo, muitas conversas internas
Imagine o túnel a transportar uma transferência e uma shell ao mesmo tempo. Um segmento da ligação externa perde-se. Os segmentos seguintes podem chegar ao anfitrião recetor, e a pilha TCP recetora irá confirmá-los, mas não os pode entregar para cima enquanto a lacuna não for preenchida. O OpenVPN não tem, portanto, nada para ler para a shell, embora os bytes da própria shell tenham chegado há muito.
Isto é bloqueio à cabeça da fila dentro de uma ligação, e não exige que as duas aplicações partilhem uma ligação interna. Partilham a externa, o que basta.
O efeito de segunda ordem piora a situação. Um emissor interno que não vê confirmação pode retransmitir dados que a camada externa já está a tentar entregar. Essa retransmissão torna-se tráfego adicional do túnel num caminho que acabou de provar que pode perder segmentos, pelo que o trabalho de recuperação compete com a recuperação que importa.
Nada disto garante um colapso. Num caminho limpo, com idas e voltas curtas e filas generosas, o arranjo pode ser perfeitamente utilizável durante anos. A falha aparece quando perda, atraso e profundidade de fila se alinham, e é por isso que os relatos se concentram em uplinks congestionados e ligações móveis em vez de escritórios bem aprovisionados.
Porque é que um temporizador interno mais longo não pode ajudar
Elevar o tempo limite mínimo de retransmissão numa rota, com rto_min, adia a retransmissão por temporizador do tráfego TCP gerado localmente que usa essa rota. É um botão real com um efeito real, e trata do problema de segunda ordem em vez do primeiro.
ip route show
ip route show cache 2>/dev/null | head
ss -tin dst 10.70.0.1
O que não pode fazer é libertar bytes externos fora de ordem, recuperar o segmento em falta mais depressa, ou fazer o servidor de aplicações remoto adotar o mesmo temporizador. A pilha recetora continua a entregar por ordem, e o emissor no extremo distante continua a usar as suas próprias medições de ida e volta.
Também não desativa todos os outros mecanismos de recuperação de perdas. Onde uma perda real exige recuperação por temporizador, um mínimo maior faz a aplicação esperar mais antes de agir, o que é o oposto do que uma sessão sensível à latência precisa.
Um limite fixo de doze segundos aparece nos conselhos sobre este tema como se a existência de um túnel TCP o implicasse. O temporizador de retransmissão descrito na RFC 6298 deriva do tempo de ida e volta medido com recuo exponencial, pelo que um limite constante é uma escolha de política para um caminho específico e não um requisito do protocolo. Aplicado de forma ampla, esconde o sintoma ao mesmo tempo que altera a recuperação de todos os fluxos que essa rota cobre.
O que o tcp-nodelay faz e não faz
A opção de túnel que desativa o buffering ao estilo Nagle no socket de transporte tem um propósito estreito: impede que as escritas pequenas esperem por mais dados antes da transmissão. Isso ajuda o tráfego interativo a evitar um atraso extra num caminho que de resto está saudável.
Não transforma o transporte num transporte sem ordem, e não encurta a recuperação após um segmento perdido. Uma shell que pára por um tempo limite de retransmissão continuará a parar com essa opção ativa, porque a opção nunca tocou na regra de ordenação.
Confundir as duas é fácil porque ambas são descritas como correções de latência. Uma remove uma espera artificial introduzida pelo buffering; a outra exigiria mudar o que o transporte garante, e nenhuma opção de socket faz isso.
Como comparar transportes de forma justa
Se o operador o permitir e ambos os serviços existirem, compare um serviço UDP e um serviço TCP a transportar a mesma carga de trabalho pelo mesmo caminho de acesso ao mesmo servidor. Mudar de país, fornecedor ou geração de servidor entre os dois testes introduz uma variável que dominará o resultado.
Procure a pausa partilhada em vez de um número de débito. Várias aplicações internas a bloquear em conjunto durante uma perda externa é a observação que identifica o bloqueio à cabeça da fila; um único valor mais baixo num teste de velocidade não o faz.
Capturas autorizadas na interface do túnel e na interface física separam as duas camadas com clareza, porque as retransmissões internas e externas pertencem a ligações diferentes com espaços de sequência diferentes. Ver retransmissões internas enquanto o fluxo externo mostra uma lacuna é a prova direta.
Passar para UDP remove o fluxo fiável adicional e deixa o TCP interno fazer a sua própria recuperação. Não promete perda zero nem latência baixa, e um uplink saturado, filas sobredimensionadas ou um problema de MTU continuarão a precisar de correção. O tráfego fora do túnel nunca fica retido atrás da lacuna de sequência do túnel, embora ainda possa sofrer quando ambos os caminhos partilham uma ligação congestionada, e esse estrangulamento partilhado merece a sua própria investigação em vez de ser dobrado nesta.