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.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221012082731.png)
I protocolli di trasporto vengono eseguiti nei sistemi terminali (periferici) e non nei router della rete:
- lato invio: il livello di trasporto scinde i messaggi applicativi, spezzandoli in parti più piccole ed aggiungendo a ciascuna un'intestazione di trasporto, per creare i segmenti. Il livello di trasporto poi passa il segmento al livello di rete, dove viene incapsulato all’interno di un pacchetto a livello di rete (datagramma) e inviato a destinazione.
- lato ricezione: il livello rete estrae il segmento dal datagramma e lo passa al livello superiore, quello di trasporto. Quest'ultimo riassembla i i segmenti in messaggi e li passa al livello applicazione.
È 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.
- messaggi dell'applicazione = lettere nella busta
- processi = cugini
- host (sistemi periferici) = condomini
- protocollo a livello di trasporto = Anna e Andrea
- protocollo a livello di rete = servizio postale (compresi i postini)
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
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221012230839.png)
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.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221012233406.png)
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:
- DatagramSocket mySocket1 = new DatagramSocket(12534);
- DatagramSocket mySocket2 = new DatagramSocket(12535);
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.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221013104335.png)
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:
- indirizzo IP di origine
- numero di porta di origine
- indirizzo IP di destinazione
- numero di porta di destinazione
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.
- L'applicazione server TCP presenta una socket di benvenuto che si pone in attesa di richieste di connessione da parte dei client TCP sulla porta numero 12000.
- Il client TCP crea una socket e genera un segmento per stabilire la connessione.
- Una richiesta di connessione è un segmento TCP con numero di porta di destinazione 12000 e uno speciale bit di richiesta di connessione posto a 1 nell'intestazione. Il segmento include anche un numero di porta di origine, scelto dal client.
- L'host che esegue il processo server, quando riceve il segmento con la richiesta di connessione con la porta di destinazione 12000, crea la connessione sul processo specifico che è in attesa sulla porta 12000.
- Il livello di trasporto sul server prende nota, dal segmento con la richiesta di connessione, i seguenti valori:
- numero di porta di origine nel segmento
- indirizzo IP dell'host di origine
- numero di porta di destinazione nel segmento
- il proprio indirizzo IP (server)
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.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221013191901.png)
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
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221013195845.png)
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é:
- Non dispone di un meccanismo di controllo della congestione, che ritarderebbe l'invio. Questo non andrebbe bene per le applicazioni in tempo reale che spesso richiedono una velocità minima di trasmissione e non sopportano ritardi eccessivi nella trasmissione dei pacchetti, mentre tollerano una certa perdita di dati.
- Non richiede di stabilire una connessione, che potrebbe aggiungere un ritardo. UDP può inviare raffiche di pacchetti dati, senza effettuare un handshake come invece fa TCP. Proprio per questo il DNS utilizza UDP, mentre HTTP usa TCP perché l'affidabilità risulta critica per le pagine web con testo.
- Nessuno stato di connessione nel mittente e destinatario, inoltre non conserva i parametri per il controllo della congestione e parametri sul numero di sequenza e di acknowledgment. Per questo un server dedicato ad un'applicazione può supportare molti più client attivi.
- L'intestazione è corta, aggiunge solamente 8 byte al segmento (TCP 20).
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221015095149.png)
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
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221015101540.png)
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.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221015105929.png)
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.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221015110014.png)
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.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221015224252.png)
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.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221015230928.png)
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:
- Rilevamento dell'errore: Deve essere presente un meccanismo che consenta al destinatario di rilevare gli errori sui bit ed UDP utilizza il campo checksum proprio per questo scopo. Per queste tecniche si richiede l'invio di bit extra (oltre ai dati) tra mittente e destinatario.
- Feedback del destinatario: Dato che mittente e destinatario possono essere molto distanti tra loro, l'unico modo che ha il mittente per sapere se un pacchetto sia stato ricevuto correttamente o meno è quello di ricevere un feedback dal destinatario. Per una notifica positiva si manderanno pacchetti ACK e per quella negativa il NAK, che spesso sono costituiti da un solo bit: 0 per NAK e 1 per ACK.
- Ritrasmissione: Un pacchetto ricevuto con errori sarà ritrasmesso dal mittente quando.
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à:
- inizializzare un contatore ogni volta che invia un pacchetto (che si tratti del primo invio o di una ritrasmissione)
- rispondere a un interrupt generato dal timer con l’azione appropriata
- fermare il contatore
Passaggi stop-and-wait
Il trasmettitore:
- invia una PDU (e mantiene una copia locale per un'eventuale ritrasmissione)
- imposta un timeout
- attende la ricezione del rispettivo ACK:
- se non riceve alcun ACK entro il timeout, invia nuovamente la stessa PDU
- se riceve l'ACK:
- controlla che l'ACK non contenga errori (checksum)
- controlla il numero di sequenza
- se è tutto OK (quindi l'ACK si riferiva alla PDU inviata) procede con l'invio della PDU successiva
Il ricevitore, quando riceve una PDU:
- controlla se ci sono errori (checksum)
- controlla il numero di sequenza
- se è corretto e in ordine, invia l'ACK e passa l'SDU ai layer superiori
- se il checksum o il numero di sequenza sono errati, cancella la PDU (drop)
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221017153508.png)
Efficienza di Stop-and-wait
Il protocollo stop-and-wait funziona, ma a livello prestazionale non è molto conveniente. Assumiamo che:
- il canale abbia un tasso trasmissivo
Gbit/s - il ritardo di propagazione è di
per andata e per il ritorno ( ) - la dimensione dei pacchetti è
bit (intestazione + dati) - il tempo di trasmissione del pacchetto sul collegamento è di:
microsecondi
Quindi se il mittente comincia ad inviare pacchetti a
Se definiamo l'efficienza come la frazione di tempo in cui il mittente è stato effettivamente occupato nell’invio di bit sul canale, essa sarà:
Il mittente è stato in grado di spedire
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.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221017171036.png)
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.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221017174610.png)
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221017174713.png)
Finestre di trasmissione e ricezione
Finestra di trasmissione
- Al massimo tanto grande quanto la memoria allocata dal sistema operativo del trasmettitore
- |
| (cardinalità di ) indica la dimensione della finestra
Finestra di ricezione
- Al massimo tanto grande quanto la memoria allocata dal sistema operativo del ricevitore
Puntatore low
- I pacchetti che vanno da 0 a
corrispondono ai pacchetti già trasmessi e che hanno ricevuto acknowledgment.
Puntatore up
- Potrebbe non coincidere con l'ultimo pacchetto di
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221018161454.png)
Acknowledgements
A seconda del protocollo, si possono utilizzare diversi tipi di ACK
ACK individuale
- Indica la ricezione corretta di un pacchetto specifico
- ACK(n) significa "Ho ricevuto il pacchetto n"
ACK cumulativo
- Indica la ricezione corretta di tutti i pacchetti fino a un certo indice
- ACK(n) significa "Ho ricevuto tutto fino al pacchetto n (escluso)"
ACK negativo (NACK)
- Richiede la ritrasmissione di un pacchetto singolo
- NACK(n) significa "Inviami di nuovo il pacchetto n"
Piggybacking
- Inserimento di un ACK data in un pacchetto dati
3.4.3 Go-Back-N (GBN)
Il mittente può avere fino a
Il numero dei pacchetti in sospeso viene però limitato a
Il numero di sequenza di un pacchetto viene scritto in un campo a dimensione fissa dell’intestazione del pacchetto. Dato che
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
Ricezione di un ACK
L'ACK del pacchetto con il numero di sequenza
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
Con il GBN il destinatario non deve memorizzare nel buffer i pacchetti che giungono fuori sequenza, perché tanto il mittente ritrasmetterebbe sia il pacchetto
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221018173756.png)
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
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.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221018174844.png)
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.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221018185700.png)
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.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221018193603.png)
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.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221018194653.png)
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221018194802.png)
Consideriamo ora due scenari:
- Nel primo gli ACK dei primi tre pacchetti vanno persi e il mittente ritrasmette i pacchetti. Il destinatario riceve quindi un pacchetto con numero di sequenza 0, copia del primo pacchetto inviato
- Nel secondo gli ACK dei primi tre pacchetti vengono tutti consegnati correttamente. Il mittente, di conseguenza, sposta in avanti la propria finestra e spedisce il quarto, il quinto e il sesto pacchetto, con numeri di sequenza rispettivamente pari a 3, 0 e 1. Il pacchetto con numero di sequenza 3 va perso, ma arriva il pacchetto con numero di sequenza 0, che contiene nuovi dati. Di conseguenza il destinatario non sa distinguere se il numero di sequenza 0 è associato alla ritrasmissione del primo pacchetto o alla trasmissione originaria del quinto.
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
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221019141144.png)
Ricordiamo che la finestra di trasmissione è
Riepilogo
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221019092809.png)
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.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221019201824.png)
Panoramica
Protocollo punto-punto:
- un mittente, un destinatario
Flusso di byte affidabile e consegnato in ordine
- Nessun "limite di messaggi", tanto verranno comunque consegnati tutti
Con pipelining:
- Meccanismi di controllo di flusso e di congestione TCP definiscono la dimensione della finestra
- Le dimensioni delle finestre sono dinamiche (sia al mittente sia al destinatario)
- Usa ACK cumulativi
Full duplex:
- Flusso di dati bidirezionale nella stessa connessione
- MSS: dimensione massima di un segmento (maximum segment size)
Orientato alla connessione:
- Setup della connessione tramite handshaking (scambio di messaggi di controllo)
- Inizializza lo stato di mittente e destinatario prima dello scambio di dati
Flusso controllato: - Il trasmettitore non sovraccaricherà il ricevitore
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).
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221019204337.png)
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:
- Il campo numero di sequenza e il campo numero di acknowledgment vengono utilizzati dal mittente e dal destinatario TCP per implementare il trasferimento dati affidabile
- Il campo finestra di ricezione viene utilizzato per il controllo di flusso ed è il numero di byte che il destinatario è disposto ad accettare.
- Il campo lunghezza dell’intestazione, di 4 bit, specifica la lunghezza dell’intestazione TCP in multipli di 32 bit. Questa lunghezza è variabile a causa delle opzioni TCP, ma solitamente questo campo è vuoto e quindi la lunghezza è di 20 byte.
- Il campo opzioni è facoltativo e di lunghezza variabile. Viene utilizzato quando mittente e destinatario negoziano la dimensione massima del segmento (MSS) o come fattore di scala per la finestra nelle reti ad alta velocità.
- Il campo flag è di 6 bit. Il bit ACK viene usato per indicare che il segmento contiene un acknowledgment per un segmento che è stato ricevuto con successo. I bit RST, SYN e FIN vengono utilizzati per impostare e chiudere la connessione. Il bit PSH, se ha valore 1 indica che il destinatario dovrebbe inviare immediatamente i dati al livello superiore, ma generalmente non viene usato. Il bit URG viene usato per indicare nel segmento la presenza di dati che l’entità mittente a livello superiore ha marcato come “urgenti”, ma generalmente non viene usato. Il campo puntatore ai dati urgenti, di 16 bit, indica la posizione dell’ultimo byte di dati urgenti, ma anche questo generalmente non viene usato.
Dimensione della finestra di ricezione (RWND)
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221020001244.png)
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.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221020094210.png)
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
-
Supponiamo che l’Host A abbia ricevuto da B tutti i byte numerati da 0 a 535 e che A stia per mandare un segmento all’Host B. L’Host A è in attesa del byte 536 e dei successivi byte nel flusso di dati di B. Pertanto, l’Host A scrive 536 nel campo del numero di acknowledgment del segmento che spedisce a B.
-
Supponiamo che l’Host A abbia ricevuto un segmento dall’Host B contenente i byte da 0 a 535 e un altro segmento contenente i byte da 900 a 1000, ma per qualche motivo l'Host A non ha ancora ricevuto i byte da 536 a 899. L'Host A sta ancora attendendo il byte 536 (e i successivi), perciò il prossimo segmento di A destinato a B conterrà 536 nel campo del numero di acknowledgment. Dato che TCP effettua l’acknowledgment solo dei byte fino al primo byte mancante nel flusso, si dice che tale protocollo offre acknowledgment cumulativi (cumulative acknowledgment).
-
Infine supponiamo che l'Host A abbia ricevuto il terzo segmento (i byte da 900 a 1000) prima di aver ricevuto il secondo (i byte da 536 a 899). Pertanto, il terzo segmento non è arrivato in ordine. Dato che gli RFC (ovvero la specifica) non impongono delle regole in merito alla ricezione di segmenti fuori sequenza nella connessione, lasciano la decisione a chi implementa TCP. Sostanzialmente le scelte sono due:
- il destinatario scarta immediatamente i segmenti non ordinati
- il destinatario mantiene i byte non ordinati e attende quelli mancanti per colmare i vuoti (approccio più utilizzato perché più efficiente in termini di banda occupata)
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.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221020105103.png)
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".
- 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 (
) e lo pone nel campo numero di sequenza del segmento SYN iniziale, che poi viene incapsulato in un datagramma IP e inviato al server. - 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 (
). Infine il server sceglie il proprio numero di sequenza iniziale ( ) e lo pone nel campo del numero di sequenza. Il segmento di connessione approvata viene anche detto segmento SYNACK. - 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 (
) 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.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221020181250.png)
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.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221020185944.png)
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.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221022103314.png)
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:
Dato che il valore tipico di
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.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221022114516.png)
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:
Se i valori di SampleRTT presentano fluttuazioni limitate, allora DevRTT sarà piccolo, in caso di notevoli fluttuazioni, DevRTT sarà grande. Il valore tipico di
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:
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.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221026210649.png)
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:
- LastByteRead che è il numero dell'ultimo byte nel flusso di dati che il processo applicativo in B ha letto dal buffer
- LastByteRcvd che è il numero dell’ultimo byte, nel flusso di dati, che proviene dalla rete e che è stato copiato nel buffer di ricezione di B.
La finestra di ricezione RWND viene impostata quindi alla quantità di spazio disponibile nel buffer:
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
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221027090139.png)
Dato che lo spazio disponibile varia col tempo, rwnd è dinamica.
Il mittente tiene traccia di due variabili:
- LastByteSent che è il numero dell'ultimo byte nel flusso di dati che è stato mandato
- LastByteAcked che è il numero dell'ultimo byte nel flusso di dati per cui si è ricevuto acknowledgment
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
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
I pacchetti dall’Host A e dall’Host B passano attraverso un router e un collegamento uscente condiviso di capacità
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221027192429.png)
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
Se però il tasso di invio supera
Il problema nell'operare al limite della capacità di collegamento è che quando la velocità di invio si avvicina a
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 è
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221029162520.png)
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é
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221029165119.png)
La velocità di invio media dell’host in questo scenario non supera R/2, visto che abbiamo ipotizzato che nessun pacchetto vada smarrito.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221029165202.png)
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.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221029182757.png)
Mettiamo caso che
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221029170919.png)
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.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221029190322.png)
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
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221029190421.png)
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
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221029193119.png)
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
Consideriamo il caso in cui
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020231113122821.png)
Ogni volta che si scarta un pacchetto tutte le risorse usate per portare il pacchetto fino a quel punto risultano sprecate.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221124184719.png)
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.
- Additive increase: Se il mittente TCP si accorge di condizioni di scarso traffico sul percorso che porta alla destinazione, incrementa il proprio tasso trasmissivo (cioè la dimensione della finestra) di 1MSS ogni RTT cercando di occupare la banda disponibile, finché non si rilevano perdite (un evento di triplice ACK).
- Multiplicative decrease: Se il mittente percepisce traffico lungo il percorso riduce la finestra della metà, ma poi riprende ancora a crescere per capire se ci sia ulteriore ampiezza di banda disponibile.
Tale comportamento viene detto a dente di sega
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221029222408.png)
Futuro di TCP
La formula approssimata per il throughput di TCP è:
Dove:
- p
probabilità di perdere un pacchetto - MSS
maximum segment size [Byte] - RTT
round-trip time [s]
Es: RTT
Fairness
Consideriamo
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221029233355.png)
Esaminiamo ora il caso di due connessioni TCP che condividono un collegamento con capacità trasmissiva
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221030002147.png)
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
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à)
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:
Dove
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
- La finestra è l'insieme di dati(byte o segmenti).
- La finestra di ricezione (RWND) è l'insieme dei dati che può essere gestito dal ricevitore e la sua dimensione è il limite massimo di dati che il ricevitore può immagazzinare nel buffer di ricezione.
- La Finestra di congestione (CWND) è l'insieme di dati che si possono inviare nella rete e la sua dimensione si adotta alla quantità massima di dati che il trasmettitore pensa di poter inviare senza sovraccaricare la rete (quindi senza che si crei una congestione).
- La Finestra di trasmissione è l'insieme di dati che si possono inviare senza sovraccaricare la rete e senza saturare il ricevitore, proprio per questo
min(CWND, RWND) = min(CWND, ).
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
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.
- Il mittente è più prudente ed abbassa SSTHRESH = MAX(CWND/2, 2), anticipando il passaggio a Congestion avoidance che è una fase più prudente.
- Viene aumentato l'intervallo di timeout RTO = RTO
2 , perché ci fidiamo un po' di più della capacità della rete di trasmettere per convogliare il traffico (le diamo più tempo). - Infine viene reimpostato CWND a 1 e ricomincia di nuovo il processo di slow start per ritrasmettere il segmento che ha causato il 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.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221102120155.png)
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
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
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
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221102122500.png)
Parametri su cui poter agire
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221102165813.png)
CWND si può aumentare agendo su slow start e congestion avoidance, mentre si può ridurre con AIMD.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221103225643.png)
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221103225116.png)
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
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
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
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
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
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221103144752.png)
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221103144818.png)
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.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221103235607.png)
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.
3.8 Riepilogo
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
Dove
Dopo una congestione, la finestra è scalata di
TCP-friendly: non penalizza troppo i flussi TCP standard che condividono lo stesso bottleneck link.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221103170534.png)
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221103170652.png)
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:
- la banda sul link
collo di bottiglia (bottleneck bandwidth) - L'RTT
L'idea è di trasmettere pacchetti a una velocità che non "dovrebbe" incontrare accodamenti
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221103171320.png)
È 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).
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221103171759.png)
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221103171819.png)
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:
- 3,3 Mbit/s con CUBIC
- 9100 Mbit/s con BBR
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:
- CUBIC ha un tempo di andata e ritorno medio di 1090 ms
- BBR di solo 43 ms
Velocità di trasmissione
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221103172531.png)
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221103172623.png)
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221103172732.png)
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:
- Evitare fenomeni di Head-of-Line blocking, utilizzando di base UDP invece di TCP
- Ridurre la latenza rispetto alle connessioni TCP, compattando i messaggi per ridurre l'overhead di connessione
Può essere implementato a livello applicativo anziché nel kernel, come Chrome, Firefox, Safari... È il protocollo di livello trasporto del futuro HTTP/3.
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221103173158.png)
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221103173235.png)
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"
/%F0%9F%8C%90%20Reti/_images/Pasted%20image%2020221103173605.png)
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.