3. Il livello trasporto

3.1 Servizi a livello di trasporto

I protocolli a livello di trasporto forniscono la comunicazione logica tra processi applicativi di host differenti. Per comunicazione logica si intende che tutto proceda come se gli host, che eseguono i processi, fossero direttamente connessi anche se effettivamente non lo sono. Questi processi si scambiano messaggi usando la comunicazione logica fornita dal livello di trasporto, senza preoccuparsi dei dettagli dell’infrastruttura fisica utilizzata per trasportarli.

Pasted image 20221012082731.png|400

I protocolli di trasporto vengono eseguiti nei sistemi terminali (periferici) e non nei router della rete:

È possibile mettere a disposizione più protocolli di trasporto, come TCP e UDP, delle applicazioni di rete ed Internet possiede queste due.

3.1.1 Relazione tra il livello di trasporto e livello di rete

Un protocollo a livello di trasporto mette a disposizione una comunicazione logica tra processi che vengono eseguiti su host diversi, mentre un protocollo a livello di rete fornisce comunicazione logica tra host.

Un analogia con la posta ordinaria è il seguente:
Vi sono due gruppi di ragazzi (cugini), uno di Milano e l'altro di Roma, che ogni settimana si inviano delle lettere. Anna (Roma) ha il compito di raccogliere la posta dai suoi fratelli e sorelle e provvede a imbucarla, ma ha anche il compito di distribuire le lettere in arrivo. Le stesse operazioni vengono svolte da Andrea (Milano). In questo caso il servizio postale fornisce comunicazione logica tra le due case, perché trasferisce la posta da casa a casa e non da persona a persona. A loro volta Anna e Andrea costituiscono il servizio postale, anche se ne rappresentano solo una parte (quella periferica) nell’ambito del processo di consegna end-to-end.

Di conseguenza Anna e Andrea svolgono il proprio lavoro localmente e non sono coinvolti nello smistamento della posta negli uffici postali intermedi o nel trasporto da un ufficio postale a un altro. Analogamente, i protocolli a livello di trasporto risiedono nei sistemi periferici e trasferiscono i messaggi dai processi applicativi al livello di rete e viceversa, ma non fornisce alcuna indicazione su come i messaggi siano trasferiti all’interno della rete Internet. Infatti i router intermedi non riconoscono né operano su alcuna informazione che il livello di trasporto possa aver aggiunto ai messaggi delle applicazioni.
Supponiamo ora che Anna e Andrea siano in vacanza e quindi vengono sostituiti da una coppia di cugini (Susanna ed Enrico), che però sono giovani e inesperti. Capita quindi che raccolgono e distribuiscono la posta con frequenza minore e, occasionalmente, smarriscono alcune lettere. Di conseguenza, Susanna ed Enrico non mettono a disposizione lo stesso modello di servizio di Anna e Andrea. In modo analogo, una rete di calcolatori può rendere disponibili più protocolli di trasporto, ciascuno dei quali può offrire alle applicazioni un modello di servizio differente.

3.1.2 Panoramica del livello di trasporto di Internet

Internet, più nello specifico una rete TCP/IP, mette a disposizione del livello applicazione due diversi protocolli. Uno è UDP (user datagram protocol), che fornisce alle applicazioni un servizio non affidabile e non orientato alla connessione, l’altro è TCP (transmission control protocol), che offre un servizio affidabile e orientato alla connessione. Lo sviluppatore sceglie se utilizzare UDP o TCP quando crea le socket. Segmento è il pacchetto a livello di trasporto e datagramma per il pacchetto a livello di rete.

Il protocollo a livello di rete, chiamato IP (Internet protocol), fornisce comunicazione logica tra host. Il suo modello di servizio è detto best effort, ovvero che IP fa del suo meglio per consegnare i segmenti tra gli host comunicanti, ma non offre garanzie. Non assicura né la consegna dei segmenti, né il rispetto dell’ordine originario e non garantisce neppure l’integrità dei dati all’interno dei segmenti. Per queste ragioni si dice che IP offre un servizio non affidabile.
I modelli di servizio UDP e TCP estendono il servizio di consegna di IP "tra sistemi periferici" a quello di consegna "tra processi in esecuzione sui sistemi periferici". Il passaggio da consegna host-to-host a consegna process-to-process viene detto multiplexing e demultiplexing a livello di trasporto.

UDP è inaffidabile come IP, perché non garantisce che i dati arrivino senza errori e nemmeno che arrivino al processo destinatario. Il traffico UDP non viene regolato e quindi le applicazioni possono spedire a qualsiasi velocità e per tutto il tempo che vogliono.

TCP invece fornisce un trasferimento dati affidabile grazie alle tecniche di controllo di flusso, ai numeri di sequenza, agli acknowledgment e ai timer. Assicura quindi che i dati vengano trasferiti da un processo a un altro in modo corretto e ordinato
Fornisce anche il controllo di congestione per evitare che le connessioni TCP intasino i collegamenti e i router tra gli host comunicanti con un’eccessiva quantità di traffico. Permette infatti alle proprie connessioni di condividere equamente la larghezza di banda del collegamento, questo perché regola la velocità alla quale il lato mittente della connessione TCP immette traffico in rete.

3.2 Multiplexing e demultiplexing

Pasted image 20221012230839.png|400

Nell’host destinatario il livello di trasporto riceve segmenti dal livello di rete immediatamente sottostante. Il livello di trasporto ha il compito di consegnare i dati di questi segmenti al processo applicativo appropriato in esecuzione nell’host. Un processo può gestire una o più socket, quindi il livello di trasporto trasferisce i dati a una socket che fa da intermediario con il processo. Siccome a ogni dato istante può esserci più di una socket nell'host di ricezione, ciascuna avrà un identificatore univoco il cui formato dipende dal fatto che si tratti di socket UDP o TCP.

Il livello di trasporto utilizza le PCI (protocol control information) nell'header per identificare la socket di ricezione e quindi vi dirige il segmento, questo trasporto viene detto demultiplexing. Il compito di radunare i frammenti di dati da diverse socket sull’host di origine e incapsulare ognuno con intestazioni + PCI a livello di trasporto (che verranno poi utilizzate per il demultiplexing) per creare dei segmenti e passarli al livello di rete, viene detto multiplexing.
In questo caso il livello di trasporto nell’host centrale deve effettuare il demultiplexing dal livello di rete di segmenti che possono arrivare sia per il processo P1 sia per P2, ciò avviene indirizzando i dati del segmento in ingresso alla giusta socket. Il livello di trasporto nell’host centrale deve, inoltre, raccogliere i dati in uscita dalle socket dei due processi, creare i segmenti a livello di trasporto e passarli al livello di rete.

Pasted image 20221012233406.png|400

Il multiplexing richiede che le socket abbiano identificatori unici e che ciascun segmento presenti campi che indichino la socket cui va consegnato il segmento. Questi campi sono il numero di porta sorgente e il numero di porta destinazione.
La destinazione finale di un segmento non è un host ma un processo che gira sull'host. L'interfaccia tra l'applicazione e il livello di trasporto è la porta, che è identificata da un intero a 16 bit, con valori che vanno da 0 a 65535, ma quelli che vanno da 0 a 1023 sono chiamati well-known port number (che sono statici) perché sono riservati per i protocolli applicativi ben noti come HTTP (porta 80), FTP (porta 21), SMTP (porta 25) e DNS (porta 53). Gli altri numeri di porta sono dinamici, dato che vengono assegnati automaticamente dal sistema operativo quando si apre una connessione o si crea una socket.
Ne segue che il funzionamento del demultiplexing sia abbastanza chiaro. L'host riceve i datagrammi IP, ogni datagramma ha un indirizzo IP di origine e un indirizzo IP di destinazione. Ogni datagramma trasporta un segmento a livello di trasporto e ogni segmento ha un numero di porta di origine e un numero di porta di destinazione. Il livello di trasporto utilizza il numero di porta di destinazione per dirigere il segmento verso la socket corrispondente e poi i dati passano dalla socket al processo assegnato.

Multiplexing e Demultiplexing senza connessione (UDP)

Quando una socket UDP viene creata si può lasciare che il livello di trasporto le assegni automaticamente un numero di porta compreso tra 1024 e 65535, che non sia ancora stato utilizzato, oppure le si può assegnare uno specifico numero di porta:

La socket UDP è quindi identificata da 2 parametri, ovvero l'indirizzo IP di destinazione e il numero dalla porta di destinazione.

Supponiamo che un processo dell'host A, con porta UDP 9157, voglia inviare un blocco di dati applicativi a un processo con porta UDP 6428 nell’Host B. Il livello di trasporto di A crea un segmento che include i dati applicativi, i numeri di porta di origine (9157) e di destinazione (6428).

l livello di trasporto passa quindi il segmento risultante al livello di rete, che lo incapsula in un datagramma IP, ed effettua un tentativo best-effort di consegna del segmento all’host in ricezione. Se il segmento arriva all’Host B, il suo livello di trasporto esamina il numero di porta di destinazione del segmento (6428) e lo consegna alla propria socket identificata da 6428. L’Host B però potrebbe avere in esecuzione più processi, ciascuno con la propria socket UDP e relativo numero di porta. Quando i segmenti UDP giungono dalla rete, l’Host B dirige ciascun segmento (ossia ne esegue il demultiplexing) alla socket appropriata esaminando il numero di porta di destinazione del segmento.
Dato che una socket UDP viene identificata da una coppia di un indirizzo IP e di un numero di porta di destinazione, se due segmenti UDP presentano diversi indirizzi IP e/o diversi numeri di porta di origine, ma hanno la stessa coppia di indirizzo IP e numero di porta di destinazione, saranno diretti allo stesso processo di destinazione tramite la medesima socket.

Pasted image 20221013104335.png|400

Il numero di porta di origine serve anche come parte di un indirizzo di ritorno. Quando B vuole restituire il segmento ad A, la porta di destinazione del segmento da B verso A assumerà il valore della porta di origine del segmento da A verso B. L’indirizzo di ritorno completo è costituito dall’indirizzo IP di A più il numero di porta di origine.

Multiplexing e Demultiplexing orientato alla connessione (TCP)

La socket TCP è identificata da 4 parametri:

L'host ricevente usa i quattro parametri per inviare il segmento alla socket appropriata. Al contrario di UDP, due segmenti TCP in arrivo, aventi indirizzi IP di origine o numeri di porta di origine diversi, vengono diretti a due socket differenti, anche a fronte di indirizzo IP e porta di destinazione uguali, con l’eccezione dei segmenti TCP che trasportano la richiesta per stabilire la connessione.

La socket di connessione appena creata viene identificata da questi quattro valori e tutti i segmenti successivi, che avranno gli stessi valori, verranno diretti verso questa socket. Ora che la connessione TCP è attiva, client e server possono scambiarsi dati.
L'host server può ospitare più socket TCP contemporanee collegate a processi diversi, ognuna identificata da una specifica quaterna di valori. Quando il segmento TCP arriva all’host, i quattro campi citati prima vengono utilizzati per dirigere (fare demultiplexing) il segmento verso la socket appropriata.

Pasted image 20221013191901.png|400

L'host B dà inizio a due sessioni HTTP verso il server C, mentre l'host A apre una sessione verso C. Gli host A e B e il server C hanno ciascuno il proprio indirizzo IP.

L’Host B assegna due diversi numeri di porta di origine (5775 e 9157) alle sue due connessioni HTTP. Dato che l’Host A sta scegliendo i numeri di porta di origine indipendentemente da B, potrebbe anch’esso attribuire una porta di origine 5775 alla sua connessione HTTP. Ma questo non è un problema: il server C sarà ancora in grado di fare correttamente demultiplexing delle due connessioni che, pur avendo lo stesso numero di porta di origine, hanno indirizzi IP differenti.

Web server e TCP

Pasted image 20221013195845.png|400

Supponiamo che un host stia eseguendo un web server, per esempio Apache, sulla porta 80. Quando i client, per esempio i browser, inviano segmenti al server tutti hanno tutti porta di destinazione 80 (sia i segmenti per stabilire la connessione iniziale che quelli che trasportano messaggi di richiesta HTTP). Il server distingue i segmenti, provenienti da client diversi, tramite gli indirizzi IP e i numeri di porta di origine. Il web server genera un nuovo processo per ogni connessione ed ognuno ha la propria socket, ma non esiste sempre una corrispondenza uno a uno tra le socket di connessione e i processi. Gli odierni server spesso utilizzano un solo processo, ma creano un nuovo thread (che è come fosse un sottoprocesso molto leggero da gestire per il SO) e una socket di connessione per ciascun client. Un host server può supportare più socket TCP collegate allo stesso processo, in un dato istante.
Se client e server usano HTTP persistente, allora scambiano messaggi HTTP attraverso la stessa socket per tutta la durata della connessione. Se invece client e server usano HTTP non persistente, viene creata e chiusa una nuova connessione TCP per ciascuna coppia richiesta/risposta, e da quel momento viene creata e chiusa una nuova socket per ogni richiesta/risposta.

3.3 Trasporto non orientato alla connessione: UDP

UDP è un protocollo di trasporto essenziale, perchè oltre alla funzione di multiplexing/demultiplexing e una forma di controllo degli errori molto semplice, non aggiunge nulla a IP. Quando un applicazione usa UDP è come se comunicasse in modo diretto con IP, perchè in fase di invio aggiunge il numero di porta di origine e di destinazione, due piccoli campi e passa il segmento al livello di rete, il quale lo incapsula in un datagramma IP ed effettua un tentativo di consegna all'host di destinazione in modalità best-effort. Se il segmento arriva, UDP utilizza il numero di porta di destinazione per consegnare i dati del segmento al processo applicativo corretto, ma spesso i segmenti possono essere perduti o consegnati fuori sequenza all'applicazione. Si dice infatti che UDP è non orientato alla connessione perchè non esiste handshaking tra mittente e destinatario, ed ogni segmento UDP è gestito indipendentemente dagli altri. DNS è un tipico esempio di protocollo a livello applicativo che utilizza UDP.
Nonostante UDP offra un servizio di trasferimento non affidabile, risulta più adatto per molte applicazioni perché:

Pasted image 20221015095149.png|400

UDP è utilizzato spesso nelle applicazioni multimediali, ma dato che non implementa il controllo di congestione, quando si ha un'alta richiesta di banda pochi pacchetti UDP riuscirebbero a raggiungere la destinazione. Le applicazioni possono ottenere un trasferimento dati affidabile anche con UDP, ma ciò avviene se l'affidabilità è insita nell’applicazione stessa (es meccanismi di notifica e di ritrasmissione). UDP è impiegato per il DNS e per SNMP.

3.3.1 Struttura dei segmenti UDP

Pasted image 20221015101540.png|400

Nella struttura del segmento UDP, i dati dell'applicazione occupano il campo dati. Per esempio, per DNS il campo dati contiene un messaggio di richiesta o di risposta, mentre nel caso delle applicazioni di streaming audio sono i campioni audio a riempire il campo dati. L’intestazione UDP presenta solo quattro campi di due byte ciascuno. I numeri di porta consentono all’host di destinazione di trasferire i dati applicativi al processo corretto (demultiplexing), il campo lunghezza specifica il numero di byte del segmento UDP (intestazione più dati), perché può essere diversa tra un segmento ed il successivo, mentre il checksum è utilizzato dall'host ricevente per verificare se sono avvenuti errori nel segmento.

3.3.2 Checksum UDP

Il checksum UDP serve per il rilevamento degli errori, ovvero i bit alterati, nel segmento trasmesso.
Il mittente tratta il contenuto del segmento come una sequenza di interi da 16 bit, le somma tutte e calcola il complemento a 1 di questa somma. L'eventuale riporto finale viene sommato al primo bit. Il valore ottenuto, viene posto nel campo checksum del segmento UDP.

Pasted image 20221015105929.png|400

Il ricevente somma tutte le parole di 16 bit del segmento, incluso il checksum, e controlla se il risulato è una parola di 16 bit tutti uguali a 1. Se ciò avviene non è stato rilevato alcun errore (ma potrebbero esserci comunque errori), altrimenti è stato rilevato almeno un errore.

Pasted image 20221015110014.png|400

UDP mette a disposizione un checksum, nonostante molti protocolli a livello collegamento (tra cui Ethernet) prevedano il controllo degli errori, perché non c'è garanzia che tutti i collegamenti tra origine e destinazione controllino gli errori, ma anche perché l'errore si potrebbe verificare mentre il segmento si trova nella memoria di un router. Sebbene mette a disposizione tale controllo, UDP non fa nulla per risolvere le situazioni di errore.

Pasted image 20221015224252.png|400

Con il controllo di parità viene inserito un bit di parità in un campo separato che permette di rilevare se è stato ricevuto un numero dispari di bit 1, in questo caso tale campo viene impostato a 1.

3.4 Principi del trasferimento dati affidabile

TCP è un protocollo di trasferimento dati affidabile implementato appoggiandosi a un livello di rete (IP) che non è affidabile end-to-end. Per il livello trasporto verrà utilizzato il termine pacchetto anziché segmento.

Pasted image 20221015230928.png|400

Trasferimento dati affidabile su un canale perfettamente affidabile: rdt1.0

Consideriamo il caso in cui il canale sottostante è completamente affidabile. In questo caso il mittente, con l'evento rdt_send(), crea un pacchetto contenente i dati e lo invia sul canale con l'evento utd_send(). Il lato ricevente rdt_rcv() raccoglie i pacchetti dal sottostante canale e ne rimuove i dati, passandoli al livello superiore deliver_data().
In questo caso non c'è differenza tra un'unità di dati e un pacchetto e, dato che il canale è perfettamente affidabile, non c'è bisogno che il lato ricevente fornisca informazioni al mittente perché nulla può andare storto.

Trasferimento dati affidabile su un canale con errori sui bit: rdt2.0

Un modello più realistico del canale sottostante è quello in cui i bit in un pacchetto possono essere corrotti. Tali errori si verificano nei componenti fisici delle reti quando il pacchetto viene trasmesso, propagato o inserito nei buffer. Si assume però che i pacchetti vengano ricevuti in ordine, nonostante i loro bit possano essere corrotti.
Ad esempio, quando si parla al telefono, chi riceve il messaggio potrebbe confermare con un "OK" se ha capito, oppure chiede di ripetere. Questo protocollo di dettatura dei messaggi usa notifiche (acknowledgment) positive (“OK”) e notifiche negative (“Per favore, ripeti”). Nel contesto di una rete di calcolatori, i protocolli di trasferimento dati affidabili, basati su ritrasmissioni, sono noti come protocolli ARQ (automatic repeat request). Per gestire la presenza di errori nei bit, i protocolli ARQ devono avere tre funzionalità aggiuntive:

Stop-and-Wait

Il mittente crea un pacchetto contenente i dati da inviare e il checksum, per poi spedirlo. Il protocollo del mittente è ora in attesa di un pacchetto ACK o NAK dal destinatario. Se riceve un ACK, il mittente sa che il pacchetto trasmesso più di recente è stato ricevuto correttamente dal destinatario e pertanto il protocollo ritorna allo stato di attesa dei dati provenienti dal livello superiore. Invece, se riceve un NAK, il protocollo ritrasmette l'ultimo pacchetto e attende una risposta alla ritrasmissione. Quando il mittente è nello stato di attesa di ACK o NAK, non può recepire dati dal livello superiore, pertanto non invierà nuovi dati finché non è certo che il destinatario abbia ricevuto correttamente il pacchetto corrente. Proprio per questo comportamento i protocolli rdt2.0 sono noti come protocolli stop-and-wait.
Tuttavia c'è la possibilità che i pacchetti ACK o NAK possano essere alterati e per questo bisogna aggiungere a questi pacchetti dei bit di checksum. Questo consente al mittente di trovare ed anche correggere gli errori sui bit, ma ciò risolve il problema solo per un canale che può danneggiare pacchetti e non perderli. Il mittente potrebbe quindi inviare nuovamente il pacchetto dati corrente a seguito della ricezione di un pacchetto ACK o NAK alterato, ma questo introdurrebbe la duplicazione dei pacchetti nel canale.
Per risolvere questo problema si aggiunge al pacchetto dati il campo contenente il numero di sequenza, che permetterà al destinatario di capire se il pacchetto ricevuto rappresenti o meno una ritrasmissione.
In questo caso stiamo ipotizzando che il canale non perda pacchetti, per cui i pacchetti ACK e NAK non devono indicare il numero di sequenza del pacchetto di cui rappresentano la notifica.

Trasferimento dati affidabile su un canale con perdite ed errori sui bit: rdt3.0

Supponiamo ora che il canale di trasmissione, oltre a danneggiare i bit, possa anche smarrire i pacchetti. Per la gestione degli errori sui bit si utilizzano il checksum, numeri di sequenza, pacchetti ACK e ritrasmissione. Per la perdita dei pacchetti invece bisognerà introdurre un nuovo meccanismo, perché il mittente non sa se un pacchetto dati sia andato perduto, se sia stato smarrito un ACK o se il pacchetto o l’ACK abbiano semplicemente subìto un notevole ritardo. In tutti questi casi il pacchetto verrà ritrasmesso. Il mittente quindi dovrà:

Passaggi stop-and-wait

Il trasmettitore:

Il ricevitore, quando riceve una PDU:

Pasted image 20221017153508.png|400

Efficienza di Stop-and-wait

Il protocollo stop-and-wait funziona, ma a livello prestazionale non è molto conveniente. Assumiamo che:

Quindi se il mittente comincia ad inviare pacchetti a t=0, l'ultimo bit entra nel canale lato mittente al tempo t=LR=8μs. Assumendo che i pacchetti ACK siano estremamente piccoli, quindi il loro tempo di trasmissione è trascurabile, e che il destinatario possa spedire l'ACK non appena venga ricevuto l'ultimo bit di un pacchetto di dati, l'ACK giunge al mittente all'istante t=RTT+L/R+=30,008 ms.

Se definiamo l'efficienza come la frazione di tempo in cui il mittente è stato effettivamente occupato nell’invio di bit sul canale, essa sarà: Umittente=L/RRTT+L/R=0,00830,008=0,00027. In altre parole, il mittente è stato attivo per soli 2,7 centesimi dell'1% di tempo.

Il mittente è stato in grado di spedire 8000 bit in 30,008 ms, con un throughput effettivo percepito a livello applicazione di t=L/(Ttrans+RTT)=8000 bit / 30,008 ms = 33 kByte/s. Questo nonostante fosse disponibile un collegamento da 1 Gbit/s.

3.4.2 Protocolli per il trasferimento dati affidabile con pipeline

La soluzione ai ritardi della modalità stop-and-wait è quella di consentire al mittente di inviare più pacchetti senza attendere gli ACK.

Pasted image 20221017171036.png|400

Questa tecnica è nota come pipelining. Per applicare questa tecnica è necessario tenere traccia dei numeri di sequenza univoci dei pacchetti in transito e quindi l'intervallo dei numeri di sequenza disponibili viene incrementato. Bisogna inoltre che il lato di invio e di ricezione memorizzino i segmenti nei loro buffer, per poterli ritrasmettere nel caso ce ne fosse bisogno.
Ci sono due forme di protocolli con pipelining: Go-back-N e selective repeat.

Pasted image 20221017174610.png|400 Pasted image 20221017174713.png|400

Finestre di trasmissione e ricezione

Finestra di trasmissione WT: insieme di PDU che il trasmettitore può trasmettere senza avere ancora ricevuto l'ACK corrispondente

Finestra di ricezione WR: insieme di PDU che il ricevitore può accettare e immagazzinare

Puntatore low WLOW: puntatore al primo pacchetto nella finestra di trasmissione WT.

Puntatore up WUP: puntatore all'ultimo pacchetto già trasmesso

Pasted image 20221018161454.png|400

Acknowledgements

A seconda del protocollo, si possono utilizzare diversi tipi di ACK

ACK individuale
ACK cumulativo
ACK negativo (NACK)
Piggybacking

3.4.3 Go-Back-N (GBN)

Il mittente può avere fino a N pacchetti senza ACK in pipeline. Per questo N viene spesso chiamata ampiezza della finestra e il protocollo GBN viene detto protocollo a finestra scorrevole.
Il numero dei pacchetti in sospeso viene però limitato a N.
Il numero di sequenza di un pacchetto viene scritto in un campo a dimensione fissa dell’intestazione del pacchetto. Dato che k è il numero di bit di tale campo, l’intervallo di possibili numeri di sequenza è [0,2k1]. Per cui lo spazio dei numeri di sequenza può essere pensato come un insieme ciclico di 2k elementi e quando il numero di sequenza arriverà a 2k1, si ricomincerà immediatamente da 0.
Per esempio, TCP ha un campo a 32 bit per i numeri di sequenza ed essi contano i byte nel flusso dei dati anziché i pacchetti.

Il mittente GBN deve rispondere a tre tipi di evento:

Invocazione dall'alto

Quando il livello applicazione chiede di inviare il pacchetto, il mittente controlla se la finestra sia piena, ossia se ci sono N pacchetti in sospeso senza acknowledgment. Se la finestra non è piena, crea e invia un pacchetto e le variabili vengono aggiornate di conseguenza, altrimenti il mittente restituisce i dati al livello superiore che ritenterà più tardi. Solitamente il mittente mantiene questi dati nei buffer per inviarli non appena possibile, oppure utilizza un meccanismo di sincronizzazione che consenta al livello superiore di chiedere l'invio del pacchetto solo quando la finestra non è piena.

Ricezione di un ACK

L'ACK del pacchetto con il numero di sequenza n verrà considerato un ACK cumulativo, che indica che tutti i pacchetti con un numero di sequenza minore o uguale a n sono stati correttamente ricevuti dal destinatario.

Evento di timeout

Per risolvere il problema di pacchetti dati o ACK persi viene utilizzato un contatore. Dato che il mittente ha un timer per il più vecchio pacchetto senza ACK, quando il timer scade, ritrasmette tutti i pacchetti che ancora non hanno ricevuto un ACK. Se si riceve un ACK, ma ci sono ancora pacchetti aggiuntivi trasmessi e non riscontrati, il timer viene fatto ripartire. Se invece non ci sono pacchetti in sospeso in attesa di ACK, il contatore viene fermato.

Le azioni del destinatario sono semplici:

Dato che il ricevente invia solo ACK cumulativi, se un pacchetto, con numero di sequenza n, viene ricevuto correttamente ed è in ordine, il destinatario manda un ACK per quel pacchetto e consegna i suoi dati al livello superiore. In tutti gli altri casi, il destinatario scarta i pacchetti e rimanda un ACK per il pacchetto in ordine ricevuto più di recente. I pacchetti vengono consegnati uno alla volta al livello superiore, quindi se il pacchetto k è stato ricevuto e consegnato, tutti i pacchetti con un numero di sequenza inferiore sono anch’essi stati consegnati.

Con il GBN il destinatario non deve memorizzare nel buffer i pacchetti che giungono fuori sequenza, perché tanto il mittente ritrasmetterebbe sia il pacchetto n+1 ricevuto correttamente che il pacchetto n perso. L'unico dato che il destinatario deve memorizzare è il numero di sequenza del successivo pacchetto nell’ordine.

Pasted image 20221018173756.png|400

3.4.4 Selective Repeat

Quando l’ampiezza della finestra e il prodotto tra larghezza di banda e ritardo sono entrambi grandi, nella pipeline si possono trovare numerosi pacchetti e quindi un errore su un solo pacchetto può provocare un elevato numero di ritrasmissioni. Per risolvere questo problema si utilizzano i protocolli a ripetizione selettiva (SR, selective-repeat protocol), che evitano le ritrasmissioni non necessarie facendo ritrasmettere al mittente solo quei pacchetti su cui esistono sospetti di errore (smarrimento o alterazione).
Anche in questo caso si utilizza un'ampiezza di finestra pari a N per limitare il numero di pacchetti privi di ACK nella pipeline.
Tuttavia, a differenza di GBN, il destinatario SR invia un riscontro per i pacchetti correttamente ricevuti sia in ordine sia fuori sequenza. Questi vengono memorizzati in un buffer finché non sono stati ricevuti tutti i pacchetti mancanti, ossia quelli con numeri di sequenza più bassi, quando poi un blocco di pacchetti può essere trasportato in ordine al livello superiore.

Pasted image 20221018174844.png|400

Se il destinatario non invia un ACK per un pacchetto, che magari lui ha già ricevuto, la finestra del mittente non può avanzare. Ne segue che nei protocolli SR le finestre del mittente e del destinatario non sempre coincidono.

Il mittente SR intraprende tre tipi di evento:

Dati ricevuti dall'alto

Quando si ricevono dati dall’alto, il mittente SR controlla il successivo numero di sequenza disponibile per il pacchetto. Se è all’interno della finestra del mittente, i dati vengono impacchettati e inviati, altrimenti sono salvati nei buffer o restituiti al livello superiore per una successiva ritrasmissione, come in GBN.

Timeout

La soluzione per la perdita di pacchetti sono ancora i contatori, però ora ogni pacchetto deve avere un proprio timer logico, dato che al timeout sarà ritrasmesso un solo pacchetto.

ACK ricevuto

Se si riceve un ACK, il mittente SR etichetta tale pacchetto come ricevuto, ammesso che sia nella finestra. Se il numero di sequenza del pacchetto è uguale al più piccolo numero di sequenza che si stava aspettando, la finestra si muove verso il pacchetto che non ha ricevuto ACK con il più piccolo numero di sequenza.

Il destinatario invece:

Se il pacchetto ricevuto ricade all’interno della finestra del ricevente, al mittente viene restituito un pacchetto di ACK selettivo. Se il pacchetto non era già stato ricevuto viene inserito nel buffer e, nel caso presenti un numero di sequenza uguale alla base della finestra di ricezione, allora questo pacchetto e tutti i pacchetti nel buffer aventi numeri consecutivi vengono consegnati al livello superiore, spostando la finestra verso il prossimo pacchetto atteso.
Nel caso in cui l'ACK venga perso ed il mittente ritrasmetta nuovamente un pacchetto che il destinatario aveva già memorizzato, il destinatario genera comunque l'ACK per confermare di aver ricevuto il pacchetto.

Pasted image 20221018185700.png|400

Relazione tra dimensione delle finestre e numeri di sequenza in selective repeat

La mancanza di sincronizzazione tra le finestre del mittente e del destinatario ha conseguenze importanti quando abbiamo a che fare con un intervallo finito di numeri di sequenza.

Pasted image 20221018193603.png|400

Consideriamo un intervallo di quattro numeri di sequenza per i pacchetti 0, 1, 2 e 3, e un’ampiezza di finestra pari a 3. Supponiamo che i pacchetti 0, 1 e 2 vengano trasmessi e ricevuti correttamente e che il destinatario invii gli ACK. A questo punto, la finestra del destinatario si sposta sul quarto, quinto e sesto pacchetto, che presentano rispettivamente numeri di sequenza 3, 0 e 1.

Pasted image 20221018194653.png|400 Pasted image 20221018194802.png|400

Consideriamo ora due scenari:

Dato che si possono riutilizzare i numeri di sequenza, si deve prestare attenzione alla duplicazione dei pacchetti. In pratica bisogna assicurarsi che un numero di sequenza non venga riutilizzato, finché il mittente non sia “sicuro” che ogni pacchetto mandato precedentemente con numero di sequenza x non si trovi più nella rete. Questo accade perché si assume che un pacchetto non possa "sopravvivere" nella rete per un periodo superiore a un lasso di tempo fissato.

Pasted image 20221019141144.png|400

Ricordiamo che la finestra di trasmissione è WT, mentre la finestra di ricezione è WR.

Pasted image 20221019092809.png|400

3.5 Trasporto orientato alla connessione: TCP

TCP viene definito negli RFC 793, 1122, 1323, 2018 e 2581.

3.5.1 Connessione TCP

TCP viene detto orientato alla connessione perché, prima di effettuare lo scambio dei dati, i processi mittente e destinatario devono effettuare l’handshake, ovvero devono scambiarsi dei segmenti preliminari per stabilire i parametri del successivo trasferimento dati.
Il protocollo TCP va in esecuzione solo sui sistemi periferici e non negli elementi di rete intermedi, quindi i router e gli switch a livello di collegamento non salvano lo stato della connessione TCP. I router intermedi non vedono le connessioni TCP ma solo datagrammi.
Una connessione TCP è punto-punto, perché viene effettuata tra un singolo mittente e un singolo destinatario, ed offre un servizio full-duplex, ovvero che i dati a livello applicazione possono fluire da entrambi i processi nello stesso momento.

Inizializzazione connessione

Supponiamo ora che il processo di un host (client) voglia inizializzare una connessione con il processo in un altro host (server). Il processo applicativo client informa il livello di trasporto client di voler stabilire una connessione verso un processo nel server. Per stabilire una connessione TCP viene utilizzato l'handshake a tre vie, dove il client invia per primo uno speciale segmento TCP, il server risponde con un secondo segmento speciale TCP ed infine il client risponde con un terzo segmento speciale. Una volta instaurata una connessione TCP, i due processi applicativi si possono scambiare dati.

Trasferimento dati

Il processo client quindi manda un flusso di dati attraverso la socket e TCP li dirigerà verso il buffer di invio della connessione, quando TCP lo riterrà più "conveniente" (specifica RFC 793).
Successivamente la massima quantità di dati che verranno prelevati e posizionati in un segmento viene limitata dalla dimensione massima di segmento (MSS maximum segment size). Questo valore viene generalmente impostato determinando prima la lunghezza del frame più grande che può essere inviato a livello di collegamento dall’host mittente locale, la cosiddetta unità trasmissiva massima (MTU, maximum transmission unit) e poi scegliendo un MSS tale che il segmento TCP (una volta incapsulato in un datagramma IP) stia all’interno di un singolo frame a livello di collegamento, considerando anche la lunghezza dell’intestazione TCP/IP normalmente pari a 40 byte. I protocolli Ethernet e PPP (point-to-point) hanno un MTU di 1500 byte, quindi un valore tipico di MSS è 1460 byte.
L'MSS rappresenta la massima quantità di dati a livello di applicazione nel segmento e non la massima dimensione del segmento TCP con intestazioni incluse.
TCP poi aggiunge ad ogni blocco di dati del client un'intestazione TCP per formare i segmenti TCP e poi verranno passati al sottostante livello di rete, dove sono incapsulati separatamente nei datagrammi IP a livello di rete che vengono poi immessi nella rete.
Quando il destinatario TCP riceve un segmento, i dati del segmento vengono memorizzati nel buffer di ricezione della connessione TCP e l'applicazione leggerà il flusso di dati da questo buffer.

Pasted image 20221019201824.png|400

Panoramica

Protocollo punto-punto:

Flusso di byte affidabile e consegnato in ordine

Con pipelining:

Full duplex:

Orientato alla connessione:

Il controllo di congestione evita di saturare la rete

3.5.2 Struttura dei segmenti TCP

La MSS limita la dimensione massima del campo dati di un segmento e, quando TCP invia un file di grandi dimensioni, come un’immagine che fa parte di una pagina web, di solito frammenta il file in porzioni di dimensione MSS (a eccezione dell’ultima, che avrà generalmente dimensioni inferiori).

Pasted image 20221019204337.png|400

L'intestazione include i numeri di porta di origine e di destinazione, utilizzati per il multiplexing/demultiplexing dei dati, ed un campo checksum che è composto da header TCP + dati + pseudoheader IP (altri sottocampi che TCP guarda per fare il checksum). Ci sono poi:

Dimensione della finestra di ricezione (RWND)

Pasted image 20221020001244.png|400

Numeri di sequenza e ACK di TCP

TCP vede i dati come un flusso di byte non strutturati, ma ordinati. I numeri di sequenza si applicano al flusso di byte trasmessi e non alla serie di segmenti trasmessi. Pertanto il numero di sequenza per un segmento è il numero nel flusso di byte del primo byte del segmento.
Se un processo nell'host A vuole inviare un flusso di dati a un processo sull’Host B su una connessione TCP, TCP sull'host A numera ogni byte del flusso di dati. Ipotizziamo che il flusso di dati sia un file da 500.000 byte, che MSS valga 1000 byte e che il primo byte del flusso sia numerato con 0. TCP quindi costruisce 500 segmenti, al primo viene assegnato il numero di sequenza 0, al secondo 1000, al terzo 2000 e così via. Ogni numero di sequenza viene inserito nel campo numero di sequenza dell’intestazione del segmento TCP appropriato. In realtà però il numero di sequenza iniziale è scelto casualmente dai partecipanti alle connessioni TCP per evitare che un segmento ancora presente nella rete, per via di una connessione tra due host precedente e già terminata, venga interpretato erroneamente come segmento valido in una connessione successiva tra gli stessi due host, che devono anche utilizzare gli stessi numeri di porta della vecchia connessione.

Pasted image 20221020094210.png|400

Dato che TCP è full-duplex, i segmenti che provengono dall’Host B presentano un proprio numero di sequenza relativo ai dati che fluiscono da B ad A e sono diversi dai numeri di sequenza, provenienti dall'Host A, dei dati che fluiscono da A a B. Il numero di acknowledgment che l’Host A scrive nei propri segmenti è il numero di sequenza del byte successivo che l’Host A attende dall’Host B.

Esempi

Telnet

Telnet è un protocollo a livello di applicazione impiegato per il login remoto che utilizza TCP ed è progettato per funzionare tra qualsiasi coppia di host, anche se al giorno d'oggi si preferisce utilizzare il protocollo SSH (secure shell) perchè, a differenza di Telnet, cripta i dati inviati.
Supponiamo che l’Host A (client) inizi una sessione Telnet con l’Host B (server). Ogni carattere immesso dall’utente (nel client) verrà spedito all’host remoto, il quale restituirà una copia di ciascun carattere, che sarà mostrata sullo schermo dell’utente. Questo “eco” viene utilizzato per assicurarsi che i caratteri visibili all’utente siano già stati ricevuti ed elaborati nel sito remoto. Quindi, nel lasso di tempo che intercorre tra la digitazione sul tasto e la visualizzazione sul monitor, ciascun carattere attraversa la rete due volte.
Ipotizziamo che i numeri di sequenza iniziali, ovvero il numero di sequenza del primo byte nel campo dati, siano rispettivamente 42 e 79 per client e server. Pertanto, il primo segmento inviato dal client avrà numero di sequenza 42 e quello inviato dal server 79. Bisogna ricordare che il numero di acknowledgment è il numero di sequenza del successivo byte di dati che l’host sta aspettando. Dopo l’instaurazione della connessione TCP, ma prima dell’invio dei dati, il client è in attesa del byte 79 e il server del byte 42.

Pasted image 20221020105103.png|400

Il primo segmento è inviato dal client al server e contiene nel campo dati il byte ASCII per la lettera ‘C’, nel campo numero di sequenza del primo segmento contiene 42 e, visto che il client non ha ancora ricevuto dati dal server, questo primo segmento avrà 79 nel proprio campo numero di acknowledgment.

Il secondo segmento è spedito dal server al client ed ha un duplice scopo. Innanzitutto il server pone 43 nel campo ACK per indicare al client di aver ricevuto con successo i primi 42 byte e di attendere i byte da 43 in poi. Ma il secondo motivo di questo segmento è perché si manda indietro un "eco" della lettera 'C'. Di conseguenza, il secondo segmento presenta nel proprio campo dati la lettera ‘C’, in ASCII, e ha numero di sequenza 79, ossia il numero iniziale del flusso di dati da server al client in questa connessione TCP, dato che si tratta proprio del primo byte di dati spedito dal server.
Dato che il segmento che trasporta dati dal server al client contiene anche il riscontro dei dati dal client al server, si dice che tale ACK è piggybacked sul segmento dati dal server al client (l'ACK viaggia "a cavalluccio" sui dati).

Il terzo segmento viene inviato dal client al server e ha come unico scopo di dare un acknowledgment ai dati inviati dal server, infatti il suo campo dati è vuoto, ed ha valore 80.

3.5.3 Setup della connessione TCP

La procedura di setup della connessione TCP si chiama "three-way handshake".

  1. Il lato TCP client invia uno speciale segmento al TCP lato server. Questo segmento però non contiene dati a livello applicativo, ma ha il bit SYN impostato a 1. Inoltre il client sceglie a caso un numero di sequenza iniziale (x) e lo pone nel campo numero di sequenza del segmento SYN iniziale, che poi viene incapsulato in un datagramma IP e inviato al server.
  2. Quando il datagramma IP, contenente il segmento TCP SYN, arriva all’host server (se arriva), il server estrae il segmento dal datagramma, alloca i buffer e le variabili TCP alla connessione e invia un segmento di connessione approvata al client TCP. Anche questo segmento non contiene dati a livello applicativo, ma il bit SYN è posto a 1 ed il campo ACK viene incrementato (x+1). Infine il server sceglie il proprio numero di sequenza iniziale (y) e lo pone nel campo del numero di sequenza. Il segmento di connessione approvata viene anche detto segmento SYNACK.
  3. Alla ricezione del segmento SYNACK anche il client alloca buffer e variabili alla connessione e poi invia al server un altro segmento in risposta al segmento di connessione approvata del server. Il client pone il valore (y+1) nel campo ACK dell'intestazione del segmento TCP ed il bit SYN è posto a zero, dato che la connessione è stata stabilita. In questo terzo passo dell'handshake a tre vie il campo dati del segmento può contenere informazioni che vanno dal client verso il server.

Ora client e server possono scambiarsi segmenti contenente dati e d'ora in poi il bit SYN sarà posto a zero.

Pasted image 20221020181250.png|400

Chiusura (tear-down) di una connessione TCP

Gentile

Ciascuno dei due processi che partecipano alla connessione può terminarla e, in tal caso, le risorse negli host, ovvero buffer e variabili, vengono deallocate. Se per esempio il client decide di chiudere la connessione, il suo processo applicativo invia un comando di chiusura, che forza il client TCP a inviare un segmento TCP speciale al processo server. Nell'intestazione del segmento troviamo il bit FIN con valore 1. Il server risponde inviando un ACK, ma la connessione è semi-chiusa. Per chiuderla completamente il server invia il proprio segmento di shutdown, con il bit FIN a 1, al client. Infine il client manda a sua volta un ACK in risposta al segmento del server e fa partire un timer per poter inviare nuovamente l'ultimo ACK in caso vada perso. Nel caso in cui il server riceva l'ACK o trascorso il tempo di attesa, tutte le risorse degli host sono deallocate e la connessione è chiusa.

Pasted image 20221020185944.png|400

Brusca

Una chiusura brusca avviene quando sono presenti delle congestioni non gestibili o che si trovano in uno stato errato. Per esempio se ricevo un ACK per una connessione mai aperta oppure quando un host riceve un segmento TCP i cui numeri di porta o il cui indirizzo IP di origine non corrispondano ad alcuna socket attiva nell’host. Supponiamo, per quest'ultimo caso, che l'host riceva un pacchetto TCP SYN con porta di destinazione 80, ma che non stia accettando connessioni su quella porta (non ha in esecuzione un web server sulla porta 80). In tal caso invierà al mittente un segmento speciale di reset con il bit RST a 1, con lo scopo di comunicare alla sorgente di non rimandare quel pacchetto. Quando uno host invia un segmento con flag RST a 1, entrambi gli host liberano le risorse allocate dal sistema operativo. Nel caso in cui RST venga perso, il destinatario non saprà che la connessione è stata chiusa e quindi invierà l'ACK nuovamente. La connessione risulterà non aperta e quindi si ritornerà al punto in cui il mittente invierà nuovamente l'RST.

Pasted image 20221022103314.png|400

3.5.4 Timeout e stima del tempo di andata e ritorno

TCP utilizza un meccanismo di timeout e ritrasmissione per recuperare i segmenti persi, ma il problema principale è stabilire la durata degli intervalli di timeout. Innanzitutto il timeout dovrebbe essere più grande del tempo di andata e ritorno sulla connessione (RTT), ossia del tempo trascorso da quando si invia un segmento a quando se ne riceve l’acknowledgment, altrimenti ci sarebbero delle ritrasmissioni inutili. Tale RTT però varia. Se l'RTO fosse troppo piccolo il timeout sarebbe prematuro e quindi ci sarebbero delle ritrasmissioni non necessarie, ma se fosse troppo grande ci sarebbe una reazione lenta alla perdita dei segmenti.

Viene quindi misurato il SampleRTT, ovvero la quantità di tempo che intercorre tra l’istante di invio del segmento (ossia quando viene passato a IP) e quello di ricezione dell’acknowledgment del segmento. Tale valore non viene misurato per ogni segmento perché, ogni volta che ne viene inviato uno, appena si riceve l'ACK ne verrà considerato un altro (ne viene considerato un altro anche nel caso in cui scada il timeout per quel segmento e non sia stato ricevuto un ACK). SampleRTT non viene mai calcolato per i segmenti ritrasmessi.
Dato che SampleRTT varia, questo a causa della congestione nei router e al diverso carico sui sistemi periferici, occorre calcolare una media delle misure più recenti e non semplicemente il valore corrente di SampleRTT. Tale valore si chiama EstimatedRTT e, quando si ottiene un nuovo SampleRTT, TCP aggiorna questo valore secondo la formula:

EstimatedRTT=(1α)×EstimatedRTT+α×SampleRTT

Dato che il valore tipico di α è 0,125, la formula diventa:

EstimatedRTT=0,875×EstimatedRTT+0,125×SampleRTT

EstimatedRTT è una media ponderata dei valori SampleRTT, per cui tale media attribuisce maggiore importanza ai campioni recenti rispetto a quelli vecchi. Tale media è detta media mobile esponenziale ponderata, dato che l'influenza dei vecchi campioni diminuisce esponenzialmente.

Pasted image 20221022114516.png|400

Oltre ad avere una stima di RTT è anche importante possedere la misura della sua variabilità, ovvero una stima di quanto SampleRTT generalmente si discosta da EstimatedRTT:

DevRTT=(1β)DevRTT+β|SampleRTTEstimatedRTT|

Se i valori di SampleRTT presentano fluttuazioni limitate, allora DevRTT sarà piccolo, in caso di notevoli fluttuazioni, DevRTT sarà grande. Il valore tipico di β è 0,25.

I valori utilizzati per impostare l'intervallo di timeout di TCP devono essere scelti con attenzione. L'intervallo infatti non può inferiore a quello di EstimatedRTT, perché altrimenti verrebbero inviate ritrasmissioni non necessarie, ma non dovrebbe essere neanche molto maggiore di EstimatedRTT perché TCP non ritrasmetterebbe rapidamente il segmento perduto. Quindi è meglio impostare il timeout a EstimatedRTT più un certo "margine di sicurezza", ovvero DevRTT, che dovrebbe essere grande quando c’è molta fluttuazione nei valori di SampleRTT e piccolo in caso contrario. L'intervallo di timeout di ritrasmissione sarà quindi:

TimeoutInterval: RTO=EstimatedRTT+4DevRTT

Il valore iniziale di RTO è di 1 secondo, o un qualunque valore maggiore. Quando si verifica un timeout, RTO viene raddoppiato per evitare un timeout prematuro riferito a un segmento successivo per cui si riceverà presto un acknowledgment. Tuttavia, appena viene ricevuto un segmento ed EstimatedRTT viene aggiornato, TimeoutInterval viene ricalcolato secondo la formula precedente.

Quando si ha una sola misura di RTT, si pone EstimatedRTT = SampleRTT e DevRTT = RTT/2.

3.5.4 Trasferimento dati affidabile

TCP offre un trasferimento dati affidabile usando acknowledgment e timer. Quando TCP ritiene che i segmenti o i rispettivi ACK siano andati perduti o siano stati alterati, ritrasmette i segmenti. Alcune versioni di TCP considerano la ricezione di tre ACK duplicati come un NAK, generando quindi la ritrasmissione del segmento prima del timeout. TCP inoltre utilizza numeri di sequenza per consentire al destinatario l’identificazione di segmenti persi o duplicati, ma non può affermare con certezza se un segmento, o il suo ACK, siano stati persi, alterati o eccessivamente ritardati. Quindi TCP ritrasmette il segmento.
TCP usa anche il pipelining, consentendo al mittente di avere più segmenti trasmessi, ma non ancora riscontrati in ogni dato istante.

TCP crea un servizio di trasporto dati affidabile al di sopra del servizio inaffidabile e best-effort di IP, assicurando che il flusso di byte che i processi leggono dal buffer di ricezione TCP sia quello che è stato spedito.

3.5.5 Controllo di flusso

Gli host agli estremi delle connessioni TCP riservano dei buffer di ricezione per la connessione, dove TCP posiziona i byte corretti e in sequenza. I processi applicativi però potrebbero non leggere immediatamente i dati da questi buffer, perché magari sono impegnati in qualche altro compito, oppure potrebbero leggerli molto lentamente. In questo caso può accadere che il mittente mandi in overflow il buffer di ricezione inviando molti dati troppo rapidamente.
Il controllo di flusso offerto da TCP permette al ricevitore di controllare la velocità di trasmissione del mittente, per evitare che saturi il buffer. Questo servizio confronta la frequenza di invio del mittente con quella di lettura dell’applicazione ricevente.

Pasted image 20221026210649.png|400

Il mittente mantiene una variabile chiamata finestra di ricezione che gli fornisce un'indicazione dello spazio libero disponibile nel buffer del destinatario e, dato che TCP è full-duplex, i due mittenti hanno finestre di ricezioni distinte.
Supponiamo che l'host A stia inviando un file all'host B su una connessione TCP.
Il ricevitore comunica quanto spazio libero ha nel proprio buffer di ricezione, la cui dimensione è RcvBuffer, includendo il valore della RWND nell'header TCP di ogni segmento che invia al mittente.
Il ricevitore inizializza RWND con il valore di RcvBuffer, tenendo conto di variabili specifiche per ogni connessione:

La finestra di ricezione RWND viene impostata quindi alla quantità di spazio disponibile nel buffer:

rwnd = RcvBuffer - [LastByteRcvd LastByteRead].

Il mittente limita la quantità di dati "in volo", ovvero che ancora non sono stati ACKati. Dato che TCP garantisce che il buffer del ricevitore non vada il overflow (lo costringerebbe a scartare pacchetti corretti per mancanza di spazio), dovremo avere che LastByteRcvd – LastByteRead RcvBuffer.

Pasted image 20221027090139.png|400

Dato che lo spazio disponibile varia col tempo, rwnd è dinamica.

Il mittente tiene traccia di due variabili:

La differenza di tali valori esprime la quantità di dati spediti da A per cui non si è ancora ricevuto un acknowledgment e se tale valore è mantenuto minore di RWND si garantisce che l’Host A non mandi in overflow il buffer di ricezione dell’Host B. Quindi, l’Host A si assicura che per tutta la durata della connessione sia rispettata la disuguaglianza:

LastByteSent – LastByteAcked rwnd

Nel caso in cui il buffer dell'host di ricezione si riempia, quindi RWND = 0, viene inviata una notifica al mittente ma poi TCP non invia nuovi segmenti con nuovi valori di RWND. TCP manda un segmento al mittente solo se ha dati o un acknowledgment da mandare, quindi il mittente non può sapere se si è liberato dello spazio. Per risolvere tale problema le specifiche richiedono che il mittente continui ad inviare segmenti con un byte di dati quando RWND è a zero ed il destinatario risponderà con un acknowledgment. Questo perché prima o poi il buffer inizierà a svuotarsi ed i riscontri conterranno un valore non nullo di RWND.

3.6 Principi del controllo di congestione

La congestione è data quando troppi trasmettitori stanno mandando troppi dati e la rete non riesce a gestire tutto il traffico. Questo può portare alla perdita dei pacchetti, che tipicamente è il risultato di un overflow dei buffer nei router, ma anche a lunghi ritardi perché vi è un accodamento nei buffer dei router. I mittenti TCP possono quindi essere rallentati dalla congestione della rete IP, per cui c'è bisogno di una forma di controllo del mittente che viene detta controllo di congestione.

Scenario 1: due mittenti e un router con buffer illimitati

Consideriamo uno scenario in cui ci sono due host con una connessione che condivide un singolo router intermedio. Ipotizziamo che un'applicazione dell'host A stia inviando dati sulla connessione a una frequenza media di λin byte/s e non ci sono ritrasmissioni. A livello di trasporto poi i dati vengono incapsulati e inviati, senza porre rimedio a eventuali errori, controllo di flusso o di congestione. Viene anche ignorato l’overhead aggiuntivo dovuto alle informazioni dell’intestazione di trasporto e dell’intestazione del livello inferiore. L'host B opera in modo simile.
I pacchetti dall’Host A e dall’Host B passano attraverso un router e un collegamento uscente condiviso di capacità R. Il router possiede buffer di dimensione illimitata che gli consentono di memorizzare i pacchetti entranti quando la loro velocità di arrivo supera la capacità del collegamento uscente. Definiamo λout il tasso percepito dall'applicazione del destinatario.

Pasted image 20221027192429.png|400

Nel grafico superiore è mostrato il throughput per connessione, ovvero il numero di byte per secondo che vengono inviati al ricevente, in funzione del tasso di invio. Finché non supera il valore di R/2, il throughput del ricevente equivale alla velocità di invio del mittente e quindi tutto ciò che viene trasmesso dal mittente viene ricevuto dal destinatario con un ritardo finito (la velocità di invio è uguale alla velocità di ricezione).
Se però il tasso di invio supera R/2, il throughput resta R/2 perché è dovuto alla condivisione della capacità di collegamento tra le due connessioni. Il collegamento non è in grado di consegnare pacchetti al destinatario a un tasso superiore a R/2, anche se la velocità di invio degli host fosse elevatissima.
Il problema nell'operare al limite della capacità di collegamento è che quando la velocità di invio si avvicina a R/2 (da sinistra), il ritardo medio cresce sempre più e quando viene superato il numero medio di pacchetti in coda nel router cresce senza limite. Il ritardo medio tra origine e destinazione tende quindi all’infinito (ipotizzando che le connessioni mantengano questa velocità di invio per un periodo di tempo infinito e che la capacità dei buffer sia infinita).

Scenario 2: due mittenti e un router con buffer limitati

Assumiamo ora che la dimensione dei buffer nel router sia limitata, quindi i pacchetti che giungono in un buffer già pieno sono scartati ma prima o poi il mittente lo ritrasmetterà. Il tasso di trasmissione verso la socket è λin, il tasso percepito dall'applicazione del destinatario è λout ed il tasso al quale il livello di trasporto invia segmenti è λin (carico offerto). Al livello di trasporto, il tasso di invio include le ritrasmissioni per cui λin > λin.

Pasted image 20221029162520.png|400

Consideriamo il caso ideale in cui il mittente sia in grado di determinare se il buffer nel router abbia spazio a disposizione e trasmetta dati (un pacchetto) solo se il router ha spazio nel buffer. In questo caso non ci sarebbe alcun smarrimento perché λin = λin e il throughput della connessione sarebbe λin, quindi tutto ciò che viene trasmesso è ricevuto.

Pasted image 20221029165119.png|400

La velocità di invio media dell’host in questo scenario non supera R/2, visto che abbiamo ipotizzato che nessun pacchetto vada smarrito.

Pasted image 20221029165202.png|200

Consideriamo ora un caso più realistico rispetto all'esempio precedente in cui il mittente ritrasmette solo quando è certo che un pacchetto sia andato perduto. Il mittente potrebbe quindi impostare il proprio timeout con un valore sufficientemente grande da essere praticamente certo che il pacchetto di cui non si è ancora ricevuto acknowledgment sia stato perduto.

Pasted image 20221029182757.png|400

Mettiamo caso che λin, ovvero (la velocità di trasmissione dei dati originari più le ritrasmissioni), valga R/2. Con questo valore di carico offerto alla rete, il tasso con cui i dati vengono consegnati all’applicazione destinataria è R/3 quindi su 0,5 R (R/2) di dati trasmessi, 0,333 R byte/s (in media) sono quelli originali e 0,166 R byte/s (in media) sono quelli ritrasmessi. Per cui si presenta un altro costo legato alla congestione di rete in cui il mittente deve effettuare ritrasmissioni per compensare i pacchetti scartati (perduti) a causa dell’overflow nei buffer.

Pasted image 20221029170919.png|200

Consideriamo ora il caso più reale in cui il mittente abbia una conoscenza limitata della rete e quindi possa andare in timeout, ritrasmettendo un pacchetto che ha ricevuto ritardi in coda ma non sia andato perduto. In questo caso, sia il pacchetto originale sia quello ritrasmesso possono raggiungere il destinatario, il quale manterrà una sola copia e scarterà le altre.

Pasted image 20221029190322.png|400

Anche in questo caso si ha un costo legato alla congestione di rete, ovvero ritrasmissioni non necessarie da parte del mittente come risposta a lunghi ritardi che portano il throughput ad assumere il valore R/4 quando il carico offerto tende a R/2.

Pasted image 20221029190421.png|200

Scenario 3: più mittenti e più router con buffer limitati

In questo caso supponiamo che i pacchetti siano trasmessi da quattro host, ciascuno su percorsi composti da due collegamenti sovrapposti tra loro. Ogni host utilizza un meccanismo di timeout e ritrasmissione per implementare il servizio affidabile di trasferimento dati e tutti e quattro hanno lo stesso valore di λin. Supponiamo anche che la capacità dei router sia di R byte/s.

Pasted image 20221029193119.png|400

Consideriamo la connessione dall'host A all'host C che passa per i router R1 e R2. Questa connessione condivide il router R1 con la connessione D-B e il router R2 con la connessione B-D. Per valori estremamente piccoli di λin, gli overflow dei buffer sono rari e il throughput è approssimativamente uguale al traffico inviato in rete, mentre per valori leggermente più grandi di λin il corrispondente throughput è anch’esso più grande, dato che più dati originali vengono trasmessi nella rete e consegnati alla destinazione, mentre gli overflow sono ancora piuttosto rari. Di conseguenza, per piccoli valori di λin, un incremento di λin provoca un incremento di λout.

Consideriamo il caso in cui λin (e quindi λin) è molto grande. Il traffico da A verso C che giunge al router R2, dopo essere stato inoltrato da R1, non può presentare un tasso di arrivo maggiore di R, la capacità del collegamento da R1 a R2, indipendentemente dal valore di λin. Se λin è estremamente grande per tutte le connessioni (B–D inclusa), il tasso di arrivo del traffico A-C su R2 può essere molto più elevato di quello del traffico B–D e, dato che sul router R2 il traffico da A verso C e quello da B verso D sono in competizione per il limitato spazio nei buffer, la quantità di traffico B-D che passa con successo attraverso R2 (ossia, che non viene persa a causa dell’overflow) diventa sempre più piccola al crescere del traffico trasportato da A-C. Quando questo tende a infinito, un buffer vuoto presso R2 viene immediatamente colmato da un pacchetto A-C e il throughput della connessione B-D presso R2 tende a 0. Ne segue che il throughput end-to-end di B-D si annulla in caso di traffico pesante.

Pasted image 20231113122821.png|300

Ogni volta che si scarta un pacchetto tutte le risorse usate per portare il pacchetto fino a quel punto risultano sprecate.

Pasted image 20221124184719.png|400

3.7 Controllo di congestione TCP

Non esiste un unico algoritmo TCP per gestire la congestione. Molte varianti sono state proposte nel tempo, ciascuna per rimuovere una limitazione delle versioni precedenti: TCP Tahoe, Reno, New Reno, Vegas, Westwood, CUBIC, BBR. Ma spesso l'implementazione di TCP dipende dal sistema operativo.

Il controllo di congestione è una delle caratteristiche più importanti di TCP perché adatta il tasso di trasmissione alle condizioni della rete ed evita di saturare e congestionare la rete. Esistono diversi approcci possibili:

Controllo di congestione end-to-end

Il livello di rete IP non fornisce supporto al livello di trasporto per il controllo di congestione. Per questo motivo la perdita dei segmenti TCP, indicata da un timeout o da acknowledgment triplicati, viene considerata chiara indicazione di congestione di rete e TCP diminuisce, di conseguenza, l’ampiezza della propria finestra.

Controllo di congestione assistito dalla rete

I componenti a livello di rete, ossia i router, forniscono un feedback esplicito al mittente sullo stato di congestione della rete. Tale avviso può essere semplicemente un bit che indica traffico su un collegamento (SNA, DECbit, TCP/IP Explicit Congestion Notification (ECN), ATM).

TCP deve utilizzare il controllo di congestione end-to-end anziché quello assistito dalla rete perché il livello IP non offre ai sistemi periferici un feedback esplicito sulla congestione della rete.

3.7.1 Additive Increase Multiplicative Decrease (AIMD)

Il controllo di congestione AIMD di TCP consiste nell’imporre a ciascun mittente un limite alla velocità di invio sulla propria connessione in funzione della congestione di rete percepita.

Tale comportamento viene detto a dente di sega
Pasted image 20221029222408.png|400

Futuro di TCP

La formula approssimata per il throughput di TCP è:

Thr(RTT,p)[Gbit/s]<MSSRTT1p

Dove:

Es: RTT = 220 ms, MSS = 1460 Byte, p = 1011 THR = 2.1 Gbit/s

Fairness

Consideriamo K connessioni TCP, ciascuna con un differente percorso end-to-end, ma che condividono uno stesso link con capacità trasmissiva (banda) di R bit/s che costituisce il collo di bottiglia del sistema. AIMD viene utilizzato perché fornisce un meccanismo di controllo fair, dove ciascuna connessione ottiene la stessa porzione di banda del collegamento e quindi la velocità trasmissiva di ogni connessione è R/K.

Pasted image 20221029233355.png|400

Esaminiamo ora il caso di due connessioni TCP che condividono un collegamento con capacità trasmissiva R. Assumiamo che le connessioni abbiano gli stessi valori di MSS e RTT (e pertanto abbiano uguali finestre di congestione e lo stesso throughput), che debbano trasmettere una gran quantità di dati e che non vi siano altre connessioni TCP o datagrammi UDP che attraversano questo collegamento condiviso

Pasted image 20221030002147.png|400

In questo grafico la banda della connessione 1 è sull'asse x e la banda della connessione 2 è sull'asse y.
All'estremo destro linea di pieno utilizzo dell'ampiezza di banda (dove c'è la R) la connessione 1 avrà un throughput maggiore, mentre nell'estremo sinistro sarà la connessione 2 ad avere un throughput maggiore. Il nostro obiettivo è avvicinarci il più possibile al punto di incontro tra la linea di pieno utilizzo dell'ampiezza di banda e la linea di equa condivisione dell'ampiezza di banda (quella tratteggiata), perché in questo punto entrambe le connessioni avranno un throughput pari a R/2.

Supponiamo che le dimensioni della finestra TCP siano tali che a un certo istante di tempo le connessioni 1 e 2 raggiungano i throughput corrispondenti al punto A. Dato che la porzione di banda del collegamento utilizzata congiuntamente è minore di R, non si verificheranno perdite e le due connessioni aumenteranno la loro finestra di 1 MSS per RTT. Di conseguenza, il throughput congiunto procede lungo la semiretta a 45° (pari incremento per entrambe le connessioni) uscente dal punto A (multiplicative increase). La banda congiunta potrebbe essere maggiore di R (supera la diagonale), e si potrebbe quindi verificare una perdita di pacchetti. Se le connessioni 1 e 2 subiscono una perdita di pacchetti quando raggiungono i throughput indicati dal punto B, allora decrementeranno le loro finestre di un fattore 2 (multiplicative decrease). I throughput raggiunti di conseguenza si troveranno pertanto sul punto C, a metà strada lungo un segmento che collega il punto B all’origine. Essendo l’utilizzo di banda condivisa minore di R in prossimità del punto C, le due connessioni ancora una volta incrementano il loro throughput lungo la linea a 45° passante per C (nuovamente si avrà un multiplicative increase). Al punto D si possono ancora verificare delle perdite, nel qual caso le due connessioni decrementeranno di nuovo l’ampiezza delle loro finestre di un fattore 2 (di nuovo multiplicative decrease), e così via. Si può concludere che la banda utilizzata dalle due connessioni può fluttuare lungo la linea di equa condivisione della banda e che le due connessioni si avvicineranno sempre di più nel punto in cui si avrà un'equa ripartizione della banda, indipendentemente dal punto del piano in cui si trovano inizialmente.

Fairness e UDP

Le applicazioni multimediali non usano TCP quasi mai perché non desiderano ridurre il tasso di trasmissione anche se la rete è molto congestionata. Utilizzano invece UDP perché non possiede il controllo di congestione, così possono inviare audio e video a un tasso costante. Tali applicazioni preferiscono perdere occasionalmente pacchetti, piuttosto che non perderli ma ridurre il proprio tasso trasmissivo a livelli “equi” nei momenti di traffico. Di conseguenza le applicazioni multimediali che fanno uso di UDP non sono fair perché non cooperano con altre né adeguano la loro velocità trasmissiva in modo appropriato.

Fairness con connessioni TCP parallele

Un'applicazione basata su TCP può aprire più connessioni in parallelo tra due host, come per esempio fanno i web browser. Le applicazioni che utilizzano connessioni in parallelo ottengono una porzione di banda maggiore sui collegamenti congestionati. Consideriamo un collegamento di banda (capacità) R cui accedono 9 applicazioni client/server, ciascuna delle quali utilizza una connessione TCP. Se giunge un'altra applicazione con una connessione TCP, allora la frequenza trasmissiva di tutte le applicazioni sarà circa uguale a R/10. Ma se invece la nuova applicazione usa 11 connessioni TCP in parallelo, allora otterrà un’allocazione non equa superiore a R/2, questo a discapito delle altre.

Finestre

Gli estremi di una connessione TCP gestiscono un buffer di ricezione, uno di invio e diverse variabili tra cui LastByteRead e rwnd. Il controllo di congestione fa tenere traccia agli estremi della connessione di una variabile aggiuntiva, ovvero la finestra di congestione (congestion window, CWND), dove CWND è il numero di byte che il trasmettitore può inviare nella rete. La quantità di dati che non hanno ancora ricevuto acknowledgment inviata da un mittente deve essere:

LastByteSentLastByteAcked = |WT| min(CWND, RWND) = min(CWND, |WR|

Dove WT è la finestra di trasmissione, mentre WR è la finestra di ricezione. La differenza tra LastByteSent e LastByteAcked esprime la quantità di dati spediti da A per cui non si è ancora ricevuto un acknowledgment. Proprio per questo LastByteSentLastByteAcked = |WT|.

Per concentrarci sul controllo di congestione (anziché su quello di flusso), assumiamo che la quantità di dati che non hanno ricevuto acknowledgment è limitata soltanto da CWND e che la finestra di congestione sia sempre in uso perché il mittente ha sempre dati da inviare.

Ricapitoliamo il significato delle finestre

In TCP ci sono diversi algoritmi per adattare la CWND. In assenza di perdite abbiamo Slow start e Congestion avoidance, mentre per migliorare l'efficienza di TCP in caso si verifichino perdite abbiamo Fast retransmit e Fast recovery.

Slow start

Quando si stabilisce una connessione TCP il valore di CWND viene in genere inizializzato a 1 MSS, mentre la soglia di SSTHRESH (slow start threshold) viene posta dallo standard uguale a RWND (o RWND/2 a seconda delle implementazioni).

Per ogni ACK valido ricevuto, viene aumentato CWND di 1 MSS e WLOW (puntatore al primo pacchetto nella finestra di trasmissione WT) viene spostato al primo byte (o segmento che è quello con cui lavoriamo noi) non ACKed. Nello specifico TCP invia il primo segmento (CWND = 1 MSS) nella rete e attende un riscontro. Se il segmento riceve un acknowledgment prima che si verifichi un evento di perdita, il mittente incrementa la finestra di congestione di 1 MSS e invia due segmenti di dimensione massima (CWND = 2 MSS). Questi segmenti ricevono a loro volta degli acknowledgment e il mittente incrementa la finestra di congestione di 1 MSS per ciascuno di essi portandola CWND a 4 MSS e così via. Questo comporta ad un raddoppio della velocità trasmissiva a ogni RTT. Quindi, in TCP, la velocità di trasmissione parte lentamente, ma cresce in modo esponenziale durante la fase di slow start. Ogni volta che ricevo un ACK posso trasmettere nuovi segmenti, come consentito dall'ampiezza della finestra di congestione CWND.

Questa crescita esponenziale termina quando CWND raggiunge o supera una soglia di SSTHRESH. In questo caso la fase di slow start termina e TCP passa alla modalità congestion avoidance.

La fase di slow start può terminare anche quando c'è un evento di perdita (e quindi una congestione) indicata da un evento di timeout.

L'ultimo modo in cui la fase di slow start può terminare è quando vengono rilevati tre acknowledgment duplicati, in questo caso TCP entra nella fase di fast recovery.

Pasted image 20221102120155.png|400

Si cerca in ogni modo di evitare di tornare alla slow start perché si riduce la congestion window a 1, come se si ripartisse da zero ogni volta.

Congestion avoidance

Quando TCP entra nello stato di congestion avoidance, il valore di CWND è circa la metà di quello che aveva l’ultima volta in cui era stata rilevata la congestione. Quindi, invece di raddoppiare il valore di CWND ogni RTT, TCP adotta un approccio più conservativo, incrementando CWND di 1 MSS ogni RTT. Per ogni ACK valido ricevuto, il mittente TCP incrementa la propria CWND di MSS MSSCWND e sposta WLOW al primo segmento non ACKed. Se per esempio MSS vale 1460 byte e CWND 14.600 byte, allora in un RTT vengono spediti dieci segmenti. Ciascun ACK in arrivo (assumendo un ACK per segmento) incrementa l’ampiezza della finestra di congestione di 1/10 MSS e quindi, dopo la ricezione degli acknowledgment relativi a tutti e dieci i segmenti, il valore della finestra di congestione sarà stato aumentato di 1 MSS.

Questo incremento lineare di 1 MSS per RTT termina quando si verifica un timeout. In questo caso si passa alla fase di slow start, in cui viene abbassata la soglia di SSTHRESH = MAX(CWND/2, 2) e viene aumentato il timeout RTO = RTO 2 per fidarci della rete. Viene reimpostata la congestion window CWND a 1 segmento e viene ritrasmesso il segmento che ha causato il timeout.

Un evento di perdita può essere anche il risultato della ricezione di tre acknowledgment duplicati, però la rete continua a consegnare segmenti dal mittente al ricevente. In questo caso TCP imposta il valore di SSTHRESH pari a metà del valore di cwnd al momento del ricevimento dei tre ACK duplicati e dimezza il valore di CWND, aggiungendo 3 MSS per tenere conto dei duplicati ricevuti (CWND = CWND/2 + 3MSS). Infine TCP entra nello stato di fast recovery.

Pasted image 20221102122500.png|400

Parametri su cui poter agire

Pasted image 20221102165813.png|400

CWND si può aumentare agendo su slow start e congestion avoidance, mentre si può ridurre con AIMD.

Pasted image 20221103225643.png|400 Pasted image 20221103225116.png|400

Ad ogni RTT vengono inviati i pacchetti e contemporaneamente viene aumentata la dimensione della congestion window (verticale). Prima invio 1 pacchetto, poi 2, poi 4 e così via fino 16, quando verrà raggiunto il threshold (40 segmenti ). Da qui si passa in congestion avoidance, dove ad ogni RTT viene aumentata linearmente la dimensione della finestra, fino a quando non ci sarà la perdita di un pacchetto dovuta al timeout. Riduco CWND a 1, abbasso la soglia di SSTHRESH e la prossima fase di slow start durerà meno. Quindi, con questa versione di TCP, in cui sono ignorati gli ACK, anche con una sola perdita si riduce CWND a 1.

Ricordiamo che TCP usa ACK cumulativi e non Selective e Repeat, quindi dirà sempre il primo dei pacchetti che gli mancano senza scartare gli altri.

Fast Retransmit

Gli ACK duplicati, in fondo, ci dicono che la rete funziona e quindi diamo fiducia alla rete. Se funziona, continuiamo a trasmettere. Alla ricezione del 3° ACK duplicato ritrasmetto il segmento indicato dall'ACK ("fast retransmit") ed entro in una fase di "fast recovery". Mi ricordo il valore di WUP (ultimo segmento della finestra) per sapere quanti segmenti sono "in volo" e lo chiamo RECOVER = WUP. Questo mi permetterà di capire quando la fase è completata.

Fast Recovery

Alla ricezione del 3° ACK duplicato, per prudenza abbasso SSTHRESH = CWND/2. Ma, dato che suppongo di aver perso solo quel segmento, imposto CWND = SSTHRESH + 3 MSS (se ricevo 3 ACK duplicati vuol dire che sono stati consegnati correttamente 3 segmenti). Se ho la possibilità e CWND me lo permette, trasmetto altri segmenti perché ho "fatto spazio". NON SPOSTO il puntatore WLOW perché non ho ancora ricevuto conferma che 11 sia stato inviato. Difatti posso inviare comunque i pacchetti all'interno della finestra senza spostare il puntatore.

Se arrivano altri ACK duplicati vuol dire che sto continuando a consegnare segmenti correttamente e quindi il valore della congestion window CWND è incrementato di 1 MSS per ogni ACK duplicato. Se CWND me to permette, continuo a trasmettere altri segmenti, ma NON SPOSTO il puntatore WLOW perché non ho ancora ricevuto l'ACK del pacchetto 11.

Quando arriva un ACK valido per il segmento perso, ovvero l'ACK per il segmento 11 che avevamo chiamato precedentemente RECOVER, imposto CWND = SSTHRESH (quindi riduco la congestion window), passo alla fase di congestion avoidance e sposto in avanti WLOW al primo segmento non ACKed. In questo caso termina quando ricevo l'ACK 16.

Se arriva un ACK parziale, che conferma un segmento precedente al segmento RECOVER, ritrasmetto il primo segmento per cui non ho un ACK e riduco CWND = CWND - numero di segmenti ACKed + 1. Sposto WLOW al primo segmento non ACKed.

Pasted image 20221103144752.png|400 Pasted image 20221103144818.png|400

In questo esempio vengono consegnati i pacchetti 10 (che fa slittare in avanti di uno la finestra di congestione da [10, ..., 14] a [11, ..., 15] permettendo di trasmettere il pacchetto 15), mentre 11 viene perso. Tutti gli ACK dopo la perdita di 11 sono duplicati (tutti chiedono 11) e questo fa entrare in fast retransmit. Senza aspettare, dopo tre ACK ricevuti ritrasmetto il pacchetto 11 e svolgo tutte le operazioni sulla colonna di sinistra per un approccio più prudente. A questo punto arriva il quarto ACK (quello di 15) duplicato che porterà l'aumento di CWND di 1, portandola a [11, ..., 16], e viene ritrasmesso 16. Quando arriverà, per effetto della ritrasmissione di 11 della fast retransmit, l'ACK che libera fino al 16, verrà spostata la finestra a [16, 17] (contiene quindi 2 segmenti). Questo perché l'ACK ci ha confermato che i pacchetti fino al 16 escluso sono stati ricevuti, per confermare il 16 bisognerà aspettare l'ACK 17. Ora si può ripartire da CWND = SSTHRESH = 2 e trasmettere il pacchetto 17, perché 16 è già stato trasmesso. Da qui si entrerà nella fase di congestion avoidance.

Pasted image 20221103235607.png|400

Come si può notare in questo esempio, quando avviene una perdita non si ritorna a slow start con una CWND = 1 ma si entra in fast recovery ad una soglia più elevata. In questa fase si continua a trasmettere pacchetti e ad aumentare la finestra e, una volta che arriva l'ACK che riscontra i pacchetti trasmessi, si può proseguire con la congestion avoidance.

Si ritorna alla fase di slow start solo quando scatta il timeout. Fast retransmit e fast recovery servono a evitare di ridurre troppo la finestra di congestione, tollerando qualche ritardo.

Il controllo della congestione di TCP stabilisce di rallentare la trasmissione basandosi solo sulle indicazioni dei pacchetti persi. Questo ha funzionato bene per molti anni perché i piccoli buffer degli switch Internet e dei router erano ben adattati alla bassa larghezza di banda dei collegamenti Internet. Ad oggi abbiamo bisogno di un algoritmo che risponda alla congestione effettiva, piuttosto che alla perdita di pacchetti.
Alcune soluzioni sono: CUBIC, BBR e QUIC.

CUBIC

Algoritmo di controllo della congestione di rete in TCP. In particolare, fa variare la lunghezza della finestra di congestione secondo una funzione cubica del tempo. Migliora la scalabilità e la stabilità su reti veloci e a lunga distanza. Viene utilizzato da anni come impostazione predefinita nel kernel Linux, mentre Windows lo ha adottato solo nel 2017.

Principi di CUBIC

Per un migliore utilizzo e stabilità della rete, CUBIC usa entrambi i profili concavo e convesso di una funzione cubica per aumentare la dimensione della finestra di congestione

CWNDcubic(t)=C(tK)3+CWNDmax

Dove K=CWNDmax(1β)C3 e, da RFC 8312, C=0.4 e β=0.7
Dopo una congestione, la finestra è scalata di β, non di 0.5. CUBIC bilancia scalabilità e velocità di convergenza.
TCP-friendly: non penalizza troppo i flussi TCP standard che condividono lo stesso bottleneck link.

Pasted image 20221103170534.png|400 Pasted image 20221103170652.png|400

BBR

Bottleneck Bandwidth and Roundtrip propagation time (BBR). Algoritmo per il controllo della congestione (Google, 2016).
BBR non si basa più sulle perdite ma stima due parametri:

L'idea è di trasmettere pacchetti a una velocità che non "dovrebbe" incontrare accodamenti

Pasted image 20221103171320.png|400

È stato progettato per rispondere alla congestione effettiva, piuttosto che alla perdita di pacchetti e modella la rete per inviare alla velocità della larghezza di banda disponibile. È incentrato sul miglioramento delle prestazioni della rete quando la rete non è molto buona.
Server-side algorithm, ovvero non richiede al client di implementare BBR.

Concetto di pacing: invece di inserire nella CWND (e inviare) tutti i pacchetti consentiti, li inserisco al ritmo al quale può inviarsi il nodo più lento (a monte del bottleneck link).

Pasted image 20221103171759.png|400 Pasted image 20221103171819.png|400

Maggiore produttività

Secondo Google, in un server con un collegamento Ethernet a 10 Gigabit che invia dati lungo un percorso con un RTT di 100 ms e tasso di perdita di pacchetti dell'1% si ha come throughput:

Ottimo insieme ad HTTP/2 che usa una singola connessione. Il risultato finale è un traffico più veloce sulle odierne dorsali ad alta velocità e una larghezza di banda notevolmente aumentata e tempi di download ridotti.

Latenza inferiore

BBR inoltre consente riduzioni significative della latenza nelle reti dell'ultimo miglio che connettono gli utenti a Internet. Se per esempio consideriamo un collegamento con 10 Mbit/s, un tempo di andata e ritorno di 40 ms e un tipico buffer di collo di bottiglia di 1000 pacchetti:

Velocità di trasmissione

Pasted image 20221103172531.png|400

Pasted image 20221103172623.png|400

Pasted image 20221103172732.png|400

QUIC

Inizialmente progettato da Google nel 2012 è stato l'acronimo di Quick UDP Internet Connections, ma IETF lo considera semplicemente come il nome del protocollo. Ha due obiettivi:

Può essere implementato a livello applicativo anziché nel kernel, come Chrome, Firefox, Safari... È il protocollo di livello trasporto del futuro HTTP/3.

Pasted image 20221103173158.png|400 Pasted image 20221103173235.png|400

Molte connessioni HTTP richiederanno TLS per funzioni di sicurezza (autenticazione, crittografia, ...). QUIC incorpora nel processo di handshake iniziale lo scambio delle chiavi di configurazione e dei protocolli supportati. Quando un client apre una connessione, il pacchetto di risposta include anche i dati per i pacchetti futuri necessari all'uso della crittografia in TLS. Così facendo si elimina la necessità di impostare la connessione TCP e poi negoziare il protocollo di sicurezza tramite altri pacchetti. Altri protocolli possono essere serviti nello stesso modo, combinando insieme più passi in una singola richiesta-risposta. Questi dati possono poi essere utilizzati sia per le richieste successive nella configurazione iniziale, sia per le richieste future.

Dialogo "istantaneo"

Pasted image 20221103173605.png|400

UDP o TCP

TCP è ampiamente adottato e spesso le infrastrutture Internet sono "sintonizzate" per TCP e limitano o bloccano UDP.
Google ha condotto degli esperimenti esplorativi e ha scoperto the solo un piccolo numero di connessioni sono state bloccate in questo modo, perciò lo stack di rete di Chromium apre contemporaneamente sia una connessione QUIC sia una connessione TCP e questo permette un fallback con una latenza trascurabile.

Con TCP le socket sono identificate della quadruple (IP sorgente, porta sorgente, IP destinazione, porta destinazione), quindi tutte le connessioni vanno in timeout e vengono ristabilite. Invece, QUIC include un ID di connessione al server che non dipende dalla fonte o dalla rete usata. Si può così ristabilire la connessione inviando nuovamente un pacchetto contenente tale ID. L'ID sarà ancora valido anche se l'indirizzo IP dell'utente cambia.