Il livello trasporto (LV4) estende il servizio di consegna tra host di network differenti del LV3 con un servizio di consegna tra processi applicativi in esecuzione sugli host.
Parleremo sempre di processi, come attori di protocolli LV4.
Vedremo:
- TCP - Transfer Conrol Protocol. Controllo, caratteristica fondamentale di TCP
- UDP- User Datagram Protocol
Servizi minimi rispetto a IP:
- multiplazione e demultiplazione tra processi sullo stesso host
- rilevamento dell'errore mediante checksum. In IPv4 esiste un rilevamento degli errori sull'header del pacchetto. Ora di parla di proteggere il payload.
UDP cerca di soddisfare questi requisiti minimi. TCP offre servizi aggiuntivi:
- controllo congestione
- affidabilità trasferimento
#Nota UDP è meno complesso e più leggero
La logica di multiplazione è attuata introducendo un identificativo intero per ogni processo in esecuzione su un host. Questo id viene chiamato porta. Nell'header del segmento vengono introdotti due valori aggiuntivi:
- porta sorgente
- porta destinazione
L'unica condizione è che ogni processo in esecuzione su uno stesso host abbia un numero di porta differente. Non serve avere consistenza tra porta sorgente e destinazione.
È un paradigma concettuale:
- Server -> Listening port. Un processo server apre una porta (se non già occupata) in ascolto (lo richiede al SO). Si mette in attesa di comunicazione per un certo protocollo. Fase di apertura passiva di una connessione.
- Client -> Connection init. Un processo client cerca di aprire una connessione con il server. È una fase di apertura attiva di una connessione.
#Attenzione non abbiamo mai visto nulla di simile. In IP si manda un flusso di pacchetti indipendentemente dallo stato del destinatario, non serve una fase di apertura della comunicazione.
L'apertura di una porta in ascolto avviene mediante API del sistema operativo. Avviene il binding con una porta: il processo si mette in ascolto, il SO registra la mappa porta->applicativo, quindi gli inoltra i segmenti ricevuti dal LV3.
Ogni comunicazione è identificata da una coppia di porte.
I campi porta occupano 2byte negli header di TCP e UDP.
Esistono 3 soglie, per convenzione:
- 0-1024 Well-known ports. Sono associate a protocolli standard, al pari dei blocchi di indirizzi IP privati. Ai protocolli standard corrisponde una well-known ports. Associare un processo ad una wp richiede i privilegi di root.
- 1024-49151 Registered ports. L'uso di queste porte è registrato ma non esistono vincoli come nel caso sopra. Non devono essere usate senza autorizzazione, ma la maggior parte dei sistemi è libero di associarle ad ogni processo.
- 49152-65535 Dynamic or private ports. Porte libere, prive di controlli.
Le porte alte vengono spesso utilizzate dai client, dove potrebbe persistere il vincolo di utilizzi delle well-known ports.
Perché? utilizzare porte standard lato server permette di dedurla implicitamente a partire dal protocollo utilizzato, come quando si naviga su internet; mentre la porta del client è appresa dinamicamente dal server (subisce l'inizializzazione).
I sistemi operativi salvano queste porte in file salvati in memoria principale: /etc/services in Linux
#Attenzione il numero di porta è univoco a parità di host e tipo di protocollo. Ad esempio UDP e TCP possono condividere la stessa porta sullo stesso host.
Lo scope del numero di porta è locale a (host, transport protocol)
Ogni comunicazione su Internet è distinta dalla quintupla: (IP SRC, IP DST, SRC PORT, DST PORT, TRANSPORT PROTO)
Un flusso di pacchetti (packet flow) identifica tutti i pacchetti che appartengono alla stessa comunicazione; identificato dalla quintupla sopra descritta.
#Nota che viene usato impropriamente anche per comunicazioni che non usano protocolli di livello trasporto. Nel caso di ICMP, ad esempio, esiste un numero identificativo della specifica esecuzione di ICMP echo.
User Datagram Protocol. Definito in RFC 768.
Cosa ci offre?
- multiplazione
- rilevamento degli errori sull'header
Cosa non offre?
- best effort: non ha garanzie di affidabilità
- connnectionless: non definisce flussi, riordino di pacchetti e simili
L'header ha dimensione 8 byte (comprende solo le porte)
| source port [16 bit] | destination port [16 bit] |
| len (header+payload) [16 bit] | checksum (header) [16 bit] |
| payload |
Il checksum è calcolato anche sulla base di informazioni provenienti dall'header IP: pseudo-header UDP:
| IP SRC |
| IP DST |
| zero padding | protocollo | UPD len |
Serve a catturare le infromazioni mancanti per avere integrità end-to-end.
- UDP len: informazione ridondante -> inserita "senza uno specifico motivo" per allineare a 16 bit il pseudo header
- zero padding #completa
Il checksum è calcolato sulla base di 3 informazioni:
Il checksum è calcolato come somma binaria di tutti i campi allineati a 16 bit, poi complementate a 1. Molto semplice, eseguito in hw indipendentemente dal processore.
Obiettivo: sommando il checksum appena calcolato e quello inserito nel segmento si deve ottenere 11111...1111.
La lunghezza del pseudo header dovrà essere un multiplo di 16 bit.
L'obiettivo del TCP è creare un canale di comunicazione affidabile virtuale al di sopra di un canale inaffidabile, ovvero quello reale, sottostante.
Compito: rilevazione e ritrasmissione (rimediare agli errori significa reinviare un pacchetto)
Si usa per creare stream affidabili di comunicazione.
L'uso di checksum permette la rilevazione di alterazione del pacchetto, errori trasmissivi, invio parziale, ecc. ovvero errori di trasmissione
Il checksum non torna utile in caso di duplicazione o di perdita di un pacchetto. Ha cognizione sull'integrità dal singolo pacchetto, non sull'interno flusso di dati.
Emergono problemi di duplicazione dei pacchetti e consegne non in ordine se si implementano strategie per aumentare l'affidabilità per lo stream.
Il TCP fornisce garanzie di affidabilità molto rigide, ma non sempre sono tutte necessarie. A livello applicativo potrebbero esserne richieste solo una parte; ad esempio uno stream in tempo reale potrebbe non avere problematiche riguardanti duplicazione o consegna fuori ordine dei pacchetti.
Per creare protocolli di trasporto personalizzati si parte tipicamente da UDP.
#Vedi Protocollo QUIC
Rilevare la perdita di pacchetti è possibile attraverso un meccanismo di notifica detto acknowledgment + timeout. Aggiungendo la ritrasmissione possiamo rimediare alla perdita.
Quali errori cattura?
- perdita pacchetto -> il destinatario non invia l'ACK, il mittente se ne accorge mediante un timeout
- pacchetti duplicati -> così com'è non risolve il problema, ma è risolvibile mediante un numero univoco
Ack e ritrasmissione sono gestite trasparentemente rispetto alla logica applicativa.
#Nota questa è una logica di acknowledgment positivo; esistono anche logiche di ACK in negativo: tipicamente chiamato NACK - Negative ACK -, inviato quando il destinatario riceve un pacchetto con checksum non valido. In TCP questo non viene utilizzato perché si tratta di un'eventualità molto rara, dato che il layer 2 fa già uso di un meccanismo di rilevazione e correzione degli errori (CRC).
Come scegliere il timeout? abbiamo visto i timeout per le ARP request, gestiti e harcodati sul SO; le richieste ARP girano su una rete locale. Il TCP gira su Internet, la scala è completamente diversa.
Il dimensionamento del timeout avviene adattivamente rispetto alla comunicazione, non è statico, ma viene deciso e cambiato a runtime.
Abbiamo visto paradigmi orientati alla commutazione di circuito: basati su instaurazione della comunicazione, come la (vecchia) rete telefonica analogica.
Un paradigma di comunicazione orientato alla connessione prevede 3 fasi:
- apertura
- scambio dei dati
- chiusura
Viene creato l'oggetto astratto connessione senza sprecare risorse aggiuntive intermedie. È una connessione astratta/virtuale, non aggiunge un carico/consumo di risorse in mezzo alla rete, ma solamente tra mittente e destinatario. Mantenere uno stato implica impegnare delle risorse. Tutti i protocolli che richiedono il mantenimento dello stato potrebbero essere limitanti rispetto alla scalabilità del sistema. Bisogna gestire allocazione e deallicazione della memoria.
Sotto questo punto di vista:
- apertura: fase in cui le risorse vengono prese per mantenere lo stato
- chiusura: fase di liberazione delle risorse
TCP è un protocollo stream-oriented: a livello applicativo (da sopra) non si vogliono limitazioni rispetto alla lunghezza dei dati, se ne occupa il livello trasporto. Si parla di segmentazione (termine geometrico da retta - stream - a più segmenti).
TCP cerca di minimizzare la frammentazione ai livelli sottostanti. Dimensione di ogni frame H2N == Path MTU.
#Attenzione In UDP è il processo applicativo che spezza il flusso di informazioni, non viene implementata nessuna logica di segmentazione.
Ogni segmento viene identificato da un numero di sequenza, che lo identifica all'interno dell'intero flusso, utilizzato come offset all'interno del buffer di memoria.
- trasferimento con buffer: i dati possono "salire" al livello applicativo con un certo ritardo rispetto alla ricezione; a causa dei meccanismi di riordino e affidabilità i pacchetti vengono bufferizzati.
- connessione full-duplex: il canale di comunicazione consente ad entrambi i partecipanti di comunicare conteporaneamente.
- controllo di flusso e di congestione:
- il controllo di flusso è un meccanismo che regola la velocità di scambio dei dati a livello end-to-end, ovvero il throughput rispetto alle capacità dei capi della comunicazione. Ad esempio un link lento dalla parte del dst, con alte velocità da parte del mittente, potrebbe portare alla perdita di pacchetti lato destinatario.
- il controllo di congestione: agisce sulla velocità di trasmissione in base alle condizioni della rete
- non vengono gestite comunicazioni in tempo reale
- non vi è garanzia di disponibilità di banda tra mittente e destinatario
- no multicast affidabile
Formato del segmento:
| src port [16 bit] | dst port [16 bit] |
| sequence number [32 bit] |
| ack number [32 bit] |
| hlen[4 bit] | resvd[3 bit] | codebit[9 bit] | win size[16 bit] |
| checksum [16 bit] | urgent pointer [16 bit] |
| optional data [0-320 bit] | zero padding [variabile bits] |
- sequence number: numero di sequenza relativo al flusso. #Nota che è piuttosto grosso
- acknowledgment number: ogni pacchetto ha un numero di ack che conferma la ricezione di un certo pacchetto
Perché non è stato fatto con un campo solo? ogni pacchetto in questo modo può svolgere una doppia funzione. Per non usare sempre pacchetti di "solo acknowledgment". Quando il destinatario diventa mittente, invierà dei dati insieme alla conferma. Si chiama piggybacking (letteralmente "cavalluccio"). Favorisce la riduzione del traffico in contesti di comunicazione bidirezionale.
- hlen: utilizzato perché la dimensione dell'header è variabile, in quanto contiene campi opzionali. Valgono tutte le considerazioni fatte per hlen dell'header di IP. La stessa antifona vale per strutture dati dalla dimensione variabile. Memorizza un numero di word (il risultato in byte è un multiplo di 4 byte).
- reserved: per usi futuri
- code bit:
- URG - urgent - dati segnalati come urgenti, il mittente lo setta per dare la priorità; il SO deve inviare il prima possibile i dati all'applicativo. Si usa per dati out-of-band
- ACK - acknowledgment - il valore del campo acknowledgment number è valido
- PSH - push - il dst deve passare i dati all'applicazione il prima possibile
- SYN - synchronize - legato all'apertura della connessione
- FIN - finish - legato alla fine della connessione. Prima opzione per l'apertura.
- RST - reset - legato alla fine della connessione. Seconda opzione per l'apertura.
- window size: dimensione della finestra di ricezione; numero di byte che si è disposti ad accettare.
- checksum: controllo di integrità; analogo a UDP (vengono protetti anche gli indirizzi IP).
- urgent pointer: quando il bit URG è settato, non tutto il payload ha priorità massima, ma solo parte di esso; urgent pointer punta al termine dei dati urgenti. Utilizzato raramente. #Nota che lo standard non lo regola rigorosamente, ogni SO adotta l'implementazione che preferisce. Nelle implementazioni moderne si invia tipicamente un solo urgent byte.
- TCP options: campo opzionale di lunghezza variabile, usato per negoziare la dimensione massima del segmento scambiato
- zero padding: dovuto al fatto che hlen memorizza un numero di word (multipli di 4 byte).
I dati urgent sono stati pensati per contesti applicativi interattivi. Ad esempio SSH o, all'epoca, Telnet.
Si pensi alla gestione di segnali legati ad eventi speciali come ^C, ^Z, ecc.
Si vuole un meccanismo di prioritizzazione anche per la shell remota.
Mediante il campo option è possibile negoziare la MSS. Viene negoziato tra mittente e destinatario.
- All'inizio il mittente imposta MSS = MyMTU + len(IP Header).
- Nell'instaurazione della connessione il destinatario propone il suo MTU.
- si sceglie il
min(SRC MTU, DST MTU)
Lo standard definisce MSS = 536 bytes nel caso in cui non si riesca a negoziare.
L'MSS è ri-negoziabile durante la comunicazione.
Il numero di sequenza per un segmento è dato dal primo SEQ, chiamato ISN - Initial Sequence Number, un numero pseudo-casuale, per rendere pressoché nulla la probabilità di due SEQ coincidenti sulla stessa porta, stesso dispositivo.
Ad ogni invio di pacchetto si incrementa il SEQ di un valore pari all'MSS. #Nota Il SEQ è pari all'offset del primo byte che ci aspettiamo dal payload di quel pacchetto. Non indica quello che abbiamo ricevuto, ma quello che ci aspettiamo di ricevere
La logica è modulare (wrappata) -> si usa il valore in modulo se dovesse eccedere
#Attenzione I SEQ di ogni "direzione" sono totalmente indipendenti
L'acknowledgment number corrisponde al primo byte che ci aspettiamo di ricevere nel prossimo pacchetto.
#Approfondisci su piggybacking ("portare un cavallo sulle spalle"), come definito sullo standard TCP
Viene definito dallo standard una piccola finestra temporale (ie. intervallo di tempo) che il destinatario può aspettare prima di inviare un ack, nella speranza di accumulare dati da inviare all'altro capo nel frattempo.
Sottoprotocollo per l'instaurazione di una connessione TCP.
Esistono altri protocolli che usano four-way handshake (WAP - Wireless Application Network) o simili.
- SYN. Il client invia al server un segmento di controllo con SYN = 1 e specifica il suo ISN (CLIENT_ISN)
- SYN-ACK. Il server risponde con un segmento di controllo con SYN=1 e un proprio ISN (SERVER_ISN) totalmente indipendente da quello del cliente; conferma con ACK pari a CLIENT_ISN+1
- ACK. Il cliente segnala l'apertura definitiva inviando un segmento di controllo con SYN=0, ACK = SERVER_ISN+1 e SEQ = CLIENT_ISN+1
A questo punto la comunicazione è stata instaurata e il ruolo di client e server non è più distinguibile. Ognuno ha un proprio stato disgiunto da quello dell'altro interlocutore, grazie ai SEQ indipendenti; in questo modo gli stati di cliente e server sono esclusivi, si evitano problemi di sincronizzazione.
In definitiva:
- SYN [C->S]. Connection requested
- SYN-ACK [C<-S]. Connection granted
- ACK [C->S]. Connection confirmed.
Durante la fase di SYN si può negoziare MSS e window size (controllo di flusso). Abbiamo parlato di allocazione di risorse. Durante la fase di handshake client e server allocano le risorse necessarie, come il window buffer.
Chiusura gentile, cortese
La connessione è full-duplex, va chiusa da entrambe le parti. Fissando un client C e un server S, senza particolare riguardi.
- C->S. FIN. SEQ = x
- C<-S. ACK. ACK = x+1. Chiusura immediata della connessione della connessione client verso server. Il server lo conferma immediatamente.
- Potrebbe avvenire lo scambio di qualche altro messaggio tra C e S. Durante questo periodo il server chiude l'applicazione e attende chiusura. Quando è pronto conferma la chiusura al client
- C<-S. FIN. SEQ = y. Il server chiude la connessione ed invia il FIN .
- C->S. ACK. ACK = y+1. il client invia un ACK appena arriva il FIN. Attende il timeout dell'ACK inviato. Allo scadere del timeout la connessione server-client è chiusa.
- Ora la comunicazione è effettivamente chiusa.
Perché 4 passaggi e non 3?
- chi riceve per primo un FIN ha una logica applicativa ancora attiva, delle risorse occupate, ecc.
- deve fermare l'applicazione, deallocare le risorse, ecc.
- L'ACK è quindi associato alla ricezione del FIN da parte del server
- Nel mentre chiude applicazione, quando è pronto anche il server invia il FIN al client
- Il client notifica la ricezione con ACK. La comunicazione è definitivamente chiusa
#Nota il pacchetto con il flag FIN potrebbe contenere ancora informazioni funzionali alla comunicazione.
Il problema risiede nel fatto che il server potrebbe avere ancora dei pacchetti da recapitare al client. Con una logica in 3 passaggi avverrebbe così:
- FIN. Client ha finito di usare il servizio.
- FIN-ACK. Il server chiude la connessione.
- ACK. Client ha ricevuto la chiusura dal parte del server.
Quindi:
- o il server chiude immediatamente la connessione, senza inviare gli ultimi pacchetti
- oppure attente del tempo, facendo scadere il timeout che il client si aspetta
La chiusura unpolite della connessione è, scelti arbitrariamente H1 e H2:
- H1 -RST-> H2
- fine. La comunicazione è chiusa.
Questa è una chiusura best-effort -> inaffidabile, al contrario di tutto il protocollo TCP.
Se spengo direttamente la macchina, l'entità con cui comunico si accorge con molto ritardo che ho chiuso la connessione. Intanto ha delle risorse allocate.
Non ho tempo di fare una chiusura polite con FIN
Si cerca di essere fair. Il caso di errori lato server o chiusura brusca (es. SIGKILL) si prova comunque a mandare un messaggio di comunicazione best-effort.
Altro caso: RST-ACK -> quando il client usa a livello applicativo diverso da quello che parla il server. Il server insieme all'ACK sul pacchetto ricevuto accorpa un RST.