Tcp / IP parte 8
16 Aprile 2020Il protocollo Kerberos e le sue implementazioni
26 Luglio 2020
Tcp / Ip parte 9
Immaginate di dover spedire una lettera a un vostro amico. Il TCP negozia con la controparte la lunghezza massima del segmento, come vedremo più avanti. Quindi inizia a riempire il primo segmento un carattere alla volta. Quando il segmento è pieno viene spedito e viene fatto partire il contatore a tempo per quel segmento. Quindi il TCP inizia a riempire il secondo segmento, che parte regolarmente, e così dicendo. Man mano che i segmenti partono arrivano dalla controparte le conferme di ricezione.
Supponiamo che a un certo punto, dopo aver spedito 450 ottetti, arrivi per due volte al mittente la conferma che il destinatario è riuscito a ricostruire il flusso fino al 300° carattere e che si aspetta il 301°.
È evidente che qualcosa è andato storto e che si sono persi dei dati. Il TCP allora spedisce un segmento che contiene di nuovo dal 301° carattere in poi, diciamo fino al 400°.
Dato che i caratteri dal 370° al 450° erano comunque arrivati regolarmente, il successivo messaggio di conferma richiederà direttamente il 451° carattere, e non il 401°.
Vantaggi e svantaggi: Un vantaggio è che il valore di conferma è estremamente semplice da calcolare e di immediata comprensione.
Inoltre, se una conferma di ricezione va persa, non è detto che questo causi automaticamente la ritrasmissione dei dati.
Ci sarà comunque la conferma successiva che fornirà l’indicazione esatta del punto a cui è arrivato il destinatario nel ricostruire il flusso.
Lo svantaggio più grosso è che il mittente non ha modo di sapere quanti dati siano effettivamente arrivati con successo al destinatario, dato che basta un buco nel flusso per far segnalare come validi un numero di byte molto inferiore a quelli effettivamente ricevuti.
Questo crea seri problemi al mittente, che non sa se ritrasmettere tutti i dati successivi, e quindi sprecare tempo a ritrasmettere dati già arrivati, o trasmettere solo una piccola parte e aspettare la conferma che il potenziale buco si è chiuso.
Entrambi gli schemi sono alquanto inefficienti.
Sta allo sviluppatore dello stack TCP/IP decidere quali algoritmi utilizzare, tenendo presente che un algoritmo troppo complesso ha comunque lo svantaggio di avere potenzialmente basse prestazioni.
Un altro punto importante è il calcolo della lunghezza ottimale del segmento.
Abbiamo detto più sopra che ogni conferma di ricezione contiene una soglia di capacità (window advertisement) la quale specifica il numero di ulteriori ottetti che il destinatario è in grado di ricevere.
Questo meccanismo permette di adattare la finestra di spedizione alle dimensioni del buffer di ricezione.
Tuttavia è anche necessario definire la lunghezza del segmento oltre che in funzione delle capacità di trasmissione del mittente e di ricezione del destinatario, anche e soprattutto in funzione delle caratteristiche della rete, come per esempio la grandezza massima del frame fisico, o Maximum Transfer Unit (MTU).
La lunghezza massima di un segmento, o Maximum Segment Size (MSS), viene calcolata appunto sulla base dell’MTU se entrambi gli estremi della connessione si trovano nella stessa rete fisica, altrimenti lo standard raccomanda di utilizzare un valore di 536 byte, equivalente alla dimensione normale di un datagramma IP meno le dimensioni standard delle intestazioni IP e TCP sommate insieme, 40 byte appunto.
Tale calcolo è ovviamente solo un primo tentativo di ottimizzare l’utilizzo della rete da parte del TCP.
Durante la trasmissione il TCP può modificare tale valore in funzione della situazione contingente.
Non esiste tuttora un algoritmo standard per definire il giusto valore per l’MSS, data la complessità del problema.
Una cattiva definizione dell’MSS può seriamente penalizzare la comunicazione. Se il segmento è troppo piccolo, il rapporto tra i dati trasmessi e quelli utilizzati nella trasmissione stessa è sfavorevole.
Per esempio, un segmento di cinque byte utilizza solo un ottavo della larghezza di banda (bandwidth) disponibile, dato che per ogni cinque byte di dati ce ne sono ben quaranta di intestazione.
Se il segmento è molto grande, altrettanto è il datagramma IP.
Se il datagramma è più grande dell’MTU, verrà spezzato in più frammenti non indipendenti fra loro, per cui basta che si perda un solo frammento per perdere tutto il datagramma e di conseguenza il segmento TCP.
Il controllo di flusso: Il controllo del flusso dei dati è un aspetto estremamente importante in un sistema in cui sono collegate macchine anche molto differenti fra loro per dimensioni e capacità di trasmissione .
Per controllo del flusso si intende la possibilità di regolare dinamicamente la quantità di dati che vengono immessi nella rete.
Non solo è importante che il destinatario possa regolare la velocità di spedizione in funzione della sua capacità di ricezione, ma è fondamentale che ogni gateway intermedio possa frenare il flusso dei dati che riceve per evitare di entrare in saturazione. Il meccanismo descritto della soglia di capacità permette di risolvere il primo problema ma non il secondo.
Ovviamente se il TCP fosse libero di accumulare questi comandi per poi spedirli tutti in una volta l’applicazione sarebbe di difficile utilizzo.
Esiste poi la possibilità che il TCP debba spedire dei dati che non fanno parte del flusso normale e che vanno immediatamente gestiti dalla controparte indipendentemente dallo stato in cui si trova la ricostruzione del messaggio originario.
Tali dati sono detti urgenti, e anche in questo caso esiste un bit nell’intestazione del segmento che informa il destinatario del fatto che il segmento va gestito immediatamente.
Il concetto è analogo a quello del BREAK da tastiera. Se avete lanciato un programma che va in loop è necessario poterlo interrompere senza dover far ripartire il sistema.
Su molti sistemi operativi basta premere una sequenza di tasti, come per esempio Control-C (^C) per bloccare l’esecuzione del programma.
Similarmente, se un estremo della connessione deve bloccare (o sbloccare) l’elaborazione del flusso di dati dall’altra parte, dovrà poter mandare un messaggio urgente che abbia la precedenza rispetto ai normali segmenti di dati.
Si dice che tale messaggio è fuori banda (out of band).
Benché il TCP presenti all’utente una visione continua dei dati, detta flusso, l’unità di trasferimento dei dati del TCP è il segmento.
Un segmento è formato come al solito da una intestazione e da un’area dati.
Al contrario del datagramma IP, il segmento ha dimensioni variabili nel tempo, cioè i vari segmenti spediti a fronte di uno stesso flusso possono avere lunghezze differenti. I segmenti sono utilizzati dal TCP per aprire e chiudere una connessione, trasferire dati, spedire conferme di ricezione e modificare la finestra di spedizione, quel meccanismo che garantisce un utilizzo ottimale della rete, come spiegato in precedenza.
Due caratteristiche peculiari del TCP sono che lo stesso segmento può portare contemporaneamente sia dati veri e propri sia dati di controllo, e che le informazioni di controllo possono riferirsi sia allo stesso flusso dell’area dati, sia al flusso opposto (piggybacking).
L’intestazione: Innanzitutto abbiamo i numeri di porta del mittente e del destinatario, esattamente come nell’UDP. Come già nell’UDP, infatti, gli indirizzi IP delle due controparti sono contenuti nell’intestazione del datagramma IP.
Al contrario di quanto avveniva nell’UDP, tuttavia, la conoscenza da parte del TCP degli indirizzi IP non rompe il paradigma che vuole un certo isolamento fra le responsabilità dei vari livelli dello stack.
Il TCP infatti, architetturalmente, ragiona in termini di connessioni, e queste comprendono sia l’informazione relativa alle porte, sia quella relativa agli indirizzi IP.
Anzi, ogni qual volta l’IP consegna un segmento al TCP, gli passa anche gli indirizzi IP contenuti nell’intestazione del datagramma.
Anche nel caso del segmento TCP la verifica della correttezza dell’intestazione da parte del destinatario viene effettuata utilizzando un meccanismo di somma di controllo con pseudointestazione.
All’interno dell’intestazione TCP, infatti, esiste un campo chiamato somma di controllo (checksum).
Il TCP imposta inizialmente tale campo di 16 bit a zero.
Costruisce quindi una pseudointestazione che contiene gli indirizzi IP del mittente e del destinatario, il numero di protocollo del sottosistema di trasmissione (nel caso del TCP basato su IP è sei) e la lunghezza del segmento TCP compresa l’intestazione.
A questo punto appende alla pseudo intestazione il segmento IP e aggiunge alla fine dello stesso tanti zeri quanti ne servono per far sì che il blocco risultante sia un multiplo di parole da 16 bit (padding). Divide quindi il blocco in parole da 16 bit e ne calcola la somma a complemento uno.
Il risultato viene quindi salvato nel campo apposito dell’intestazione e sia la pseudointestazione sia i bit aggiunti in fondo vengono rimossi prima di spedire il segmento.
Il destinatario ovviamente effettuerà un calcolo analogo per verificare che il valore di controllo così ottenuto corrisponda con quello arrivato nell’intestazione del segmento.
Nell’intestazione ci sono tre campi calcolati in ottetti. Il primo è il numero di sequenza (sequence number), che rappresenta la posizione dell’area dati del segmento TCP all’interno del flusso di dati. Il secondo è il numero di conferma (acknowledgement number), ovverosia il numero di sequenza dell’ottetto che il mittente si aspetta di ricevere per continuare la ricostruzione.
Da notare che tale valore corrisponde al flusso opposto rispetto a quello in cui viaggia il segmento che lo contiene.
Il terzo campo è il puntatore ai dati “urgenti”. Come detto prima, è possibile che il TCP debba spedire dei dati urgenti che vanno elaborati indipendentemente dal flusso normale di dati, e con priorità rispetto a quest’ultimo. In questo caso il segmento contiene un segnalatore (flag) che informa il destinatario della presenza d’informazioni urgenti nell’area dati.
I dati urgenti sono posizionati all’inizio dell’area dati, e il puntatore in questione indica dove tali dati finiscono e dove ricominciano i dati normali, se ce ne sono.
I segnalatori: Separati da un’area riservata per usi futuri c’è il campo che contiene la posizione dell’area dati nel segmento e un blocco di sei segnalatori.
Il primo, misurato in parole da 32 bit, indica di fatto la lunghezza dell’intestazione del segmento in tale unità di misura. questo campo è necessario in quanto in fondo all’intestazione esiste una zona riservata a eventuali opzioni che rende la lunghezza dell’intestazione non fissata a priori. Il secondo campo contiene invece sei indicatori.
Data infatti nel segmento la presenza contemporanea, almeno in potenza, sia di dati di controllo sia di dati applicativi normali e urgenti, è necessario utilizzare dei segnalatori per informare il destinatario su cosa effettivamente contiene il segmento.
Tutti i segnalatori sono attivi se impostati a uno, inattivi altrimenti. Il primo segnalatore indica se l’area dati contiene dati urgenti. Il secondo indica la presenza nel segmento di una conferma di ricezione valida.
Dato infatti che il campo corrispondente esiste sempre e comunque nell’intestazione, se il segmento non trasporta alcuna conferma di ricezione è necessario informare in qualche modo il destinatario che tale campo va ignorato.
Il terzo bit è posto a uno quando si vuole forzare la trasmissione dei dati all’utente finale indipendentemente dal fatto che il buffer di ricezione sia o meno completamente riempito.
Il quarto segnalatore serve per interrompere immediatamente la connessione (reset). Tale evento avviene solo in situazioni eccezionali e causa l’interruzione immediata delle trasmissioni da ambo le parti e il rilascio del contenuto dei buffer di ricezione.
Il quinto bit è detto di sincronizzazione, ed è utilizzato durante la fase iniziale di negoziazione della connessione, detta in gergo handshake. In pratica, i segmenti scambiati quando questo bit è impostato a uno servono a sincronizzare i numeri di sequenza delle due controparti prima d’iniziare la trasmissione vera e propria dei dati.
L’ultimo bit serve a informare il destinatario che il mittente intende terminare in modo pulito la connessione e che non ha più dati da spedire.
All’apertura e alla chiusura della connessione il TCP utilizza un algoritmo chiamato saluto a tre vie (three-way handshake) che garantisce la corretta sincronizzazione delle due operazioni.
L’ultimo campo fisso è quello relativo alla soglia di capacità del mittente (window advertisement) che contiene il numero di ulteriori ottetti che esso è in grado di ricevere.
A questo punto è di nuovo importante ricordare il concetto di piggybacking, a cui già si è accennato.
Ogni segmento può portare contemporaneamente informazioni in cui una controparte è vista sia come chi spedisce i dati contenuti nel segmento, cioè come mittente, sia come chi ha ricevuto o deve ricevere dati dall’altro capo della connessione, cioè come destinatario.
Quando noi parliamo di mittente, per evitare confusione, ci riferiamo sempre al mittente del segmento di cui stiamo parlando.
È importante comunque tenere sempre presente che alcuni dati del segmento hanno senso solo se si considera il mittente quale destinatario di dati precedenti o ancora da venire.
In fondo all’intestazione c’è un’area opzionale che può essere utilizzata a vari scopi. In genere essa contiene opzioni che permettono alle due controparti di negoziare alcuni aspetti della comunicazione.
Un esempio è il calcolo della lunghezza ottimale del segmento.
L’implementazione del protocollo TCP: Abbiamo visto che tutto il meccanismo funziona ed è affidabile grazie alle conferme di ricezione e alla ritrasmissione dei pacchetti probabilmente andati perduti.
Ma come fa a sapere il mittente che un pacchetto è andato effettivamente perduto? Ovviamente perché non è arrivata la conferma di ricezione, direte voi.
Va bene, ma quanto devo aspettare tale conferma prima di assumere che sia necessaria una ritrasmissione?
E qui son dolori. Se aspetto troppo rischio di rallentare la comunicazione in modo inaccettabile. Se aspetto troppo poco rischio di ritrasmettere inutilmente troppi segmenti, magari semplicemente un po’ in ritardo.
Tutto il sistema si basa sul calcolo del tempo di attesa massimo, o time-out. Il TCP calcola il time-out sulla base del tempo intercorso fra la spedizione di un segmento e l’arrivo della conferma corrispondente. Sembra facile, ma non è così.
Il TCP calcola continuamente il time-out, ogni volta che arriva una conferma di ricezione. In questo modo il sistema è sempre aggiornato in funzione dello stato effettivo della connessione e della rete.
Il time-out è calcolato come media pesata dei tempi intercorsi fra la spedizione del segmento e la ricezione della conferma.
Chiamiamo quest’ultimo tempo rilevato di andata e ritorno (Round Trip Sample) o RTS. Il tempo stimato di andata e ritorno (Round Trip Time) è calcolato utilizzando una specifica formula. In pratica ogni nuovo RTS pesa più o meno sul calcolo dell’RTT in base al valore di alfa. Se alfa è molto vicina a zero, l’RTT varia rapidamente a ogni cambiamento dell’RTS, per cui il sistema risponde rapidamente alle variazioni. Se viceversa alfa è vicina a uno, è necessario che la nuova RTS rimanga stabile più a lungo per avere effetto sull’RTT.
A questo punto il time-out viene calcolato moltiplicando l’RTT per un valore maggiore di uno. Se il valore di beta è troppo vicino a uno la perdita di un pacchetto viene immediatamente rilevata, ma si rischia di ritrasmettere più pacchetti del necessario.
Viceversa beta è troppo alto si rischia di aspettare troppo a lungo prima di ritrasmettere un pacchetto perso, abbassando così le prestazioni della connessione. In genere si raccomanda per beta un valore di due.
Una scelta difficile: La scelta di alfa e di beta sembra dunque essere critica, ma i problemi non sono ancora finiti.
Infatti, se un segmento è trasmesso due volte, quando arriva la conferma di ricezione, a chi si riferisce? Al pacchetto originale o a quello ritrasmesso? Se usiamo il primo pacchetto per il calcolo dell’RTS rischiamo di far crescere esponenzialmente il valore di time-out.
Infatti un pacchetto è ritrasmesso quando scade il time-out precedente. Di conseguenza il nuovo RTS è ovviamente più grande del vecchio time-out. Se viene perso un nuovo pacchetto l’RTS cresce ancora, e così via.
Se usiamo il pacchetto ritrasmesso abbiamo il problema opposto, cioè il time-out rischia di ridursi sempre di più, o almeno si è dimostrato sperimentalmente che si stabilizza su valori alquanto bassi.
Supponiamo infatti di avere un ritardo in rete: la conferma di ricezione arriva conseguentemente in ritardo. Nel frattempo il mittente ha rispedito il pacchetto che credeva perso.
Appena arriva la conferma questa è associata al segmento ritrasmesso generando così un RTS molto piccolo. Il time-out si riduce, aumentando il rischio di considerare persi pacchetti la cui conferma di ricezione arriva in ritardo, e così via.
- Karn propose nel 1987 d’ignorare i pacchetti ritrasmessi nel calcolo del time-out. Questo evitava il problema suddetto, ma ne creava un altro.
Se un pacchetto è ritrasmesso perché si è avuto un repentino calo di prestazioni della rete, il time-out rimarrà sempre troppo basso, in quanto il mittente continuerà a ritrasmettere pacchetti le cui conferme arrivano in ritardo rispetto al time-out calcolato prima del calo di prestazioni.
Dato che tali conferme vengono regolarmente ignorate per il calcolo dell’RTT, il time-out non viene più aggiornato almeno fintanto che la rete non torna normale, cosa per giunta complicata dal sovraccarico dovuto all’inutile ritrasmissione dei pacchetti.
La soluzione consiste nell’aumentare il time-out precedente a una ritrasmissione di un fattore arbitrario, diciamo gamma, fino a un limite massimo ragionevole calcolato sulla base dei possibili cammini all’interno della rete .
In genere gamma non è minore di due. Questa tecnica è detta di backoff.
Conclusione: Implementare il protocollo TCP non è certo banale. Il che tra l’altro fa capire come non tutti i pacchetti TCP siano uguali: anzi, è proprio il contrario.
Una scelta oculata degli algoritmi implementativi può fare seriamente la differenza fra un prodotto e un altro.
Il fatto che essi implementino lo stesso standard non dà alcuna indicazione sulla qualità delle prestazioni dello stack che state utilizzando.
Se poi alcuni parametri possono essere personalizzati dall’utente, una opportuna calibrazione del programma studiata sulle caratteristiche specifiche della vostra rete, può modificare significativamente i tempi di risposta del sistema. Naturalmente non è fra gli scopi di questi articoli entrare nel dettaglio di tutte le problematiche TCP/IP.
Se vi siete persi le puntate precedenti le trovate qui:
