TCP come abbiamo più volte detto è un servizio orientato alla connessione questo vuol dire che i due host prima di cominciare a scambiarsi dati devono effettuare l'handshake (una operazione che consiste nello scambio di alcuni segmenti particolari che consente ai sistemi periferici di stabilire la connessione TCP).
Gli elementi della rete (router e switch a livello di collegamento) non salvano lo stato della connessione, la connessione risiede nei sistemi periferici, gli elementi intermedi sono ignari che i pacchetti che viaggiano nella rete siano elementi di una connessione TCP, essi vedono solo dei datagrammi.
TCP offre un servizio full-duplex: pacchetti possono viaggiare da e verso il mittente/destinatario contemporaneamente.
TCP è una connessione di tipo punto a punto: ha luogo fra un singolo mittente e un singolo destinatario, il multicast (pacchetti che viaggiano da un mittente e più destinatari) in TCP non è fattibile.
clientSocket.connect((serverName, serverPort))
# dove serverName: nome del server
# serverPort: porta su cui opera il processo server
Quando un client contatta un server in TCP invia un pacchetto speciale, il server risponde con un pacchetto altrettanto speciale e infine il cliente invia una ulteriore conferma di aver ricevuto tale pacchetto, questa volta il pacchetto conterrà anche i dati della richiesta che il client vuole effettuare. Questa è la procedura di handshake che viene detto a tre vie (per il numero di scambio di pacchetti). I primi due pacchetti non contengono payload (dati), ma l'ultimo si.
Stabilita la connessione i due processi possono cominciare a scambiarsi dati.
Quando un processo vuole inviare dati su TCP:
Un pacchetto TCP è un insieme di dati (payload) e elementi di controllo del protocollo.
Se deve essere trasportato un dato con dimensione superiore a MSS, il dato viene suddiviso in più pacchetti. In altri casi possono anche essere inferiori della lunghezza di MSS.
Il campo header length indica la lunghezza dell’intestazione del pacchetto TCP: l’intestazione in TCP è variabile in termini di dimensione.
Campo flag:
Vediamo in dettaglio cosa fa il flag URG e in cosa è diverso da PSH.
Diciamo, per esempio, che il mittente ha necessità (livello di applicazione) di inviare dei dati urgenti. Quindi crea i dati e li invia al buffer TCP (livello di trasporto):
Si noti che il segmento transita in IP normalmente, non subisce alcun trattamento speciale.
Quando il segmento giunge nel buffer TCP del mittente, inoltre TCP informa l'applicazione destinataria che è appena arrivato un segmento urgente e gli viene consegnato. Ora spetta all'applicazione ricevente agire su tali dati urgenti.
Il flag URG è utile quando il mittente vuole interrompere un trasferimento che è avvenuto, per esempio per errore.
Il flag PSH comunica al livello di trasporto (del destinatario) che il segmento deve essere inviato immediatamente al livello superiore (applicazione).
TCP vede i dati come un flusso di byte ordinati.
Il numero di sequenza si riferisce al primo byte nel flusso di byte del segmento.
Supponiamo che debba essere inviato un file di 500.000 byte.
Diciamo che MSS valga 1000 byte e che il primo byte nel flusso sia numerato con 0.
Per questo flusso di dati TCP creerà 500 segmenti. Il primo avrà numero di sequenza 0, il secondo 1000, il terzo 2000 e così via.
Per i numeri di acknowledgment, dobbiamo ricordare che TCP è full-duplex e che i dati viaggiano in entrambe le direzioni.
Il numero di ACK che l'host A, scrive nei propri segmenti è il numero di sequenza del byte successivo che l'host A attende dall'host B.
Esempio: 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.
Come ulteriore esempio, 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.
Per qualche motivo l’Host A non ha ancora ricevuto i byte da 536 a 899.
In questo esempio, l’Host A sta ancora attendendo il byte 536 (e i successivi) per ricreare
il flusso di dati di B.
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).
L'RFC lascia la scelta a chi implementa il protocollo:
TCP come per rdt, usa un meccanismo di timeout e ritrasmissione per recuperare i pacchetti persi. Il timeout dovrebbe essere più grande di un RTT. Ma di quanto? E come dovrebbe essere stimato?
SampleRTT: il tempo che intercorre tra la consegna del segmento a IP e l'istante in cui arriva l'ACK relativo ad esso. Esso non viene calcolato per i pacchetti ritrasmessi e non viene calcolato per tutti i pacchetti, ma solo uno su dieci per esempio. Per ottenere una approssimazione dell'RTT, viene fatta una media di tutti gli RTT calcolati fino a quel momento.
Ad ogni nuovo SampleRTT si aggiorna la media (che TCP chiama EstimatedRTT):
EstimatedRTT è una media ponderata che attribuisce maggiore importante ai nuovi pacchetti rispetto che a quelli vecchi.
Oltre ad avere una stima di RTT, è anche importante avere una variazione: DevRTT, ovvero quanto SampleRTT si scosta da EstimatedRTT.
Se i SampleRTT variano poco, DevRTT sarà piccolo, altrimenti sarà grande.
Il timeout non deve essere tanto maggiore di EstimatedRTT, altrimenti TCP non ritrasmetterebbe velocemente il segmento perduto, ma neanche troppo inferiore rispetto EstimatedRTT, altrimenti andrebbero ritrasmessi pacchetti che non dovrebbero essere ritrasmessi.
Il timeout quindi viene impostato a EstimatedRTT incrementato di un certo margine.
TCP offre un trasferimento dati affidabile, sfruttando molte delle caratteristiche descritte in rdt. Avevamo accennato in rdt (che è un protocollo puramente teorico) che ogni segmento utilizza un timer, questo nella pratica sarebbe molto costoso, quindi TCP utilizza un singolo timer unico.
Descrizione del mittente TCP:
loop(forever){
event: data received from application above
create TCP segment with sequence number NextSeqNum
if(time currently not working)
start timer
pass segment to IP protocol
NextSeqNum = NextSeqNum + length(data)
event: timer timeout
retransmit not-yet-acknowledged segment with smallest
sequence number
start timer
event: ACK received, with ACK field value of y
if(y > SendBase){
SendBase = y;
if(there are currently not-yet-acknowledged segments)
start timer
}
}
Nel terzo evento (ACK received, with ACK filed value of y), avviene la ricezione di un ACK con il campo "numero di sequenza ACK" uguale a SendBase
(numero di sequenza del più vecchio byte che non ha ricevuto ACK, di conseguenza SendBase - 1
è l'ultimo numero di sequenza del segmento per cui si è ricevuto ACK). TCP marca i segmenti con numero di sequenza inferiore a SendBase
. Poi se ci sono segmenti che necessitano di ACK, riavvia il timer.
L'hos A ha inviato un segmento con numero di sequenza
L'host B lo riceve, ma l'ACK si perde.
Il timer di A scade e A ritrasmette il segmento con numero di sequenza
B ha già ricevuto quel pacchetto, lo scarta e invia nuovamente l'ACK.
L'host A invia i segmenti
L'host B invia gli ACK per quei pacchetti dopo il timeout.
Ad A scade il timer e rispedisce il segmento
Il secondo pacchetto viene rispedito solo se l'ACK del secondo segmento non arriva entro il nuovo timeout.
L'host A invia due pacchetti con numero di sequenza
L'host B invia gli ACK: l'ACK 100 si perde.
Prima dello scadere del timer, arriva l'ACK 120 al mittente che gli segnala che il pacchetto
Il periodo di timeout può rivelarsi relativamente lungo (per esempio quando un pacchetto si smarrisce ed impedisce al mittente di ritrasmettere il pacchetto subito). In molti casi il mittente può rilevare la perdita ancora prima che si verifichi il timeout grazie agli ACK duplicati relativi ad un segmento per cui è già stato ricevuto ACK dal mittente.
Tabella politica di generazione ACK.
Evento | Azione del destinatario TCP |
---|---|
Arrivo ordinato di un segmento con un numero di sequenza atteso. Tutti i dati fino al numero di sequenza atteso sono già stati riscontrati. | ACK ritardato. Attende fino a 500 millisecondi per l'arrivo ordinato di un altro segmento. Se in questo intervallo non arriva il successivo segmento, invia ACK. |
Arrivo ordinato di un segmento con un numero di sequenza atteso. Un altro segmento ordinato è in attesa di trasmissione dell'ACK. | Invia immediatamente un singolo ACK cumulativo, riscontrando entrambi i segmenti ordinati. |
Arrivo non ordinato di segmento con numero di sequenza superiore a quello atteso. Viene rilevato un buco. | Invia immediatamente un ACK duplicato, indicando il numero di sequenza del prossimo byte atteso (che è l'estremità inferiore del buco). |
Arrivo di segmento che colma parzialmente o interamente il buco. | Invia immediatamente un ACK, ammesso che il segmento comincia all'estremità inferiore del buco. |
Se il mittente riceve tre ACKs duplicati per lo stesso dato, considera questo evento come indice che il segmento che lo segue è andato perduto. In questo caso TCP effettua una ritrasmissione rapida del pacchetto prima che scada il timer.
Come si vede nella figura avviene la perdita di un pacchetto.
Il pacchetto precedente e quelli successivi al pacchetto perduto arrivano a destinazione. Il destinatario invia ACK duplicati per il segmento con numero di sequenza
I segmenti successivi, vengono salvati nel buffer del destinatario.
Ricordiamo che gli acknowledgment sono cumulativi, e che i segmenti ricevuti correttamente, ma in modo disordinato, non vengono notificati singolarmente dal destinatario, ovvero se un destinatario TCP riceve i pacchetti 1, 2, 5, salverà 5 nel buffer e solo quando riceverà 3 e 4, invierà ACK per 5. Mentre l'ACK per 2 verrà inviato subito. Quindi il mittente TCP deve memorizzare solo il numero di sequenza più basso tra i byte trasmessi che non hanno ancora ricevuto ACK (SendBase
) e il numero di sequenza del successivo byte da inviare (nextseqnum
). In questo contesto TCP somiglia molto a Go-Back-N. Ma ci sono delle differenze.
Esempio: supponiamo che il mittente invii una sequenza di segmenti da 1 a N che arrivano al destinatario in ordine e senza errori. Ipotizziamo che il pacchetto con numero
In questo caso GBN re-invierebbe non solo il segmento per cui è stato perso l'ACK, ma anche tutti gli altri a partire da
Una modifica proposta di TCP, il cosiddetto riscontro selettivo, consente al ricevente di inviare ACK non cumulativamente, quindi pacchetti ricevuti in disordine il destinatario invia ACK non cumulativo, ma selettivo. Se combinato con la ritrasmissione selettiva (ovvero evitando la ritrasmissione dei segmenti per cui si è già ricevuto un ACK in modo selettivo da destinatario), TCP è molto simile a un generico protocollo SR. Dunque è opportuno classificare il meccanismo di ripristino degli errori di TCP come un ibrido tra SR e GBN.
Gli estremi delle connessioni TCP hanno dei buffer per i segmenti in arrivo. Quando la connessione TCP riceve byte corretti e in sequenza, li posiziona nel buffer. Il processo applicativo legge i dati da questo buffer, ma non necessariamente nell'istante in cui arrivano. Il processo potrebbe ricevente potrebbe leggere i dati solo molto tempo dopo il loro arrivo. Nel caso in cui il processo ricevente è relativamente lento ad eseguire altre operazioni, il mittente potrebbe inviare più pacchetti di quanto il buffer ricevente venga svuotato dal processo ricevente. TCP offre un controllo del flusso alle applicazioni per evitare che il mittente saturi il buffer del ricevente.
Il controllo del flusso è un servizio di confronto sulla velocità, dato che paragona la frequenza di invio del mittente con quella di lettura dell'applicazione ricevente.
Il controllo della congestione riguarda invece la rete IP (livello di rete), che vedremo successivamente.
Parliamo del controllo del flusso e, per il momento, supponiamo che il ricevente TCP scarti i segmenti non in ordine. TCP mantiene una variabile chiamata finestra di ricezione (receive window) che fornisce al mittente un'indicazione sullo spazio libero nel buffer del ricevente.
Supponiamo che l'host A voglia inviare un file di grandi dimensioni all'host B tramite una connessione TCP. L'host B alloca un buffer, la cui dimensione è rcvBuffer
. Definiamo le seguenti variabili:
lastByteRead
: numero dell'ultimo byte nel flusso di dati che il processo applicativo in B ha letto dal buffer.lastByteRcvd
: numero dell'ultimo byte nel flusso di dati che proviene dalla rete e che è stato copiato nel buffer di ricezione di B.rwnd
) viene impostata alla quantità di spazio disponibile nel buffer:rwnd
è dinamica, dato che lo spazio varia col tempo. B comunica ad A quanto spazio disponibile è presente nel buffer della connessione, scrivendo il valore corrente di rwnd
nel campo apposito dei segmenti che manda ad A.rwnd
con il valore di rcvBuffer
e deve tenere traccia delle variabili specifiche per ogni connessione.lastByteSent
e lastByteAcked
.In questo schema esiste un problema tecnico secondario, per accorgercene supponiamo che il buffer di ricezione dell'host B sia pieno, quindi rwnd = 0
e che dopo averlo notificato all'host A, non abbia più nulla da inviare ad A e vediamo cosa succede.
Quando B svuoterà il buffer, TCP non invia nuovi segmenti con il valore aggiornato di rwnd
(che si è svuotato). TCP fa pervenire segmenti ad A solo se B ha dati o ACK da mandare ad A. Di conseguenza l'host A non viene informato del fatto che sul buffer di B si è liberato dello spazio. A è bloccato e non invia dati. Per questo motivo le specifiche TCP impongono che il mittente continui ad inviare pacchetti al destinatario per dargli modo di rispondere. Prima o poi rwnd
non sarà più nullo e B dovrà avere modo di farlo sapere ad A.
UDP non offre controllo del flusso, nel caso di UDP, semplicemente alcuni pacchetti andranno persi a causa di overflow nel buffer del destinatario.
Quando un processo client chiede ad un processo server di stabilire una connessione TCP, la connessione avviene attraverso i seguenti passi:
client_isn
) e lo pone nel campo numero di sequenza. Questo viene fatto al fine di evitare attacchi alla sicurezza.client_isn + 1
e genera un numero di sequenza iniziale server_isn
e lo comunica al client. Tale segmento viene detto di SYNACK.server_isn + 1
nel campo ACK. SYN è posto a 0. Il campo dati può contenere dati.Se un client invia una connessione su una porta su cui il server non è in ascolto il server restituisce un segmento speciale con bit RST.nmap
, un tool per analizzare le porte in ascolto su un host, come funziona? Invia segmenti TCP SYN, i possibili risultati sono tre:
nmap
restituisce "open"Letteralmente inondazione di SYN.
Abbiamo visto come funzione l'handshake. Se l'ultimo segmento non viene inviato dal client, dopo un certo periodo di tempo (1 minuto circa), la connessione viene chiusa dal server.
Questo pone le basi per un attacco SYN FLOOD di tipo DoS. Chi attacca manda un gran numero di segmenti di SYN senza completare il terzo passo della procedura di handshake. Se partecipano più host l'attacco diventa di tipo distribuito (DDoS).
Una contromisura efficace è il SYN cookie.
Vediamo tre scenari via via più complessi. Non presenteremo subito una soluzione alla congestione, ma cercheremo di capire cosa succede quando gli host aumentano il loro tasso trasmissivo e le reti si congestionano.
Il router condiviso ha buffer illimitato.
Il collegamento uscente (verso la i server sulla destra) sul router ha capacità
Il buffer del router ha una capacità (illimitata) tale da consentirgli di memorizzare i segmenti con la velocità con cui arrivano nel router, anche quando la velocità sul collegamento entrante supera quella uscente.
Queste immagini mostrano le prestazioni della connessione per uno dei due host.
In questo primo scenario vengono illustrate le prestazioni per l'host A.
L'immagine di sinistra mostra il throughput per connessione (la velocità con cui ricevono i destinatari) in funzione del tasso di invio. Vediamo che al crescere del tasso di invio, cresce anche il tasso di ricezione.
Essendoci due mittenti, la capacità del collegamento è divisa tra di essi (
Finché il tasso di invio non supera il valore di
Ma se il tasso di invio supera
Il collegamento, semplicemente, non è in grado di consegnare pacchetti al destinatario a un tasso superiore a
Per quanto elevata sia la velocità di invio da parte degli host, né A né B avranno mai un throughput superiore a
Il grafico a destra, mostra le conseguenze di operare al limite della capacità del collegamento.
Quando la velocità di invio si avvicina a
Quando supera
Di conseguenza, avere un throughput aggregato vicino a
Il router condiviso ha buffer limitato: i segmenti che arrivano quando il buffer è pieno vengono scartati.
Adesso il buffer è limitato. I pacchetti che arrivano quando il buffer è pieno vengono scartati. Si suppone che il livello di trasporto sia affidabile, ovvero che i pacchetti scartati vengano prima o poi ritrasmessi. Le prestazioni dipendono da come vengono effettuate le ritrasmissioni. Distinguiamo dei casi:
Grafico (a)
Grafico (b)
Grafico (c)
In questo caso supponiamo che i pacchetti siano trasmessi da 4 host, ciascuno su percorsi composti da due collegamenti sovrapposti tra loro. Ciascun host, inoltre, utilizza un meccanismo di timeout e ritrasmissione per implementare il servizio di trasporto affidabile.
Tutti e quattro hanno lo stesso valore di
La capacità dei collegamenti dei router è
Consideriamo la connessione dall'host A all'host C che passa per i router
Il router
Il router
Per valori estremamente piccoli di
Per valori leggermente più grandi, il throughput è anch'esso più grande, gli overflow rimangono ancora rari. Di conseguenza per piccoli valori di
Esaminata la situazione in cui il traffico è estremamente scarso, passiamo a considerare il caso in cui
Prendiamo
Il traffico (da A a C, linea azzurra) che giunge a
Se
Dato che su
Praticamente ogni volta che un pacchetto nel secondo hop (salto, router) viene scartato, il lavoro fatto dal primo router per inviare quel pacchetto viene sprecato. Di fatti la rete avrebbe funzionato allo stesso modo, se il primo router non avesse spedito quel pacchetto che il secondo avrebbe rifiutato. La capacità trasmissiva sprecata potrebbe essere utilizzata in modo più proficuo. Sarebbe utile per esempio che il router desse priorità ai pacchetti che hanno già superato un certo numero di router.
Identifichiamo i principali orientamenti al controllo della congestione:
Navigazione: