Una shell interattiva sul tunnel si blocca per diversi secondi mentre è in corso una copia di file, poi riprende come se nulla fosse successo. Il download è l’unica cosa con un problema, eppure anche la shell aspetta, e entrambi si riprendono nello stesso momento. Questo schema è la firma di un unico flusso affidabile che trasporta molte conversazioni, e sopravvive a quasi ogni modifica di regolazione che le persone provano per prima.

Vale la pena leggere con attenzione il sintomo perché indica un meccanismo e non un’impostazione: la pausa è condivisa, quindi la causa è condivisa, e l’oggetto condiviso è la connessione esterna. Tutto il resto nel registro sembra normale mentre accade.

Il sintomo: tutto si ferma insieme

Due connessioni interne che si bloccano insieme sono l’indizio. Un download lento e una shell lenta sono conversazioni TCP separate con numeri di sequenza e stato di congestione separati, quindi un guasto in una non dovrebbe toccare l’altra. Quando si fermano all’unisono, qualcosa sopra di loro sta ordinando i loro byte.

Quel qualcosa è il trasporto del tunnel. In modalità TCP, OpenVPN scrive i pacchetti incapsulati in un’unica connessione TCP verso il peer, e il contratto di TCP è la consegna in ordine di un singolo flusso di byte. I byte successivi non vengono consegnati all’applicazione prima di quelli mancanti, indipendentemente da quale conversazione interna appartengano.

Notate cosa il sintomo non dice. Non dice che il percorso è rotto, che il server è sovraccarico o che la cifratura è lenta. Questi producono forme diverse: velocità uniformemente bassa, timeout o errori che colpiscono una direzione. Una pausa condivisa che termina quando un segmento perso viene ritrasmesso è specificamente un effetto di ordinamento.

Un flusso di byte esterno, molte conversazioni interne

Immaginate il tunnel che trasporta contemporaneamente un download e una shell. Un segmento della connessione esterna va perso. I segmenti successivi possono arrivare all’host ricevente, e lo stack TCP ricevente li riscontrerà, ma non può consegnarli verso l’alto finché il vuoto non è colmato. OpenVPN quindi non ha nulla da leggere per la shell, anche se i byte della shell sono arrivati molto tempo prima.

Questo è il blocco in testa alla coda all’interno di una connessione, e non richiede che le due applicazioni condividano una connessione interna. Condividono quella esterna, il che basta.

L’effetto di secondo ordine lo peggiora. Un mittente interno che non vede riscontri può ritrasmettere dati che lo strato esterno sta già cercando di consegnare. Quella ritrasmissione diventa traffico aggiuntivo del tunnel su un percorso che ha appena dimostrato di poter perdere segmenti, quindi il lavoro di recupero compete con il recupero che conta.

Niente di tutto ciò garantisce un collasso. Su un percorso pulito con andate e ritorni brevi e code generose, la sistemazione può essere perfettamente utilizzabile per anni. Il guasto compare quando perdita, ritardo e profondità delle code si allineano, ed è per questo che le segnalazioni si concentrano su uplink congestionati e collegamenti mobili invece che su uffici ben dimensionati.

Perché un timer interno più lungo non può aiutare

Alzare il tempo minimo di ritrasmissione su una rotta, con rto_min, rinvia la ritrasmissione a tempo per il traffico TCP generato localmente che usa quella rotta. È una manopola reale con un effetto reale, e affronta il problema di secondo ordine invece del primo.

ip route show
ip route show cache 2>/dev/null | head
ss -tin dst 10.70.0.1

Ciò che non può fare è liberare byte esterni fuori ordine, recuperare più velocemente il segmento mancante o far adottare lo stesso timer al server applicativo remoto. Lo stack ricevente consegna comunque in ordine, e il mittente all’altro capo usa comunque le proprie misurazioni di andata e ritorno.

Non disattiva nemmeno ogni altro meccanismo di recupero delle perdite. Dove una perdita reale richiede un recupero a tempo, un minimo più grande fa aspettare più a lungo l’applicazione prima di agire, che è l’opposto di ciò che serve a una sessione sensibile alla latenza.

Un limite fisso di dodici secondi compare nei consigli su questo tema come se l’esistenza di un tunnel TCP lo implicasse. Il timer di ritrasmissione descritto nella RFC 6298 deriva dal tempo di andata e ritorno misurato con backoff esponenziale, quindi un limite costante è una scelta di politica per un percorso specifico e non un requisito del protocollo. Applicato in modo ampio, nasconde il sintomo mentre cambia il recupero di ogni flusso coperto da quella rotta.

Cosa fa e cosa non fa tcp-nodelay

L’opzione del tunnel che disattiva il buffering in stile Nagle sul socket di trasporto ha uno scopo ristretto: impedisce alle scritture piccole di attendere altri dati prima della trasmissione. Ciò aiuta il traffico interattivo a evitare un ritardo aggiuntivo su un percorso per il resto sano.

Non rende il trasporto disordinato e non accorcia il recupero dopo un segmento perso. Una shell che si ferma per un timeout di ritrasmissione si fermerà comunque con quell’opzione attiva, perché l’opzione non ha mai toccato la regola di ordinamento.

Confondere le due cose è facile perché entrambe sono descritte come correzioni di latenza. Una rimuove un’attesa artificiale introdotta dal buffering; l’altra richiederebbe di cambiare ciò che il trasporto garantisce, cosa che nessuna opzione di socket fa.

Come confrontare i trasporti in modo equo

Se l’operatore lo consente ed entrambi i servizi esistono, confrontate un servizio UDP e un servizio TCP che trasportano lo stesso carico sullo stesso percorso di accesso verso lo stesso server. Cambiare paese, fornitore o generazione di server tra i due test introduce una variabile che dominerà il risultato.

Cercate la pausa condivisa invece di un numero di throughput. Diverse applicazioni interne che si bloccano insieme durante una perdita esterna è l’osservazione che identifica il blocco in testa alla coda; un singolo valore più basso in uno speed test non lo fa.

Catture autorizzate sull’interfaccia del tunnel e sull’interfaccia fisica separano nettamente i due strati, perché le ritrasmissioni interne ed esterne appartengono a connessioni diverse con spazi di sequenza diversi. Vedere ritrasmissioni interne mentre il flusso esterno mostra un vuoto è la prova diretta.

Passare a UDP rimuove il flusso affidabile aggiuntivo e lascia che il TCP interno faccia il proprio recupero. Non promette perdita zero né bassa latenza, e un uplink saturo, code sovradimensionate o un problema di MTU dovranno comunque essere risolti. Il traffico fuori dal tunnel non è mai trattenuto dietro il vuoto di sequenza del tunnel, anche se può ancora soffrire quando entrambi i percorsi condividono un collegamento congestionato, e quel collo di bottiglia condiviso merita un’indagine propria invece di essere piegato in questa.

Articoli correlati