TCP
Da Hacknowledge.
TCP (Transmission Control Protocol) è il principale protocollo di trasporto affidabile utilizzato nel protocollo TCP/IP, e quindi uno dei fondamenti di internet stessa. A differenza di UDP, protocollo di trasporto non affidabile e fondato sulla filosofia del best effort per far giungere un pacchetto a destinazione, TCP è un protocollo orientato alla connessione; ciò vuol dire che il TCP cerca di riprodurre in un ambiente tipicamente packet switching come quello di internet, e quindi senza canali di connessione dedicati tra gli host che comunicano, le caratteristiche di affidabilità di una rete circuit switching. Ciò è possibile instaurando delle connessioni virtuali tra i due host, il che dà garanzie maggiori circa il recapito effettivo dei pacchetti, e queste connessioni virtuali si instaurano attraverso una precisa sequenza di pacchetti scambiati tra gli host, sequenze richieste dal protocollo stesso.
[modifica] Struttura di un header TCP
Un header TCP ha una struttura relativamente complessa, che può apparire ridondante, ma che serve proprio a garantire la maggiore affidabilità di recapito dei dati.
Per ora conosciamo i campi source port e destination port, simili a quelli di UDP, che indicano rispettivamente la porta sorgente e quella di destinazione, e quello di checksum, anch'esso presente in UDP. Gli altri campi dell'header li esamineremo uno per uno.
[modifica] Instaurazione della connessione
Nel modello TCP, affinché si instauri una connessione è necessario che su un host ci sia un processo server in ascolto e che accetti connessioni dall'esterno, e un processo client in esecuzione su un altro host che faccia richiesta di connessione al server. Per instaurare la connessione il processo client deve ovviamente conoscere indirizzo IP (presente nell'header IP) e la porta su cui il server è in ascolto (anche TCP, come UDP, offre un servizio di multiplazione/de-multiplazione della connessione basata sull'astrazione delle porte). A questo punto invia al server un pacchetto speciale detto SYN segment, un pacchetto avente il flag SYN (syncronize) dell'header TCP settato a 1. Il pacchetto SYN inviato dal client include l'ISN (Initial Sequence Number), che è un numero pseudo-casuale a 32 bit generato dal client e che servirà a numerare i pacchetti. Se ad esempio il client genererà un ISN pari a 2134, tutti i pacchetti scambiati tra client e server verranno numerati da 2134 in poi. La numerazione dei pacchetti
- serve a garantire un sistema di consegna affidabile, in quanto uno dei due host può così accorgersi se un pacchetto è stato smarrito o se è arrivato un duplicato
- parte da un numero casuale per motivi di sicurezza, e per evitare packet injection
Oltre all'ISN il SYN segment del client include anche
- MRW (Maximum Receive Window), ovvero il massimo numero di byte che il client è in grado di ricevere all'interno del suo buffer (utile per la regolazione del flusso dei pacchetti)
- MSS (Maximum Segment Size), ovvero la dimensione massima del segmento
Il segmento SYN non ha un payload, ovvero non ha dati aggiuntivi all'interno del pacchetto, semplicemente un header TCP così strutturato.
A un tale pacchetto il server, se accetta la connessione, risponde con un altro SYN segment così strutturato:
- ISN del server, numero pseudo-casuale a 32 bit generato dal server che servirà a numerare i pacchetti inviati dal server
- ACK del server (acknowledgement), contenente l'ISN del client+1 (sta a indicare 'ho ricevuto il SYN segment del client e il suo ISN, ora mi aspetto che il prossimo pacchetto che mi verrà inviato dal client avrà un sequence number pari a client_ISN+1')
- MRW del server
- MSS del server
Anche questo pacchetto TCP non presenta payload aggiuntivo e contiene solo l'header TCP.
A questo pacchetto il client risponde con un ultimo segmento TCP, con SYN=0, ACK pari all'ISN del server+1 (per dire 'ho ricevuto il pacchetto n.ISN dal server e mi aspetto che ora mi invii un pacchetto con ISN=ISN+1') e numero di sequenza pari a client_ISN+1. Questo dà il via allo scambio di dati vero e proprio.
Questi 3 scambi di segmenti TCP tra client e server rappresentano il celebre 3 ways handshaking (o più brevemente 3whs) del protocollo TCP, necessario per instaurare una connessione affidabile tra due host di una rete. Il 3whs del TCP si può così schematizzare: Immagine:3whs.png
Il 3whs si può così riassumere in 'linguaggio comune':
- Client: Voglio aprire una connessione con te. Ti invio un header TCP con il mio sequence number.
- Server: Acconsento ad aprire una connessione. Ti invio un header TCP avente come ACK il tuo sequence number+1, a testimonianza del fatto che ho ricevuto il tuo header TCP e che ora mi aspetto che tu mi invii un pacchetto con quel sequence number, e il mio sequence number.
- Client: Ho ricevuto il tuo pacchetto, e quindi uso come ACK il tuo sequence number+1, e ti invio un header TCP con sequence number pari al mio ISN+1.
- A questo punto client e server possono scambiarsi i dati veri e propri
[modifica] Chiusura della connessione
Esistono due modi per chiudere una connessione previsti dal TCP: un modo polite (chiusura standard della connessione attraverso uno scambio di pacchetti tra client e server) e un modo brute (reset della connessione attraverso un pacchetto reset inviato dal client o dal server).
[modifica] Polite close
La chiusura pulita della connessione TCP è così strutturata:
- Il client invia al server un segmento TCP con il flag FIN settato a 1
- Il server invia al client un segmento TCP con ACK per confermare la ricezione del pacchetto FIN
- Il server chiude tutte le risorse di memoria dedicate alla connessione con il client, quindi invia al client un pacchetto con FIN=1
- Il client invia al server l'ACK del pacchetto ricevuto e rimane in attesa (TIME_WAIT) di eventuali pacchetti rimasti in sospeso prima di chiudere definitivamente la connessione
[modifica] Reset della connessione
In genere le connessioni TCP vengono chiuse in modo pulito tramite lo scambio di pacchetti FIN visto sopra. Tuttavia, TCP prevede anche una chiusura brusca della connessione (reset), generalmente dovuta a un'interruzione brusca da parte dell'utente, a un'interruzione da tastiera o simili. In questo caso uno dei due host invia all'altro un segmento TCP con il flag RST=1. Quando l'altro capo riceve questo pacchetto chiude immediatamente la connessione rilasciando tutte le risorse occupate.
[modifica] Flag TCP
Abbiamo già incontrato alcuni dei flag di un header TCP. Esaminiamoli ora tutti:
- URG: indica che nel pacchetto TCP sono presenti dati urgenti (es. un'interruzione da tastiera data da uno dei due host). Quando questo flag è settato a 1, il campo urgent pointer dell'header TCP è settato e punta alla zona del pacchetto in cui cominciano i dati non urgenti.
- ACK: il flag è settato a 1 quando il destinatario deve prendere in considerazione i dati contenuti nel campo acknowledgement number, ovvero quando il segmento TCP è inviato come conferma di avvenuta ricezione di un precedente pacchetto TCP.
- PSH: quando settato, questo flag indica che il mittente richiede al destinatario di notificare al processo destinatario il settaggio stesso di questo flag per qualche ragione.
- RST: quando settato, produce un'interruzione brusca della connessione (reset).
- SYN: usato nel 3whs per iniziare una connessione TCP (syncronize).
- FIN: usato per terminare in modo pulito una connessione TCP.
[modifica] Affidabilità del protocollo
L'affidabilità di TCP come protocollo di trasporto è data da varie caratteristiche:
- Implementazione del checksum, così come UDP, per controllare eventuali danneggiamenti sui bit durante la trasmissione
- Garanzia di trasmissione dei pacchetti e del canale di comunicazione tra client e server una volta effettuato il 3whs
- Garanzia di ricezione tramite l'acknowledgement e di ricezione ordinata dei pacchetti tramite il sequence number
Il meccanismo dell'acknowledgement (ACK) consente uno scambio di informazioni tra client e server estremamente affidabile. Se infatti l'host destinatario di un pacchetto deve sempre inviare un segmento ACK al mittente per segnalare l'avvenuta ricezione di un pacchetto, è possibile sapere se l'host destinatario ha ricevuto o meno il pacchetto. L'host mittente userà un time-out calcolato tra il tempo di avvio del pacchetto e la ricezione dell'ACK. Se a time-out scaduto il segmento ACK non è ancora stato ricevuto, allora il mittente considera il pacchetto appena inviato come smarrito e provvede a inviarlo nuovamente.
Quando un host invia un pacchetto ad un altro host, immediatamente si 'congela' e rimane in attesa del segmento di ACK del destinatario, che segnala l'avvenuta consegna, prima di continuare a inviare dati. Se scaduto il time-out il mittente non ha ancora ricevuto l'ACK, i possibili motivi sono due:
- Il pacchetto inviato è stato perso. In tal caso, l'host mittente invia nuovamente il pacchetto.
- L'host destinatario ha effettivamente ricevuto il pacchetto dal mittente, ma l'ACK che ha inviato è andato perso. In tal caso l'host mittente re-invia ugualmente il pacchetto al destinatario. Quest'ultimo però controlla il sequence number e nota che il pacchetto con quel sequence number è già stato ricevuto, quindi lo scarta, ma invia ugualmente l'ACK all'host mittente.
[modifica] Scelta del timeout
Una fase cruciale nello standard TCP è la scelta del timeout da parte del mittente. Un timeout troppo breve porterebbe ad una ritrasmissione eccessiva di pacchetti, con il rischio di creazione di duplicati, mentre invece un timeout troppo lungo rischierebbe di far rimanere troppo a lungo l'host mittente 'congelato' in attesa di ACK, rallentando le prestazioni della comunicazione. Si sceglie, per convenzione, un timeout maggiore del RTT (Round Trip Time) per evitare il rischio di timeout troppo brevi. L'RTT è definito come il tempo medio tra l'invio di un pacchetto e la ricezione dell'ACK per quel pacchetto stesso. Nelle prime versioni di TCP per convenzione si prendeva un tempo di timeout pari al doppio del RTT. Ma è una stima grossolana e soprattutto poco flessibile.
Qui entra in ballo il Sample RTT, definito come il tempo trascorso tra l'invio di un pacchetto e la ricezione del corrispondente ACK. Questo tempo varia dinamicamente, essendo una media pesata dei vari RTT calcolati per ogni pacchetto. Tramite il Sample RTT è possibile dare una stima dell'Estimated RTT al tempo t, definito come stima dell'RTT all'istante t, ed espresso come media pesata dell'Estimated RTT all'istante t-1 e del Sample RTT all'istante t:
EstimatedRTT(t) = (1 − x) * EstimatedRTT(t − 1) + x * SampleRTT(t)
dove x è un valore variabile tra 0 e 1. A questo punto il timeout del mittente è definito come l'Estimated RTT all'istante t a meno di una deviazione che rappresenta il margine massimo di errore.
[modifica] Buffering e prestazioni del protocollo
Il protocollo TCP, nella sua affidabilità, è però un protocollo relativamente lento di per sé. Questo perché quando un pacchetto è inviato da uno dei due host, il mittente non saprà con precisione quando questo pacchetto verrà ricevuto (ovvero quando il destinatario invierà l'ACK corrispondente), né il destinatario sa quando il mittente invierà un pacchetto. La soluzione implementata dagli sviluppatori del protocollo è stata quella di inserire tutti i dati da inviare da un host all'altro in un buffer, una piccola memoria in cui i pacchetti vengono memorizzati provvisoriamente e inoltrati solo quando richiesti. In tal modo è possibile inoltrare i pacchetti in modo consono al flusso di traffico sulla rete (i pacchetti sono memorizzati e vengono inoltrati solo quando richiesti, evitando quindi di intasare ulteriormente una rete trafficata).
Nel TCP è anche da prendere in considerazione il problema delle prestazioni. Il meccanismo stop-and-wait implementato da TCP per la gestione della comunicazione (invio del pacchetto e attesa dell'ACK da parte del ricevente prima di continuare la comunicazione) assicura sì affidabilità alla comunicazione, ma rende quest'ultima anche notevolmente meno prestante rispetto ad una comunicazione connection-less come può essere quella UDP. La soluzione adottata è stata quella dell'invio di pacchetti in pipeline. Una volta che un host può inviare dati su un canale non invia un solo pacchetto, ma ne invia più per volta, per poi rimanere in attesa dei relativi ACK, anch'essi inviati in pipeline, e così via. In tal modo le prestazioni della connessione crescono in maniera considerevole. Tuttavia, è necessario implementare un paio di cose per poter sfruttare questo meccanismo:
- Buffering dei pacchetti, sia lato mittente che destinatario, in modo da poter conservare da un lato i pacchetti inviati ma di cui non si è ancora ricevuto un ACK, dall'altro i pacchetti ricevuti, che non necessariamente sono stati recapitati in ordine
- Sliding window, ovvero un meccanismo che consenta ai due host di 'tastare il polso' alla rete e tener conto del suo grado di congestionamento, e in base ad esso regolare il flusso dei pacchetti inviati in pipeline.
[modifica] Sliding window
Il buffering dei paccheti consente un meccanismo in grado di prevedere all'interno del protocollo TCP un controllo del flusso dei dati. Questo meccanismo è quello della sliding windows, un meccanismo implementato all'interno di TCP in grado di far inviare o ricevere ai due host al massimo un tot di byte per volta, in funzione del grado di congestionamento della connessione. La finestra scorrevole identifica quali pacchetti sono da inviare, o da ricevere, in un dato istante di tempo. Per fare ciò il mittente considera 3 variabili durante l'invio di pacchetti:
- SWS (Sender Window Size): numero massimi di pacchetti che il mittente può inviare in contemporanea e/o memorizzare nel buffer TCP di invio, ovvero numero massimo di pacchetti inviabili in pipeline senza aver ancora ricevuto un ACK dal destinatario.
- LAR (Last Acknowledge Received): numero di sequenza dell'ultima conferma di ricezione ricevuta dal destinatario.
- LSS (Last Segment Sent): numero di sequenza dell'ultimo segmento inviato.
Queste variabili devono essere stabilite in modo che la differenza fra LAR e LSS sia sempre minore o uguale a SWS, ovvero non vengano mai inviati più segmenti contemporaneamente, senza aspettare conferma dal destinatario, di quanti sono consentiti dalla window size del mittente. Una volta che il destinatario invia un acknowledge per un pacchetto, il mittente incrementa di 1 LAR ed LSS e può inviare un nuovo segmento.
Dal lato del destinatario invece le variabili da considerare sono le seguenti:
- RWS (Receiver Window Size): numero massimo di pacchetti che il destinatario può ricevere in contemporanea nel suo buffer.
- LSR (Last Segment Received): sequence number dell'ultimo segmento ricevuto.
- LAS (Last Acceptable Segmento): sequence number dell'ultimo segmento accettabile.
Anche qui, si deve fare in modo che la differenza fra il numero di sequenza dell'ultimo segmento accettabile e quello dell'ultimo segmento ricevuto sia sempre minore o uguale dell'RWS, ovvero che non vadano a finire nel buffer di ricezione più segmenti di quanti ne può contenere. Se il destinatario riceve un segmento avente sequence number monore dell'LSR o maggiore dell'LAS, quest'ultimo viene scartato in quanto è al di fuori della finestra di ricezione del destinatario.
[modifica] Algoritmi per l'affidabilità
Il metodo della sliding window funziona quando tutto va bene durante la trasmissione. Quando qualcosa va male, ovvero quando non viene ricevuto un ACK entro il timeout, ci sono due strategie possibili da adottare per mantenere l'affidabilità della comunicazione.
- Go-Back N. Se entro il timeout il mittente non ha ricevuto l'acknowledge del segmento n, e intanto ha continuato l'invio, inviando segmenti fino al numero di sequenza m, allora provvede a re-inviare al destinatario tutti i segmenti da n a m. Il destinatario risponderà con un ACK cumulativo, inviando tanti segmenti di acknowledge quanti sono i segmenti ricevuti dal mittente.
- Ritrasmissione selettiva. Se entro il timeout il mittente non ha ricevuto l'acknowledge del segmento n, allora re-invia al destinatario solo il segmento n. Il destinatario invierà al mittente l'ACK solo per l'n-esimo segmento.
[modifica] Controllo del flusso
TCP consente, tramite l'invio in pipeling, il buffer in invio e ricezione e la sliding window, un controllo del flusso per il traffico, al fine di impedire un invio eccessivo di pacchetti a un destinatario che non può contenerli nel proprio buffer. Ciò è possibile in quanto il destinatario informa esplicitamente il mittente circa la dimensione dello spazio disponibile nel suo buffer di ricezione, e il mittente regola la sua sliding window in base a quella del destinatario (se il destinatario non ha più spazio nel buffer di ricezione indica al mittente una sliding window di dimensione nulla, e a quel punto la trasmissione si ferma e il mittente attende un aggiornamento della dimensione della sliding window prima di inviare nuovi segmenti). La dimensione della sliding window varia quindi dinamicamente durante una sessione TCP. [modifica] Controllo della congestione
TCP si preoccupa anche di inviare i pacchetti in maniera consona al livello di congestione della rete. Ciò è possibile valutando i segmenti smarriti, ovvero i segmenti per cui l'ACK non arriva entro il timeout previsto. In questa eventualità, vuol dire che la rete deve gestire più pacchetti di quanti le sue capacità fisiche ne consentano di gestire, e quindi sono inevitabili delle perdite. In tal caso, TCP considera come finestra effettiva, ovvero come numero effettivo di pacchetti inviabili e memorizzabili in pipeline, il valore minimo fra la dimensione della finestra concordata fra i due host e quello della finestra di congestione. Per valutare la dimensione della finestra di congestione, e quindi per valutare le prestazioni della rete, TCP procede nel seguente modo:
- All'inizio di una nuova connessione, o dopo un episodio di congestione, la dimensione della finestra è pari a 1
- La dimensione della finestra viene incrementata progressivamente (slow start del TCP)
- In caso di congestione, si riduce nuovamente la dimensione della finestra
Lo slow start può essere gestito da due diversi algoritmi.
[modifica] Algoritmo Tahoe
if not(pacchetto_perso) {
ogni w segmenti di ACK
incrementa di 1 la finestra di congestione
} else {
soglia=finestra di congestione/2
finestra di congestione=1
esegui nuovamente slow start
}
[modifica] Algoritmo Reno
until (pacchetto_perso) {
ogni w ACK ricevuti
incrementa di 1 la finestra di congestione
}
soglia=finestra di congestione/2
if (perdita causata da timeout) {
finestra di congestione=1
esegui slow start
}
if (perdita causata da 3 ACK duplicati ricevuti)
dimezza la finestra di congestione
Questo algoritmo, a differenza del Tahoe, differenzia le perdite dovute a un timeout da quelle dovute a un eccesso di ACK duplicati. Nel primo caso resetta la finestra di congestione a 1 come il Tahoe, nel secondo si limita a dimezzarla. Attualmente questo è l'algoritmo più diffuso per effettuare il controllo della congestione a livello TCP.
[modifica] Fairness del TCP
TCP, come visto, oltre a garantire l'affidabilità della connessione garantisce anche il controllo sulle risorse degli host e della rete. Oltre alle caratteristiche già esaminate, TCP evita anche la congestione dei link della rete (collo di bottiglia). Ciò avviene nel seguente modo: se N sessioni TCP condividono lo stesso link, allora ogni sessione deve usare 1/N-mo della capacità del link.



