Il capitolo 11 ha regolato la sorgente rispetto alla capacità della destinazione. Ora il punto stretto si sposta dentro la rete. Il controllo di flusso impedisce che il trasmettitore saturi il ricevitore; il controllo di congestione impedisce che il traffico immesso saturi le code dei router lungo il percorso. Sono due problemi distinti anche quando producono lo stesso effetto finale: rallentare la sorgente.
Nel primo caso la destinazione notifica esplicitamente la propria advertised window, AW. Nel secondo il TCP opera end-to-end, con un controllo implicito e closed loop: interpreta ACK duplicati e scadenze del time-out come segnali dello stato della rete. In entrambi i casi è la cadenza degli ACK a regolare concretamente la trasmissione. Se gli ACK arrivano, liberano spazio nella finestra; se rallentano o non arrivano, anche la sorgente rallenta.
Per rappresentare il secondo limite, ogni connessione mantiene una Congestion Window, CW. La finestra si chiude quando il livello di congestione aumenta e si apre quando la congestione si risolve. Il vincolo effettivo resta il più severo tra quello imposto dal ricevitore e quello stimato per la rete:
W = min{AW, CW}
AW limite imposto dalla destinazione
CW limite imposto dal controllo di congestione
W dati che la sorgente può mantenere in volo
W, AW e CW sono normalmente espresse in byte. Negli esempi è più comodo misurarle in segmenti di dimensione MSS; dire W = 4 significa allora quattro MSS. La notazione non cambia il meccanismo: in ogni istante W non può superare nessuno dei due limiti.
Non confondere i soggetti protetti: AW protegge la destinazione, CW protegge la rete. La formula da cui partire è sempre W = min{AW, CW}. Negli sviluppi del capitolo si assumerà spesso AW >> CW, e dunque W = CW, ma questa è un'ipotesi di lavoro, non la definizione generale.
Per trasmettere senza interruzioni occorre mantenere in volo una quantità di dati sufficiente a riempire il percorso mentre si attende il ritorno degli ACK. Se B è la capacità minima lungo il percorso in bit/s e RTT il round-trip time, il prodotto banda per ritardo espresso in byte fornisce la finestra ideale:
W = RTT·B/8
Se la finestra è inferiore a questo valore, la sorgente esaurisce i dati inviabili prima che arrivi un ACK e lascia capacità inutilizzata. Il throughput normalizzato, indicato nelle slide con S, vale:
W < RTT·B/8 → S = 8W/(RTT·B)
W > RTT·B/8 → S = 1, ma i pacchetti si accodano nei router
Una W troppo piccola sottoutilizza la capacità; una W oltre il prodotto banda×ritardo non aumenta il throughput, che è già saturo, ma accumula pacchetti nelle code e rende più probabile la congestione. Il controllo cerca quindi il bordo fra tubo pieno e code in crescita, senza conoscere in anticipo dove si trovi.
Con B = 100 Mbit/s e RTT = 20 ms, la finestra ideale è 0,020 · 100·106 / 8 = 250 000 byte. Se la sorgente usa soltanto 125 000 byte, S vale 0,5: metà della capacità rimane inutilizzata. Con 375 000 byte, invece, S resta 1 e i 125 000 byte eccedenti devono attendere in coda.
All'apertura della connessione la capacità disponibile B è incognita: non esiste un valore evidente con cui inizializzare CW. Inoltre B può cambiare durante la connessione, perché altre sorgenti entrano o escono dal percorso e modificano il carico sul collo di bottiglia. Una finestra fissa sarebbe quindi sbagliata sia all'inizio sia a regime.
Il TCP separa il problema in due dinamiche. Lo Slow Start parte con una finestra minima ma la fa crescere rapidamente, per avvicinarsi in pochi RTT alla capacità disponibile. Il Congestion Avoidance prosegue con cautela: permette di sfruttare nuova capacità, ma evita di aumentare la finestra alla stessa velocità esplosiva.
| Fase | Scopo | Dinamica approssimata | Uscita |
|---|---|---|---|
| Slow Start (SS) | raggiungere rapidamente una regione utile | raddoppio di W per RTT | W raggiunge SSThr oppure scade RTO |
| Congestion Avoidance (CA) | sondare con prudenza la capacità residua | circa +1 MSS per RTT | perdita rilevata da RTO o ACK duplicati |
Per isolare la congestione, le slide assumono trasmettitore e ricevitore correttamente configurati, buffer abbastanza grandi e applicazioni sempre pronte a produrre o consumare dati. Non vi sono quindi stagnazioni applicative né silly window syndrome. Soprattutto si assume AW >> CW, da cui segue W = CW: l'evoluzione della finestra è determinata interamente dal controllo di congestione.
All'inizio dello Slow Start si pone W ≤ 2·MSS; l'esempio elementare usa W = MSS. La sorgente trasmette la finestra iniziale e si ferma in attesa dell'ACK. Se l'ACK arriva entro RTO, la finestra aumenta di un MSS. La stessa regola viene applicata a ogni ACK successivo:
SS: W = W + MSS per ACK
La crescita è per ACK, non per RTT. Tuttavia, se in un RTT vengono confermati tutti i segmenti della finestra corrente, ciascuno produce un incremento di un MSS: una finestra di 1 MSS diventa 2, quella da 2 diventa 4, poi 8. Per questo l'andamento temporale è esponenziale, nonostante il nome «partenza lenta».
L'approssimazione assume RTT circa costante, osserva W a tempi multipli del RTT e, in genere, non usa ACK ritardati in questa fase (quickack mode). La durata fino alla soglia è:
Tss = RTT·log2(SSThr)
La formula è una stima nella convenzione normalizzata delle slide: la soglia è espressa in MSS e la crescita parte dall'unità. Lo Slow Start termina quando W raggiunge la Slow Start Threshold, SSThr, oppure quando scade RTO.
Le tre frasi da tenere unite sono: W = W + MSS per ogni ACK; ciò produce un raddoppio per RTT; quindi Tss = RTT·log2(SSThr). Dire soltanto «la finestra aumenta di uno» è ambiguo e confonde lo Slow Start con la Congestion Avoidance.
Raggiunta SSThr, la crescita esponenziale sarebbe troppo aggressiva. In Congestion Avoidance il TCP applica a ogni nuovo ACK un incremento inversamente proporzionale alla finestra corrente:
CA: W = W + MSS²/W per ACK
Durante un RTT arrivano approssimativamente W/MSS ACK, ciascuno dei quali aggiunge MSS²/W. La somma degli incrementi è quindi circa un MSS per RTT. Se si usa il delayed ACK e arriva un ACK ogni due segmenti, l'aumento complessivo è circa mezzo MSS per RTT.
| Modalità degli ACK | Incremento per ACK | Incremento approssimato per RTT |
|---|---|---|
| un ACK per segmento | MSS²/W | +1·MSS |
| un ACK ogni due segmenti | MSS²/W | +½·MSS |
Con MSS = 1 e W = 4, si inviano quattro segmenti. Dopo il primo ACK la finestra diventa 4 + 1/4 = 4,25; il denominatore del passo seguente è già cambiato. L'evoluzione esatta è:
| ACK relativo | Calcolo | W [MSS] |
|---|---|---|
| 0 | valore iniziale | 4,00 |
| 1 | 4 + 1/4 | 4,25 |
| 2 | 4,25 + 1/4,25 | 4,49 |
| 3 | 4,49 + 1/4,49 | 4,71 |
| 4 | 4,71 + 1/4,71 | 4,92 |
Non si raggiunge esattamente 5, perché ogni ACK riduce leggermente l'incremento successivo. La curva è dunque sub-lineare. Per l'analisi si trascura questo scarto e si assume una crescita strettamente lineare nel tempo: è l'approssimazione che consentirà di descrivere il TCP come AIMD.
Se un segmento non viene riscontrato e scade RTO, il TCP assume che la rete sia congestionata. Con una buona stima del RTT, la scadenza è quasi sempre dovuta a una perdita; su una tecnologia affidabile, la perdita è a sua volta quasi sempre dovuta alla saturazione delle code nei router.
La reazione è severa indipendentemente dalla fase corrente. Se il TCP era in Slow Start, riparte da capo; se era in Congestion Avoidance, termina CA e torna a SS. In entrambi i casi riduce la finestra iniziale e aggiorna la soglia:
W ≤ 2·MSS
SSThr = max(FS/2, 2·MSS)
Il flight size, FS, è la quantità di byte già trasmessi ma non ancora confermati. In generale FS ≤ W: la finestra è il permesso massimo, il flight size è ciò che si trova davvero in volo. Per questo la soglia si calcola su FS e non automaticamente su W.
| Evento | Interpretazione | Nuova soglia | Nuova fase |
|---|---|---|---|
| scadenza di RTO | congestione critica o assenza di evidenza che la rete continui a consegnare | SSThr = max(FS/2, 2·MSS) | Slow Start con W ≤ 2·MSS |
La soglia non può scendere sotto 2·MSS. Scrivere soltanto SSThr = W/2 perde due dettagli: si usa il flight size, che può essere minore di W, e si applica il massimo SSThr = max(FS/2, 2·MSS).
Tre ACK duplicati raccontano una storia diversa da un time-out. Il segmento mancante è probabilmente perso, ma i segmenti successivi stanno raggiungendo il ricevitore: sono proprio loro a generare i duplicati. La rete è congestionata, ma non tanto da essersi fermata. Il Fast Retransmit ritrasmette subito il segmento mancante; il Fast Recovery evita l'inefficienza di ripartire da Slow Start.
SSThr = max(FS/2, 2·MSS)
W = SSThr + 3·MSS window inflation
per ogni ulteriore ACK duplicato: W = W + MSS
all'ACK completo: W = SSThr, poi CA
I tre MSS aggiunti nella window inflation rappresentano i tre segmenti successivi al mancante che risultano già ricevuti: ciascuno ha prodotto un ACK duplicato e ha quindi lasciato la rete. Ogni duplicato ulteriore certifica la consegna di un altro segmento e consente di aumentare W di un MSS. Quando arriva l'ACK del segmento perduto, la finestra viene «sgonfiata» a SSThr e la connessione prosegue in CA.
| Passo | Evento | SSThr | W |
|---|---|---|---|
| 1 | tre ACK duplicati, Fast Retransmit | max(4/2, 2) = 2 | 2 + 3 = 5 |
| 2 | ritrasmissione del segmento mancante | 2 | 5, Fast Recovery |
| 3 | ACK completo del segmento mancante | 2 | W = 2, ingresso in CA |
| Passo | Evento | SSThr | W |
|---|---|---|---|
| 1 | tre ACK duplicati | max(6/2, 2) = 3 | 3 + 3 = 6 |
| 2 | un ulteriore ACK duplicato | 3 | W = 7 |
| 3 | un altro ACK duplicato | 3 | W = 8 |
| 4 | ACK completo | 3 | W = 3, CA |
Le esercitazioni fissano AW = 32, SSThr = 8, CW = 2 all'apertura, segmenti da un MSS e 34 segmenti complessivi. L'avvio procede per finestre 2 → 4 → 8; raggiunta la soglia, CA porta CW a 9 e la finestra 15,…,23 contiene il segmento 20, che si perde. I segmenti successivi producono duplicati con AckN = 20.
| Variante | Rilevazione | Riduzione | Segmenti inviati nel recupero | Ripresa |
|---|---|---|---|---|
| solo SS + CA | attende RTO | W = 1, SSThr = 4 da FS = 9 nella convenzione dell'esempio | ritrasmette 20 | SS: 1 → 2 → 4 |
| Tahoe | Fast Retransmit al terzo duplicato | W = 1, SSThr = 2 con FS = 4 | 20 | SS, poi CA |
| Reno | Fast Retransmit + Fast Recovery | SSThr = 2, W = 2 + 3 = 5 | 20 e, se consentito, 24 | CA con W = 2 |
Il confronto mostra perché il segnale conta: la scadenza di RTO non dà prova di attività residua e impone una ripartenza prudente; tre duplicati dimostrano invece che almeno tre segmenti hanno attraversato la rete, e Reno conserva questa informazione gonfiando temporaneamente la finestra.
Una sola perdita nella finestra mette in luce la velocità del Fast Retransmit. Due perdite nella stessa finestra mettono in luce i limiti delle varianti classiche. Si consideri l'esempio delle slide: i primi dodici segmenti sono confermati, W = 5, e nella finestra successiva si perdono i segmenti 13 e 16.
La perdita del 13 viene scoperta da RTO. La sua ritrasmissione fa avanzare l'ACK fino al 16, ma la seconda perdita richiede un altro RTO. Il costo dominante sono dunque due time-out, non le due ritrasmissioni.
Tahoe riconosce il 13 con tre ACK duplicati e lo ritrasmette subito, ma porta immediatamente W a 1 e riparte in SS. Poiché 16 e 17 sono ancora non confermati, la finestra collassata non consente di trasmettere abbastanza nuovi segmenti da generare tre duplicati per il 16. Si finisce ancora ad attendere RTO: il vantaggio del primo Fast Retransmit resta piccolo.
Reno entra in Fast Recovery con SSThr = 2 e W = SSThr + 3 = 5. Quando la ritrasmissione del 13 produce AckN = 16, interpreta l'ACK come completamento del recupero, esce da FR e torna a W = 2. Ma quell'ACK è soltanto parziale: conferma una parte della finestra presente all'ingresso in FR. L'uscita è prematura e anche il 16 finisce per richiedere RTO.
New Reno memorizza SeqN(T0), il massimo numero di sequenza trasmesso quando arriva il terzo ACK duplicato. Nell'esempio vale 17. Un ACK successivo inferiore a quello necessario per confermare SeqN(T0) è parziale: indica un'altra perdita, reinizializza RTO e mantiene il recupero attivo. Nella traccia delle slide l'ACK parziale porta temporaneamente a W = W + 1 − 3, compensando il nuovo segmento confermato e i duplicati già contabilizzati.
New Reno esce da Fast Recovery soltanto quando riceve l'ACK che copre SeqN(T0). Così può ritrasmettere prima il 13 e poi il 16 senza aspettare il secondo time-out. Lo stesso principio distingue l'esempio con perdite 21 e 28: Reno chiude FR dopo il recupero del 21 e attende RTO per il 28; New Reno riconosce l'ACK parziale e recupera entrambi dentro la stessa fase.
| Funzionalità | RFC 1122 | Tahoe | Reno | New Reno |
|---|---|---|---|---|
| stima della varianza RTT | sì | sì | sì | sì |
| RTO con backoff esponenziale | sì | sì | sì | sì |
| algoritmo di Karn | sì | sì | sì | sì |
| Slow Start | sì | sì | sì | sì |
| Congestion Avoidance | sì | sì | sì | sì |
| Fast Retransmit | — | sì | sì | sì |
| Fast Recovery | — | — | sì | sì |
| reset RTO anche con ACK parziali | — | — | — | sì |
Tahoe aggiunge Fast Retransmit a SS e CA, ma dopo la perdita porta W al minimo e torna in Slow Start. Con perdite multiple la finestra collassata può impedire la produzione di altri ACK duplicati: il recupero successivo attende RTO.
Reno aggiunge Fast Recovery. Conserva il flusso durante il primo recupero, ma considera il primo nuovo ACK come conclusivo. Se quell'ACK è parziale, esce troppo presto e la perdita successiva può ancora richiedere RTO.
New Reno conserva SeqN(T0) e distingue gli ACK parziali. Resta in Fast Recovery finché non è confermata tutta la finestra presente a T0, reinizializzando RTO e ritrasmettendo il nuovo segmento indicato dall'ACK parziale.
In una rete abbastanza stabile la durata dello Slow Start è molto inferiore a quella della Congestion Avoidance: Tss << Tca. In prima approssimazione la vita lunga di una connessione è quindi una successione di fasi CA. Durante ciascuna fase la finestra cresce a tasso costante; a ogni perdita viene dimezzata. Ne risulta il caratteristico profilo a dente di sega.
Questa legge prende il nome di Additive Increase, Multiplicative Decrease, AIMD. Indicando con r(t) il bit rate della connessione:
incremento additivo: r(t) = r(0) + ct con c > 0
decremento moltiplicativo: r(t) = a·r(0) con a < 1
Nel TCP classico l'incremento deriva da circa un MSS per RTT e il decremento tipico dimezza la finestra. «Additivo» significa che connessioni con uguali MSS e RTT aggiungono la stessa quantità; «moltiplicativo» significa che ciascuna conserva la stessa frazione del proprio rate prima della perdita.
Il controllo è greedy: continua ad aprire la finestra finché trova capacità, così tende a occupare tutta la banda disponibile. Allo stesso tempo AIMD consente un'equa distribuzione tra connessioni comparabili. Se però una rete è molto inaffidabile, per esempio wireless, non ogni perdita segnala congestione: attribuirle tutte alle code porta a riduzioni non necessarie e richiede algoritmi speciali.
Due connessioni con gli stessi MSS e RTT condividono un collo di bottiglia di capacità R. Nel piano con assi r1 e r2, la retta r1 + r2 = R rappresenta il pieno utilizzo; la retta r1 = r2 rappresenta l'equa allocazione. Il punto desiderato è la loro intersezione, (R/2, R/2).
Durante l'incremento additivo entrambi i rate crescono dello stesso valore e la traiettoria si muove parallelamente a r1 = r2. Quando la somma raggiunge il limite, il decremento moltiplicativo scala entrambi i rate verso l'origine. Ripetendo le due mosse, la differenza assoluta si riduce e il sistema tende al punto di equa allocazione.
L'equità precedente dipende dall'ipotesi di RTT uguali. L'incremento di CW avviene approssimativamente una volta per RTT: una connessione con RTT breve compie più incrementi nello stesso secondo. Due connessioni che attraversano lo stesso collo di bottiglia ma sperimentano RTT diversi aumentano quindi le finestre a velocità diverse, e il TCP tende a favorire quella con RTT più breve.
Dopo un evento di congestione, AIMD dimezza CW. Se le condizioni della rete restano stabili, la finestra ideale è ancora vicina al precedente massimo, ma tornarvi con una crescita lineare può richiedere molto tempo. Il limite è particolarmente evidente quando il prodotto banda×ritardo è grande: molte unità di MSS separano WMAX/2 da WMAX.
TCP CUBIC, descritto nella RFC 8312 e adottato dalle versioni recenti di Linux considerate nelle slide, sostituisce in CA la crescita lineare di Reno con una curva cubica rispetto al tempo trascorso dall'ultima riduzione:
W(t) = C(t−K)³ + WMAX
C è una costante, t il tempo dall'ultima riduzione, K l'istante in cui, in assenza di perdite, si raggiungerà nuovamente WMAX. La forma consente una crescita più rapida lontano dal massimo precedente e più prudente nelle sue vicinanze.
I costi del TCP emergono anche sopra il trasporto. HTTP/1.0 non persistente apre una connessione TCP per ogni oggetto della pagina. HTTP/1.1 riusa una connessione, ma pagine complesse ricorrono spesso a connessioni multiple per aumentare il parallelismo. Ciascuna connessione deve raggiungere separatamente il massimo throughput e, con HTTPS, aggiungere al 3-Way Handshake TCP la negoziazione TLS.
Il pipelining di HTTP/1.1 consente di inviare più richieste senza attendere le risposte precedenti, ma le risposte restano ordinate: una risposta lenta trattiene quelle successive, producendo head-of-line blocking. HTTP/2 multipla richieste e risposte nella stessa connessione TCP, alternando i dati di flussi differenti. Tuttavia una perdita TCP blocca la ricostruzione ordinata del flusso di byte e rallenta tutte le richieste multiplexate, anche quelle i cui dati non sono stati persi.
QUIC, RFC 9000, cambia il punto in cui vengono realizzate queste funzioni. Usa UDP come base, ma gestisce autonomamente connessioni affidabili e sicure con controllo di congestione. All'interno della stessa connessione distingue flussi diversi, così una perdita su un flusso non impone l'head-of-line blocking agli altri. QUIC è parte integrante di HTTP/3, RFC 9114, e consente inoltre un'apertura più veloce evitando di sovrapporre in sequenza tutti i passaggi delle architetture precedenti.
| Soluzione | Parallelismo | Costo evidenziato |
|---|---|---|
| HTTP/1.0 | una connessione non persistente per oggetto | ripetizione dell'apertura e della salita a regime |
| HTTP/1.1 | persistenza, pipelining e spesso connessioni multiple | negoziazioni multiple e HoL nell'ordine delle risposte |
| HTTP/2 su TCP | flussi HTTP multiplexati su una connessione | una perdita TCP rallenta tutti i flussi |
| HTTP/3 su QUIC | flussi distinti nella connessione QUIC | la perdita di un flusso non blocca gli altri flussi |
Qui si chiude la descrizione algoritmica e si apre il problema quantitativo. Il capitolo 13 userà il dente di sega, la durata delle fasi e la probabilità di perdita per costruire modelli di prestazione: non cambierà il meccanismo, ma ne calcolerà throughput e tempi di completamento.
Il controllo di flusso protegge la destinazione, che notifica esplicitamente AW. Il controllo di congestione protegge la rete e adatta implicitamente CW osservando ACK e perdite. Entrambi regolano la sorgente attraverso la cadenza degli ACK, ma rispondono a colli di bottiglia diversi.
Con la relazione W = min{AW, CW}. La sorgente non può avere in volo più byte di quanti ne accetti la destinazione né più di quanti il controllo ritenga sostenibili per la rete. L'ipotesi AW >> CW semplifica in W = CW, ma non vale necessariamente sempre.
La finestra ideale è W = RTT·B/8 byte. Se W < RTT·B/8, il throughput normalizzato è S = 8W/(RTT·B) e la capacità è sottoutilizzata. Se W supera il valore ideale, S = 1 ma i pacchetti eccedenti si accodano nei router, aumentando il rischio di congestione.
All'apertura la capacità B è incognita e durante la connessione può cambiare. SS parte da una finestra minima e cresce rapidamente per raggiungere una regione utile; CA continua a sondare la capacità disponibile con una crescita prudente, evitando che la finestra esploda a regime.
Per ogni ACK ricevuto entro RTO si applica W = W + MSS. Poiché in un RTT arrivano ACK per tutta la finestra, W raddoppia approssimativamente ogni RTT: crescita esponenziale. Nella convenzione normalizzata delle slide, Tss = RTT·log2(SSThr). SS termina a SSThr o alla scadenza di RTO.
A ogni nuovo ACK si applica W = W + MSS²/W. Su tutti gli ACK di una finestra l'aumento totale è circa un MSS per RTT, quindi lineare; con delayed ACK, un ACK ogni due segmenti, è circa mezzo MSS per RTT. La crescita esatta è leggermente sub-lineare.
Assume congestione critica, imposta SSThr = max(FS/2, 2·MSS), riduce W ≤ 2·MSS e riparte in Slow Start. FS è il flight size, cioè i byte trasmessi ma non ancora confermati, e può essere minore della finestra.
Dopo tre ACK duplicati si riduce la soglia e si pone W = SSThr + 3·MSS. I tre MSS contabilizzano i tre segmenti successivi al mancante già usciti dalla rete e ricevuti. Ogni duplicato ulteriore aggiunge un MSS; con l'ACK completo si torna a W = SSThr e si prosegue in CA.
Tahoe collassa W e può non generare abbastanza duplicati per scoprire la seconda perdita. Reno mantiene FR, ma ne esce al primo nuovo ACK anche se è parziale. New Reno memorizza SeqN(T0), resta in FR davanti agli ACK parziali e ne esce solo quando è confermata tutta la finestra presente a T0.
L'incremento additivo segue r(t) = r(0) + ct, il decremento moltiplicativo r(t) = a·r(0). Due connessioni comparabili aggiungono la stessa quantità, poi conservano la stessa frazione del rate. Nel piano r1/r2 l'alternanza avvicina la traiettoria all'intersezione fra r1 = r2 e r1 + r2 = R.
La finestra aumenta di circa un MSS ogni RTT. Una connessione con RTT breve esegue più incrementi nello stesso intervallo di tempo rispetto a una con RTT lungo; condividendo lo stesso collo di bottiglia, tende quindi a ottenere una quota maggiore di capacità.
Dopo il dimezzamento, la crescita lineare può impiegare molto tempo per tornare alla finestra ideale quando il prodotto banda×ritardo è grande. CUBIC usa W(t) = C(t−K)³ + WMAX: cresce più rapidamente lontano dal massimo precedente e rallenta nelle sue vicinanze.
Su TCP i byte devono essere consegnati in ordine: in HTTP/2 la perdita di un segmento può rallentare tutti i flussi multiplexati. QUIC, RFC 9000, usa UDP come base ma realizza connessioni affidabili, sicure e congestion-controlled con flussi distinti; la perdita su un flusso non blocca gli altri. È il trasporto di HTTP/3, RFC 9114.