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.
|