Il livello Trasporto.

Bibliografia:
Computer Networking. Cap. 3. Autori: James F. Kurose (University of Massachusetts, Amherst) Keith W. Ross (Polytechnic Institute of NYU).

Trasporto orientato alla connessione: TCP

Il TCP è il protocollo del livello Trasporto orientato alla connessione e affidabile, implementato in Internet.

Il TCP, come l'UDP, fornisce il multiplexing, il demultiplexing, e il riconoscimento, ma non la correzione, degli errori (i pacchetti errati vengono ritrasmessi), In più utilizza i timer, le conferme cumulative e i numeri di sequenza. L'UDP non è connesso. Il TCP è orientato alla connessione perchè prima che un processo applicativo possa spedire i dati deve avvenire una fase di sincronizzazione (apertura connessione) tra i due processi del livello trasporto residenti sui sistemi terminali che ospitano le applicazioni. Questa fase consiste in uno scambio di segmenti, che ha lo scopo di inizializzare alcune variabili che devono rappresentare lo stato della comunicazione. Queste variabili di stato costituiscono la connessione.

La connessione TCP non è nè un collegamento fisico tra due sistemi terminali, nè un circuito virtuale individuato da una successione di punti di attraversamento dei messaggi determinata dal pacchetto di chiamata. Lo stato della connessione risiede solo nei due sistemi terminali, i sistemi intermedi non mantengono alcuna relazione tra i pacchetti di una stessa comunicazione. I sistemi intermedi vedono datagrammi, non connessioni.

Il trasferimento dati del TCP è è un servizio full duplex. Cioè i due processi del livello applicazione, residenti sui sistemi terminali, possono scambiarsi pacchetti in entrambe le direzioni. Se c'è una connessione TCP tra un processo A su un host con un processo B su un altro host, allora i messaggi possono viaggiare contemporaneamente da A a B e, viceversa, da B ad A. Una connessione TCP è sempre point-to-point, cioè tra un solo processo mittente e un solo processo destinatario. Il broadcasting, cioè un trasmettitore e molti ricevitori, non è consentito con il TCP.

Per comprendere come avviene la fase di apertura connessione, si supponga che un processo in esecuzione su un host voglia stabilire una connessione con un altro processo residente su un altro host. L'host che emette la richiesta di apertura della connessione è il client, mentre l'altro è il server. Il processo applicativo client chiede al TCP di stabilire una connessione con un altro processo applicativo server. In C questa operazione è realizzata con il costruttore della classe socket.

Socket clientSocket = new Socket("hostname", "port number");

Dove "hostname" è il nome del computer server o il suo indirizzo IP e "port number" è il numero di porta su cui il processo server è in attesa di richieste di apertura connessione.

La fase di apertura connessione verrà esaminata in seguito, per adesso basta sapere che il client invia un pacchetto di servizio speciale per chiedere l'apertura della connessione, il server risponde con un altro pacchetto speciale che sta ad indicare che la richiesta è stata accettata ed infine il client spedisce un pacchetto di conferma. I primi due pacchetti non portano dati provenienti dal livello applicazione, mentre il terzo potrebbe contenere alcune informazioni. Questa fase di apertura della connessione è anche denominata three-way handshake.

Dopo aver stabilito la connessione, i due processi applicativi possono scambiarsi messaggi, anche in full-duplex. Si consideri l'invio di dati dal processo client al processo server. Il processo client passa un flusso di dati attraverso il socket (la porta del processo). Attraverso questa porta i dati giungono sotto il controllo del processo TCP client. Il TCP inserisce questi dati nel buffer di trasmissione della connessione, che è stato creato durante la fase di apertura. Il TCP deve prelevare blocchi di dati dal buffer e creare i segmenti da consegnare al processo del livello rete. La massima quantità di dati che si può inserire in un segmento è determinata da un parametro, Maximum Segment Size (MSS). La MSS dipende dall'implementazione del TCP (determinata dal sistema operativo) valori tipici sono 1,500 byte, 536 byte e 512 byte. Il parametro MSS è scelto dopo aver determinato la massima dimensione di una trama di livello collegamento che l'host mittente può generare, la cosiddetta maximum transmission unit (MTU). Questa trama dovrà contenere il segmento TCP incapsulato in un pacchetto di livello Rete e l'header di livello collegamento (40 byte). Notare che il nome MSS non è appropriato, perchè si riferisce alla massima quantità di dati del livello applicazione, non la massima dimensione del segmento TCP compreso l'header.

Il TCP aggiunge una intestazione ad ogni blocco di dati per formare i segmenti TCP. I segmenti vengono passati al livello rete dove, ciascuno di essi, viene incapsulato in un pacchetto IP. I pacchetti IP vengono inviati nella rete. Quando il processo TCP destinatario riceve un segmento, i dati contenuti nel segmento vengono memorizzati nel buffer di ricezione della connessione TCP. Il processo del livello applicazione legge il flusso dei dati da questo buffer. Dal lato del trasmettitore esiste il buffer di trasmissione e dal lato del ricevitore esiste il buffer di ricezione.

Riepilogando, una connessione TCP consiste di buffer, variabili e di un socket collegato ad un processo in un host e di buffer, variabili ed un socket collegato ad un processo nell'altro host.

I sistemi intermedi non mantengono nessuna variabile e nessun buffer per memorizzare la connessione tra i due host.

Struttura del segmento TCP

Un segmento consiste di due parti: l'header, contenente alcuni campi di servizio, e i dati. Nel campo dati c'è un blocco del messaggio proveniente dal processo applicativo. Il campo dati ha una dimensione fissata dal MSS (massima dimensione di un segmento). Quando il TCP deve inviare un file di grande dimensione, lo frammenta in blocchi di lunghezza MSS (tranne l'ultimo, che ha dimensione minore). Alle applicazioni interattive bastano blocchi di dati di dimensione minore di MSS; ad esempio il campo dati di un messaggio di login come Telnet è molto corto. Poichè l'header TCP è lunga 20 byte (12 byte più grande dell'header UDP), i segmenti inviati da Telnet potrebbero essere lunghi 21 byte.

struttura del segmento TCP

← 32 bit →

numero porta sorgente

numero porta destinazione

Numero di sequenza

Numero di conferma

header
length

Flag: URG - ACK - PSH - RST - SYN - FIN

Finestra del ricevitore

Checksum

Urgent data pointer

 
Opzioni

 
Dati

In comune con l'UDP, l'header del TCP comprende il campo checksum e i campi numeri di porta sorgente e destinazione, che servono per le operazioni di multiplexing/demultiplexing dei dati scambiati con i processi del livello applicazione. Nell'header di un segmento TCP sono contenuti anche:

Nella pratica i bit PSH e URG non sono usati, ma sono stati menzionati, perchè sono previsti nel formato del pacchetto TCP.

Numeri di sequenza e numeri di conferma

Il TCP vede i dati come un flusso ordinato di byte non strutturati. I numeri di sequenza, infatti, si riferiscono alla numerazione dei byte del flusso dei dati trasmessi, non si riferiscono alla numerazione dei segmenti. Il numero di sequenza di un segmento è il numero d'ordine del primo byte nel segmento. Si supponga che un processo nell'host A vuole inviare un messaggio ad un processo sull'host B su una connessione TCP. Il TCP nell'host A implicitamente numera tutti i byte del messaggio. Se il messaggio è un file da 500.000 byte, e se l'MSS è 1000 byte, assumendo che il primo byte abbia numero 0, il TCP costruisce 500 segmenti. Al primo segmento viene assegnato il numero di sequenza 0, al secondo segmento viene assegnato il numero di sequenza 1000, al terzo il numero di sequenza 2000, e così via. Ciascun numero di sequenza viene inserito nel campo Numero di Sequenza dell'header del segmento.

Scomposizione del file in segmenti TCP

Primo Segmento

Secondo Segmento

500-mo Segmento

0 1 1000 1999 499.000 499.999

Si ricordi che il TCP è un protocollo full-duplex, cioè l'host A può trovarsi a ricevere dati dall'host B mentre sta inviando dati all'host B (sulla stessa connessione). I segmenti che giungono dall'host B contengono i numeri di sequenza relativi al flusso da B ad A. Il numero di conferma che l'host A inserisce nel segmento è il numero di sequenza del prossimo byte che l'host A sta aspettando di ricevere dall'host B. Si supponga che l'host A abbia ricevuto dall'host B tutti i byte numerati da 0 a 535 e che sia in procinto di inviare un segmento all'host B. L'host A aspetta di ricevere dall'host B il byte 536 e i suoi successivi. L'host A inserisce 536 nel campo acknowledgment (Numero di conferma) del segmento diretto a B.

Come altro esempio si supponga che l'host A abbia ricevuto un segmento dall'host B contenente i byte da 0 a 535 e poi un altro segmento contenente i byte da 900 a 1000. L'host A non ha ancora ricevuto i byte da 536 a 899. In questo caso l'host A, per poter ricostruire il messaggio proveniente da B, è ancora in attesa del byte 536 (e dei successivi). Quindi il prossimo segmento che A invierà a B conterrà il numero 536 nel campo acknowledgment (Numero di Conferma). Poichè TCP conferma solo i byte fino al primo che manca, si dice che TCP adopera le conferme cumulative.

Su quest'ultimo problema si possono fare alcune importanti considerazioni. L'host A ha ricevuto il terzo segmento (i byte da 900 a 1000) prima di ricevere il secondo (i byte da 536 a 899). Il terzo segmento è arrivato fuori ordine. Il comportamento del TCP in una simile situazione potrebbe essere: (i) il ricevitore scarta i byte fuori ordine; oppure (ii) il ricevitore memorizza i byte fuori ordine e aspetta che gli arrivino i mancanti per inserirli tra quelli già ricevuti. Quest'ultima soluzione è più efficiente perchè evita che il canale venga occupato con pacchetti ripetuti. La prima scelta semplificherebbe la codifica del TCP, ma assume che il TCP ricevitore scarti i segmenti fuori ordine.

Nell'esempio si è assunto che il primo numero di sequenza sia 0. Nella pratica entrambi i lati della connessione TCP scelgono a caso il primo numero di sequenza. Questo allo scopo di ridurre la possibilità che un segmento ancora memorizzato in una apparecchiatura di rete, in attesa di essere smistato, e appartenente a una connessione tra due host già terminata, venga acquisito erroneamente in una nuova connessione tra gli stessi due host (usano anche lo stesso numero di porta della connessione precedente).

Telnet: Esempio di uso dei numeri di sequenza e dei numeri di conferma.

Telnet è un protocollo del livello applicazione usato per richiedere il servizio di terminale virtuale. Si appoggia sul TCP ed è progettato per funzionare tra qualsiasi coppia di host. Telnet è un'applicazione interattiva. Si supponga che l'host 88.88.88.88, apra una sessione Telnet con l'host 99.99.99.99. L'host 88.88.88.88 che inizia la sessione, è il client e l'host 99.99.99.99 è il server. Ogni carattere scritto dall'utente (sul client) verrà inviato all'host remoto; l'host remoto restituirà una copia di ciascun carattere ricevuto, per mostrarlo sullo schermo del client, allo scopo di assicurare che il carattere mostrato è stato ricevuto ed elaborato dall'host remoto. Quindi, ogni carattere attraversa due volte il canale.

Ad esempio, l'utente digita il carattere 'C'. Si supponga che il numero di sequenza iniziale del client sia 42 e il numero di sequenza iniziale del server sia 79. Si ricordi che il numero di sequenza di un segmento è il numero d'ordine del primo byte contenuto nel campo dati. Quindi il primo segmento inviato dal client al server avrà numero di sequenza 42; il primo segmento inviato dal server avrà numero di sequenza 79. Si ricordi anche che il numero della conferma è il numero del prossimo byte atteso. Dopo che la connessione è stata stabilita, ma prima che i dati vengano inviati, il client è in attesa del byte numero 79. e il server è in attesa del byte numero 42.

Numeri di sequenza e numeri di conferma durante una sessione Telnet su una connessione TCP

La Figura mostra che vengono trasmessi tre segmenti. Il primo segmento è inviato dal client al server, e contiene la rappresentazione ASCII della lettera 'C' nel campo dati. Questo primo segmento contiene 42 nel suo campo numero di sequenza. Inoltre, siccome il client non ha ancora ricevuto dati dal server, in questo primo segmento il valore del campo Numero di Conferma è 79.

Il secondo segmento è inviato dal server al client. Ha un duplice scopo. Fornisce la conferma al client che il server ha ricevuto i dati. Inserendo 43 nel campo acknowledgment (Numero di Conferma), il server sta comunicando al client che ha ricevuto correttamente i dati fino al byte 42 e sta aspettando i byte a iniziare dal 43. Il secondo scopo di questo segmento è di ripetere la lettera 'C'. Il campo dati del secondo segmento contiene 'C'. Il numero di sequenza del secondo segmento è 79. infatti questo è il primo byte che sta inviando il server. Notare che le conferme dei dati dal client al server sono contenute in un segmento che porta anche i dati dal server al client;

Il terzo segmento è inviato dal client al server. Ha solo lo scopo di confermare il dato ricevuto dal server. Il campo dati di questo segmento è vuoto quindi il client non aspetta di ricevere la conferma. Il campo acknowledgment number (Numero di Conferma) del segmento contiene 80, perchè il client ha ricevuto correttamente tutti i dati fino al byte numero 79 e adesso aspetta i prossimi byte a iniziare dal numero 80. Il campo numero di sequenza è presente anche se il segmento non contiene dati.

Stima della durata del timer.

Il TCP usa il timeout generato dal timer per decidere la ritrasmissione di un pacchetto che si ritiene perso. Dopo avere inviato un pacchetto, il trasmettitore resta in attesa del pacchetto di conferma. La scelta della durata del tempo di attesa dipende da due considerazioni. L'intervallo di attesa deve essere di poco maggiore del round-trip time (RTT), cioè il tempo trascorso dal momento in cui viene inviato un segmento al momento in cui viene ricevuta la conferma. Se l'attesa fosse di durata minore si avrebbero delle ritrasmissioni inutili. La soluzione del problema richiede il calcolo dell'RTT. Inoltre ci si chiede se è opportuno avviare un timer per ogni segmento non confermato.

Stima del Round-Trip Time

Per comprendere il modo in cui il TCP gestisce il timer si consideri da cosa dipende il round-trip time tra il mittente e il ricevitore. L'RTT campione, denotato SampleRTT, di un segmento è il tempo trascorso dal momento in cui il segmento viene trasmesso (cioè consegnato al livello IP) al momento in cui si riceve un segmento di conferma (ACK) per quel segmento. Il TCP prende una misura del SampleRTT di un segmento trasmesso e non ancora confermato. Questa misura viene, quindi, effettuata ogni RTT secondi. Inoltre il TCP non calcola il SampleRTT di un segmento che è stato ritrasmesso. Il valore del SampleRTT varierà da un segmento al successivo, a causa del traffico irregolare presente in rete, quindi la stima attendibile dell'RTT è il valore medio dei valori SampleRTT misurati. Questo valore medio dei SampleRTT, mantenuto dal TCP, è chiamata EstimatedRTT. Per ogni nuovo SampleRTT, il TCP aggiorna la media EstimatedRTT secondo la seguente formula:

    EstimatedRTT = (1-α)·EstimatedRTT + α·SampleRTT

La formula è scritta secondo lo stile delle espressioni usate in un linguaggio di programmazione: Il nuovo valore di EstimatedRTT è la media ponderata tra il valore precedente di EstimatedRTT ed il nuovo valore di SampleRTT. Il valore raccommandato di α è 0.125 (cioè 1/8), di conseguenza la formula diventa:

    EstimatedRTT = 0.875·EstimatedRTT + 0.125·SampleRTT

EstimatedRTT è la media ponderata dei valori SampleRTT, in cui viene dato un peso maggiore alla misura più recente, che indica l'intensità del traffico corrente. Accanto all'RTT medio, occorre valutare l'intervallo di variabilità di sampleRTT, cioè il minimo ed il massimo valore osservato. In statistica si usa calcolare la deviazione standard di un insieme di misure. In questo caso la deviazione standard delle misure del sampleRTT è:

    DevRTT = (1-β)·DevRTT + β· (SampleRTT - EstimatedRTT)

Se SampleRTT varia di poco dal suo valore medio EstimatedRTT la deviazione standard sarà piccola. Il valore consigliato per β è 0.25.

Calcolo dell'intervallo di Timeout

Con il valore medio EstimatedRTT e con la deviazione standard DevRTT, si può scegliere il valore da assegnare al timer per segnalare il timeout. La durata di questo intervallo è ottenuto da un compromesso tra due situazioni estreme. Dovrebbe essere maggiore o uguale di EstimatedRTT, per evitare inutili ritrasmissioni di pacchetti. Ma il timer non dovrebbe essere impostato ad un valore troppo più alto di EstimatedRTT per non introdurre un ritardo eccessivo quando un pacchetto realmente si perde. Il timer dovrebbe essre inizializzato con EstimatedRTT più un certo margine. Il margine dovrebbe essere grande quando le oscillazioni dei valori SampleRTT sono grandi e dovrebbe essere piccolo quando le variazioni di sampleRTT sono piccole. Il metodo per calcolare la durata del timer è:

    TimeoutInterval = EstimatedRTT + 4·DevRTT

Il valore iniziale di TimeoutInterval è 1 secondo. Poi, quando si verifica un timeout, il valore di TimeoutInterval è raddoppiato per evitare un nuovo timeout sul successivo segmento che dovrà essere confermato. Comunque, appena viene ricevuto un segmento si aggiorna EstimatedRTT, e il TimeoutInterval viene ricalcolato.

Trasferimento dati affidabile

Si ricordi che il servizio del livello rete (IP) non è affidabile. IP non garantisce la consegna dei datagrammi, non assicura che questi vengano consegnati in ordine e non offre la certezza che i dati contenuti nel datagramma siano privi di errori. I datagrammi IP transitano nei sistemi intermedi, dove vengono memorizzati in attesa di essere smistati, e potrebbero perdersi nella rete oppure arrivare con ritardi diversi. Poichè il livello trasporto affida al livello rete, i segmenti da consegnare al destinatario, i segmenti possono subire questi problemi.

Il TCP crea un servizio di trasferimento di dati affidabile appoggiandosi sul servizio di tipo best-effort (minimo costo) non affidabile di IP. Per servizio affidabile si intende che il processo ricevitore legge dal buffer del TCP un flusso di byte identico al flusso di byte trasmessi, cioè privo di errori, senza byte mancanti, senza ripetizioni e nello stesso ordine con cui sono partiti.

Le tecniche per realizzare un protocollo affidabile, richiedono l'impiego di un timer associato ad ogni segmento trasmesso, del quale si aspetta la conferma. Poichè la gestione dei timer introduce un sovraccarico per il protocollo, si preferisce usare un solo timer anche se il trasmettitore invia più segmenti. Il servizio affidabile offerto dal TCP verrà descritto in due fasi. Prima si illustra un protocollo semplificato che usa un timer per ritrasmettere i segmenti persi, poi si introduce una versione completa che, oltre al timer, usa anche le conferme duplicate. Si ipotizza che la trasmissione avvenga in una sola direzione, dall'host A all'host B, e che l'host A debba inviare un file di grandi dimensioni all'host B.

Il programma che segue mostra una descrizione semplificata del comportamento del trasmettitore TCP. Ci sono tre eventi che si possono verificare durante la trasmissione e la ritrasmissione: dati ricevuti dal processo del livello applicazione, timeout e ricezione di un ACK. Quando si verifica il primo evento, il TCP riceve i dati dal processo del livello applicazione, li incapsula in un segmento e consegna il segmento al livello IP. Ogni segmento contiene un numero di sequenza che è il numero d'ordine del byte nel flusso dei dati. Inoltre, se il timer non è già stato avviato da un segmento precedentemente trasmesso, il TCP avvia il timer nel momento in cui consegna il segmento al processo del livello rete. Si deve notare che il timer è associato al più vecchio segmento trasmesso e non ancora confermato. La durata del timer è stabilita secondo le modalità di calcolo del TimeoutInterval, che dipende dai valori EstimatedRTT e DevRTT.

/* si assume che il mittente non applica il controllo del flusso o della congestione, che la lunghezza di un segmento sia minore di MSS, e che il trasferimento dati avvenga in una sola direzione */ 
sendbase = nextseqnum = numero di sequenza iniziale
loop (forever) { 
  switch(event) 
    event: ricezione dati dal processo del livello superiore 
      crea un segmento TCP con numero di sequenza nextseqnum
      avvia il timer per il segmento nextseqnum 
      passa il segmento a IP 
      nextseqnum = nextseqnum + length(data) 
    event: timeout per il segmento con numero di sequenza y
      ritrasmetti il segmento con numero di sequenza y
      calcola un nuovo intervallo del timer per il segmento y
      riavvia il timer per il numero di sequenza y
    event: ricevuto ACK, con conferma relativa a y 
      if (y > sendbase) {/*conferma cumulativa di tutti i dati fino a y */
        elimina i timer relativi ai segmenti con numero di sequenza < y 
        sendbase = y
      } else { /* la conferma è un duplicato */ 
        incrementa il numero conferme ripetute di y
        if (numero di conferme ripetute per y == 3) { 
          /* TCP ritrasmette */ 
          ripeti il segmento con numero di sequenza y
          riavvia il timer per il segmento y 
        }
      } /* fine del ciclo forever */ 

Il secondo gestore di evento viene richiamato quando si verifica il timeout. Il TCP, in questo caso, ristrasmette il segmento per il quale era stato avviato il timer e riavvia il timer.

Il terzo evento gestito dal TCP mittente è l'arrivo di un pacchetto ACK inviato dal ricevitore. Più esattamente, viene ricevuto un segmento contenente un valore valido nel campo "Numero di Conferma". Al verificarsi di questo evento il TCP confronta il valore y della conferma con il valore SendBase. La variabile di stato SendBase è il numero di sequenza del più vecchio byte non confermato (SendBase-1 è il numero di sequenza dell'ultimo byte di cui si sa che è stato ricevuto correttamente ed in ordine). Siccome il TCP usa le conferme cumulative, il valore di y indica che tutti i byte che precedono quello in posizione y sono stati ricevuti correttamente. Se y è maggiore di SendBase, allora il segmento ACK sta confermando uno o più segmenti non confermati e il mittente aggiorna la sua variabile di stato SendBase. Inoltre il mittente avvia nuovamente il timer se ci sono altri segmenti in attesa di conferma.

Per comprendere il comportamento del mittente in presenza di un ACK duplicato, si deve trovare la ragione per cui il destinatario ha inviato più di una conferma per lo stesso segmento. La tabella che segue riepiloga la politica di generazione degli ACK del ricevitore. Quando il ricevitore TCP acquisisce un segmento con un numero di sequenza maggiore di quello che si aspetta di ricevere, individua un segmento mancante nel flusso dei dati. Siccome il TCP non usa le conferme negative, il destinatario non può indicare esplicitamente quale segmento non ha ricevuto. Il ricevitore si limita a riconfermare l'ultimo byte dei dati ricevuto in ordine. Se il TCP mittente riceve tre ACK ripetuti per lo stesso segmento, assume che il segmento che segue quello confermato è stato smarrito nella rete. In questo caso il TCP ritrasmette il segmento mancante prima che il timer scada.

Casi particolari.

Ritrasmissione in conseguenza della perdita della conferma

La Figura mostra il caso di un host A che invia un segmento all'host B. Si supponga che questo segmento abbia numero di sequenza 92 e contenga 8 byte di dati. Dopo aver inviato il segmento, l'host A passa in attesa di ricevere un segmento da B contenente il valore 100 nel campo "Numero di Conferma". Il segmento dati inviato da A viene ricevuto da B, ma la conferma si danneggia. In questo caso si verifica il timeout e l'host A ritrasmette lo stesso segmento. Naturalmente, quando l'host B riceve il segmento ripetuto il TCP si accorge che quei dati sono stati già ricevuti e scarta il segmento.

Nel secondo caso, l'host A spedisce due segmenti uno di seguito all'altro ed avvia un timer per il primo segmento. Il primo segmento ha numero di sequenza 92 e contiene 8 byte nel campo dati, il secondo segmento ha numero di sequenza 100 e contiene 20 byte nel campo dati. Entrambi i segmenti arrivano integri a B, che quindi invia due conferme separate. Nel campo "Numero di Conferma", il primo ACK contiene 100; il secondo contiene 120. Si supponga che, quando scade il timeout del primo segmento, l'host A non ha ricevuto nessuna delle due conferme. L'host A ritrasmette il primo segmento con numero di sequenza 92 e riavvia il timer. L'host A ritrasmetterà il secondo segmento solo se verificherà il nuovo timeout e non è arrivata la conferma con numero 120. Nell'esempio, si suppone che l'ACK del secondo segmento arrivi prima che scada il timeout e quindi non viene ritrasmesso.

La conferma cumulativa evita la ritrasmissione del primo segmento

Nel terzo caso, l'host A invia due segmenti come nel caso precedente. La conferma del primo segmento si perde nella rete, ma prima che scada il timeout del primo segmento giunge la conferma contenente il numero 120. L'host A quindi sa che entrambi i segmenti sono giunti a destinazione e non ripete nessuno di essi.

Raddoppiare l'intervallo di timeout

Una prima modifica al comportamento del TCP consiste nel regolare la durata dell'interval timer osservando gli eventi timeout. Come visto, ogni volta che si verifica un timeout, il TCP ritrasmette il segmento non ancora confermato contenente il più piccolo numero di sequenza. Ma ad ogni ritrasmissione, il TCP raddoppia la durata dell'interval timer, anzichè calcolarlo sulla base dei valori medi e della deviazione standard dell'RTT. Ad esempio, se il TimeoutInterval associato al segmento più vecchio, non ancora confermato, è 0.75 sec, quando il TCP ritrasmette questo segmento, reimposta l'interval timer al valore 1.5 sec. Se si verifica nuovamente un timeout, il TCP ritrasmette il segmento e imposta l'interval timer al valore 3 sec. Quindi l'interval timer ha una crescita esponenziale.

In ogni altro caso in cui il timer viene avviato (ricezione di un ACK) il TimeoutInterval viene calcolato con i valori più recenti di EstimatedRTT e di DevRTT. Questa modifica contribuisce, anche se in forma marginale, a controllare la congestione. Il motivo più probabile della generazione del timeout è la congestione della rete, cioè si stanno accumulando troppi pacchetti nelle code dei router che si trovano lungo il percorso tra gli host sorgente e destinazione, provocando la sovrascrittura o il ritardo dei pacchetti. Quando la rete è prossima alla congestione, se la sorgente continua a ritrasmettere pacchetti, la congestione si può aggravare. Invece, il TCP riduce il flusso dei pacchetti emessi.

Ritrasmissione

Uno dei problemi con la ritrasmissione dei pacchetti dovuta alla generazione del timeout è che il timeout potrebbe essere troppo lungo. Quando si perde un segmento, questo lungo intervallo di attesa costringe il mittente a ritardare la ripetizione del pacchetto perso, incidendo sul ritardo totale. Il mittente, però, può riconoscere la perdita di un pacchetto prima che si verifichi il timeout, in particolare quando vede due pacchetti di conferma (ACK) duplicati. Un ACK duplicato è un pacchetto di conferma che era stato già ricevuto. Per comprendere il comportamento del mittente in presenza di conferme duplicate, si pensi perchè il ricevitore ha ripetuto la conferma. La Tabella riepiloga la politica di generazione delle conferme da parte del ricevitore. Quando al TCP ricevitore arriva un segmento con numero di sequenza maggiore di quello atteso, individua un vuoto nel flusso dei dati, cioè un segmento mancante. Questo indica che si è perso un segmento nella rete.

Generazione dei segmenti ACK

Evento

Azioni del ricevitore TCP

Arriva un segmento in ordine, con il numero di sequenza atteso. I dati precedenti sono confermati e non manca nessun segmento.

Ritarda la conferma di 500 ms presumendo che possa arrivare un altro segmento in ordine. Se in questo intervallo di tempo non arriva nessun segmento invia un ACK

Arriva un segmento in ordine, con il numero di sequenza aspettato. Un altro segmento in ordine era in attesa di essere confermato. Non mancano segmenti.

Invia un solo ACK, confermando cumulativamente entrambi i segmenti in ordine.

Arriva un segmento con numero di sequenza maggiore di quello atteso. Manca un segmento.

Ripete l'ACK, specificando il numero di sequenza del prossimo byte atteso.

Arriva un segmento che rientra in parte o completamente nel flusso dei dati mancanti

Manda l'ACK, purchè quel segmento abbia il numero di sequenza uguale al primo dei segmenti mancanti.

Il TCP non usa le conferme negative, quindi il ricevitore non invia una conferma al mittente indicando esplicitamente il segmento mancante. Invece, conferma semplicemente (cioè genera un ACK duplicato) il numero d'ordine dell'ultimo byte che ha ricevuto. (Notare che in Tabella è previsto il caso in cui il ricevitore non scarti i segmenti fuori ordine). Poichè un mittente può inviare un gran numero di segmenti uno dopo l'altro, se un segmento si perde ci potrebbero essere più conferme duplicate una dopo l'altra. Se il TCP mittente riceve tre conferme per gli stessi dati, assume che il segmento, inviato dopo quello che è stato confermato tre volte, sia stato perso. In un caso simile, il mittente ripete il segmento mancante senza aspettare che si generi il timeout. Il comportamento del ricevitore può essere riepilogato così:

event: ACK ricevuto, con il numero di conferma y
  if (y > SendBase) {
    SendBase=y
    if (ci sono segmenti in attesa di essere confermati)
      avvia il timer
  } else { /* un ACK duplicato per un segmento già confermato */
    conta un altro ACK duplicato per il segmento y
    if (numero di ACK duplicati ricevuti con valore y è 3)
      /* ritrasmissione anticipata */
      ritrasmetti il segmento con numero di sequenza y
    }
  break;

Il raffinamento del comportamento del protocollo è anche una conseguenza delle osservazioni pratiche nel corso dell'esistenza del TCP.

Confronto tra le tecniche Go Back N e Selective Repeat.

Per concludere l'analisi del meccanismo di recupero dei pacchetti persi da parte del TCP si consideri il seguente problema: Il protocollo TCP usa la tecnica GBN o la tecnica SR? Si ricordi che le conferme del TCP sono cumulative e i segmenti ricevuti correttamente ma fuori ordine non sono confermati separatamente dal ricevitore. Di conseguenza il TCP mittente ha bisogno di ricordare solo il più piccolo numero di sequenza di un byte trasmesso ma non ancora confermato (SendBase) ed il numero di sequenza del prossimo byte da trasmettere (NextSeqNum). Fino a questo punto sembrerebbe che il TCP usi la tecnica GBN. Ma ci sono alcune differenze sostanziali tra il TCP e la tecnica Go-Back-N. Alcune realizzazioni del TCP memorizzano in un buffer i segmenti ricevuti correttamente ma fuori ordine. Si osservi cosa succede quando un mittente invia una sequenza di segmenti 1, 2, …, N, e arrivano tutti in ordine e senza errori. Se per ipotesi la conferma di un pacchetto n < N si perdesse, ma le restanti N-n conferme arrivano al mittente prima della scadenza dei rispettivi timer, la tecnica GBN ritrasmetterebbe, non solo il segmento n, ma anche tutti i pacchetti n+1, n+2, …, N. Il TCP ritrasmetterebbe solo il segmento n. Ma il TCP non ritrasmetterebbe il segmento n se gli giungesse la conferma del segmento n+1 prima del timeout del segmento n.

Una proposta di modifica del TCP, la cosiddetta conferma selettiva, permette al ricevitore TCP di confermare i segmenti fuori ordine anzichè confermare cumulativamente l'ultimo segmento ricevuto correttamente e in ordine. In questo caso il comportamento del TCP assomiglia alla tecnica Selective Repeat. Il comportamento del TCP si può considerare ibrido.

Il TCP adotta la tecnica Go-Back-N perchè le conferme sono cumulative, ma il ricevitore non invia le conferme per i segmenti fuori ordine. Di conseguenza il TCP trasmettitore deve solo mantenere il più piccolo numero di sequenza del byte trasmesso ma non ancora confermato (sendbase) ed il prossimo numero di sequenza del byte da trasmettere (nextseqnum). Ma si deve notare che il componente del TCP che si occupa dell'affidabilità non realizza esattamente questa tecnica. Una delle differenza può essere individuata dal seguente esempio: il trasmettitore spedisce i segmenti con numero di sequenza 1, 2,..., N, e ognuno viene acquisito correttamente. Si supponga che la conferma di uno dei pacchetti, tranne l'ultimo, si perda. ma tutte le conferme successive giungano prima del rispettivo timeout. Con la tecnica Go-Back-N si dovrebbe trasmettere non solo il pacchetto non confermato, ma anche tutti i successivi. TCP ritrasmette al massimo un segmento, quello non confermato, il TCP non ritrasmette un segmento se prima del suo timeout arriva una conferma di un segmento successivo.

Controllo del Flusso

Il TCP gestisce un buffer di ricezione su ciascun estremo della connessione, nel quale memorizza i messaggi in arrivo, in attesa che il processo applicativo li prelevi. Il TCP avverte il processo destinatario che c'è un messaggio da prelevare, ma il processo applicativo potrebbe ritardare la lettura, ad esempio perchè è impegnato in altre operazioni. Se il ricevitore è lento e il trasmettitore invia messaggi, si potrebbe verificare una sovrascrittura nel buffer di ricezione. Per questo il TCP offre, alle applicazioni, il servizio di controllo del flusso. Il controllo del flusso realizza una regolazione della frequenza di invio dei segmenti, cioè il processo più veloce si adegua ai tempi del processo più lento.

Si deve distinguere il comportamento del TCP quando rallenta il flusso di generazione dei segmenti per evitare la congestione e quando rallenta il flusso per adeguarsi alla velocità del processo applicativo. Nel seguito si descriverà il modo in cui il TCP offre alle applicazioni il servizio di controllo del flusso. Si assume che il ricevitore TCP scarti i segmenti ricevuti fuori ordine.

Per gestire il controllo del flusso, il TCP trasmettitore usa una variabile chiamata receive window. Il valore contenuto in questa variabile informa il mittente di quanto spazio libero esiste nel buffer del ricevitore. In una connessione full-duplex ciascun trasmettitore vede la finestra di ricezione del proprio destinatario. Inoltre l'ampiezza della finestra di ricezione cambia dinamicamente per tutto il tempo che esiste la connessione.

Si consideri il trasferimento di un file. Si supponga che l'host A debba inviare un file di grandi dimensioni all'host B servendosi di una connessione. L'host B riserva un buffer di ricezione a questa connessione e indica la sua dimensione con RcvBuffer. Allo scopo di sincronizzare i processi mittente e destinatario, si usano le seguenti variabili:

LastByteRead = il numero dell'ultimo byte nel flusso dei dati che il processo applicativo destinatario nell'host B ha letto dal buffer.

LastByteRcvd = Il numero dell'ultimo byte ricevuto memorizzato nel buffer del ricevitore, sull'host B.

Affinchè non si verifichi la sovrascrittura nel buffer di ricezione, deve valere la condizione:

LastByteRcvd - LastByteRead ≤ RcvBuffer

L'ampiezza della finestra di ricezione, indicata con RcvWindow, contiene la quantità di spazio libero nel buffer:

RcvWindow = RcvBuffer - (LastByteRcvd - LastByteRead)

Il destinatario, l'host B, fa conoscere al mittente, l'host A, quanto spazio libero ha nel buffer di ricezione scrivendo il valore corrente della variabile RcvWindow nel campo "larghezza della finestra" del segmento. Inizialmente l'host B imposta RcvWindow = RcvBuffer.

L'host A, a sua volta, deve gestire altre due variabili, LastByteSent e LastByteAcked, il cui significato dovrebbe essere ovvio. La differenza tra queste due variabili, LastByteSent - LastByteAcked, è il numero di byte che il trasmettitore ha spedito ma dei quali non ha ancora ricevuto la conferma. Mantenendo la quantità di dati non confermati minore del valore RcvWindow, l'host A si assicura che non sovrascriverà i dati nel buffer di ricezione sull'host B. Per tutta la durata della connessione, l'host A deve rispettare la condizione:

LastByteSent - LastByteAcked ≤ RcvWindow.

Si supponga che il buffer sia pieno e quindi RcvWindow = 0. Il trasmettitore viene avvertito di questa situazione, ma per ipotesi, l'host B non ha più niente da comunicare all'host A, quindi dopo che il processo destinatario svuota il buffer di B, non ci sono segmenti da inviare ad A nei quali inserire il nuovo valore di RcvWindow. Il TCP invia segmenti solo se ci sono dati di un processo applicativo da inviare o se ci sono conferme da spedire. Se si verifica una simile situazione l'host A non saprà mai che si è liberato spazio nel buffer del ricevitore, e resta bloccato, senza poter trasmettere. Le specifiche del TCP impongono che, quando RcvWindow = 0, il trasmettitore invii segmenti contenenti solo un byte allo scopo di forzare la conferma.

UDP non fornisce il servizio "controllo del flusso". UDP inserisce i dati ricevuti in una coda di dimensione finita, dalla quale il processo destinatario, tramite il socket, li leggerà. Se il processo non legge i dati con la necessaria velocità, il trasmettitore sovrascrive i dati.

Gestione della connessione TCP

Un processo client che intende aprire una connessione con un processo server chiede il servizio al TCP:

Socket clientSocket = new Socket("hostname", "port number");

Il TCP client avvia la fase di apertura della connessione con il TCP server, secondo i seguenti passi:

  • Passo 1. Il TCP client invia un segmento speciale al TCP server. È un segmento che non trasporta dati del processo applicativo, ma possiede la flag SYN nell'intestazione del segmento a valore 1. Per questo motivo il segmento è denominato SYN segment. Inoltre il client sceglie un numero di sequenza iniziale (client_isn) e lo inserisce nel campo numero di sequenza del segmento. Il segmento viene consegnato al processo del livello Rete per essere inviato. La scelta di un numero di sequenza iniziale per il segmento di apertura con un valore casuale ha anche lo scopo di impedire attacchi alla sicurezza.

  • Passo 2. Quando il datagramma IP contenente il segmento con il bit SYN=1 arriva al destinatario, il server estrae il segmento SYN dal datagramma, riserva lo spazio per i buffer e per le variabili necessarie a gestire la connessione ed invia un segmento al client che indica che la connessione è pronta. Nemmeno questo segmento contiene dati del processo applicativo. Nell'intestazione contiene tre importanti informazioni: primo, la flag SYN è impostata a 1; Secondo, il campo "Numero di Conferma" contiene il valore client_isn+1. Infine, il server sceglie il proprio numero di sequenza iniziale server_isn e inserisce questo valore nel campo numero di sequenza del segmento. Questo segmento di risposta significa che il ricevitore ha confermato la ricezione del pacchetto con il numero di sequenza client_isn, è pronto a stabilire la connessione e sta indicando al client il proprio numero di sequenza iniziale, server_isn. Il segmento di conferma è anche detto SYNACK.

  • Passo 3. Quando il client riceve il segmento SYNACK dal server, riserva lo spazio per il buffer e per le variabili della connessione. Il client invia un ultimo segmento che conferma al server la ricezione del segmento SYNACK (connection-confirm). A questo scopo, il client scrive il valore server_isn+1 nel campo "Numero di Conferma" dell'intestazione del segmento TCP. Il bit SYN è impostato a 0 perchè la connessione è stata aperta. Questo terzo segmento può anche contenere dati che il client invia al server.

Quando i tre passi sono stati completati il client ed il server possono scambiarsi segmenti dati. In ognuno di questi segmenti il bit SYN avrà il valore 0. Notare che per aprire la connessione sono stati usati tre segmenti, come illustrato in Figura. la fase di apertura connessione è anche detta three-way handshake.

Ognuno dei due processi può chiudere la connessione. La fase di chiusura della connessione serve a rilasciare le risorse (i buffer e le variabili). Si supponga che il client decida di chiudere la connessione. Il processo applicativo del client chiede la chiusura. Il client invia un pacchetto di servizio al processo server. In questo segmento il bit FIN, nell'intestazione del pacchetto, è impostato a valore 1. Quando il server riceve questo pacchetto invia il pacchetto di conferma e poi manda il pacchetto di chiusura della sua connessione, contenente nell'header il bit FIN=1. Infine il client invia la conferma. A questo punto le risorse sono state rilasciate su entrambi gli host.

Durante la connessione, i processi TCP subiscono una serie di transizioni di stato.

La Figura illustra una sequenza degli stati che attraversa il client.

Il client inizia nello stato CHIUSA. Quando il processo applicativo richiede l'apertura della connessione, creando un socket, il client invia un segmento SYN al server e si porta nello stato INVIATO_SYN. In questo stato il client aspetta che il server gli invii il segmento di conferma del segmento precedente e che, nell'header, abbia il bit SYN=1. Alla ricezione di questo segmento il client passa nello stato STABILITA. Da questo stato il client può inviare e ricevere i pacchetti contenenti dati, provenienti dal processo applicativo.

Ad un certo punto, il client decide di chiudere la connessione. Invia al server un segmento con il bit FIN uguale a 1 e si porta nello stato ATTESA_FIN_1. In questo stato il client resta in attesa che il server invii un segmento di conferma. Alla ricezione di questo segmento il client passa nello stato ATTESA_FIN_2. Il client aspetta un altro segmento, proveniente dal server, contenente il bit FIN uguale a 1. Con la ricezione di questo segmento, il client risponde inviando la conferma e si porta nello stato ATTESA. Lo stato ATTESA costringe il client a ripetere l'invio della conferma finale, qualora si perdesse la precedente. La durata dello stato varia da 30 secondi a 2 minuti, a seconda dell'implementazione. Al termine di questa attesa la connessione è ritenuta effettivamente chiusa e tutte le risorse usate dal client, compresi i numeri di porta, vengono rilasciati.

La figura a lato illustra le transizioni di stato subite dal server.

Nei due grafi precedenti, viene mostrato solo il modo in cui viene aperta e poi chiusa la connessione. Non sono stati esaminati problemi quali ad esempio: entrambi i sistemi chiedono contemporaneamente l'apertura (o la chiusura) della connessione. Finora si è assunto che entrambi i sistemi, client e server, sono pronti per comunicare, cioè il server è in ascolto sulla porta a cui il client invia il suo segmento SYN.

Si consideri cosa succede quando un host riceve un segmento TCP in cui i numeri di porta sorgente o indirizzo IP sorgente non corrispondono a nessun socket uscente nell'host. Ad esempio, si supponga che un host riceve un pacchetto SYN con numero di porta destinazione 80, ma l'host non ha nessun processo in ascolto sulla porta 80. L'host invierà uno speciale segmento reset al mittente. In questo segmento il bit RST, contenuto nell'header, ha il valore 1. Il significato di questo segmento dal server al client è: Non esiste un socket per tale segmento. Prego non ripetere.

Quando un host riceve un pacchetto UDP il cui numero di porta destinazione non corrisponde ad alcun socket, l'host invia un datagramma speciale di tipo ICMP.

Il programma nmap è uno strumento per la scansione delle porte, alla ricerca di processi in ascolto. Per sondare una porta particolare, ad esempio la numero 6789, su un host destinazione, nmap invia un segmento SYN con numero porta destinazione 6789 a quell'host. Si possono verificare tre casi:

Nmap è uno strumento che scandisce le vulnerabilità dei sistemi e può essere scaricato all'indirizzo: www.nmap.org.