Guida TCP/IP
Miliardi di bit viaggiano ogni giorno sulla Rete. Vi siete mai chiesti
come fanno ad arrivare al corretto destinatario? In questo articolo, primo
di una serie vi presentiamo "la suite di protocolli TCP/IP"
cioè le regole utilizzate per la trasmissione su Internet. Non è certo
fondamentale sapere come funziona uno spinterogeno o un albero di
trasmissione per guidare un'automobile. Analogamente, al giorno d'oggi,
non serve sapere come funziona un computer per poterlo utilizzare. La
tecnologia ci scherma sempre di più dal come una cosa funziona spostando
l'attenzione sul cosa fare per utilizzarla. Così, quella che era una
volta tecnologia per un'élite abbastanza ristretta di studiosi,
ricercatori e studenti, è oggi una realtà a disposizione di tutti. Non
solo è diventato semplice navigare nella Ragnatela, ma oggi chiunque può
facilmente costruirsi le sue pagine e agganciarle a uno dei tanti siti Web
che ospitano pagine private. Non c'è più neanche bisogno di conoscere l'HTML,
grazie alla proliferazione di editor HTML commerciali e di pubblico
dominio. In quanto ai risultati estetici, beh, lì non c'è programma che
tenga. Ma cosa c'è sotto a tutto ciò? Per chi è ancora e nonostante tutto
interessato a capire come funzionano le cose, e se vogliamo anche per
coloro ai quali la cosa non interessa per niente, ma hanno qualche minuto
per leggere un paio di paginette e poi, chissà, potrebbe sempre tornare
utile... insomma, per chi vuole, ecco a voi il "TCP/IP, questo
sconosciuto". Il nome completo è TCP/IP Internet Protocol Suite, ed è un insieme di
protocolli di trasmissione di cui i due principali sono appunto il TCP (Transmission
Control Protocol) e l'IP (Internet Protocol). Ma che cosa è esattamente
un protocollo? Essenzialmente è una serie di regole per comporre dei
messaggi e per far sì che essi possano essere scambiati tra due macchine.
Non stiamo parlando solo di computer. Anche una centrale telefonica
meccanica può ricadere in questa definizione. Un protocollo può
contenere regole estremamente dettagliate, come quelle che identificano il
significato di ogni singolo bit nella costruzione di un messaggio, oppure
fornire uno scenario di alto livello, come per esempio definire come
avviene il trasferimento di un file da un computer a un altro.
Fondamentalmente un protocollo sta alla trasmissione dati come un
linguaggio di alto livello quale il C++ sta alla programmazione. Infatti,
un linguaggio di programmazione comprende sia regole estremamente
dettagliate che devono essere seguite alla lettera - guai a dimenticare
anche un solo punto e virgola alla fine di un'istruzione C++ - sia
strutture di alto livello che vanno costruite nel modo corretto, pena
errori nella struttura logica del programma. Una generica architettura di trasmissione è formata da una torre a
più piani, dove ogni piano rappresenta una precisa responsabilità nella
trasmissione dei messaggi. Alla base della torre sta la porta di accesso
alla rete fisica, che potremmo pensare come una rete di strade. Ogni piano
prende il messaggio che arriva dal piano superiore, lo mette in una busta
con alcune informazioni aggiuntive, e lo passa come messaggio al piano
inferiore. Le regole di comunicazione tra i vari piani sono dette interfacce. Il
messaggio risultante, formato da tante buste una dentro l'altra, viene
immesso nella rete dalla porta che si trova alla base della torre. Una
volta arrivato al piano terreno infatti, esso viene trasportato alla torre
di destinazione e da qui risale un piano dopo l'altro fino all'ultimo
piano, detto anche livello applicativo. Ogni piano della torre di
destinazione apre solo la busta che gli compete e usa le informazioni
aggiuntive per recapitare la busta successiva al piano superiore. Le
informazioni aggiuntive rappresentano il protocollo di comunicazione. Ogni
piano comunica quindi solo con il piano corrispondente. Esempio: Il direttore della Pippo e Figli manda una lettera riservata
al direttore della Pluto e Consorte. Il modo con cui i due comunicano, per
esempio i riferimenti a lettere precedenti, lo stile della lettera, il
modo di salutare alla fine della lettera, e così via, rappresenta il
protocollo ad alto livello, cioè quello applicativo. Per spedire la
lettera il direttore lo passa alla sua segretaria. Ciò avviene secondo le
regole interne della Pippo e figli, ed è perciò un'interfaccia. La segretaria prende la
lettera, la mette in una busta aggiungendo il nome del destinatario e la
scritta RISERVATO. Queste informazioni sono per la sua controparte nella
Pluto e consorte, ed è quindi anch'esso un protocollo. La busta viene
quindi passata all'Ufficio Posta dell'edificio secondo la procedura
ordinaria (altra interfaccia), il quale aggiunge l'indirizzo completo, il
CAP e altre informazioni necessarie alla spedizione, e la passa quindi al
corriere, che rappresenta il meccanismo fisico di trasferimento del
messaggio. Terzo protocollo. Quando la lettera arriva, questa viene
gestita dall'Ufficio Posta della Pluto e consorte che, dopo aver buttato
la busta esterna con l'indirizzo dell'edificio, la passa alla segreteria
del direttore. Questa registra l'arrivo della missiva, la toglie dalla
busta più interna, e poi consegna la lettera vera e propria al direttore
della Pluto e consorte. È evidente che perché il sistema funzioni bisogna che i vari
protocolli siano rispettati da entrambe le aziende. Mentre infatti le
interfacce possono essere diverse (ogni azienda ha le sue procedure
interne), i protocolli devono essere stati concordati prima, altrimenti
alcune informazioni potrebbero andare perse, o addirittura la lettera
potrebbe non essere recapitata in tempo. Pensate a una segretaria italiana
che riceve una busta da una consociata asiatica con sopra scritto URGENTE
in cinese! Ne nasce una considerazione importante. La base di ogni
protocollo è il concetto di standardizzazione. Più vasta è
l'accettazione dello standard, più forte e diffuso è il protocollo. Gli
standard internazionali sono in genere i più importanti, ma non sempre.
Un esempio è proprio il TCP/IP, nato per volontà dell'agenzia americana
DARPA (Defense Advanced Research Projects Agency) e poi diventato di fatto
il maggior sistema di protocolli per l'interconnessione di reti a livello
mondiale. Internet è fatta a strati Internet è basato su tre livelli
concettuali: il livello applicativo (Application Services), quello del
trasporto (Reliable Stream Transport Service) e quello della spedizione
dei pacchetti (Connectionless Packet Delivery Service). Per capire il TCP/IP,
è necessario a questo punto capire bene che cosa è Internet. Tanto per
cominciare Internet non è una rete di comunicazione. Una rete di
comunicazione è in genere legata alle necessità specifiche di chi l'ha
disegnata e dell'hardware utilizzato per implementarla. Costruire una rete
ideale che vada bene per qualsiasi esigenza, o pensare di poter limitare a
un solo tipo di hardware l'implementazione di una qualunque rete non solo
non è fattibile, ma neanche auspicabile, date le limitazioni delle
tecnologie attuali. A volte è necessario far correre i dati molto
velocemente in un ambito molto ristretto, come per esempio all'interno di
un edificio. Altre volte si ha l'esigenza di trasmettere dati a migliaia
di chilometri di distanza in modo molto affidabile, anche se questo può
significare un rallentamento nella velocità di trasmissione. Se si
cercasse di utilizzare lo stesso hardware in entrambi i casi, i costi
sarebbero assolutamente inaccettabili. La soluzione è l'interconnessione delle reti, o internetworking.
Grazie a ponti di collegamento (detti gateway) e la definizione di
opportuni protocolli, si possono collegare fra di loro reti anche molto
diverse, fornendone agli utenti una visione comune. Questa è la forza di
Internet rispetto alle varie reti proprietarie, e di conseguenza del TCP/IP
sui vari protocolli proprietari. Il TCP/IP è un insieme di regole
pubbliche, aperte a tutti, o come si dice nell'ambiente, un sistema aperto
(open system), che permette l'interconnessione di reti anche molto
differenti, indipendentemente dalla tecnologia usata da ogni rete. I suoi
principali vantaggi sono appunto l'indipendenza dalle tecnologie delle
singole reti interconnesse, la possibilità di far comunicare fra di loro
ogni computer connesso al sistema, la possibilità di trasmettere conferme
di ricezione (acknowledgement) direttamente dal destinatario al mittente,
e soprattutto una notevole quantità di protocolli applicativi per
qualunque possibile bisogno, come vedremo più avanti. Il TCP/IP definisce
quindi una unità di trasmissione dati chiamata datagram, e le regole da
seguire per trasmettere un datagram in una particolare rete. Il principio che sta alla base dell'interconnessione è quello di
schermare le applicazioni dalle caratteristiche fisiche delle reti in modo
semplice e flessibile. Questo avviene attraverso un livello intermedio che
si occupa di spedire e ricevere piccoli pacchetti di dati fra due punti
qualsiasi del sistema di reti. Questo meccanismo si chiama
packet-switching. Esso consiste nella divisione di ogni messaggio in un
certo numero di pacchetti di dati. Ogni pacchetto è formato da poche
centinaia di byte, e contiene una intestazione che fornisce informazioni
sul destinatario e su come raggiungerlo. Questo meccanismo ha il vantaggio
di ottimizzare l'utilizzo della rete, parallelizzando la trasmissione di
più messaggi contemporaneamente. Lo svantaggio è che ogni nuovo sistema
che si aggancia alla rete per trasferire dati riduce la disponibilità
della rete per tutti gli altri sistemi già connessi. Una rete infatti ha
una certa capacità ben definita, che dipende sostanzialmente dalla
tecnologia hardware e software che utilizza. Tale capacità viene misurata
in bit per second (bps). Questa grandezza non rappresenta la velocità dei
dati in rete, come si potrebbe pensare in prima istanza, bensì dà una
misura del numero massimo di bit che possono essere trasmessi nella rete
in un secondo. La velocità reale di un singolo messaggio dipende da tanti
fattori, come il numero di sistemi che stanno utilizzando la rete, la
qualità delle connessioni e di conseguenza il numero di tentativi
necessari per trasferire correttamente i dati, le modalità di
trasmissione e i dati aggiuntivi necessari al trasferimento degli stessi. Ci sono altri modi per trasferire dati in una rete: per esempio, quando
fate una telefonata, la rete stabilisce un collegamento diretto fra il
vostro telefono e quello della persona chiamata. A questo punto il
telefono incomincia a campionare il microfono della vostra cornetta in
modo continuo, trasferendo il segnare al ricevitore all'altro capo. Il
tutto a 64.000 bit per secondo, che è la velocità di campionamento
necessaria a digitalizzare la voce. Questo avviene comunque,
indipendentemente dal fatto che stiate parlando o meno. Anche se state in
silenzio la linea è saturata al massimo della sua capacità. Questo
meccanismo è detto circuit-switching. Al contrario del meccanismo usato
dal TCP/IP, quello cioè a pacchetti, la linea è completamente assegnata
alla comunicazione in atto, per cui il fatto che altri stiano telefonando
non riduce la capacità della connessione. D'altra parte la linea è
utilizzata completamente indipendentemente dal fatto che ci siano o meno
dati da trasferire. Di qui gli elevati costi di tale meccanismo. La
telefonata, infatti, la pagate lo stesso sia che parliate molto
velocemente, sia che stiate completamente in silenzio. Questo meccanismo
è troppo costoso per una rete informatica, specialmente se si tiene conto
che la disponibilità di tecnologie hardware sempre più raffinate e
veloci per il trasferimento dei dati bilanciano in buona parte quello che
è uno dei punti deboli del sistema a pacchetti, e cioè l'impossibilità
di garantire a ogni utente e in ogni momento una certa capacità di
trasferimento ben definita. Torniamo al sistema a pacchetti. Per trasferire dati da un sistema a un
altro ogni sistema ha un nome unico ben definito. Non esistono cioè due
sistemi con lo stesso nome, anche se in reti diverse, indipendentemente da
quale è il nome locale di un sistema nella sua rete di appartenenza.
All'interno di ciascuna rete, i vari computer usano la tecnologia hardware
e software specifica di quella rete. Tuttavia, grazie a questo strato
intermedio di software, le varie applicazioni hanno una visione unica e
globale del sistema interconnesso di reti, detto appunto internet. Notate
la "i" minuscola. Il concetto di internet è infatti quello
appena descritto. Viceversa Internet con la "I" maiuscola,
identifica quel sistema di reti, basato sull'architettura internet, che
viene detto anche Connected Internet. La connessione tra due reti avviene attraverso macchine opportune che
sono collegate fisicamente a entrambe le reti, e hanno la responsabilità
di far passare i vari pacchetti da una rete all'altra e viceversa. Tali
macchine sono dette internet gateway, o anche IP router. Sono loro il vero
elemento portante di una internet. Ogni router non solo deve sapere che
determinati pacchetti vanno passati da una rete a un'altra, ma deve
passare dall'altra parte anche pacchetti destinati a ulteriori reti
connesse attraverso altri router. Essi però ragionano solo in termini di
reti, non di destinazione finale. A un router non interessa chi è
effettivamente il destinatario di un pacchetto, ma solo a quale rete
appartiene. Questo semplifica molto l'implementazione di un router. Alla
base del meccanismo dei router c'è l'indirizzo IP, o IP address. Ogni cosa che conosciamo ha un nome. Cane, casa, auto, e via dicendo.
Se ci interessa specificare meglio ciò di cui stiamo parlando, possiamo
assegnare un nome anche a un sottogruppo di cose. Così abbiamo che i cani
bassotti sono alquanto diversi dai San Bernardo, una catapecchia non è
certo una villa, e una Ferrari costa un po' più di una Cinquecento. Se
poi dobbiamo identificare una cosa in modo chiaro e univoco, è necessario
assegnarle un nome che solo quella cosa ha. Già un nome come Mario Rossi
non va bene, perché non è unico, e comunque, anche se scegliessimo oggi
un nome veramente strano e originale, non avremmo la garanzia in futuro di
non ritrovarci con un caso di omonimia. Ecco allora le targhe per le
automobili, i codici fiscali per le persone, i numeri di telefono, e via
dicendo. Ognuno di questi nomi ha tre caratteristiche. La prima è che
esiste un organo competente centrale che li assegna, proprio per
garantirne l'univocità. La seconda, è che hanno una struttura a
sottogruppi. Esistono cioè degli elementi che garantiscono l'univocità a
un certo livello, all'interno del quale esiste una certa libertà di
scelta, e così via, livello dopo livello. Per esempio, il codice fiscale
viene costruito in modo che un uomo e una donna non possano mai avere lo
stesso codice, anche se fossero nati lo stesso giorno, nella stessa città
e si chiamassero nello stesso modo. Similmente, i numeri di telefono di
due città diverse si distinguono per il prefisso e se queste si trovano
anche in stati diversi, per il prefisso internazionale. Affinché internet possa rappresentare un sistema universale di
comunicazione, permetta cioè di far comunicare qualunque macchina
connessa a una delle sue reti con una qualsivoglia altra macchina connessa
alla stessa o a un'altra rete, è necessario fornire ogni macchina di un
nome unico a livello globale. Internet fornisce ogni sistema di un nome,
che identifica il sistema stesso, di un indirizzo, che mi dice dove si
trova il sistema, e di un cammino, che mi dice come raggiungere il
sistema. Ogni macchina connessa a una rete è detta host, nella
terminologia internet. Lo stesso termine ha significati differenti in
altri contesti informatici, come per esempio in quello client/server, o
nel caso di mainframe. Attenzione a non fare confusione quindi. In
internet un host può essere anche un vecchio 8088 con 640K di RAM e 10M
di disco fisso. L'indirizzo, o IP address, è un campo composto da 32 bit. I primi bit
permettono di distinguere 5 forme standard identificate da una lettera del
alfabeto, e dette classi. Le prime tre classi dell'IP address contengono
sia l'indirizzo di una rete (netid), sia quello di una macchina nella
stessa (hostid). In realtà l'indirizzo non identifica necessariamente una
macchina, ma una connessione alla rete. Per esempio, un router ha almeno
due indirizzi, avendo connessioni ad almeno due reti. Questo in quanto un
router appartiene a entrambe le reti, e quindi sono necessari due
indirizzi dato che un IP address ha posto per un solo indirizzo di rete.
Se l'indirizzo dell'host è 0, allora l'IP address si riferisce alla rete
stessa. Se viceversa tutti i bit riservati all'indirizzo dell'host sono 1,
allora l'indirizzo viene utilizzato per identificare tutti gli host della
rete (broadcasting). Uno speciale indirizzo formato da 32 bit posti a uno
è chiamato local network broadcast address e serve solo in casi molto
particolari. Il concetto di broadcasting è quello della diffusione a
tutto raggio, un po' come fa un'emittente radiofonica. In generale
internet interpreta i campi formati da tutti uno come all, cioè
"tutti", mentre quelli formati da tutti zero come this, cioè
"questo", "qui". Questo per quanto riguarda le classi
A, B e C. La classe D è usata per un particolare tipo di distribuzione
dei dati detto multicasting. La classe E è riservata a usi futuri. Dato
che specificare ogni singolo bit di un indirizzo IP sarebbe alquanto poco
pratico e di scarsa leggibilità, la convenzione è quella di leggere ogni
ottetto, cioè ogni gruppo di 8 bit, come un intero, e di separare i
quattro ottetti con un punto. Oltre a i casi speciali già descritti,
l'indirizzo di classe A 127.0.0.0 è riservato per un particolare processo
di test che rimanda indietro i dati al mittente senza propagarli nella
rete. Uno dei vantaggi di questo schema è la possibilità da parte
dell'organismo centrale che assegna gli indirizzi (Network Information
Center) di delegare ai responsabili delle singole reti l'assegnazione di
una parte dell'indirizzo all'interno della rete stessa. La cosa avviene un
poco come con i numeri di telefono. A livello internazionale ogni stato ha
il suo prefisso internazionale. Per esempio, per l'Italia, è 39.
All'interno ogni stato divide il paese in aree geografiche a cui assegna
un ulteriore codice. Per esempio, Roma è identificata dal 6, Milano dal
2, Firenze da 55, e così via. All'interno poi della provincia o della
città possono essere definite ulteriormente sottoaree a cui si assegnano
due, tre o quattro cifre. Per esempio 529 oppure 7054. Infine ogni
telefono in tali aree avrà il suo numero. Così, se Mr. Smith deve
chiamare dagli Stati Uniti il signor Mario Rossi abitante all'EUR, a Roma,
comporrà per esempio il numero 011.39.6.529.4467. In questo caso lo 011
serve per uscire dagli USA, un po' come il nostro 00. Analogamente in internet i numeri di classe C sono assegnati alla
piccole reti, quelle cioè con meno di 256 host, quelli di classe B alle
reti con al massimo 65536 host, e quelli di classe A alle reti con oltre
16 milioni di host. Ogni rete decide poi come suddividere gli indirizzi
che gli sono stati riservati al suo interno come meglio crede. Ovviamente,
una internet privata non ha la necessità di seguire queste regole, né a
utilizzare indirizzi assegnati dal NIC, ma il non farlo potrebbe impedire
in futuro la connessione alla TCP/IP Internet. Dato che l'indirizzo può essere a volte abbastanza ostico da
ricordare, è possibili associare a ogni host anche un nome, che può
essere utilizzato come mnemonico per un IP address, e la cui risoluzione
è responsabilità di particolari macchine chiamate name server. In
realtà il name server è un programma software che può girare in
qualunque macchina connessa alla rete, e che mantiene l'associazione tra
nomi e indirizzi IP, fornendo tali corrispondenze quando richiesto da un
altro programma chiamato name resolver. Di fatto, si preferisce far girare
il name server su una macchina dedicata, che prende anch'essa, a questo
punto, il nome di name server. Potete pensare al name server come a una
agenda telefonica elettronica, che contiene una lista parziale di nomi e
numeri telefonici. In internet infatti, non esiste un singolo elenco
telefonico, ma tanti name server che cooperano per fornire quello che è
un vero e proprio elenco distribuito. In realtà il sistema funziona in
modo gerarchico, un po' come se una certa agenda contenesse solo i
prefissi internazionali e il puntatore alle agende di ogni singolo stato,
le quali a loro volta contengono i prefissi regionali e i puntatori agli
elenchi regionali, e così via, fino ad arrivare all'agenda che contiene
solo le estensioni telefoniche di un singolo edificio. I nomi Internet sono basati su una serie di regole dette Domain Name
System (DNS), che si basa appunto su uno schema gerarchico in cui il nome
è suddiviso in varie parti separate fra loro da punti. Per esempio,
vnet.ibm.com. Ogni suffisso è a sua volta un dominio. Quindi, nel nostro
esempio, ibm.com è un dominio di secondo livello, mentre com è un
dominio di terzo livello. I domini ufficiali riconosciuti dal NIC al
livello più elevato sono riportati in tabella 1. Una rete può richiedere
di essere registrata come categoria, oppure usando il dominio geografico.
Per esempio, l'Italia ha come dominio base it. Supponiamo che il governo
decida di costruire un insieme di reti cittadine interconesse fra loro e
connesse a Internet. Si può pensare di assegnare a ogni provincia un
dominio xxxxxx.it. Per esempio, Firenze avrebbe come dominio firenze.it.
L'università di Firenze potrebbe registrare la sue rete come unifi.edu, e
in tal caso sarebbe direttamente il NIC a dover dare l'autorizzazione per
tale nome, essendo il dominio edu sotto responsabilità dell'organismo
centrale di controllo, oppure potrebbe decidere di far parte del dominio
cittadino, come unifi.firenze.it, e quindi potrebbe richiedere il permesso
di registrare tale nome direttamente all'amministratore del dominio di
Firenze. A questo punto, se il dipartimento di Fisica di Arcetri vuole
registrare un proprio dominio, deve chiederlo solo all'Università stessa,
ricevendo così, per esempio, arcetri.usf.fi.it oppure fisica.usf.fi.it. Esiste una piccola complicazione. Ogni oggetto connesso alla rete ha un
tipo. Oggetti di tipo diverso possono avere lo stesso nome. Per cui, per
poter risolvere un nome e ottenere indietro l'indirizzo IP, è necessario
anche specificare il tipo di oggetto: macchina, utente, casella postale, e
via dicendo. Dal solo nome non è possibile evincere il tipo di oggetto. Il DNS definisce anche come associare i nomi agli indirizzi IP, e come
ottenere quest'ultimi dal nome. In realtà lo schema è ancora più
generale di quanto può sembrare, in quanto permette di estendere la
sintassi del nome per usi specifici, sfruttando anche il concetto di tipo.
Per esempio, nel caso di una casella postale (tipo MX), il nome sarà del
tipo utente@dominio. Per esempio ddejudicibus@tecnet.it Innanzi tutto una internet è un sistema di interconnessione fra reti
differenti che utilizza sia sistemi dedicati per la connessione, detti
gateway, sia uno strato (layer) di protocolli che mostrano alle
applicazioni una visione omogenea di una rete virtuale e che sono basati
sulla trasmissione di piccoli pacchetti di dati. Ogni pacchetto porta con
sé l'indirizzo del destinatario il quale identifica univocamente sia la
rete di destinazione che la connessione alla quale deve essere recapitato
il pacchetto. Un sistema connesso a più reti della stessa internet avrà
quindi più indirizzi IP. Un opportuno software, spesso installato su
macchine dedicate, permette di associare a ogni indirizzo un nome di più
facile utilizzo da parte degli utenti del sistema. Il formato di questo
nome si basa su un insieme di regole dette DNS. Quella che è
universalmente conosciuta come Internet è di fatto la principale rete
interconnessa esistente, che si estende praticamente su tutto il pianeta. Data questa premessa, vediamo di approfondire la trattazione dei
protocolli TCP/IP. Innanzi tutto qualunque trasferimento di dati implica
la trasmissione di bit da un sistema a un altro. Tali dati devono essere
correttamente interpretati dai vari sistemi connessi alla rete. Data
l'enorme varietà di hardware e di sistemi operativi questo è tutt'altro
che banale. Nei protocolli di trasmissione i bit vengono convenzionalmente
raggruppati per multipli di otto, detti ottetti. Una volta questo
corrispondeva al bus da 8 bit, cioè un byte, tipico dei computer. Oggi la
maggior parte dei computer usa parole di almeno 32 bit. Tuttavia non tutte
le macchine memorizzano tali parole nello stesso modo. Esistono vari modi
per memorizzare un intero rappresentato da 32 bit. In quello detto Little
Endian, la posizione più bassa in memoria contiene il byte di ordine più
basso dell'intero. Nei sistemi Big Endian avviene esattamente il
contrario, cioè la posizione più bassa in memoria contiene il byte di
ordine più elevato. In altri sistemi ancora il raggruppamento viene fatto
con parole da 16 bit, in cui la parola meno significativa viene appunto
prima. Il risultato è lo stesso del Little Endian ma con i byte invertiti
all'interno di ogni singola parola. È evidente che non è pensabile che
sia la rete a gestire tutti questi modi diversi di interpretare i dati,
anche perché di solito i protocolli di trasmissione non entrano nel
merito di come ragionano i singoli sistemi, ma si occupano solamente di
trasferire in modo più o meno affidabile i dati a loro affidati. Ne
consegue la necessità di definire un formato standard valido per tutti i
dati che corrono lungo i collegamenti, lasciando a i vari sistemi il
compito di effettuare le opportune conversioni locali. Lo standard
internet prevede che gli interi vengano trasmessi a partire dal byte più
significativo, secondo lo stile del Big Endian. Così in un pacchetto, un intero ha il byte più significativo verso la
testa del pacchetto e quello meno significativo verso la coda dello
stesso. A questo punto i sistemi sono in grado di scambiarsi i dati in modo non
equivoco. Ma come fa a sapere la rete internet che un sistema è
collegato, e soprattutto, come avviene l'associazione tra l'IP address e
l'indirizzo fisico di rete? Ogni rete fisica infatti ha un suo formato per
gli indirizzi fisici assegnati alle connessioni di rete. In generale
esistono due modi di assegnare indirizzi fisici alle macchine connesse in
rete. In una rete piccola, come può essere una Token Ring, cioè un
anello di un paio di centinaia di macchine al massimo, a ogni connessione
può essere assegnato un intero basso, per esempio compreso tra 1 e 254.
Questo sistema ha il vantaggio di associare l'indirizzo fisico alla
connessione piuttosto che alla scheda che permette la stessa. Per cui, se
la scheda si rompe, l'utente può cambiarla senza dover tuttavia
modificare l'indirizzo fisico di rete, purché imposti sulla nuova scheda
lo stesso indirizzo di quella vecchia. Lo svantaggio è che non esiste
alcun controllo che impedisca a due utenti sulla stessa rete di impostare
lo stesso indirizzo fisico, creando così una collisione. In altri tipi di
reti, come per esempio Ethernet, ogni scheda ha già preimpostato da parte
del costruttore un indirizzo fisico fisso, per cui non c'è alcun rischio
di collisione, ma cambiare la scheda vuol dire dover necessariamente
cambiare indirizzo fisico. Inoltre, dato che questo indirizzo è unico non
solo fra le schede installate su una certa rete, ma in assoluto fra tutte
le schede costruite, esso è generalmente molto lungo. Nel caso di
Ethernet è di ben 48 bit. Associare un IP address a un sistema con indirizzi formati da piccoli
numeri e per giunta tali che a parità di connessione l'indirizzo non
cambia mai, come nel caso di una rete proNET-10, è molto semplice. Per
esempio, per un IP address di classe C, si può usare l'indirizzo fisico
come host identifier. Così, se la rete ha IP address del tipo
10.214.32.x, l'host con indirizzo fisico 16 avrà IP address 10.214.32.16.
Un altro paio di maniche è gestire indirizzi molto più lunghi dei 32 bit
utilizzati per gli indirizzi internet, e per giunta che possono cambiare
nel tempo a parità di connessione. Ovviamente si potrebbe tenere da
qualche parte una tabella per gli accoppiamenti, e di fatto si fa così,
ma non è certo molto pratico pensare che qualcuno la tenga aggiornata a
mano. Il problema è stato risolto efficacemente utilizzando un meccanismo
di risoluzione dinamica implementato dal protocollo ARP, o Address
Resolution Protocol. ARP funziona più o meno così. Quando un host deve spedire un
pacchetto a un certo destinatario, spedisce a tutti gli host nella stessa
rete fisica un messaggio in cui chiede chi è l'host con quel ben preciso
IP address. Nello stesso messaggio mette anche i propri indirizzi, sia
quello fisico che quello IP. Si adopera cioè una tecnica di broadcasting.
L'host il cui IP è quello cercato, rimanda indietro al richiedente il
proprio indirizzo fisico, permettendo così l'associazione tra i due. Ciò
è possibile in quanto esso ha comunque ricevuto anche l'indirizzo fisico
del mittente. Ma allora per ogni pacchetto che va spedito a un certo IP
address è necessario prima mandare un pacchetto a tutti gli host nella
rete? E perché allora non mandare direttamente il pacchetto da
trasmettere a tutti, invece di chiedere prima chi è che ha un certo
indirizzo IP? Ovviamente la cosa non funziona così, anche perché si
rischierebbe di appesantire inutilmente la rete con pacchetti che vengono
recapitati ai sistemi sbagliati. Quello che si fa è di mantenere presso
ogni host una tabella con tutti gli accoppiamenti già trovati, e di
aggiornarla periodicamente per evitare che diventi obsoleta. A questo
punto i meccanismi di broadcasting servono ad aggiornare tali tabelle. Per
esempio, se un host deve spedire un pacchetto a un certo indirizzo IP,
prima controlla nella sua tabella se non ha già l'indirizzo fisico del
destinatario. Solo nel caso l'informazioni manchi, l'host spedisce a tutti
gli altri host il messaggio di richiesta. Quando questo arriva a un
qualunque host, sia esso il vero destinatario o no, ogni host aggiorna la
sua tabella con l'indirizzo fisico e quello IP del mittente, tanto per
guadagnare tempo. Il destinatario, in più, spedisce indietro anche il suo
indirizzo fisico al mittente, così da potergli permettere di aggiornare
la sua tabella di indirizzi. Un'ulteriore tecnica che si usa per
assicurarsi che tali tabelle siano sempre aggiornate, è quella di far
distribuire la propria coppia di indirizzi, fisico ed IP, ogni qual volta un sistema si
connette alla rete, per esempio al reboot. ARP non viene considerato propriamente un protocollo internet, quanto
un meccanismo della rete fisica. Su ARP si basa il protocollo IP per far
comunicare fra loro le varie macchine quando non è possibile risolvere in
altro modo gli indirizzi IP in indirizzi fisici. Un protocollo analogo è
il RARP, o Reverse Address Resolution Protocol, con il quale una macchina
senza disco fisso (diskless) è in grado di conoscere il proprio indirizzo
IP a partire da quello fisico. Per far ciò la rete deve avere uno o più
RARP Server, i quali contengono una tabella di associazione fra gli
indirizzi IP e quelli fisici di tutte le macchine diskless. Anche questo
protocollo si basa su un messaggio mandato in broadcasting. L'esistenza di
questo protocollo è legata al fatto che una macchina diskless non può
memorizzare il proprio indirizzo IP in alcun posto, non avendo memoria
secondaria. E veniamo ora al TCP/IP vero e proprio. Come detto prima l'architettura
internet è basata su tre livelli. L'Application Services è il livello
più alto, cioè quello delle applicazioni. I programmi che utilizzate
quando usate internet ricadono in questo livello. Il Reliable Stream
Transport Service è il livello intermedio. Esso si occupa
dell'affidabilità della comunicazione, gestendo gli errori di
trasmissione e la perdita di eventuali dati. Esso inoltre fornisce una
visione della comunicazione ad alto livello, in cui esiste una connessione
tra i due host che si trasmettono grandi volumi di dati. Il livello più
basso, chiamato Connectionless Packet Delivery Service è quello che
effettua la spedizione vera e propria dei singoli pacchetti, senza
garantire l'affidabilità sulla singola trasmissione, nella modalità
detta connectionless. Il protocollo su cui si basa il livello più basso della torre internet
è appunto l'Internet Protocol, o IP. Tale protocollo si basa su alcuni
concetti fondamentali. Innanzi tutto il servizio che fornisce è detto
unreliable, cioè inaffidabile, in quanto non dà alcun garanzia che il
singolo pacchetto arrivi effettivamente a destinazione. In secondo luogo
è detto connectionless, cioè senza connessione diretta, in quanto la
trasmissione non avviene direttamente verso il destinatario, ma il
messaggio è lanciato nella rete lasciando poi a questa il compito di
portarlo a destinazione utilizzando l'indirizzo IP dell'host destinatario.
Infine si parla di best-effort delivery, cioè spedizione al meglio delle
possibilità, in quanto la rete fa tutto il possibile per portare comunque
a destinazione il pacchetto. In pratica l'IP si comporta come un naufrago
su un'isola deserta che lancia nella corrente un messaggio in una
bottiglia per un tizio che si trova su di un'altra isola dello stesso
arcipelago, contando sul fatto che se la bottiglia arriva sull'isola
sbagliata qualcuno ributterà a mare il messaggio fintanto che non
arriverà a destinazione. Detta così c'è quasi da stupirsi che internet
funzioni così bene. Anzi, che funzioni del tutto! In realtà non
dimentichiamoci che sopra al livello più basso ce n'è un altro che
garantisce appunto l'affidabilità della comunicazione. Torniamo comunque
all'IP. Esso è formato da tre regole base: come è fatto il pacchetto da
trasmettere, detto IP datagram, come avviene la scelta del cammino che il
pacchetto segue per raggiungere il destinatario, come gli host e i gateway
devono trattare i pacchetti e in particolare le modalità per l'emissione
dei messaggi di errore e quelle per la soppressione dei pacchetti. Prima però di entrare nel dettaglio dei singoli campi, vediamo come si
comporta l'IP nella gestione dei pacchetti di dati. Questo ci permetterà
più avanti di comprendere meglio il significato di alcuni campi dell'IP
datagram. Innanzi tutto va ricordato che l'IP è un protocollo unreliable, non
dà cioè alcuna garanzia che il singolo pacchetto arrivi effettivamente a
destinazione, ed è connectionless, ovverosia il messaggio non viene
spedito direttamente al destinatario ma viene immesso nella rete lasciando
poi a questa il compito di portarlo a destinazione. Esso inoltre è di
tipo best-effort delivery, in quanto la rete fa tutto il possibile per
portare comunque a destinazione il pacchetto. Detto questo, vediamo come avviene la trasmissione vera e propria dei
dati. L'unità fisica di trasferimento dei dati in una rete è la frame.
Questa è composta di due parti: l'intestazione (header) e l'area dati
(data area). L'unità di misura è invece l'ottetto, formato da otto bit,
cioè un byte. Ogni rete fisica ha un limite massimo di capacità di
trasferimento per un singolo frame, detto Maximum Transfer Unit (MTU). L'MTU
è cioè il massimo numero di ottetti di dati che può essere trasferito
in un singolo frame. Per esempio, Ethernet ha generalmente una MTU di
1.500 ottetti (1492 secondo lo standard IEEE 802.3). Questo vuol dire che
se si devono spedire 2.000 byte di dati via Ethernet, è necessario
spezzarli in due blocchi tali che ogni blocco sia minore o uguale a 1.500.
A ogni blocco si aggiunge poi l'intestazione del frame. Dal punto di vista
della rete fisica l'IP datagram è un blocco di dati. La rete fisica
ignora cioè come tali dati vengano utilizzati dall'IP. Quindi, il primo
compito di IP è quello di decidere come costruire il datagram affinché
possa essere trasmesso in un frame fisico. L'ideale sarebbe di poter
mettere un singolo datagram in ogni frame, ottimizzando così la
trasmissione e semplificando la logica. Ma quale frame? Quello della rete
di partenza? Quello della rete di arrivo? E se durante la trasmissione il
datagram deve passare attraverso più reti con MTU differenti? Il punto è
che non c'è modo di fare una scelta che assicuri di avere un datagram per
frame. D'altra parte internet ha come obiettivo quello di svincolarsi
dalle caratteristiche fisiche delle varie reti interconnesse fra loro. E
allora? La soluzione adottata è molto semplice. Le dimensioni del
datagram sono scelte convenzionalmente secondo una logica del tutto
indipendente dalle MTU delle singole reti fisiche, dopodiché, a seconda
della rete in cui il datagram deve passare, questo è spezzato in più pezzi di
dimensioni inferiori alla MTU della rete fisica, detti frammenti (fragment). Il datagram è anch'esso un frame, che potremmo chiamare logico per
distinguerla da quello usata da una specifica rete fisica per trasmettere
i dati. Come tale anch'esso è formato da una intestazione e da un'area
dati. All'atto della frammentazione, ogni frammento viene costruito
replicando l'header del datagram, modificandone alcuni campi che vedremo
in seguito, e aggiungendo a questo un pezzo dell'area dati originaria.
L'aspetto più importante di questo meccanismo è che il riassemblaggio
dei frammenti non viene effettuato quando i vari frammenti rientrano in
una rete fisica ad alto MTU, ma sempre e comunque presso l'host di
destinazione. Così, se due reti con MTU uguale a 1.500 ottetti sono
separate da una rete con MTU più bassa, per esempio 500 ottetti, i
frammenti che arriveranno a destinazione saranno di soli 500 ottetti. In
questo caso la frammentazione avviene nel primo gateway mentre il
riassemblaggio avviene solo nell'host di destinazione. Il protocollo IP
richiede che sia gli host che i gateway siano capaci di gestire datagram
di almeno 576 ottetti. In aggiunta, questi ultimi devono essere capaci
anche di gestire datagram grandi quanto l'MTU più grande tra quelle delle
reti a cui sono connessi. Ricordiamo che un gateway, per definizione, ha
almeno due connessioni e quindi almeno due indirizzi IP. Il punto debole di questo meccanismo è che la perdita di anche un solo
frammento comporta la perdita dell'intero datagram. Dato che ogni
frammento è trasmesso indipendentemente, passare attraverso reti a bassa
MTU comporta un'elevata frammentazione anche nelle reti a maggiore MTU e
comunque aumenta i rischi di perdita dei dati. Quando un frammento arriva
a destinazione, e non è detto che il primo arrivi per primo, l'host fa
partire un timer. Se questo scade prima che tutti i frammenti siano
arrivati, il sistema cancella tutti i frammenti e considera perduto il
datagram. Il concetto di timer e di tempi è estremamente importante per
l'IP ed è spesso usato per ottimizzare la rete. Per esempio, ogni
datagram ha una scadenza. Se il datagram è ancora all'interno della rete
quando il suo tempo è scaduto, esso viene cancellato definitivamente. Lo
scopo è quello di evitare che un pacchetto possa restare all'infinito in
internet a causa di un errore in una routing table. Queste tabelle infatti
servono a gestire il processo di instradamento del pacchetto nella rete.
Se una o più tabelle sono sbagliate, si potrebbero creare cammini chiusi
in cui i datagram potrebbero rimanere intrappolati. Veniamo finalmente al
formato del datagram. Come si è già detto esso è composto di
un'intestazione e di un'area dati. L'area dati contiene semplicemente una
parte dei dati da trasmettere. Questo in quanto il datagram è piccolo
mentre l'oggetto da trasmettere può essere anche molte centinaia di
Kilobyte, se non addirittura migliaia, come per esempio un'immagine o un
file compresso. L'intestazione è invece alquanto più complessa.
Vediamola in dettaglio. I primi 4 bit contengono la versione del protocollo IP che è stato
utilizzato per creare il datagram. Infatti, come spiegato nella prima
parte di questo corso, il tutto funziona se e solo se tutti seguono le
stesse regole alla lettera. D'altra parte le convenzioni, e di conseguenza
i protocolli, seguono un processo di evoluzione, per cui un datagram
creato con una versione più recente potrebbe creare problemi a un
protocollo più vecchio se questi non avesse modo di accorgersene in
tempo. I 4 bit successivi danno la lunghezza dell'intestazione misurata in
parole da 32 bit. Questa è necessaria agli algoritmi usati per leggere il
datagram (parsing algorithms). Dato che i campi dell'intestazione
potrebbere non risultare un multiplo intero di 32, è necessario porre
alla fine dell'intestazione un campo di riempimento. Inoltre il programma
di ricezione ha bisogno di conoscere anche la lunghezza totale del
datagram, cioè la lunghezza dell'intestazione più quella dell'area dati.
Questa è memorizzata nei bit dal 16 al 31 inclusi, e il suo valore è
espresso in ottetti, al contrario del precedente. Poichè il campo è
lungo 16 bit, il datagram non può essere più grande di 216 ottetti,
cioè 65.535 byte. Il campo tra la lunghezza dell'intestazione e quella totale del
datagram identifica il tipo di servizio che va offerto al pacchetto, ed è
formato da un campo di 3 bit che specifica l'importanza che va data al
datagram, e da tre campi da 1 bit ciascuno che identificano il tipo di
trasporto desiderato per questo pacchetto. Purtroppo questo campo non può
essere sempre preso in considerazione da tutte le reti, in quanto non
sempre la rete fisica è in grado di soddisfare le richieste di priorità
e trasporto memorizzate in questo campo. Per cui esso viene considerato
una sorta di raccomandazione alla rete, piuttosto che un vero obbligo. In
ogni caso il campo di priorità può contenere valori da 0 a 7. Lo zero è
il valore di base di un normale pacchetto, mentre il 7 rappresenta la
richiesta di precedenza più elevata, e va usato per i datagram che
contengono dati per il controllo della rete stessa. I tre bit relativi al
tipo di trasporto servono a definire il livello di qualità relativo al
trasferimento del pacchetto. Se impostati a uno, essi richiedono
rispettivamente: di evitare al massimo ritardi nel recapitare il pacchetto
al destinatario, di fornire la massima capacità di trasferimento, e di
garantire un'elevata affidabilità durante il trasporto. Ovviamente è
estremamente difficile poter fornire tutti e tre questi servizi
contemporaneamente. Spesso la rete non riesce a garantirne neanche uno
solo. I tre campi successivi vengono utilizzati nel meccanismo di
frammentazione spiegato poco fa, e in particolare sono quelli che
permettono all'host che riceve i vari frammenti di riassemblare il tutto
per ottenere il datagram originario. Essi sono assolutamente necessari in
quanto non è prevista alcuna comunicazione tra il mittente e il
destinatario su come ricomporre il datagram, tanto più che la
frammentazione finale può essere il risultato di più frammentazioni
successive. Inoltre i vari frammenti possono arrivare in qualunque ordine,
dato che possono avere seguito cammini differenti. Dulcis in fundo, anche
se l'intestazione di ogni frammento è ottenuta da quella del datagram
originale, il quarto campo dell'intestazione di un frammento contiene la
sua lunghezza totale, e non quella di tutto il datagram. Quest'ultima
informazione deve essere calcolata dal destinatario in qualche modo. Ed
ecco il perché di questi tre campi. Il primo campo serve a identificare univocamente il datagram ed è
lungo 16 bit. Tutti i frammenti che appartengono a uno stesso datagram
hanno lo stesso identificativo. Il secondo campo è una maschera di 2 bit
che controlla il meccanismo di frammentazione. Il primo bit specifica se
il datagram può essere frammentato: se impostato a uno, la frammentazione
non è permessa. Il secondo bit serve a marcare l'ultimo frammento.
Vedremo tra un attimo a cosa serve. Il terzo campo contiene la posizione
dei dati del frammento nel blocco originale di dati misurato in parole da
64 bit. Questo campo si chiama fragment offset. Per esempio, se un
frammento ha un offset 7, vuol dire che il primo bit dei suoi dati
corrisponde al quattrocentoquarantanovesimo bit dei dati del frammento
originale, dato che 7 * 64 + 1 fa appunto 449. A questo punto è chiaro
come si può ottenere la lunghezza totale del datagram originario. Basta
sommare l'offset e la lunghezza totale dell'ultimo frammento,
riconoscibile grazie al secondo bit del campo di controllo. Il campo successivo, posto a partire dal 64° bit dell'ntestazione, è
lungo un byte e serve a stabilire quanto a lungo un datagram può rimanere
nella rete. È cioè il campo che specifica la scadenza di un datagram di
cui avevamo accennato in precedenza. L'idea originaria era che tale campo
contenesse il numero massimo di secondi che il pacchetto potesse restare
nella rete. Tuttavia, data l'evidente difficoltà di sincronizzare gli
orologi di tutti gli hosts e i gateway della rete, si è deciso di
semplificare il meccanismo come segue: ogni gateway che processa il
pacchetto decrementa il campo di uno quando questo arriva e memorizza il
tempo di arrivo. Se il pacchetto non riparte subito ma rimane in attesa
nel gateway, il valore di questo campo viene ulteriormente decrementato di
una unità per ogni secondo di attesa. Come il campo arriva a zero, il
datagram viene cancellato dalla rete e un messaggio di errore viene
rispedito al mittente. Il campo seguente, lungo 8 bit, identifica il protocollo di alto
livello utilizzato che ha generato i dati contenuti nel datagram, e
definisce di fatto il loro formato. Ne riparleremo in seguito, quando
vedremo i protocolli applicativi. Abbiamo quindi un campo di controllo di 16 bit che serve a verificare
l'integrità dell'intestazione, e che utilizza il meccanismo di checksum
ben conosciuto nel mondo del software. In suo valore è la somma
complementata a uno delle parole da 16 bit che compongono l'intestazione,
addizionate con il metodo del complemento a uno. Quindi vengono gli indirizzi IP del mittente e del destinatario, ognuno
lungo 32 bit. Di tali indirizzi e del loro scopo abbiamo parlato
esaustivamente nella seconda e terza parte di questo corso. Per finire abbiamo un campo di lunghezza variabile che può contenere
varie opzioni, e quindi il campo di riempimento di cui abbiamo già
parlato. Queste opzioni non sono presenti in tutti i datagram e vengono
usate prevalentemente nelle verifiche e nella identificazione dei problemi
della rete. Parliamo ora di due aspetti fin qui solo accennati: i meccanismi di
instradamento dei pacchetti (routing) e la gestione degli errori.
Iincominceremo a salire nella torre dei protocolli internet, introducendo
il primo protocollo che si poggia sull'IP, e precisamente lo User Datagram
Protocol (UDP). Come vedremo si tratta ancora di un protocollo molto
legato all'IP, ma comunque considerato al di sopra di questo. Come abbiamo già detto in precedenza, IP è un protocollo
connectionless. Questo vuol dire che non esiste un collegamento diretto
tra i due host che si scambiano dati, bensì una rete di connessioni
attraverso la quale si possono identificare vari potenziali cammini da un
host all'altro. Il cammino attraverso il quale i dati giungono all'host
destinatario è scelto dinamicamente e può variare per ogni singolo
pacchetto di dati. Tale scelta non avviene quando il pacchetto parte, ma è il risultato
di numerose decisioni prese a ogni singolo gateway. Per questo motivo i
gateway sono detti anche router. Tali scelte possono dover tenere conto di
molti elementi, quali la priorità del messaggio, il carico di rete, la
capacità delle reti intermedie, e via dicendo. La base tuttavia del
meccanismo sono le tabelle di instradamento (routing table). Vediamo di
che si tratta. Consideriamo prima una singola rete fisica. Se un host vuole spedire un
messaggio a un altro host nella stessa rete, non deve far altro che
incapsulare il messaggio in un datagramma IP fornendo quindi l'indirizzo
IP del destinatario, e passare il tutto al livello inferiore. Questi
provvederà a ricavare dall'indirizzo IP l'identificativo del destinatario
nella rete fisica, a incapsulare il datagramma in un frame, e a spedire
direttamente il tutto all'host finale . Questa tecnica si chiama
instradamento diretto (direct routing). Vediamo adesso quello che succede quando abbiamo due reti interconnesse
tramite un gateway. L'host mittente si accorge che il destinatario non è
nella sua rete fisica, dato che il network id del suo indirizzo IP è
diverso da quello a cui deve spedire il datagramma. Spedisce allora il
messaggio al gateway passando sia il datagramma che l'indirizzo IP del
gateway al livello inferiore. Il messaggio arriva quindi direttamente al gateway che estrae l'indirizzo
IP del destinatario, si accorge che fa parte della seconda rete a cui è
connesso, e spedisce quindi il tutto all'host finale attraverso la rete
fisica. Questa tecnica si chiama di instradamento indiretto (indirect
routing). In questo secondo caso la tabella di instradamento è semplice, dato
che il gateway ritrasmette sempre i messaggi che devono andare da una rete
all'altra attraverso la rete fisica. Se invece abbiamo più gateway tra i
due host, ogni gateway, tranne l'ultimo, dovrà spedire il messaggio a un
altro gateway. Per far questo userà appunto la tabella di instradamento che fornisce
per ogni possibile rete destinataria finale l'indirizzo IP del gateway
successivo. È da notare che queste tabelle non contengono di solito gli
indirizzi IP di tutti i possibili host destinatari, cosa che sarebbe
fisicamente impossibile, bensì gli indirizzi delle reti raggiungibili a
partire da quel gateway. Esiste poi la possibilità di specificare un
gateway di default e cammini specifici per host speciali. La prima cosa è
molto comune, mentre la seconda è usata solo in casi eccezionali. La
logica di instradamento è quindi quella riportata nel diagramma. La gestione dei messaggi di errore: Come si può facilmente capire dal
meccanismo di instradamento appena spiegato, non è possibile sapere se il
destinatario effettivamente esiste fintanto che il datagramma non arriva
all'ultimo gateway. In generale l'IP non contiene grossi meccanismi di
verifica, ed è per questo che è detto "inaffidabile". Esso
richiede quindi un protocollo ausiliaro per l'emissione di messaggi di
errore in rete, che permettano ai protocolli di livello superiore di
implementare una logica più affidabile e robusta. Tale protocollo è
chiamato Internet Control Message Protocol (ICMP). L'ICMP è considerato parte integrante dell'IP ed è sostanzialmente un
protocollo per la segnalazioni di errori il cui utente principale è l'IP
stesso. Solo in casi particolari l'ICMP arriva a informare dell'errore
eventuali livelli superiori all'IP. In ogni caso cosa fare quando avviene
un errore non spetta all'ICMP, ma ai processi che se ne avvalgono. L'ICMP
informa sempre l'IP mittente, non i vari gateway intermedi. Questo in
quanto del cammino percorso da un datagramma non rimane traccia, per cui
l'unico possibile destinatario di un messaggio di errore è solo chi ha
generato il datagramma. Inoltre, dato che i datagrammi ICMP viaggiano
incapsulati in datagrammi IP, come un qualunque messaggio di livello
superiore, essi sono soggetti alle stesse limitazioni in termini di
affidabilità di qualunque altro messaggio spedito via TCP/IP. Non
analizzeremo in dettaglio tutti i datagrammi ICMP, anche perché ognuno
può avere una struttura differente. Diremo solamente che l'intestazione
di un datagramma ICMP contiene sempre almeno tre campi: il tipo di
messaggio, un codice di errore, e una somma di controllo. Il livello di Trasporto:Come sicuramente ricorderete, la torre Internet
si può schematizzare più o meno su quattro livelli. Alla base della
torre sta l'hardware che rappresenta la rete vera e propria. Sopra a
questo sta il primo livello, quello cioè di interfaccia alla rete fisica,
detto appunto Network Interface o anche Data Link. I protocolli a questo
livello si scambiano blocchi di dati chiamati frame, la cui struttura è
strettamente legata alle caratteristiche hardware della rete stessa. Al di
sopra di questo livello c'è il livello di interconnessione fra reti,
ovverosia il livello dell'IP la cui unità di scambio dati è appunto il
datagramma IP, mentre il terzo livello è quello detto di Trasporto.
Concettualmente è a questo livello che si pongono sia il TCP che appunto
l'UDP. L'unità di scambio dati al terzo livello si chiama pacchetto (transport
packet). Il quarto livello è infine quello Applicativo, al quale vengono
scambiati i messaggi applicativi (message e data stream). Abbiamo visto come l'IP permette di scambiare datagrammi fra host,
cioè a livello di macchine. Tuttavia non è certo la macchina il
destinatario finale dei dati che fluiscono nella rete, bensì le
applicazioni e i programmi che girano su di essa. I moderni sistemi
operativi permettono di far girare più processi contemporaneamente, e
comunque questi non hanno le caratteristiche di permanenza che ha invece
un host. Un programma infatti è un componente dinamico e temporaneo in un
sistema. Non è quindi pensabile di poter associare a un processo un
identificativo fisso come si fa con gli host e gli indirizzi IP. Un
processo infatti non ha un identificativo univoco in un sistema, dato che
ogni volta che viene lanciato esso può assumere un identificativo
diverso. Inoltre non è detto che gli stessi dati siano sempre processati
dalla stessa applicazione. Per esempio, oggi potrei voler usare un certo
programma per gestire la mia posta elettronica, domani potrei decidere di
usarne un altro, e non è sicuramente pensabile che si debba informare
tutta la rete ogni volta che si decide di cambiare l'applicazione che
gestisce un certo tipo di dati. Quindi, più che il processo, quello che
è importante identificare è la funzione, come per esempio, trasferire
file oppure spedire posta elettronica. Lo scopo dell'UDP è appunto quello
di permettere di distinguere in un singolo host più destinatari per i
dati che arrivano dalla rete. Ma come? Facciamo un attimo una digressione. Se io devo stampare un file cosa
faccio? Collego al mio computer una stampante, attivo il driver
corrispondente, e uso una applicazione o un comando del sistema operativo
per lanciare l'ordine di stampa. Se ora stacco la stampante e ne attacco
un'altra alla stessa porta non devo far altro che cambiare il driver per
continuare a lavorare senza che il sistema si sia accorto di niente. Se
poi le due stampanti usano lo stesso driver generico devo solo staccare e
riattaccare fisicamente le stampanti senza modificare il sistema. In
generale, tutto lo scambio di dati da e verso un computer avviene
attraverso porte di I/O. Ogni applicazione accede la porta che gli serve
in modo dinamico. La periferica di I/O non ha bisogno di sapere con quale
applicazione sta parlando: la porta fa da schermo fra i due. L'UDP fa una
cosa analoga. Esso permette di associare a un indirizzo IP più punti di
ingresso e di uscita virtuali, detti protocol port. Queste porte si
comportano un po' come quelle di I/O di un computer. Ogni porta è
identificata da un numero intero positivo e i processi possono chiedere al
sistema l'accesso a tali porte. Quando un processo accede una porta, esso
si mette in attesa dei dati sulla stessa. Il meccanismo è cioè sincrono.
Inoltre, se dei dati arrivano a una porta alla quale non si è agganciato
ancora un processo, questi vengono mantenuti in memoria nell'ordine di
arrivo. Si dice cioè che le porte hanno un buffer. A questo punto, sia il
processo mittente che quello destinatario sono univocamente identificati
dall'indirizzo IP dell'host su cui girano e dal numero di porta che
utilizzano per la trasmissione in rete. Tale associazione è tuttavia dinamica, e così come più applicazioni possono stampare sulla stessa
stampante purché non contemporaneamente, così più processi possono
attaccarsi uno alla volta alla stessa porta ed essere visti come lo stesso
destinatario dalla controparte mittente. Questo permette di spedire un
file di testo con un word processor, e subito dopo spedire un file
binario, per esempio un file ZIP, con un comando di sistema. Il tutto
sempre attraverso la stessa porta e con lo stesso destinatario in termini
di host e di processo. Come abbiamo visto nel caso dell'IP e dei vari protocolli di rete,
anche il datagramma UDP è formato da una intestazione (header) e da una
parte dati. Esso è inoltre incapsulato in un datagramma IP il quale a sua
volta è contenuto in un frame della rete fisica. Al contrario tuttavia di quello IP, l'header UDP è molto più
semplice. Esso è formato dal numero di porta del mittente, da quello del
destinatario, dalla lunghezza del messaggio UDP, sia dei dati che
dell'intestazione, e da una somma di controllo (checksum) per la verifica
dell'integrità dei dati. Infatti, anche l'UDP, come l'IP, è un
protocollo cosiddetto "inaffidabile". Questo vuol dire che un
messaggio UDP può andare perso, essere duplicato, o arrivare nell'ordine
sbagliato. L'UDP non fa alcun controllo al riguardo. L'affidabilità della
comunicazione è infatti affidata a i protocolli di livello più elevato.
Tutti i campi dell'intestazione sono lunghi 16 bit. Benché il formato del
datagramma UDP sia alquanto semplice, la sua gestione può essere un po'
più complessa. Il punto riguarda la somma di controllo. Questo valore è
opzionale e, al contrario di quello che succedeva con la somma di
controllo nel datagramma IP, esso non è relativo solo all'intestazione ma
a tutto il datagramma, compresa la parte dati. Questo vuol dire che tale
campo rappresenta l'unico elemento di controllo a livello IP e UDP
dell'integrità dei dati arrivati. Se esso non viene utilizzato, il campo
va posto a zero. Questo in quanto la somma di controllo segue la logica a
complemento uno. Il che vuol dire che se la somma calcolata è zero, essa
può essere memorizzata come 16 bit impostati a uno, dato che in tale
logica un valore con tutti i bit a uno e uno con tutti i bit a zero
rappresentano lo stesso numero. Quindi: se il campo è a zero, vuol dire
che non è stato utilizzato; se viceversa ha tutti i bit ad 1, vuol dire
che la somma era zero. La somma di controllo, tuttavia, per ragioni pratiche, non riguarda
solo il datagramma UDP, ma viene calcolata anche utilizzando alcune
informazioni addizionali. Queste formano il cosiddetto pseudo-header. In pratica, quando si deve calcolare il checksum UDP si mette davanti
al datagramma UDP un altro blocco di dati che contiene l'indirizzo IP del
mittente e del destinatario, il codice del protocollo UDP (17), e la
lunghezza del datagramma UDP. Il motivo di questa tecnica risiede nel
fatto che, per verificare se un messaggio UDP è arrivato al giusto
destinatario, non basta verificare il numero di porta, ma anche
l'indirizzo IP dell'host in cui gira il processo che è collegato a quella
porta. Il datagramma UDP contiene tuttavia solo il numero di porta, per
cui il controllo fornito da una somma sul solo datagramma non potrebbe
realmente verificare che il destinatario dei dati è quello giusto. Questa tecnica ha tuttavia un prezzo: gli indirizzi IP fanno parte
appunto del livello internet, non di quello di trasporto. Perché l'UDP
conosca tali informazioni è necessario violare una legge fondamentale dei
protocolli di comunicazione, e cioè che ogni livello della torre deve
gestire i suoi dati senza esportarli agli altri livelli a cui offre, o da
cui riceve, servizi. Questo vuol dire che la separazione tra UDP ed IP non
è così pulita come dovrebbe essere, ma che i due protocolli sono in
qualche modo legati tra loro. D'altra parte i vantaggi in termini di
controllo e semplicità di implementazione sono tali che si è deciso di
fare un'eccezione ai principi dell'architettura. Da notare che la
pseudo-intestazione serve solo a calcolare la somma di controllo. Essa non
viene fisicamente trasmessa con il datagramma UDP, dato che le
informazioni che contiene fanno già parte dell'intestazione del
datagramma IP in cui quello UDP sarà incapsulato. Se l’IP rappresenta il braccio del TCP/IP, il TCP ne rappresenta la
mente. Il primo si limita a spedire rapidamente i dati che gli arrivano
senza preoccuparsi troppo se qualcosa va male. Il secondo si occupa invece
di controllare che l’informazione passatagli dai livelli superiori
arrivi correttamente a destinazione. Insieme sono sicuramente una coppia
molto affiatata. In questo articolo useremo il termine applicazioni per indicare tanto i
protocolli applicativi come FTP o SMTP, quanto i programmi applicativi
veri e propri, salvo indicazione contraria. Indicheremo inoltre con il
termine utente di un servizio colui che utilizza tale servizio, sia esso
direttamente una persona, un’applicazione, o un protocollo. Per esempio,
il TCP è un utente dell’IP. C’è subito da dire due cose importanti sul TCP. La prima è che lo
standard del TCP non definisce né l’implementazione dello stesso, né
le modalità con cui un’applicazione accede a i servizi di questo
protocollo. Esso definisce solamente le caratteristiche di tali servizi,
per cui si possono trovare molte differenti implementazioni del TCP,
ognuna con la propria interfaccia applicativa. Per chi non programma
ricordo che un’interfaccia applicativa o API (Application Programming
Interface) non è altro che l’insieme delle funzioni, delle istruzioni,
dei parametri e dei blocchi di controllo che vengono utilizzati dai
programmatori per accedere ai servizi di un sistema. Per esempio, se ho un
sistema di posta elettronica potrei definire un’API basata su due
funzioni, una chiamata spedisci, e una chiamata ricevi . Per ogni funzione
sarebbero poi da definire quali informazioni sono da passare al momento
dell’utilizzo (parametri in ingresso), quali si ottengono una volta
espletato il servizio (parametri di ritorno), eventuali codici di errore,
e le regole di utilizzo delle singole funzioni. Il motivo che sta alla
base della scelta di non standardizzare l’interfaccia con il TCP è che
in molti casi questo protocollo è direttamente definito nel sistema
operativo, o comunque fa parte del cosiddetto corredo di base di un
sistema, per cui si è voluto evitare di forzare una sintassi che potesse
essere in contrasto con quella nativa del sistema ospite. Il secondo punto
fondamentale è che il TCP è stato definito per funzionare su un
qualsiasi sistema di trasmissione dati a pacchetto, e non necessariamente
solo sull’IP. Di fatto esso può essere poggiato, per esempio,
direttamente sopra una rete Ethernet senza bisogno di un livello Internet
intermedio. Ma qual è lo scopo del TCP nell’architettura internet? Il protocollo
non fornisce le garanzie di affidabilità e robustezza necessarie per
implementare un sistema di trasmissione dati sicuro e di facile gestione.
L’IP è inaffidabile e benché schermi lo sviluppatore dalla conoscenza
della rete fisica, fornisce ancora una visione di livello troppo basso del
sistema di reti interconnesse. Questo vuol dire che l’IP è troppo
complesso per essere utilizzato direttamente dalle applicazioni. Per avere
un protocollo di trasmissione affidabile abbiamo bisogno di gestire tutte
le possibili situazioni di errore, la duplicazione o la perdita dei
pacchetti, la caduta delle connessioni o di un router, e via dicendo. Se
le applicazioni utilizzassero direttamente i servizi dell’IP, ognuna di
esse dovrebbe implementare una serie alquanto complessa di algoritmi e
servizi per tenere conto di tutto ciò. A parte il fatto che esistono
relativamente pochi programmatori in grado di far questo fra gli svariati
milioni di sviluppatori di applicazioni, nella maggior parte dei casi si
tratterebbe di reinventare ogni volta la ruota. In generale questi
problemi, seppure complessi, sono abbastanza standard, per cui si è
pensato di poggiare sui sistemi di trasmissione a pacchetti un protocollo
affidabile che potesse essere implementano da sviluppatori altamente
specializzati, lasciando così agli altri la possibilità di concentrarsi
sulla logica applicativa piuttosto che sugli aspetti specifici della
trasmissione dei dati a basso livello. Vediamo allora quali sono le caratteristiche principali del TCP,
eventualmente comparate a quelle dell’IP. Innanzi tutto il TCP fornisce una visione dei dati di tipo a flusso
(data stream), cioè i dati sono ricevuti in sequenza e nello stesso
ordine con il quale sono stati trasmessi. A questo livello cioè, l’utente
del TCP spedisce i dati come un singolo flusso di byte e nello stesso modo
li riceve. Nell’IP avevamo invece la divisione dei dati in pacchetti che
potevano subire un’ulteriore frammentazione se si trovavano a passare
attraverso reti caratterizzate da una soglia molto bassa sulle dimensioni
dei frame fisici. I pacchetti potevano inoltre arrivare in ordine sparso
rispetto a quello di trasmissione. Secondo punto: nell’IP non si sa mai a priori il cammino che effettua
un pacchetto. Il TCP fornisce al suo utente una visione del collegamento
come se esso fosse una linea dedicata. Ovviamente sotto sotto il
meccanismo è ancora quello a pacchetti, ma la cosa è schermata agli
utilizzatori del TCP. Tale caratteristica è detta vitual circuit
connection, cioè circuito di connessione virtuale. Il TCP si basi sul
concetto di connessione, piuttosto che su quello di indirizzo come fa
invece l’IP. Una connessione, per definizione, richiede la definizione
di due punti piuttosto che di uno solo, detti punti terminali o estremi
della connessione (endpoint). Parleremo anche di interlocutori per
indicare gli utenti posti agli estremi della connessione. Terzo punto: abbiamo visto che l’IP divide i dati in pacchetti che
vengono costruiti sulla base di esigenze di trasmissione legate alle varie
reti fisiche su cui si poggia il sistema. D’altra parte le applicazioni
dividono i dati in funzione delle esigenze applicative. Per esempio, un’applicazione
di posta elettronica può considerare una lettera da 8.000 caratteri una
singola unità dati, mentre un protocollo per la gestione della rete può
avere l’esigenza di spedire tanti piccoli messaggi di non più di 16
byte l’uno. Il TCP permette di disaccoppiare il modo di dividere i dati
delle applicazioni da quello dell’IP. Così la lettera di cui sopra
viene prima spezzata in tante parti, spedita via IP e poi ricomposta dal
livello TCP del destinatario, mentre per i messaggi di controllo avviene
il contrario: prima vengono accumulati in un singolo pacchetto, e poi
rispezzettati presso il destinatario. Questo meccanismo è detto buffered
transfer. Naturalmente può sorgere l’esigenza di forzare la
trasmissione dei dati anche se il buffer non è pieno. Per esempio, se
serve sapere se un certo sistema è attivo o meno manderò prima un
messaggio di interrogazione, e solo una volta ricevuta la conferma
incomincerò a spedire gli altri dati. Dato che il messaggio di
interrogazione è più piccolo del buffer, esso non verrebbe realmente
spedito dal TCP fintanto che questi non è stato riempito. È quindi
necessario forzare la trasmissione del primo messaggio (push) se si vuole
evitare di attendere inutilmente la risposta a un messaggio che in realtà
non è mai partito. Quarto punto: per quanto intelligente, il TCP si preoccupa di
trasferire i dati che gli vengono passati senza entrare in merito a il
loro significato dal punto di vista applicativo. In che modo il flusso di
dati vada interpretato semanticamente è responsabilità delle due
applicazioni che utilizzano la connessione TCP per cooperare. Questo vuol
dire che se un’applicazione manda alla sua controparte una serie di
indirizzi, questi arriveranno uno di seguito all’altro nel giusto
ordine, ma senza alcuna garanzia che ogni buffer contenga un numero intero
di indirizzi. Sta all’applicazione ricomporre un indirizzo capitato a
cavallo di due buffer consecutivi. Si parla quindi di flusso senza
struttura (Unstructured Stream). Quinto e ultimo punto: le connessioni TCP permettono il trasferimento
contemporaneo dei dati in entrambe le direzioni, quello che nel gergo
delle comunicazioni si chiama una connessione full-duplex. Si hanno cioè
due flussi che scorrono indipendentemente in direzioni opposte, senza
interagire fra loro. Le applicazioni hanno comunque la possibilità di
passare alla modalità half duplex semplicemente bloccando uno dei due
flussi di dati. Ma in che modo il TCP garantisce quella affidabilità che manca all’IP?
Il meccanismo di base utilizzato sia dal TCP che da molti altri protocolli
cosiddetti "affidabili" è quello della ritrasmissione in caso
di mancata conferma (positive acknowledgement with retrasmission). Si
tratta di un meccanismo concettualmente semplice: ogni qual volta uno dei
due interlocutori di una connessione spedisce dei dati, questi attende una
conferma dell’avvenuta ricezione. Se questa arriva entro un tempo
stabilito viene spedito il pacchetto successivo, altrimenti l’applicazione
rispedisce quello precedente. Tale tempo viene misurato con un timer che
viene fatto partire ogni volta che un pacchetto è spedito. Questo
meccanismo risolve il problema dei pacchetti persi o danneggiati, ma può
crearne un altro. Supponiamo che a causa di problemi di saturazione della
rete un pacchetto ci metta molto più tempo del previsto ad arrivare. A
questo punto il mittente, non vedendosi arrivare indietro la conferma ne
rispedisce una copia. Succede così che il destinatario riceve a una certa
distanza l’uno dall’altro due copie dello stesso pacchetto. Il
problema della duplicazione dei pacchetti viene risolto facendo numerare
sequenzialmente al mittente tutti i pacchetti da spedire e facendo
verificare al destinatario la sequenza ricevuta. Naturalmente questo non
vale solo per i messaggi ma anche per le conferme agli stessi. Infatti
anche una conferma potrebbe venire erroneamente duplicata. Per evitare
questo ogni conferma riporta il numero di sequenza del messaggio a cui si
riferisce, permettendo così al mittente di verificare che a ogni
messaggio spedito corrisponda una e solo una conferma di ricezione. È un
po' lo stesso meccanismo di una raccomandata con ricevuta di ritorno. In realtà gli algoritmi utilizzati dal TCP sono un po' più
complicati, e tengono conto di tutta una serie di situazioni che si
possono verificare. Senza contare che il tempo di attesa prima della
ritrasmissione è un punto chiave di tutto il discorso. Se si attende
troppo poco si rischia di generare un sacco di duplicati inutili,
saturando per giunta la rete, mentre se si attende troppo si rischia di
abbassare notevolmente e inutilmente le prestazioni della trasmissione dei
dati, rallentando le applicazioni alle estremità della connessione. Il meccanismo della conferma di ricezione con ritrasmissione ha inoltre
un grosso svantaggio. Anche se i tempi di attesa sono scelti in modo
ottimale, esso causa un notevole sottoutilizzo della rete. Infatti,
indipendentemente dalla capacità della rete, i due interlocutori passano
la maggior parte del tempo attendendo le varie conferme. È un po' come
avere un tubo nel quale vengono fatte cadere una a una delle palline
numerate in sequenza. All’altra estremità del tubo c’è una cesta
poggiata su un prato, un po' distante dal foro di uscita. Se la pallina
cade nella cesta fa rumore, altrimenti cade nel prato e non si sente
niente. Se ogni volta che metto una pallina nel tubo aspetto di sentire il
rumore che mi conferma che la pallina è caduta nel cesto, il tubo resta
per la maggior parte del tempo vuoto. Una tecnica di ottimizzazione usata
dal TCP per rendere più efficiente il meccanismo appena descritto è
quella delle finestre di scorrimento (sliding window). Funziona più o
meno in questo modo. Immaginate di immettere nel tubo una sequenza di
dieci palline senza attendere che la prima sia arrivata. Come si sente il
primo flop si aggiunge un’undicesima pallina, e poi una dodicesima e
così via. Se si salta un flop si reinserisce una pallina con lo stesso
numero di quella che non è arrivata, tanto il destinatario può comunque
riordinare le palline utilizzando i numeri scritti sopra. Il numero di
palline che compongono il trenino da spedire indipendentemente dalla
ricezione del flop si chiama dimensione della finestra di scorrimento (sliding
window size). Se si sceglie una dimensione tale da riempire tutto il tubo
nella sua lunghezza si sfrutta al massimo la capacità dello stesso. In pratica questo sistema divide la sequenza di pacchetti in tre fasce.
La prima è rappresentata dai pacchetti spediti e di cui si è avuta la
conferma di ricezione. La seconda è formata dai pacchetti spediti ma dei
quali non si sa ancora niente, e la terza è formata dai pacchetti ancora
da spedire. Con questa tecnica il TCP mantiene un timer per ogni singolo
pacchetto che appartiene alla seconda fascia. Il nome "Finestra di
scorrimento" deriva dal fatto che è come se ci fosse una finestra
ampia quanto il trenino di pacchetti che possono essere spediti senza
attendere la conferma dell’avvenuta ricezione che scorre in avanti un
pacchetto alla volta ogni qual volta arriva una conferma. Anche in questo
caso, come in quello del tempo di attesa prima di ritrasmettere un
pacchetto, le dimensioni della finestra di scorrimento rappresentano un
fattore critico per determinare l’efficenza del sistema. In generale, se
le dimensioni della finestra sono maggiori del tempo di attesa per il
singolo pacchetto, allora la finestra continua a scorrere regolarmente
senza interruzioni, salvo nel caso di ritrasmissioni, e la capacità di
carico della rete viene sfruttata al massimo. Affinché infatti due applicazioni possano comunicare fra di loro esse
debbono conoscersi e il sistema di trasmissione che le serve deve sapere a
chi effettivamente vanno recapitati i dati. È evidente che non basta
l'indirizzo IP, che identifica univocamente un host nella rete. Basti
pensare che un PC collegato in rete ha in genere un solo indirizzo IP, a
meno che non sia collegato a più reti fisiche, come per esempio un
gateway. Se una lettera viene spedita via rete a un certo indirizzo come
fa TCP a sapere a quale applicazione deve far arrivare i dati? Un sistema
potrebbe essere quello di assegnare un identificativo a ogni singola
applicazione, ma come garantire allora l'univocità dell'identificativo?
Senza contare che questo costringerebbe la controparte a sapere a priori
tale valore per ogni possibile destinatario. Non è inoltre detto che un
utente utilizzi sempre lo stesso programma per spedire o ricevere la posta
elettronica. In realtà, più che la specifica applicazione, quello che è
importante identificare è la funzione, come per esempio trasferire file
oppure spedire posta elettronica. La soluzione è quella di definire dei punti di ingresso e di uscita
virtuali chiamati porte. Ogni porta rappresenta di fatto un punto di
accesso a un'applicazione nel sistema. Si tratta in pratica di
un'estensione del concetto di porta hardware. Un PC moderno, per esempio,
può avere porte hardware parallele, seriali, video, audio e di vari altri
tipi. Ad ogni porta possono essere attaccati dispositivi molto differenti.
Per esempio, a una porta parallela è possibile attaccare una stampante,
uno scanner, un'unità Cd-Rom oppure un'unità disco ad alta capacità.
Tutti questi dispositivi non hanno bisogno di una porta specifica, ma
possono utilizzare la stessa porta perché gestiscono flussi di dati
simili, possono cioè usare lo stesso protocollo di base per la
trasmissione dei dati. Ovviamente, a livello applicativo, ogni periferica
darà ai propri dati una struttura differente. Questo vuol dire che i dati
costruiti per una stampante non possono certo essere mandati a uno
scanner. D'altra parte anche TCP non entra in merito della struttura
applicativa dei dati, ma solo alle modalità di trasmissione degli stessi. Ogni porta TCP è identificata da un numero. I numeri sotto il 256 sono
utilizzati per le cosiddette "porte conosciute", cioè porte
alle quali è stata assegnata una responsabilità ben precisa, mentre
quelli al di sopra sono utilizzati per le assegnazioni dinamiche. Avremo
per esempio una porta per i servizi di posta elettronica X.400 chiamata
appunto X400 (103) alla quale faranno riferimento tutte le applicazioni
che utilizzano tali servizi, oppure le due porte per il trasferimento dei
file via FTP, una per il controllo (FTP, 21) e una per i dati (FTP-DATA,
20). Una lista delle porte conosciute attualmente assegnate è riportata
nella RFC 1060, reperibile a ftp://ds.internic.net/rfc/rfc1060.txt Mentre
in UDP la porta rappresenta un elemento sufficiente alla comunicazione,
per cui il protocollo non fa altro che smistare i vari datagrammi nelle
code dati (queue) associate alle varie porte . Una connessione è
l'insieme di due punti, detti estremi della connessione (endpoint), ognuno
identificato univocamente da due coordinate: l'indirizzo IP e il numero di
porta. Una connessione è quindi rappresentata da ben quattro
identificativi: gli indirizzi IP delle due macchine su cui girano le due
applicazioni che si scambiano i dati, e i rispettivi numeri di porta. È
importante capire che l'identificazione della connessione richiede tutti e
quattro i valori, per cui la stessa porta con lo stesso indirizzo IP può
essere condivisa simultaneamente da più connessioni senza creare alcun
problema o ambiguità. Ecco perché in TCP si pensa in termini di linea dedicata. È come se
ci fosse un filo che lega univocamente i due interlocutori. Ogni
interlocutore può avere più connessioni aperte nello stesso momento a
partire dallo stesso capo purché non ce ne siano due con la stessa
controparte. Il vantaggio è che una singola applicazione, per esempio di
posta elettronica, necessita di una sola porta TCP per fornire servizi a
molte macchine contemporaneamente attraverso differenti connessioni che
condividono uno stesso estremo. Va tenuto presente che, anche se UDP e TCP
usano gli stessi numeri per le porte, non esiste possibilità di
confusione, dato che i pacchetti IP portano con sé l'identificativo del
protocollo utilizzato che è ovviamente diverso per i due protocolli. Affinché la connessione venga stabilita, entrambi gli estremi devono
dare la loro autorizzazione. L'aggancio avviene nel seguente modo. Una
delle due applicazioni che si vogliono connettere effettua un'apertura
passiva (passive open), cioè informa il suo sistema che è disposta ad
accettare una richiesta di connessione. TCP assegna all'applicazione un
numero di porta. L'altra applicazione deve invece effettuare un'apertura
attiva (active open), specificando l'indirizzo IP e la porta con la quale
si vuole connettere. A questo punto i due livelli TCP stabiliscono la
connessione e verificano che tutto sia a posto. La gestione dei dati: Vediamo adesso come TCP gestisce i dati. Innanzi
tutto, come già detto, TCP vede i dati come una sequenza non strutturata
di ottetti, cioè byte, detto flusso di dati (data stream). Questo flusso
viene diviso in segmenti ognuno dei quali viaggia di solito in un singolo
pacchetto IP. Per aumentare l'efficienza della trasmissione, TCP utilizza
una versione particolare del meccanismo a finestre di scorrimento spiegato
sopra. Ricordo che questo meccanismo consiste nel mandare un gruppetto di
dati prima di aver ricevuto la conferma di ricezione di ogni singolo
pacchetto, in modo da tenere costantemente sotto carico la linea. Se
infatti si dovesse attendere la conferma di ricezione per ogni singolo
pacchetto prima di spedire il successivo la linea resterebbe per la
maggior parte del tempo inutilizzata. Si dà insomma fiducia alla rete,
partendo dal presupposto che la perdita di dati sia l'eccezione piuttosto
che la regola. Esistono tuttavia due importanti differenze tra il meccanismo base
presentato prima e quello più sofisticato utilizzato effettivamente da
TCP. La prima è che l'unità base per i dati non è né il segmento né il
pacchetto IP ma il singolo ottetto. Ogni ottetto viene numerato e TCP
mantiene tre puntatori per ogni flusso di dati in uscita: uno che separa
gli ottetti già spediti e arrivati felicemente a destinazione da quelli
di cui non si hanno ancora notizie, uno che separa quelli già spediti da
quelli che devono ancora essere spediti senza attendere la conferma di
ricezione per i precedenti ottetti, e uno che separa questi ultimi da
quelli che non possono essere spediti fintanto che la finestra non scorre
in avanti. Una serie di informazioni speculari è mantenuta dal
destinatario che deve ovviamente ricostruire il flusso di dati nel modo
corretto indipendentemente dall'ordine di arrivo dei dati. Dato che una
connessione è full-duplex, TCP manterrà quindi per ogni connessione due
finestre di scorrimento, una per i dati in uscita e una per quelli in
ingresso: un totale di quattro finestre per connessione considerando
entrambi gli estremi. Esiste quindi un'asimmetria rispetto al meccanismo
base dove l'unità dati utilizzata nella finestra di scorrimento era la
stessa utilizzata nella trasmissione. Qui TCP utilizza il segmento come
unità dati da trasmettere, mentre ragiona in termini di ottetti per
quello che riguarda il meccanismo di ritrasmissione. La seconda differenza è che le dimensioni della finestra di
scorrimento non sono fisse ma variano nel tempo in funzione della
capacità di ricezione del destinatario. Ogni conferma di ricezione che
ritorna al mittente contiene una soglia di capacità (window advertisement)
che contiene il numero di ulteriori ottetti che il destinatario è in
grado di ricevere. In pratica questo meccanismo permette di adattare la
finestra di spedizione alle dimensioni del buffer di ricezione. Si tratta
cioè di un meccanismo di controllo del flusso dei dati che limita il
numero dei dati in ingresso man mano che il buffer di ricezione si
riempie, fino a poter interrompere momentaneamente la trasmissione nel
caso che si sia raggiunta la massima capacità di ricezione del
destinatario. Basta infatti che il destinatario mandi una soglia uguale a
zero perché il mittente interrompa la spedizione degli ottetti fino
all'arrivo di una conferma di ricezione contenente di nuovo una soglia
maggiore di zero. In realtà il mittente non smette del tutto di mandare dati. Innanzi
tutto, se ci sono dati urgenti da spedire, il mittente informa comunque il
destinatario di tale necessità trasmettendo un segmento con un indicatore
di urgenza al suo interno. Questo permette al destinatario di prendere
delle contromisure per ricevere comunque i dati urgenti, per esempio
aumentando le dimensioni del buffer. In secondo luogo, è sempre possibile
che la conferma con soglia positiva che dovrebbe far ripartire la
trasmissione dei dati vada perduta. Per questo motivo il mittente prova
ogni tanto a far partire un segmento per vedere se per caso il
destinatario è di nuovo pronto a ricevere i dati. 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 appena descritto della soglia di
capacità permette di risolvere il primo problema, non il secondo.
Quest'ultimo è detto congestione, ed è estremamente importante perché
non tenerne conto vuol dire mandare in tilt la rete. Lo standard TCP non prevede alcun meccanismo di controllo della
congestione, lasciando agli implementatori di tale protocollo il non
banale compito di sviluppare una logica capace di evitare questo tipo di
problemi. Per quello che riguarda i segmenti, il fatto che TCP sia libero di
dividere il flusso in segmenti può a volte causare problemi dal punto di
vista applicativo. Per esempio, supponiamo di implementare via TCP/IP un
terminale remoto. Questo vuol dire che tutte le operazioni effettuate con
la tastiera e il mouse su di una macchina (chiamiamola locale) saranno
visibili su di un'altra macchina (remota) come se esse fossero state
effettuate dalla tastiera e dal mouse della macchina remota. Non solo:
sarà possibile vedere lo schermo della macchina remota all'interno di una
finestra della macchina locale . Questo tipo di applicazioni è molto
utile per esempio se per un qualche motivo la macchina da controllare non
ha una sua tastiera oppure si trova in un locale non generalmente
accessibile all'operatore. È evidente che affinché l'applicazione
funzioni essa debba lavorare in tempo reale. Se cioè si preme il tasto T
sulla tastiera locale, la lettera T deve apparire immediatamente sullo
schermo della macchina remota, e quindi apparire anche all'interno della
finestra locale che riproduce tale schermo. Lo stesso se si fa click sul
pulsante di chiusura di una finestra. Ovviamente se TCP fosse libero di
accumulare questi comandi per poi spedirli tutti in una volta
l'applicazione sarebbe di difficile utilizzo. Infatti, se l'operatore
decidesse di chiudere una finestra dello schermo remoto per accedere
un'icona sottostante e TCP non spedisse il comando fintanto che il buffer
di partenza non fosse pieno, non sarebbe possibile eseguire l'operazione
successiva, cioè aprire l'icona sulla scrivania del sistema. Per questo motivo TCP
prevede la possibilità di forzare la spedizione del buffer (push). Questo
tuttavia non è sufficiente. Se infatti TCP che riceve i dati accumulasse
gli stessi nel buffer di ricezione prima di passarli all'applicazione
destinataria saremmo punto e da capo. Per questo motivo, quando un
segmento è forzato in uscita, TCP imposta a uno un certo bit
nell'intestazione del segmento in modo che questi possa venire
riconosciuto e immediatamente passato all'applicazione remota. Abbiamo detto che il TCP utilizza il metodo della finestra di
scorrimento per tenere la rete sempre impegnata al massimo della sua
capacità e che esiste un'importante differenza tra il meccanismo generale
e quello più sofisticato utilizzato effettivamente dal TCP. Tale
differenza consiste in un'asimmetria rispetto al meccanismo base dove
l'unità dati utilizzata nella finestra di scorrimento era la stessa
utilizzata nella trasmissione. Il TCP utilizza il segmento come unità
dati da trasmettere, mentre ragiona in termini di ottetti per quello che
riguarda il meccanismo di ritrasmissione. Questo comporta una
complicazione nella gestione delle conferme di ricezione (acknowledgement). A causa dell'asimmetria suddetta, la ritrasmissione in caso di mancata
ricezione non avviene per segmenti, ma a livello di ottetti. Questo vuol
dire che un segmento può contenere contemporaneamente sia nuovi dati sia
una parte dei dati persi in precedenza. Ovviamente a queste condizioni ha
poco senso numerare semplicemente i segmenti e usare questo identificativo
nelle conferme di ricezione. Né è pensabile di usare i datagrammi IP a
tale scopo, dato che questi sono generalmente di lunghezza fissa mentre i
vari segmenti TCP sono di lunghezza variabile. Ne consegue che l'unico
modo per gestire le conferme è quello di ragionare in termini di cursore
all'interno del flusso di dati. Come dire "ho ricevuto i primi 300
caratteri della lettera che mi hai spedito". Ecco che cosa accade: ogni segmento contiene la posizione dell'area
dati del segmento TCP all'interno del flusso di dati. Tale posizione si
chiama numero di sequenza (sequence number) ed è calcolata in byte. Il
destinatario estrae i vari ottetti dai segmenti ricevuti e li ricompone
per ricostruire il flusso dei dati, utilizzando i numeri di sequenza per
riordinare i vari segmenti. Questi possono infatti arrivare in qualunque ordine, o essere andati
persi. A questo punto, chi sta ricevendo i dati, avrà ricostruito in modo
completo una parte del messaggio originario e si ritroverà alcuni dati in
eccesso che non sono contigui alla parte di flusso ricostruito. Ogni volta
che il destinatario riceve un segmento, manda indietro nella conferma di
ricezione il numero di sequenza dell'ottetto che si aspetta di ricevere
per continuare la ricostruzione, cioè il valore dell'ultimo ottetto della
parte contigua ricostruita più uno. 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.
Viceversa, 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.
Quest'ultimo è detto congestione, ed è estremamente importante perché
non tenerne conto vuol dire mandare in tilt la rete. Lo standard TCP non prevede alcun meccanismo di controllo della
congestione, lasciando agli implementatori di tale protocollo il non
banale compito di sviluppare una logica capace di evitare questo tipo di
problemi. Per quello che riguarda i segmenti, il fatto che il TCP sia libero di
dividere il flusso in segmenti può a volte causare problemi dal punto di
vista applicativo. Per esempio, supponiamo di implementare via TCP/IP un
terminale remoto. Questo vuol dire che tutte le operazioni effettuate con
la tastiera e il mouse su di una macchina (chiamiamola locale) saranno
visibili su di un'altra macchina (remota) come se esse fossero state
effettuate dalla tastiera e dal mouse della macchina remota. Non solo:
sarà possibile vedere lo schermo della macchina remota all'interno di una
finestra della macchina locale . Questo tipo di applicazioni è molto
utile per esempio se per un qualche motivo la macchina da controllare non
ha una sua tastiera oppure si trova in un locale non generalmente
accessibile all'operatore. È evidente che affinché l'applicazione
funzioni essa debba lavorare in tempo reale. Se cioè si preme il tasto T
sulla tastiera locale, la lettera T deve apparire immediatamente sullo
schermo della macchina remota, e quindi apparire anche all'interno della finestra locale che riproduce tale schermo. Lo stesso
se si fa click sul pulsante di chiusura di una finestra. Ovviamente se il
TCP fosse libero di accumulare questi comandi per poi spedirli tutti in
una volta l'applicazione sarebbe di difficile utilizzo. Infatti, se
l'operatore decidesse di chiudere una finestra dello schermo remoto per
accedere un'icona sottostante e il TCP non spedisse il comando fintanto
che il buffer di partenza non fosse pieno, non sarebbe possibile eseguire
l'operazione successiva, cioè aprire l'icona sulla scrivania del sistema.
Per questo motivo il TCP prevede la possibilità di forzare la spedizione
del buffer (push). Questo tuttavia non è sufficiente. Se infatti il TCP
che riceve i dati accumulasse gli stessi nel buffer di ricezione prima di
passarli all'applicazione destinataria saremmo punto e da capo. Per questo
motivo, quando un segmento è forzato in uscita, il TCP imposta a uno un
certo bit nell'intestazione del segmento in modo che questi possa venire
riconosciuto e immediatamente passato all'applicazione remota. 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 psedointestazione 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. Ovverosia, 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 timeout. Il TCP calcola il timeout sulla
base del tempo intercorso fra la spedizione di un segmento e l’arrivo
della conferma corrispondente. Sembra facile, ma non è così. Vediamo
rapidamente i punti chiave del discorso. Il TCP calcola continuamente il timeout, 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 timeout è 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 timeout 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. Se 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 timeout. Infatti un pacchetto è ritrasmesso
quando scade il timeout precedente. Di conseguenza il nuovo RTS è
ovviamente più grande del vecchio timeout. Se viene perso un nuovo
pacchetto l’RTS cresce ancora, e così via. Se usiamo il pacchetto
ritrasmesso abbiamo il problema opposto, cioè il timeout 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 timeout si riduce,
aumentando il rischio di considerare persi pacchetti la cui conferma di
ricezione arriva in ritardo, e così via. P. Karn propose nel 1987 d’ignorare i pacchetti ritrasmessi nel
calcolo del timeout. 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 timeout rimarrà sempre troppo basso,
in quanto il mittente continuerà a ritrasmettere pacchetti le cui
conferme arrivano in ritardo rispetto al timeout calcolato prima del calo
di prestazioni. Dato che tali conferme vengono regolarmente ignorate per
il calcolo dell’RTT, il timeout 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 timeout 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. Autore: sconosciuto Guida scaricata dal sito: http://vene.tsx.org
|