You are on page 1of 139

La tabella riporta una classificazione dei vari tipi di rete, in

funzione dell’ambito operativo e delle distanze coperte.
AMBITO DISTANZA RETE
COPERTA
Edificio 100 m Reti Locali (LAN)
Comprensorio 1 km Reti Locali (LAN)
Città 10 km Reti Metropolitane (MAN)
Nazione 100 km Reti Geografiche (WAN)
Continente 1000 km Reti Geografiche (WAN)
Pianeta 10000 km Reti Geografiche (WAN)
La figura qui sopra riporta la struttura di una rete di calcolatori
di una ipotetica azienda. Essa è formata da una rete locale
(LAN: Local Area Network) in ogni sede (edificio) della
azienda; le LAN presenti all’interno di un’area metropolitana
sono collegate tra loro tramite MAN (Metropolitan Area
Network) e queste a loro volta tramite una rete geografica
(WAN: Wide Area Network).
Una Tassonomia delle Reti
2
LAN LAN LAN LAN LAN
MAN
WAN
Un esempio di Wan:
la rete dell’Università di Bologna
3
Rimini
Ravenna
Forlì
Bologna
M. Maggio
Monte
Calderaro
Cesena
Bertinoro
Ozzano
Le RETI LOCALI (LAN).
• Una rete locale è un mezzo di trasporto equamente condiviso
tra tutte le stazioni che vi si collegano, ad alta velocità e basso
tasso d’errore, limitato ad un ambito locale.
• Le velocità trasmissive sono comprese nell’intervallo 4 Mb/s -
155 Mb/s. Accanto alle vecchie reti Ethernet e Token Ring si
sono aggiunte reti più moderne quali FastEthernet a 100 Mb/s e
ATM a 34 e 155 Mb/s.
• Tutte queste tecnologie adottano tipicamente come supporto
trasmissivo preferenziale il doppino di rame e la fibra ottica
sulle dorsali. Il cavo coassiale è stato (per fortuna) quasi del
tutto abbandonato.
Le RETI METROPOLITANE (MAN).
Sono recenti estensioni delle reti locali in ambito urbano.
Sfruttano una grande varietà di tecnologie, che vanno dai ponti
radio ad alta frequenza (es ponti radio punto-punto a 38 GHz
per velocità da 2 Mb/s a 34 Mb/s, o punto-multipunto di tipo
Spread Spectrum) alle fibre ottiche.
Le prestazioni classiche raggiunte sono comprese tra 2 Mb/s e
155 Mb/s.
Le RETI GEOGRAFICHE (WAN).
Si basano solitamente su servizi offerti dai gestori nazionali di
telecomunicazione, che rendono disponibili canali per
trasmissione digitale punto a punto, i cosiddetti CDN (Canali
Diretti Numerici). Acausa della liberalizzazione del mercato
l’offerta è in continua evoluzione.
Caratteristiche delle Reti
4
L’ OSI è un progetto di ampio respiro formulato dall’ISO
(International Standard Organization) alla fine degli anni ‘70
con lo scopo di fungere da modello di riferimento per le reti di
calcolatori.
L’ISO doveva:
• standardizzare la terminologia;
• definire quali sono le funzionalità di una rete;
• servire come base comune da cui far partire lo sviluppo di
standard per l’interconnessione di sistemi informatici;
• fornire un modello rispetto a cui confrontare le architetture di
reti proprietarie.
Per gestire la complessità dei problemi, l’OSI ha adottato un
approccio a livelli (layers): il problema della comunicazione tra
due applicazioni è stato spezzato in un insieme di 7 livelli,
ciascuno dei quali esegue funzioni ben specifiche.
L’OSI ha cercato di diventare più di un modello di riferimento.
Infatti l’ISO ha standardizzato per OSI una serie di protocolli,
da collocare nei vari livelli del modello, per formare una vera
architettura di rete concorrenziale con altre, quali lo Stack
TCP/IP.
I livelli 1 (Fisico) e 2 (Data Link) sono stati accettati e sono
oggi degli standard, garantendo l’interoperabilità dei prodotti.
I protocolli di livello superiore incontrano più difficoltà a causa
dell’alto impatto che la loro adozione avrebbe sui dispositivi di
instradamento e sul software dei sistemi informativi.
L’OSI (Open System Interconnections)
5
Per ridurre la complessità del progetto, OSI introduce
un’architettura a livelli (layered architecture) i cui componenti
principali sono:
• i livelli (layers)
• le entità (entities)
• i punti di accesso al servizio (SAP: Service Access Points)
• le connessioni (connections)
In tale architettura, ciascun sistema è decomposto in un insieme
ordinato di livelli, rappresentati per convenienza come una pila
verticale. La figura seguente rappresenta i livelli che
compongono il modello di riferimento ISO-OSI.
Tale approccio di progettazione a livelli è comune a tutte le
moderne architetture di rete.
Il modello ISO/OSI
Architettura a Livelli
6
• Anche se è definito un protocollo di livello N, nessun dato è
trasferito direttamente da un livello Nall’altro su un diverso
sistema.
• Infatti ogni livello passa dati e informazioni di controllo a
quello sottostante, sino a quando si giunge al livello Fisico, che
effettua la trasmissione verso l’altro sistema.
• L’interfaccia di un livello definisce quali operazioni primitive
e quali servizi sono forniti da un livello ai livelli superiori.
In figura è rappresentato il meccanismo di comunicazione tra due entità
di livello N= 4, con il messaggio che viene passato via via ai livelli
inferiori (che aggiungono i loro header), viene trasmesso attraverso il
mezzo fisico, e giunto al sistema di destinazione risale liberandosi degli
header aggiunti, fino ad arrivare al livello stabilito.
Il modello ISO/OSI
Comunicazioni tra livelli su sistemi diversi (2)
9
• Il livello 1: FISICO.
Il livello 1 del modello OSI si occupa di trasmettere sequenze
binarie sul canale di comunicazione. Aquesto livello si
specificano ad esempio le tensioni che rappresentano 0 e 1 e le
caratteristiche dei cavi e dei connettori.
• Il livello 2: DATA LINK.
Il livello ha come scopo la trasmissione sufficientemente
affidabile di pacchetti detti frame tra due sistemi contigui.
Accetta come input dei pacchetti di livello 3 (tipicamente poche
centinaia di bit) e li trasmette sequenzialmente. Esso verifica la
presenza di errori aggiungendo delle FCS (Frame Control
Sequence) e può gestire meccanismi di correzione di tali errori
tramite ritrasmissione.
• Il livello 3: NETWORK.
Il livello 3 è il livello Network, che gestisce l’instradamento dei
messaggi, ed è il primo livello (a partire dal basso) che gestisce
informazioni sulla topologia della rete. Tale livello determina se
e quali sistemi intermedi devono essere attraversati dal
messaggio per giungere a destinazione. Deve quindi gestire
delle tabelle di instradamento e provvedere ad instradamenti
alternativi in caso di guasti (fault tollerance).
Il modello ISO/OSI
I Livelli (1)
7
• Il livello 4: TRASPORTO.
Il livello 4 del modello OSI fornisce un servizio di trasferimento
trasparente dei dati tra entità del livello 5 (sessione). Si occupa
di garantire un servizio affidabile. Deve quindi effettuare la
frammentazione dei dati, la correzione degli errori e la
prevenzione della congestione della rete. Il livello 4 è il livello
più basso, a partire dall’alto, a trascurare la topologia della rete
e la presenza di sistemi intermedi di instradamento, ed è quindi
detto livello end-to-end.
• Il livello 5: SESSIONE.
Organizza il dialogo tra due programmi applicativi, consentendo
di aggiungere a connessioni end-to-end servizi più avazati.
• Il livello 6: PRESENTAZIONE.
Definisce formalmente i dati che gli applicativi si scambiano,
come questi dati sono rappresentati localmente sul sistema, e
come vengono codificati durante il trasferimento.
• Il livello 7: APPLICAZIONE.
Il livello 7 è il livello dei programmi applicativi, facenti parte
del sistema operativo oppure scritti dall’utente, attraverso i quali
l’utente utilizza la rete.
Il modello ISO/OSI
I Livelli (2)
8
• L’insieme dei livelli, dei protocolli e delle interfacce definisce
un’architettura di rete. Le architetture di rete più note sono:
• SNA (System Network Architecture) architettura della rete
IBM;
• DNA (Digital Network Architecture), meglio nota come
DECnet, la rete della Digital Equipment Corporation;
• Internet protocol Suite, meglio nota con il nome TCP/IP, è la
rete degli elaboratori UNIX e rappresenta uno standard “de
facto” attualmente impiegato per la rete Internet.
•OSI (Open System Architecture), che è lo standard “de iure”
prodotto dall’ISO.
Principali Architetture di Rete
10
La trasmissione dei dati avviene quindi:
• attraverso una serie di passaggi da livelli superiori a livelli
inferiori nel sistema che trasmette,
• poi attraverso mezzi fisici di comunicazione,
• infine attraverso un’altra serie di passaggi, questa volta da
livelli inferiori a livelli superiori.
Notare come a livello 2, sia necessario aggiungere in coda un
campo che identifica la fine del pacchetto prima di passare lo
stesso al livello che utilizza il mezzo trasmissivo.
Incapsulamento dei Pacchetti
11
Non sempre lo scambio delle informazioni avviene direttamente
tra i due sistemi finali che contengono le applicazioni (ES: End
Systems). Può anche implicare l’attraversamento di alcuni
sistemi intermedi (IS: Intermediate Systems).
In questi Intermediate Systems esistono delle entità che
assumono la funzione di Router (in senso esteso), ovvero entità
che instradano le informazioni.
Tali entità possono essere collocate a diversi livelli del modello
OSI, ed allora gli Intermediate Systems assumono nomi diversi
a seconda del livello in cui avviene l’instradamento dei dati. Si
parla allora di repeater a livello 1, bridge a livello 2, router a
livello 3 ed infine gateway a livello 7.
Qui di seguito è rappresentata la collocazione di un Router nel
modello OSI.
Sistemi Intermedi (1)
12
Accenniamo per ora solo all’azione svolta dai Repeater, mentre
l’instradamento a livelli superiori, che comporta anche la
rigenerazione digitale del segnale, sarà approfondito
analizzando lo stack TCP/IP
Qui di seguito è rappresentata la collocazione di un Repeater nel
modello OSI. Il livello Fisico non effettua correzione dei dati
ricevuti, compito che invece spetta al livello Data Link.
• Il segnale che il Repeater riceve da un lato viene amplificato e
propagato all’altro lato, quindi viene propagato anche il rumore
che può essersi prodotto durante la trasmissione dal sistema A al
Repeater, e a tale rumore si aggiungerà il rumore prodotto nella
trasmissione dal Repeater al sistema B.
• Di conseguenza il livello Fisico del sistema B riceve un segnale
afflitto dalla somma dei rumori che si sono prodotti durante la
trasmissione sui due tratti di percorso, e quindi aumenta la
probabilità che si riscontri un errore nei dati trasmessi.
• La correzione potrà essere effettuata solo quando il segnale
giungerà al sistema 2, ma l’eventuale ritrasmissione dei dati
dovrà attraversare nuovamente i due tratti di rete.
• E’ per questo motivo che bisogna ridurre al minimo il numero
di repeater in un percorso.
Sistemi Intermedi (2)
13
14
Architettura TCP/IP e confronto con OSI
15
Interfaccia Socket
I livelli TCP/IP hanno questa relazione con i livelli di OSI.
Lo stack di protocolli TCP/IP implementa un livello network
(livello 3) di tipo:
• packet-switched;
• connectionless.
Il livello più basso (corrispondente ai livelli 1 e 2 di OSI) non è
specificato dall’architettura, che prevede di utilizzare quelli
disponibili per le varie piattaforme HW e conformi agli
standard.
Per quanto riguarda le reti in ambito locale (LAN), lo standard
riconosciuto per i livelli 1 e 2 è rappresentato dal progetto IEEE
802, che è stato riconosciuto anche da OSI.
Per capire il funzionamento dei protocolli TCP/IP, nel seguito
considereremo proprio la situazione di una rete locale. Risulta
necessario perciò soffermarsi brevemente sulla strutturazione
delle LAN.
Lo Stack TCP/IP: Le Basi
30
DEFINIZIONE: una LAN è un sistema di comunicazione che
permette ad apparecchiature indipendenti di comunicare tra loro,
entro un area delimitata, utilizzando un canale fisico a velocità
elevata e con basso tasso d`errore.
• Le LAN hanno quindi sempre un solo canale trasmissivo ad
alta velocità condiviso nel tempo da tutti i sistemi collegati.
Quando un sistema trasmette diventa proprietario
temporaneamente (per la durata di uno o pochi pacchetti)
dell'intera capacità trasmissiva della rete.
• La trasmissione è sempre di tipo broadcast, ovvero un sistema
trasmette e tutti gli altri ricevono. Tale organizzazione ha
enormi vantaggi, ma impone anche alcune complicazioni: è
necessaria la presenza di indirizzi di livello 2 per stabilire chi
sono il reale destinatario e il mittente della trasmissione e
occorre arbitrare l'accesso all'unico mezzo trasmissivo tra tutti i
sistemi che hanno necessità di trasmettere.
• L'unico canale trasmissivo presente deve anche essere
caratterizzato da un basso tasso di errore. Questo è ottenibile
abbastanza facilmente in un'area delimitata usando mezzi
trasmissivi di buona qualità. L'effetto ottenuto è quello che le
LAN, essendo intrinsecamente affidabili, non hanno la necessità
di correggere gli errori a livello 2 OSI e quindi normalmente
utilizzano protocolli di livello 2 connectionless ad alte
prestazioni.
Le LAN (Local Area Network)
31
Quando le prime LAN cominciarono a diffondersi l’IEEE decise di
costituire sei comitati per studiare il problema della
standardizzazione delle LAN e delle MAN, complessivamente
raccolti nel progetto IEEE 802. IEEE 802 introduce l’idea che le
LAN e le MAN devono fornire un'interfaccia unificata verso il
livello Network (livello rete), pur utilizzando tecnologie
trasmissive differenziate. Per ottenere tale risultato, il progetto
IEEE 802 suddivide il livello Data Link in due sottolivelli:
• LLC (Logical Link Control);
• MAC (Media Access Control).
Il sottolivello LLC è comune a tutte le LAN, mentre il MAC è
caratteristico di ciascuna LAN, così come il livello fisico al
quale è strettamente associato. Il sottolivello LLC è l'interfaccia
unificata verso il livello Network ed è descritto nell'apposito
standard IEEE 802.2, mentre i vari MAC sono descritti negli
standard specifici di ogni rete locale (ad esempio il MAC
CSMA/CD è descritto nello standard IEEE 802.3, ed il MAC Fast
Ethernet (100BaseT) è descritto in IEEE 802.3u).
32
Il Progetto IEEE 802
Il sottolivello MAC è specifico di ogni LAN e risolve il
problema della condivisione del mezzo trasmissivo.
Il MAC è indispensabile in quanto a livello 2 (Data Link) le
LAN implementano sempre una sottorete trasmissiva di tipo
broadcast in cui ogni sistema riceve tutti i frame inviati dagli
altri.
Per trasmettere in broadcast, cioè far condividere un unico
canale trasmissivo a tutti i sistemi, bisogna risolvere due
problemi:
• in trasmissione, verificare che il canale sia libero prima di
trasmettere e risolvere eventuali conflitti di più sistemi che
vogliano utilizzare contemporaneamente il canale;
• in ricezione, determinare a quali sistemi è effettivamente
destinato il messaggio e quale sistema lo ha generato.
La soluzione del primo problema è data dai vari algoritmi di
MAC, basati su principi diversi, quali la contesa, il token, la
prenotazione e il round-robin.
La soluzione del secondo problema necessita della presenza di
indirizzi a livello MAC (quindi nella MAC-PDU) che
identifichino ciascun sistema e trasformino trasmissioni
broadcast in:
• tramissioni punto-punto, se l'indirizzo di destinazione indica
un singolo sistema;
• trasmissioni punto-gruppo, se l'indirizzo di destinazione
indica un gruppo di sistemi;
• trasmissioni effettivamente broadcast, se l'indirizzo di
destinazione indica tutti i sistemi.
MAC: Media Access Control (1)
33
Quando una scheda di rete locale riceve un pacchetto, questo
non viene passato automaticamente al livello superiore (LLC)
ma viene analizzato a livello MAC, per effettuare una serie di
controlli.
Per prima cosa il MAC verifica che il pacchetto sia integro (cioè
abbia una FCS Frame Control Sequence corretta) e di
dimensioni ammesse.
Successivamente il livello MAC analizza l'indirizzo di
destinazione (MAC-DSAP).
Si possono porre tre casi:
• se l’indirizzo MAC di destinazione (il MAC-DSAP) è
broadcast, il pacchetto viene sempre passato al LLC, perchè
tutti i sistemi devono riceverlo;
• se il MAC-DSAP è single, il pacchetto viene passato al LLC
solo se il MAC-DSAP è uguale all'indirizzo hardware della
scheda o a quello caricato da software nell'apposito buffer;
• se il MAC-DSAP è multicast, si verifica se la ricezione di quel
multicast è stata abilitata dal software di livello superiore, cioè di
livello 3, cioè se la scheda appartiene al gruppo indirizzato.
Poiché non è noto a priori a quanti gruppi possa appartenere una
scheda, si usano delle tecniche di hash per mantenere la lista dei
gruppi abilitati.
Gli indirizzi di gruppo servono principalmente per scoprire quali
altri sistemi sono collegati alla rete locale, quali servizi questi
mettono a disposizione e, molto importante, le relazioni
esistenti tra gli indirizzi MAC e gli indirizzi di livello 3.
Ricezione dei Pacchetti a livello 2
36
Nel modo normale visto nella precedente slide, se una scheda di
rete inserita in una LAN riceve un pacchetto, lo passa al livello
superiore (LLC) solo se il pacchetto di livello MAC è un
pacchetto broadcast, o è un pacchetto single con indirizzo uguale
a quello della scheda di rete, o è un pacchetto multicast con
indirizzo con cui un’indirizzo dell’host è in corrispondenza.
In tal modo si ha la garanzia che i pacchetti destinati ad un host
vengano letti solo da quell’host.
In realtà esiste un’altra modalità di funzionamento delle schede
di rete per LAN, detto “modo promiscuo” (promiscuous
mode) in cui tutti i pacchetti ricevuti a livello MAC sono passati
ai livelli superiori. In tal caso l’analisi sulla destinazione dei
pacchetti dovrà essere effettuata da protocolli di livello
superiore.
Tale modalità di funzionamento viene utilizzata da applicazioni
(ad es. il “tcpdump” per Linux) per effettuare il monitoraggio
della rete, ovvero per controllare quanti e quali pacchetti
vengono trasmessi in rete, da chi, ecc.. .
Ovviamente tale modalità rappresenta un problema di sicurezza
perchè rende possibile vedere dati riservati trasmessi via rete.
Per tale motivo i sistemi operativi moderni permettono solo
all’amministratore del sistema (root) di configurare la scheda di
rete perchè funzioni in modo promiscuo, impedendo ad altri di
accedere alla porta attraverso la quale si configura la
scheda di rete.
Ricezione dei Pacchetti a livello 2
Un’eccezione: il modo PROMISCUO
37
I concetti di indirizzi single, multicast e broadcast sono
implementati sia a livello MAC che a livello IP.
Un’applicazione importantissima del meccanismo di broadcast
realizzato a livello MAC, è rappresentato dal protocollo ARP di
(livello 3) del TCP/IP, che permette ad un host S su una certa LAN,
di scoprire a partire dall’indirizzo IP dell’host di destinazione D (se
questo sta nella stessa LAN), l’indirizzo MAC dell’host
destinazione stesso, indirizzo che servirà per inviare il frame che
incapsulerà il pacchetto destinato all’host di destinazione. !!!
Il protocollo ARP opera in questo modo:
• l’host S invia a tutte le stazioni della LAN, in data link broadcast,
una richiesta del tipo: "chi ha l'indirizzo IP uguale a IP_D ?”.
• tutti gli host ricevono la risposta, e i MAC la passano a livello 3.
• solo l'host che ha quell'indirizzo IP IP_D risponde, inserendo
nella risposta il proprio indirizzo MAC;
• quando S riceve la risposta, sa a che MAC address deve inviare il
pacchetto.
• l'host S mantiene in memoria il MAC address di D per alcuni
minuti, per eventuali trasmissioni successive.
A livello IP il multicast è utilizzato per realizzare con semplicità dei
flussi di comunicazione punto-multipunto, che sono sfruttati ad es.
in applicazioni di videoconferenza, quali ad es. MBone. In tali
applicazioni un singolo pacchetto inviato dal sorgente raggiunge
molti hosts destinatari, duplicandosi opportunamente quando i
percorsi si dividono.
Utilizzo del Broadcast.
38
Qualche considerazione:
•Ogni interfaccia di rete locale è gestita da un suo livello MAC.
Su tale livello MAC si appoggia un livello LLC.
•Il livello MAC è implementato nell'hardware della scheda di rete
locale, mentre il livello LLC è di solito realizzato in software.
•Ogni livello LLC può gestire un solo livello MAC: questo
significa che un livello LLC non può avere funzionalità di
"relaying" (non può inoltrare pacchetti) tra più MAC.
•Tale funzionalità di instradamento dei pacchetti è delegata
al livello 3.
In figura vediamo le relazioni tra le Protocol Data Unit di livello
3 (Network), le LLC-PDU e le MAC-PDU, incapsulate una
dentro l’altra. Notare come ogni livello specifichi due indirizzi
di quel livello, un mittente (*-SSAP, S come Source) ed un
destinatario (*-DSAP, D come Destination) per i
pacchetti/frame passati al livello inferiore.
Relazione tra il Livello 3, Logical Link
Control e Media Access Control.
39
LLC ha lo scopo di fornire un’interfaccia unificata con il livello
network, e di fornire un supporto standard alla convivenza di
più protocolli di livello superiore sulla stessa LAN.
Proprio per questo scopo la Protocol Data Unit di LLC contiene
anch’essa due indirizzi, una destinazione e una sorgente, e questi
indirizzi rappresentano gli identificatori del protocollo di livello
superiore a cui il livello LLC deve consegnare il pacchetto che
gli è arrivato [o da cui il pacchetto è arrivato] .
LLC: Logical Link Control
Destination
Address
Source
Address
L3-Information Control
Supporto multiprotocollo offerto da LLC
La PDU di LLC
40
Il campo Options non è necessario in tutti i datagram IP. Le opzioni
sono utilizzate allo scopo di testare la funzionalità della rete sottostante.
Evitiamo di addentrarci su come sono organizzati i sottocampi del
campo Opzioni IP, e analizziamone soltanto le funzionalità previste.
Essenzialmente le opzioni sono classificabili in 3 categorie:
1) Opzione di vegistvazione del pevcovso.
• Quando il trasmettitore setta l’opzione di registrazione del percorso,
indica il numero massimo di hop che vuole memorizzare e crea spazio
sufficiente nel campo opzioni per memorizzare tali hop, 32 bit per ogni
indirizzo IP da memorizzare.
• Quando il pacchetto IP viaggia per la rete, ogni router toccato dal
datagram IP aggiunge il proprio indirizzo IP alla lista di registrazione
del percorso, almeno fino a che tale lista non è piena, nel qual caso il
router si limita ad inoltrare il messaggio.
• Quando il pacchetto IP giunge alla destinazione finale, il protocollo IP,
se vuole, può estrarre la lista dei router toccati dal pacchetto.
•Questa opzione viene utilizzata ad es. per implementare l’applicazione
detta “traceroute” che visualizza i router toccati da un pacchetto.
2) Opzioni di Instvadamento di Pvovenienza.
Le opzioni di Instradamento di Provenienza, consentono al trasmettitore
di imporre ad un pacchetto IP un certo percorso attraverso la rete, anche
se i router normalmente sceglierebbero un percorso diverso. Ciò può
essere utile per effettuare dei test sulla rete. Naturalmente per imporre
l’instradamento è necessario conoscere la topologia della rete.
Esistono due modalità per l’instradamento di provenienza. la prima,
detta instvadamento di pvovenienza sevevo specifica una sequenza di
salti consecutivi, e causa errore se due router non sono consecutivi nella
rete, cioè non stanno sulla stessa rete fisica o se il router non può seguire
quel percorso. La seconda detta instvadamento di pvovenienza
pevmissivo specifica una sequenza di indirizzi IP, ma consente
di attraversare più reti tra due indirizzi consecutivi.
53
HEADER IPv4 (5)
3) Opzione di contvassegno tempovale.
E’ simile all’opzione di registrazione del percorso, ma aggiunge
all’indirizzo IP di ogni router attraversato anche la data e l’ora in cui il
router gestisce il datagram IP, espresso secondo l’ora del meridiano di
Greenwich.
• Elabovazione delle Opzioni duvante la fvammentazione.
Ciascuna Opzione IP, viene identificata mediante un byte nel campo
Opzioni. Il primo bit (detto bit COPIA) di questo byte stabilisce, quando
posto ad 1, che l’opzione deve essere copiata in tutti gli eventuali
frammenti del pacchetto IP. In caso contrario l’opzione verrà copiata
solo in uno dei frammenti.
Questo diverso comportamento viene configurato in modo diverso per
le diverse Opzioni IP.
• Per l’opzione di registrazione del percorso, si vuole che l’opzione sia
copiata in uno solo dei frammenti, perchè essendo ogni frammento
gestito separatamente, potrebbe seguire percorsi diversi verso la
destinazione. Si avrebbero così più liste di registrazione del percorso
potenzialmente diverse. Il flag COPIA viene perciò posto a zero, e
l’opzione copiata in uno solo dei frammenti.
• Al contrario, per l’opzione di instradamento di provenienza si vuole
che tutti i frammenti seguano lo stesso percorso, e quindi il flag COPIA
viene posto ad uno.
54
HEADER IPv4 (6)
U
n
d
a
t
a
g
r
a
m
d
i

3
7
6
0

b
y
t
e
,

i
n
v
i
a
t
o

d
a

S
=
1
3
0
.
1
3
6
.
2
.
2
0
4

a

D
=
1
3
7
.
2
0
4
.
7
2
.
4
9

,

t
u
t
t
i

i

t
r
a
t
t
i

d
i


r
e
t
e

h
a
n
n
o
M
T
U

d
i

1
5
0
0

b
y
t
e
,

t
r
a
n
n
e

u
n
o

c
h
e

h
a

M
T
U

p
a
r
i

a
5
0
0

b
y
t
e
4
9
b
E
s
e
m
p
i
o
d
i
F
r
a
m
m
e
n
t
a
z
i
o
n
e
M
T
U
1
5
0
0
M
T
U
1
5
0
0
M
T
U
1
5
0
0
M
T
U
5
0
0
4
5
8
2
0
9
9
9
3
7
0
2
5
5
1
7
U
D
P
c
h
s
u
m
3
1
3
0
.
1
3
6
.
2
.
2
0
4
1
3
7
.
2
0
4
.
7
2
.
4
9
0
D
A
T
I
:


2
9
6
0
-
3
7
5
9
M
T
U
1
5
0
0
4
5
1
5
0
0
9
9
9
0
2
5
5
U
D
P
c
h
s
u
m
1
1
3
0
.
1
3
6
.
2
.
2
0
4
1
3
7
.
2
0
4
.
7
2
.
4
9
1
D
A
T
I
:







0
-
1
4
7
9
1
5
0
0
9
9
9
1
8
5
2
5
5
U
D
P
c
h
s
u
m
2
1
3
0
.
1
3
6
.
2
.
2
0
4
1
3
7
.
2
0
4
.
7
2
.
4
9
1
D
A
T
I
:




1
4
8
0
-
2
9
5
9
M
T
U
1
5
0
0
4
5
5
0
0
9
9
9
3
7
0
2
5
4
1
7
c
h
s
u
m
4
1
3
0
.
1
3
6
.
2
.
2
0
4
1
3
7
.
2
0
4
.
7
2
.
4
9
1
D
A
T
I
:





2
9
6
0
-
3
4
3
9
4
5
3
4
0
9
9
9
4
3
0
2
5
4
1
7
c
h
s
u
m
5
1
3
0
.
1
3
6
.
2
.
2
0
4
1
3
7
.
2
0
4
.
7
2
.
4
9
0
D
A
T
I
:







3
4
4
0
-
3
7
5
9
S
1
3
0
.
1
3
6
.
2
.
2
0
4
D
1
3
7
.
2
0
4
.
7
2
.
4
9
p
a
c
c
h
e
t
t
i

i
n
v
a
r
i
a
t
i
,
t
r
a
n
n
e
T
T
L
d
e
c
r
e
m
e
n
t
a
t
o
f
r
a
m
m
e
n
t
a
z
i
o
n
e
i
n
i
z
i
a
l
e
f
r
a
m
m
e
n
t
a
z
i
o
n
e
d
e
l
f
r
a
m
m
e
n
t
o
r
i
a
s
s
e
m
b
l
a
g
g
i
o
d
e
i

f
r
a
m
m
e
n
t
i
1
4
8
0
1
4
8
0
8
0
0
0
1 4 7 9
1 4 8 0
2 9 5 9
2 9 6 0
3 7 5 9
1
4
8
0
1
4
8
0
3
2
0
4
8
0
0
1 4 7 9
1 4 8 0
2 9 5 9
2 9 6 0
3 7 5 9
3 4 3 9
3 4 4 0
0
x
8
=
0
1
8
5
x
8
=
1
4
8
0
3
7
0
x
8
=
2
9
6
0
4
3
0
x
8
=
3
4
4
0
M
T
U
1
5
0
0
M
T
U
M
T
U
1
5
0
0
0
1
7
c
h
s
u
m
1
c
h
s
u
m
1
4
5
1
7
M
T
U
1
5
0
0
9
9
9
9
9
9
S
1
3
0
.
1
3
6
.
2
.
2
0
4
D
1
3
7
.
2
0
4
.
7
2
.
4
9
p
a
c
c
h
e
t
t
i

i
n
v
a
r
i
a
t
i
,
t
r
a
n
n
e
T
T
L
d
e
c
r
e
m
e
n
t
a
t
o
f
r
a
m
m
e
n
t
a
z
i
o
n
e
i
n
i
z
i
a
l
e
f
r
a
m
m
e
n
t
a
z
i
o
n
e
d
e
l
f
r
a
m
m
e
n
t
o
r
i
a
s
s
e
m
b
l
a
g
g
i
o
d
e
i

f
r
a
m
m
e
n
t
i
1
4
8
0
1
4
8
0
8
0
0
0
1 4 7 9
1 4 8 0
2 9 5 9
2 9 6 0
3 7 5 9
0
1 4 7 9
1 4 8 0
2 9 5 9
2 9 6 0
3 7 5 9
3 4 3 9
3 4 4 0
0
x
8
=
0
1
8
5
x
8
=
1
4
8
0
3
7
0
x
8
=
2
9
6
0
4
3
0
x
8
=
3
4
4
0
L’architettura TCP/IP (il cui nome più preciso è Intevnet Pvotocol
Suite) è formata da diversi componenti, che si posizionano nello
stack dei protocolli a partire dal livello 3 (network).
I protocolli appartenenti a questa architettura sono specificati
tramite standard denominati RFC (Request For Comments)
disponibili in rete. Ad es. l’RFC 791 specifica il protocollo IP.
Il protocollo IP (Intevnet Pvotocol) è il protocollo principale del
livello 3 dell’architettura TCP/IP. Si tratta di un protocollo
semplice, di tipo datagram ovvero senza connessione e non
affidabile;
• non affidabile: il pacchetto inviato può essere perso, duplicato
ritardato o consegnato fuori sequenza, ma il protocollo IP non
informerà nè il trasmettitore nè il ricevitore.
• senza connessione: ogni pacchetto viene trattato in maniera
indipendente dagli altri, pacchetti diversi aventi stesso mittente e
stesso destinatario possono seguire percorsi diversi, alcuni possono
essere consegnati ed altri no. Se le risorse della rete lo consentono
il pacchetto viene portato a destinazione, in caso contrario
verrà scartato.
Il livello Network del TCP/IP.
Il protocollo IP (versione 4)
42
Attualmente lo standard per il livello network dello stack TCP/IP è
rappresentato dal protocollo IP versione 4, ma già dal 1995 è stata
proposta una nuova versione del protocollo IP, nota col nome di IP
versione 6 (RFC 1883: Internet Protocol, Version 6 (IPv6)
Specification, December 1995 R. Hinden, S. Deering ).
IP versione 4 versione soffre di almeno 3 problemi principali, che
IPv6 vuole correggere:
• numevo degli indivizzi IP disponibili ovmai insufficiente: gli
indirizzi IP sono composti da 4 byte (32 bit) e a causa del grande
incremento del numero degli hosts nel mondo la disponibilità degli
indirizzi è in forte calo. Il protocollo IPv6 prevede invece indirizzi
formati da 16 byte (128 bit) e quindi rende disponibili un numero
enorme di indirizzi.
• tvaffico gestito esclusivamente in modo best-effovt: in IPv4 tutti i
pacchetti sono trattati allo stesso modo dai router, anche se esiste
nell’header dei pacchetti IPv4 un campo priorità, che però non viene
utilizzato. Con IPv6 si vuole definire delle classi di servizio a cui
assegnare priorità diverse. Si vuole anche gestire la comunicazione
con un meccanismo simile ad un protocollo con connessione, cioè
implementando un flusso di dati.
• sicuvezza: in IPv6 saranno rese standard e disponibili alcune
primitive per l’autenticazione e la cifratura dei dati.
Nonostante queste prospettive, il protocollo IP versione 6 è ancora
poco diffuso, e rimane ancora a livello di sperimentazione, forse
perchè l’adozione del nuovo protocollo costringerebbe a modificare
fortemente gli apparati di rete esistenti, con un grande dispendio di
denaro. Esistono a tuttoggi, in un oceano IPv4, solo delle isole in cui
si parla IPv6.
Nel seguito parleremo di IPv4, che rappresenta ancora lo
standard più diffuso.
Il protocollo IP versione 6
43
Il protocollo IP svolge le seguenti funzioni:
• distingue ogni hosts, o meglio ogni scheda di vete mediante un
identificatove, detto indivizzo IP. Un indirizzo IP di tipo single o
unicast identifica un unico host, ma uno stesso host può avere più
indirizzi IP unicast, tanti quante sono le schede di rete che
possiede. Si parla allora di MultiHomed Systems. Ad es. i router
hanno più indirizzi, perchè dovendo fungere da centri di
smistamento dispongono di più schede di rete. Un host può
comunque disporre di più schede di rete anche senza essere un
router, cioè anche se non effettua un servizio di instradamento per
pacchetti destinati ad altri hosts, ma dovrà prevedere una politica
che definisca quale scheda di rete utilizzare per inviare i dati.
• viceve i dati (una sequenza di PDU) dal livello tvaspovto (4).
• incapsula ciascuna PDU vicevuta in pacchetti di dimensione
massima 64 Kbyte (normalmente circa 1500 byte), aggiungendovi
un pvopvio headev (o intestazione).
• eventualmente fvammenta i pacchetti all’inizio o durante il
trasporto, per inserirli nei frame di livello 2.
• instvada i pacchetti sulla rete,
• effettua la vilevazione, non la correzione, degli evvovi,
• alla destinazione, se necessario, viassembla i fvammenti
ricostruendo i pacchetti originali,
• estvae dai pacchetti i dati (PDU) del livello tvaspovto,
• consegna al livello tvaspovto i dati nell`ovdine in cui sono
avvivati a destinazione, che può essere diverso dall’ordine
in cui sono partiti.
Funzioni del protocollo IPv4
44
header del
Datagram IP
area dati del Datagram IP
(PDU del livello trasporto)
Un pacchetto IP è costituito da un header e da una parte dati, che
rappresenta la porzione di dati del livello trasporto da trasferire.
L’header IP ha:
• una prima parte fissa di 20 bye,
• una seconda parte, opzionale, di lunghezza variabile, ma sempre
multiplo di 4 byte
ed è strutturato come segue.
byte 0-3
byte 4-7
byte 8-11
byte 12-15
byte 16-19
byte .....
• Il campo Vevsion (di 4 bit) indica la versione del protocollo IP
che ha generato il pacchetto. Serve al ricevente per capire il
formato del pacchetto che stà ricevendo. Se lo standard cambia, ad
es. il passaggio da IPv4 (Version=4) ad IPv6 (version=6) il
ricevente capisce da questo campo come deve trattare il pacchetto.
• Il campo HLEN (anch’esso di 4 bit) indica la lunghezza
dell’Header IP misurata in wovd di 4 byte. Se HLEN vale 7
l’header è lungo 4*7=28 byte. Tutti i campi dell’header hanno
lunghezza fissa, tranne il campo Options, di lunghezza variabile,
ed il corrispondente campo PAD (Riempimento) che serve a
rendere l’header lungo un multiplo di 4 byte.
Il pacchetto IPv4: lo HEADER
45
header del
Datagram IP
area dati del Datagram IP
(PDU del livello trasporto)
D
F
M
F
Version HLEN Type of service Total length
Identification Fragment offset
Time to live Protocol Header checksum
Source IP address
Destination IP address
32 bit
4 4 8 1 1 1
Pad (Riempimento)
Options
• Il campo Total Length (di 16 bit) indica la lunghezza totale del
pacchetto IP, espvessa in byte, e comprende sia l’header che il campo
dati. Quindi se Total Length = 50 il pacchetto IP è lungo 50 byte.
Poichè il campo è lungo 16 bit la massima lunghezza possibile per un
pacchetto IP è di 2
16
-1=65535 byte.
• Il campo Type of Sevvice (di 8 bit) rappresenta un’indicazione ai
voutev sulla qualità del tvaspovto che possibilmente il pacchetto IP
dovvebbe spevimentave. Il router dovrebbe basarsi anche su queste
indicazioni per decidere la precedenza dei pacchetti nelle sue code, e
l’instradamento. Ad es. se il pacchetto richiede un certo ritardo
massimo, il router potrebbe decidere di instradarlo su un percorso
attraverso una rete ATM a cui chiedere garanzie sul ritardo massimo
piuttosto che attraverso una rete con servizio di tipo Best Effort che
non può offrire garanzie.
• I primi 3 bit definiscono un campo Pvecedenza con valori da 0 a
7, al crescere del quale cresce l’importanza del pacchetto IP. Il
valore 0 indica precedenza normale, il valore 7 un pacchetto di
controllo della rete, e quindi maggiore importanza.
• I bit 3 (D=Delay), 4 (T=Throughput) e 5 (R=Reliability) indicano
il tipo di trasporto preferito: D settato (D=1) indica richiesta di
basso ritardo, T settato chiede un elevato throughtput (ampia banda
di trasmissione), R settato indica richiesta di massima affidabilità.
• I bit 6 e 7 non sono usati.
Di solito però i router non tengono conto delle preferenze espresse
mediante il campo Type of Service. Attualmente si studiano delle
politiche (note come Diffeventiated Sevvices) che cercano di
realizzare routing e buffering basandosi sulla classificazione
delle applicazioni proprio in base a indicazioni di questo tipo.
HEADER IPv4 (2)
46
Precedenza Non Usati R T D
0 1 2 3 4 5 6 7
L’hardware di ogni tipo di rete impone un limite superiore alla
dimensione del frame di livello 2, e quindi anche alla quantità di dati
di livello 3 che possono essere trasportati in un unico frame a livello 2.
La dimensione massima di dati di livello 3 che possono essere
trasportati in un frame del Data Link viene chiamata Massima Unità
di Tvasfevimento (MTU: Maximum Tvansfev Unit), ed è
caratteristico di ogni tipologia di rete. Per Ethernet MTU=1500 bytes.
• Se la porzione dati di un datagram IP (più la dimensione dell’header
IP) è più piccola della MTU della rete sottostante, il datagram IP potrà
essere inserito completamente in un frame di livello 2 e inviato a
destinazione.
• Se invece la porzione dati del datagram IP ( più la dimensione
dell’header IP) è più grande della MTU della rete sottostante, la
povzione dati dovrà essere spezzata in più pezzi che verranno
incapsulati in datagram IP (detti frammenti) più piccoli della MTU, e
ciascun frammento dovrà essere inserito in un frame diverso e verrà
spedito separatamente dagli altri verso la destinazione finale, dove il
protocollo IP provvederà a rimettere assieme i diversi frammenti e a
ricostituire il datagram originale. Nell’esempio un pacchetto IP con
3260 byte di dati frammentato per una MTU di 1500 byte.
47
La Frammentazione dei pacchetti IP
frammento 1 (offset 0) (MF=1)
frammento 1 (offset 1480)( MF=1)
frammento 3 (offset 2960) (MF=0)
Header Frame Area Dati Frame
Header Datagram IP Area Dati Datagram IP
header frammento 1
(20 byte)
Dati 1
(1480 byte)
header datagram IP
(20 byte)
Dati 1
(1480 byte)
Dati 2
(1480 byte)
Dati 3
(300 byte)
header frammento 3
(20 byte)
Dati 3
(300 byte)
header frammento 2
(20 byte)
Dati 2
(1480 byte)
il problema della frammentazione si propone ogni volta che nel
percorso seguito dai pacchetti IP, si deve attraversare una porzione di
rete avente una MTU minore della porzione di rete precedentemente
attraversata, sempre se la dimensione dei pacchetti IP è maggiore della
MTU più piccola.
Il router preleva allora la porzione dati del datagram IP e lo spezza in
più porzioni, in modo che ciascuna (aggiungendovi l’header) stia in un
frame, e in modo che ogni frammento dei dati, tranne l’ultimo, abbia
dimensione multipla di 8 byte, perchè così è definito il campo offset
dell’header IP.
L’ultimo pezzo in genere sarà il più corto e verrà identificato come
ultimo settandovi a zero il flag MoveFvagment, ad indicare che è
l’ultimo frammento. Negli altri frammenti MF è settato a 1.
Il protocollo IP usa tre campi dell’header per controllare il
meccanismo della frammentazione e permettere il riassemblaggio dei
datagram frammentati. Questi campi sono Identification (16 bit),
Fvagment Offset (15 bit) e il flag Move Fvagment (MF).
48
La Frammentazione dei pacchetti IP
D
F
Version HLEN Type of service Total length
Time to live Protocol Header checksum
Source IP address
Destination IP address
32 bit
4 4 8 1 1 1
Pad (Riempimento)
Options
M
F
Fragment offset Identification
L`headev del datagvam oviginale vevvà copiato integvalmente nei
fvammenti (con qualche modifica per il campo Options) e in più verrà
cambiato il campo Fvagment Offset che indica il punto dell`avea
dati del datagvam oviginale in cui comincia la povzione di dati
tvaspovtata nel fvammento. Tale offset è pensato come un multiplo
di 8 byte. Se l’offset indica ad es. 185, il frammento porta la porzione
di dati che inizia nella posizione 185*8=1480 byte.
• Tutti i fvammenti sono caratterizzati dall’avere lo stesso
identificatore (il campo Identification) del datagram originale. Tale
numero è assegnato univocamente dal trasmettitore (che mantiene un
contatore dei datagram IP trasmessi), e la coppia (IP Provenienza,
Identification) rende univocamente identificabile un certo
datagram IP, e tutti i suoi frammenti.
49
La Frammentazione dei pacchetti IP
Dopo la frammentazione, ogni frammento viaggia separatamente dagli
altri fino alla destinazione finale. Solo alla fine del viaggio avrà luogo
il riassemblaggio dei frammenti, nel tentativo di ricostruire il
datagram Originale.
Il ricevitore riconosce di avere ricevuto un frammento e non un
datagram intero in due modi:
• il pacchetto IP ricevuto ha un offset uguale a zevo, ma ha il flag
Move Fvagment settato ad uno (è il primo frammento).
• il pacchetto IP ricevuto ha un offset divevso da zevo (è un
frammento successivo). Se il More Fragment è zero è l’ultimo
frammento.
Il protocollo IP del ricevente identifica univocamente i frammenti di
uno stesso datagram mediante la coppia (IP tvasmettitove,
Identification del datagvam).
Il ricevente non conosce la dimensione del datagram originale perchè
ogni frammento mantiene nel campo Total Length la lunghezza del
frammento stesso, e non quella del datagram originale. Solo quando
riceverà il frammento con il flag Move Fvagment settato a zevo(che
indica l’ultimo frammento del datagram originale), si potrà capire la
dimensione totale del datagram originale sommando all’offset
dell’ultimo frammento la lunghezza dei dati trasportati nell’ultimo
frammento.
Se un solo frammento viene perso, è impossibile ricostruire il
datagram IP originale.
Per evitare di aspettare inutilmente un frammento perso, il ricevitore
nel momento in cui riceve un primo frammento inizializza un timer.
Se il timer scade prima che tutti i frammenti siano giunti a
destinazione il ricevitore butta via tutti i frammenti.
50
Il Riassemblaggio dei Frammenti
• Il campo Time To Live (TTL, tempo di vita, di 8 bit) indica in modo
approssimato il tempo, in secondi, che un pacchetto IP può rimanere
all’interno di una rete prima di essere scartato.
-Tale valore viene inizializzato a 255 dall’host che spedisce il
pacchetto IP. Ogni volta che un router processa quel pacchetto IP,
decrementa di una unità questo contatore. Quando il contatore
raggiunge il valore zero, il router scarta il pacchetto.
-Per ovviare al problema della congestione della rete che causa lunghe
attee in coda, quando il router riceve un pacchetto e lo mette in coda in
attesa per spedirlo, salva il valore corrente dell’orologio.
-Quando il pacchetto deve essere spedito si calcola il tempo (in
secondi) trascorso in coda e si decrementa di questo tempo il
contatore TTL.
-Grazie al contatore TTL, i pacchetti IP non possono viaggiare in
eterno nella rete anche se per un errore i router li instradano in un
percorso ciclico, e si evitano così congestioni nella rete.
-Quando un pacchetto IP viene frammentato durante il percorso, tutti i
suoi frammenti vengono incapsulati con il TTL residuo del pacchetto.
• Il campo Pvotocol (di 8 bit) indica di quale tipo è il dato trasportato
nell’area dati del pacchetto IP, ovvero indica qual’è il protocollo di
livello 4 (o 3) che ha generato i dati trasportati dal pacchetto IP. In tal
modo il livello Network sa a quale protocollo dovrà consegnare i dati
trasportati. Tra i protocolli che possono essere trasportati nell’area dati
ricordiamo:
•0 ICMP Internet Control Message Protocol
•4 IP IP in IP (incapsulamento, tunneling)
•6 TCP Transmission Control Protocol
•17 UDP User Datagram Protocol
•29 ISO-TP4 ISO Transport Protocol Class 4
•93 AX.25 AX.25 frames
51
HEADER IPv4 (continua 3)
• Il campo Headev Checksum (di 16 bit) serve a verificare che lo
headev IP sia avvivato integvo a destinazione. Viene codificato
utilizzando i byte del solo header. Se durante il trasporto l’header
subisce delle modifiche, la checksum risulta diversa e il protocollo IP
capisce che c’è stato un errore.
Notare che la checksum verifica l’integrità del solo header, e non dei
dati trasportati.
Il vantaggio di aveve una checksum sepavata pev headev e dati è
che:
• i protocolli di livello superiore possono scegliere una loro codifica
per il controllo degli errori,
• i router diminuiscono il tempo necessario a calcolare la checksum,
perchè devono processare solo l’header, che di solito è più piccolo
dei dati trasportati.
Di contro, lo svantaggio di avere a livello IP una checksum solo per
l’header IP impedisce al livello 3 di accorgersi di eventuali errori sui
dati di livello 4, fino a che tali dati non sono giunti a destinazione
finale, e solo allora il protocollo di livello 4 effettuerà il controllo sui
dati con delle proprie checksum. Però il livello 2 potrebbe avere già
riconosciuto un errore e scartato il frame.
• I campi Souvce IP addvess e Destination IP addvess contengono
gli indirizzi IPv4 a 32 bit della provenienza originale e della
destinazione finale del datagramma IP. Tali indirizzi quindi non
cambiano mai durante tutto il percorso, comunque venga instradato il
pacchetto, e comunque venga frammentato.
• Il campo Options, è di lunghezza variabile, e poichè l’header IP
deve avere lunghezza pari ad un multiplo di 4 byte, viene introdotto
ove necessario un ultimo campo Padding di riempimento,
per arrivare alla giusta lunghezza.
52
HEADER IPv4 (4)
Gli indirizzi IP, che devono essere univoci sulla rete, sono lunghi 32
bit (quattro byte) e sono tradizionalmente visualizzati scrivendo i
valori decimali di ciascun byte separati dal carattere punto.
• Gli indirizzi IP comprendono due o tre parti. La prima parte indica
l'indirizzo della rete (network), la seconda (se presente) quello della
sottorete (subnet) e la terza quello dell'host.
• Si noti che non sono gli hosts ad avere un indirizzo IP, bensì le
interfacce di rete. Quindi se un nodo ha tre interfacce, esso ha tre
indirizzi IP. Poichè la maggior parte degli hosts ha una sola
interfaccia di rete, parlando di indirizzo IP di un host si fa
riferimento all’indirizzo della sola interfaccia di rete presente.
• Gli indirizzi IP sono suddivisi in cinque classi, di cui le prime 3,
denominate A, B e C, servono ad individuare singole interfacce di
rete e differiscono per il numero di host che ciascuna rete può
indirizzare, mentre le altre due classi D ed E sono utilizzate per
servizi assai differenti. Ad es. le reti dell’Università di Bologna
(130.136.*.* , 137.204.*.* ) sono di classe B.
- Classe A. Sono concepiti per poche reti di dimensioni molto
grandi. Gli indirizzi di classe A sono riconoscibili in quanto il il bit
più significativo del primo byte è posto a zero, e quindi il primo
campo dell'indirizzo è compreso tra 0 e 127. I bit che indicano la rete
sono 7 e quelli che indicano l'host 24. Quindi si possono avere al
massimo 128 reti di classe A, ciascuna con una dimensione massima
di circa 16 milioni di indirizzi.
55
INDIRIZZAMENTO IP
- Classe B. Sono pensati per un numero medio reti di dimensioni
medio-grandi. Gli indirizzi di classe B si riconosconno perchè i 2
bit più significativi del primo byte sono posti a 10, quindi il primo
campo dell'indirizzo è compreso tra 128 e 191. I bit che indicano la
rete sono 14 e quelli che indicano l'host 16. Si possono avere circa
16000 reti di classe B, ciascuna con circa 64000 indirizzi.
- Classe C. Sono concepiti per identificare molte reti di dimensioni
piccole. Gli indirizzi di classe C hanno i primi 3 bit settati a 110 e
quindi il primo campo dell'indirizzo è compreso tra 192 e 223. I bit
che indicano la rete sono 21 e quelli che indicano l'host 8. Quindi si
possono avere al massimo 2 milioni di reti di classe C, ciascuna con
una dimensione massima di 256 indirizzi.
- Classe D. Sono indirizzi usati per applicazioni di multicast . Gli
indirizzi di classe D si riconoscono perchè i primi 4 bit del primo
byte sono settati a 1110, e quindi il primo campo dell'indirizzo è
compreso tra 224 e 239.
- Classe E. Questi indirizzi sono riservati per usi futuri. Gli
indirizzi di classe E hanno i 4 bit più significativi settati a 1111, e
quindi il primo campo dell'indirizzo è compreso tra 240 e 255.
56
INDIRIZZAMENTO IP (2)
127 qualunque numero
0 1 2 7 8 31
loopback
Esistono inoltre indirizzi IP con significato particolare, ad esempio
per gli indirizzi di broadcast e per il loopback.
57
INDIRIZZAMENTO IP (3)
tutti 0
0 1 2 7 8 31
questo host
tutti 1
0 1 2 7 8 31
broadcast su
questa network
(non ammesso come indi_
rizzo di provenienza)
(suggerimento: provare su s.o. Linux il comando ping 0.0.0.0)
(suggerimento: provare il comando ping 255.255.255.255)
l’host specificato
in questa rete
(non ammesso come indi_
rizzo di destinazione)
(serve solo all’avviamento)
tutti 0 host
0 1 2 15 16 31
(suggerimento: provare il comando ping 0.0.0.204)
rete tutti 1
0 1 2 15 16 31
Broadcast per la
rete specificata
(non ammesso come indi_
rizzo di provenienza)
(suggerimento:staccare il cavo di rete e provare il comando ping 127.5.6.7)
Quando si utilizza il loopback, il pacchetto non viene inviato sulla
rete ma viene passato ai moduli IP di ricezione, ed elaborato come
se fosse in arrivo: ciò serve ad es. per effettuare localmente
dei test su un software di rete in fase di sviluppo.
E’ necessario creare un insieme di protocolli di rete che non
dipendano dall’architettura del computer o della scheda di rete. Non
tutte le architetture di calcolatori memorizzano i dati nello stesso
modo. In particolare gli interi di 32 bit (le dimensioni dell’indirizzo
IP) sono memorizzati in due modi diversi: le macchine Little Endian
mantengono il byte meno significativo nell’indirizzo di memoria più
basso, le macchine Big Endian mantengono il byte più significativo
nell’indirizzo di memoria più basso.
• Per evitare confusione, ovvero per evitare che un indirizzo IP
possa venire scritto in due modi diversi, i protocolli TCP/IP
definiscono un ordine di byte standard della rete, che tutte le
macchine devono usare per i campi dei protocolli IP, ovviamente
non per la parte dati.
• In trasmissione, prima di scrivere ad es. un’indirizzo IP nel campo
destinazione del pacchetto IP, l’host deve convertire il numero IP
dalla sua propria rappresentazione alla rappresentazione standard
per la rete.
• In ricezione, prima di valutare l’indirizzo IP contenuto nel campo
provenienza del pacchetto IP, deve convertire tale campo dal
formato di rete standard nel formato interno della macchina.
• Internet stabilisce come ordine standard per gli interi a 32 bit,
quello che prevede che i byte più significativi siano trasmessi per
primi (stile Big Endian). Guardando viaggiare i dati da una
macchina all’altra, un intero a 32 bit comincia ad essere trasmesso
dal byte più significativo, cioè col byte più significativo più vicino
all’inizio del pacchetto. Le librerie socket forniscono per le
conversioni delle funzioni
che sono: ntohs,ntohl,
htons,htonl.
58
L’ORDINE dei BYTE in RETE
byte 3 byte 2 byte 1 byte 0
byte
address
Big Endian
byte 3 byte 2 byte 1 byte 0
fine
pacchetto
inizio
pacchetto
direzione di trasmissione
Il routing IP, ovvero l’instradamento di un pacchetto IP verso la
destinazione specificata nell’header del pacchetto IP viene
effettuato secondo le seguenti modalità:
• Un certo host deve inviare un pacchetto IP verso una destinazione
IP_D (oppure un router ha ricevuto un pacchetto e deve instradarlo
verso la destinazione IP_D).
• l’host controlla l’indirizzo IP di destinazione per capire se
l’indirizzo IP_D appartiene alla sua stessa sottorete cioè se la
destinazione è raggiungibile in modo diretto mediante il MAC
address cioè sfruttando il solo livello 2, ovvero se per raggiungere la
destinazione non è necessario attraversare router (livello 3) ma al
massimo si attraversano dei bridge (vedi subnetting).
• Se la destinazione sta sulla stessa sottorete dell’host, si usa la
consegna diretta, cioè l’host trova (col protocollo ARP) l’indirizzo
di livello 2 di destinazione (MAC_D), incapsula il pacchetto IP in
un pacchetto di livello 2 con quell’indirizzo MAC_D come
destinazione, e lo invia in rete.
• La destinazione MAC_D a livello 2 vede passare il pacchetto,
vede il proprio indirizzo MAC_D e carica il pacchetto passandolo al
livello IP ed il gioco è fatto.
• Se invece la destinazione non sta sulla stessa sottorete si usa la
consegna indiretta, cioè l’host deve inviare il pacchetto IP al router
di default, ovvero un router che l’host conosce e che sta sulla stessa
sottorete dell’host. L’host cerca (ancora con il protocollo ARP)
l’indirizzo di livello 2 del default router (MAC_R), incapsula il
pacchetto IP in un pacchetto di livello 2 con quell’indirizzo MAC_R
come destinazione, e lo invia in rete.
Il router vede passare il pacchetto con il proprio MAC address, lo
carica, estrae il pacchetto IP, vede che l’indirizzo IP_D non è
il suo e lo instrada con lo stesso meccanismo già visto.
59
Approccio al ROUTING IP (1)
60
Approccio al ROUTING IP (2)
S
cagnina
130.136.3.204
D
sangiovese
130.136.3.203
bridge (quindi livello 2), invisibile al livello IP,
instrada i pacchetti tramite il MAC address
S
cagnina
130.136.3.204
D
sangiovese
130.136.3.203
BRIDGE
Consegna Diretta (via MAC address)
Sorgente S e destinazione D appartengono alla stessa sottorete
anche se in mezzo c’e’ un bridge, perchè lavorando a livello 2
instrada i pacchetti mediante i MAC address, e non gli indirizzi IP
Consegna Indiretta (via IP address)
Sorgente S e destinazione D non stanno sulla stessa sottorete
in mezzo c’e’ un router, che lavora a livello 3,
necessario routing a livello IP
sarastro.cs.unibo.it
130.136.2.10
S
cagnina
130.136.3.8
D
ROUTER
130.136.3.252 130.136.2.253
MAC_S MAC_R3 MAC_R2
MAC_D
• Un certo indirizzo di rete di classe A, B o C permette di mappare
un certo numero di hosts, come appartenenti ad una stessa rete.
• Però una rete fisica, a seconda della sua topologia, della
disposizione degli apparati di routing e bridging, e di criteri di
opportunità vari, può essere intrinsecamente costituita da più parti,
connesse in varia maniera, ciascuna delle quali costituisce una rete
broadcast a livello 2.
• Si è cercato di indirizzare ciascuna di queste porzioni di rete in
modo separato, senza essere costretti ad utilizzare per ciascuna di
esse una diversa rete (non sono poi così tante le reti disponibili).
• Il meccanismo del subneting serve proprio a descrivere la
situazione in cui alcuni host sono in grado di comunicare tra loro in
modo diretto via livello 2, in modo da dar loro la possibilità di
scambiarsi direttamente i pacchetti IP senza utilizzare un router, e si
dice allora che gli host appartengono alla stessa sottorete.
• Il meccanismo consiste nel considerare la parte host del’indirizzo
IP di un host, come formato da due parti, la subnet e l’host. A
seconda dell’ampiezza del campo dedicato alla subnet, si possono
ottenere molte subnet contenenti ciascuna pochi host, o poche
subnet contenenti molti hosts.
• L’ampiezza dei campi subnet e host viene definita mediante un
parametro, detto netmask, configurato uguale per tutti gli host della
stessa subnet. La netmask contiene i bit ad uno in corrispondenza
dei campi network e subnet dell’indirizzo IP, e a zero in
corrispondenza del campo host.
Ad es. una netmask 11111111111111111111111100000000, più
comunemente scritta come indirizzo IP 255.255.255.0 (o in
esadecimale ffffff00), indica che il campo host coincide con
l’ultimo byte dell’indirizzo IP
61
SUBNET IP (1)
Con il subnetting dell’esempio qui sopra, a partire da uno stesso
indirizzo di classe B, otteniamo 256 diverse sottoreti, ciascuna con
254 indirizzi disponibili per gli host (256 -broadcast(1) -questo(0)).
Algoritmo di Riconoscimento di Appartenenza a stessa subnet.
Dato un indirizzo IP di un host, e la netmask per la sua sottorete,
dato un altro indirizzo IP (ad. es. di un host a cui il primo deve
spedire un pacchetto) per capire se i due indirizzi appartengono alla
stessa sottorete, ogni indirizzo viene messo in AND bit a bit con la
netmask, ottenendo due valori che sono la parte network e subnet
dei due indirizzi. Se questi due valori sono uguali, allora i due
indirizzi IP stanno sulla stessa sottorete, altrimenti stanno su
sottoreti diverse.
Ad es., una rete (classe B) 130.136.*.* con netmask 255.255.255.0,
i due indirizzi 130.136.3.204 e 130.136.3.203 stanno sulla stessa
sotorete, perchè dall’AND bit a bit di indirizzi e netmask si
ottiene 130.136.3.0, quindi i due IP sono nella stessa subnet.
62
SUBNET IP (2)
Vediamo un esempio di indirizzo di una classe B, a cui viene
applicata una netmask 255.255.255.0 , in modo che la parte host
viene partizionata in una parte subnet ed una host di 8 bit ciascuna.
1 0
0 1 2 7 8 15 16 23 24 31
network host
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0
0 1 2 7 8 15 16 23 24 31
netmask
1 0
0 1 2 7 8 15 16 23 24 31
network subnet host
indirizzo di
classe B
netmask
subnetting
Come abbiamo visto poco fa (slide 59) l’importanza di capire se due
host stanno sulla stessa sottorete risiede nel fatto che in IP il routing
minimo (consegna diretta) è realizzabile solo se i due hosts stanno
sulla stessa sottorete.
Quindi, in tecnologia IP una subnet deve coincidere con una rete
fisica (*), o meglio deve coincidere con l’insieme degli host che
possono comunicare tra loro via broadcast di livello2 , e quindi può
coincidere anche con un insieme di più reti fisiche connesse
mediante bridges (livello 2).
(*) In realtà, il concetto di subnet è stato rilassato, e si permette che:
• una stessa rete fisica (una LAN) (o più reti fisiche interconnesse
da bridge) possa contenere più subnet IP, qualora sia necessario
partizionare una rete fisica in modo logico.
• Invece una subnet IP non può mai contenere più reti fisiche
interconnesse da router, cioè una subnet IP non può mai attraversare
dei router. (**)
(**) Anche questa affermazione è messa in forse dall’avvento di
concetti quali le LAN virtuali, che però non è chiaro a quale livello
debbano essere collocate. Quindi per ora ci teniamo il concetto di
subnet IP come rete capace di comunicare per mezzo del
broadcast di livello 2.
63
SUBNET IP (3)
Bridge
Router
130.136.3.0
130.136.2.0
130.136.2.0
cagnina
130.136.3.204
130.136.3.251
130.136.2.253
sangio
vese
130.136.3.203
timur
130.136.2.7
sarastro
130.136.2.10
Per come viene effettuato il routing in IP, le informazioni che un
host deve conoscere per poter instradare correttamente i pacchetti
che vuole spedire sono:
• il proprio indirizzo MAC, che legge nella sua scheda di rete,
• il proprio indirizzo IP, che può essere assegnato e salvato su file
o gli può essere assegnato al boot (per i sistemi diskless, mediante il
protocollo RARP a partire dal MAC address),
• l’indirizzo IP di un router di default, a cui consegnare tutti i
pacchetti IP per indirizzi che non stanno nella sua stessa sottorete,
• un criterio per sapere quali hosts stanno nella sua sottorete, in
modo da poter decidere se spedire i pacchetti direttamente (previa
richiesta ARP) o se instradarli via default router. Tale criterio è
costituito dalla cosiddetta netmask, che l’amministratore di rete
assegna ad ogni host, la stessa per tutti gli hosts della sottorete.
La sottorete a cui un host (un IP address) appartiene è costituita da
quegli host che:
1) appartengono alla stessa rete IP, cioè hanno stesso indirizzo di
rete (sia essa di classe A B o C),
2) appartengono alla stessa rete fisica (o a reti fisiche diverse ma
separate solo da bridge, che inoltrano via MAC address) e
quindi propagano i broadcast di livello 2, per permettere ad es. di
comunicare tramite livello 2 direttamente e di effettuare la chiamata
al protocollo ARP,
3) hanno avuto assegnata la stessa netmask, che individua
l’appartenenza ad una stessa sottorete,
4) hanno indirizzo IP e netmask tali che l’AND bit a bit tra
indirizzo IP e netmask da’ lo stesso risultato per tutti gli IP
della sottorete (criterio di appartenenza ad una stessa
sottorete).
64
Informazioni per Routing in un Host
--- --- Ogni Ogni host host deve mantenere deve mantenere le le seguenti informazioni seguenti informazioni: :
• il proprio indirizzo MAC,
• il proprio indirizzo IP,
• l’indirizzo IP di un router di default nella stessa sottorete,
• la netmask della sua sottorete (riconoscere gli IP della sottorete).
--- Un host per inviare un datagramIP,
• fa l’AND bit a bit della netmask, una volta con l’indirizzo di
destinazione, ed una volta con il proprio indirizzo IP.
• Se i due risultati sono uguali l’indirizzo di destinazione
appartiene alla sua stessa sottorete, e l’host usa l’instradamento
diretto, inviando il datagram in un frame di livello 2 avente, come
indirizzo di livello 2, il MAC address della destinazione.
• In caso contrario l’host invia il pacchetto IP (che ha nell’header
l’indirizzo di destinazione IP) incapsulato in un frame destinato
al router di default cioè avente MAC address del router sulla
stessa rete, ed il salto successivo lo deciderà il router (Next Hop).
• Qualunque sia la destinazione a livello 2 (il router per il primo
salto o l’host destinazione in caso di consegna diretta) il MAC
address verrà ottenuto con chiamata ARP o alla cache ARP.
• La destinazione a livello 2 vede transitare il proprio indirizzo
MAC, carica il frame, toglie l’incapsulamento e passa al livello IP.
• Nell’host di destinazione, il livello IP riconosce nell’indirizzo IP
di destinazione del pacchetto IP il proprio indirizzo IP e consegna il
pacchetto IP al protocollo superiore.
• Invece il router si accorge che il pacchetto IP non è per lui, e
decide di instradarlo, consulta delle tabelle di instradamento (coppie
(subnet, interface)) e se l’IP destinazione del pacchetto sta’ nella
sottorete di una interfaccia instrada in modo diretto, altrimenti
sceglie (tra i router a lui direttamente collegati) quale usare per
instradare il pacchetto; il prossimo router sceglie il successivo hop.
65
Il Routing in Internet Protocol
Il concetto di subnet introduce quindi un livello di gerarchia nelle
reti TCP/IP. Il routing si scinde in:
• un routing all’interno della subnet,
• un routing tra subnet diverse ma gestite da una stessa autorità, i
cosiddetti autonomous systems (es. l’Univ. di Bologna),
• un routing tra diversi autonomous systems.
Il routing all’interno della subnet è banale (* in teoria) perchè la
subnet coincide con una rete fisica, che consente la consegna diretta
tra le stazioni mediante una trasmissione broadcast. Serve solo
mappare indirizzi IP in indirizzi MAC con ARP e RARP.
(*) In realtà la faccenda può complicarsi se si vuole minimizzare lo
scambio di pacchetti in reti fisiche costituite da più sottoreti fisiche
collegate da bridge. Alcuni bridge intelligenti monitorano il traffico
a livello 2, memorizzando gli indirizzi MAC contenuti nei campi
provenienza dei pacchetti di livello 2 che li attraversano. In tal
modo capiscono automaticamente in che direzione sono collocati gli
hosts con quei MAC address, ed instradano i pacchetti MAC ad
essi destinati solo in quella direzione. I pacchetti broadcast sono
invece propagati a tutti.
Il routing tra le subnet, cioè all’interno di un autonomous system è
gestito dai router (detti Interior Router), mediante tabelle di
instradamento che possono essere scritte (in toto o in parte)
manualmente dal gestore della rete o calcolate automaticamente con
algoritmi dei cosiddetti tipi distance vector o link state packet. Tra i
più recenti ricordiamo OSPF (Open Shortest Path First) che effettua
il bilanciamento di carico tra percorsi paralleli ed e’ standard.
Il routing tra autonomous systems risponde più ad esigenze
amministrative che non tecniche, ed è gestito con algoritmi
detti Exterior Gateway Protocol, quali BGP (Border Gat. Prot.).
66
Routing Gerarchico
Una connessione TCP
viene instaurata con
le seguenti fasi,
che formano il
Three-Way
Handshake (perchè
formato da almeno
3 pacchetti trasmessi):
1) il server si predispone ad accettare una richiesta di connessione,
mediante le chiamate a socket, bind, listen e infine accept che realizza
una apertura passiva (passive open) cioè senza trasmissione di dati.
2) il client effettua le chiamate a socket, bind ed infine alla connect che
realizza una apertura attiva (active open) mediante la spedizione di un
segmento TCP detto SYN segment (synchronize) in cui è settato ad 1
il flag syn, a zero il flag ack, e che trasporta un numero di sequenza
iniziale (J) che è il numero di sequenza iniziale dei dati che il client
vuole mandare al server. Il segmento contiene un header TCP con i
numeri di porta ed eventuali opzioni su cui accordarsi, e di solito non
contiene dati. Il segmento viene incapsulato in un datagramIP.
3) Il server deve rispondere al segmento SYN del client spedendogli un
segmento SYN (flag syn settato ad 1) con il numero di sequenza
iniziale (K) dei dati che il server vuole mandare al client in quella
connessione. Il segmento presenta inoltre nel campo Ack number il
valore J+1 che indica che si aspetta di ricevere J+1, e presenta il flag
ack settato ad 1, per validare il campo Ack number.
4) il client, ricevendo il SYN del server con l’Ack numer J+1 sa che la
sua richiesta di connessione è stata accettata, e dal sequence number
ricevuto K capisce che i dati del server inizieranno da K+1, quindi
risponde con un segmento ACK (flag syn settato a zero e flag ack
settato a 1) con Ack number K+1, e termina la connect.
5) al ricevimento dell’ACK K+1 il server termina la accept.
Setup delle ConnessioniTCP
87
J+... J+3 J+2 J+1
K+1 K+2 K+3 K+...
Ogni segmento di tipo SYN può contenere delle opzioni, che servono a
stabilire alcune caratteristiche della connessione che si sta instaurando.
Tra le più importanti ricordiamo:
1) MSS options: con questa opzione il TCP che manda il proprio SYN
annuncia il maximum segment size, la più grande dimensione di
segmento che è in grado di ricevere. L’opzione TCP_MAXSEG resa
disponibile dall’interfaccia socket, consente di settare questa opzione.
2) Windows scale options: la finestra scorrevole più grande che due
TCP possono concordare è 65535, perchè il campo Window Size
occupa 16 bit. Per situazioni in cui il collegamento è a larghissima
banda ma con grande ritardo di trasmissione (es. via satellite) una
dimensione di finestra più grande rende più veloce la trasmissione di
grandi quantità di dati. Per identificare finestre più grandi nell’header
TCP, si setta questa opzione che indica di considerare il campo
Window Size dopo averlo shiftato a sinistra di un numero di posizione
compreso tra 0 e 14, in modo da moltiplicare la Window Size di un
fattore fino a 2 elevato alla 14, ovvero in modo da raggiungere valori
dell’ordine del GigaByte. L’opzione SO_RECVBUF resa disponibile
dall’interfaccia socket, consente di settare questa opzione.
Opzioni TCP nel Setup
88
89
(1)
(2.1)
(2.2)
(3)
(4)
Una connessione TCP viene chiusa mediante un protocollo composto da
quattro messaggi trasmessi:
1) una delle applicazioni su un end-system(chiamiamola attiva)
effettua la chiusura attiva (active close) chiamando la funzione close()
che spedisce un segmento FIN (flag FIN settato a 1), con un numero di
sequenza M pari all’ultimo dei byte trasmessi in precedenza più uno.
Con ciò si indica che viene trasmesso un ulteriore dato, che è il FIN
stesso. Per convenzione il FIN è pensato avere dimensione pari ad 1
byte, quindi l’end-systemattivo si aspetta di ricevere per il FIN un ACK
con Ack Number pari a M+1.
2) l’end systemche riceve il FIN (chiamiamolo passivo) effettua la
chiusura passiva (passive close) all’insaputa dell’applicazione.
2.1) Per prima cosa il modulo TCP del passivo spedisce all’end-system
attivo un segmento ACK con Ack number pari a M+1, come riscontro
per il FIN ricevuto.
2.2) Poi il TCP passivo trasmette all’applicazione padrona di quella
connessione il segnale FIN, sotto forma di end-of-file che viene
accodato ai dati non ancora letti dall’applicazione. Poiche la ricezione
del FIN significa che non si riceverà nessun altro dato, con l’end-of-file
il TCP comunica all’applicazione che lo streamdi input è chiuso.
Terminazione delle (1)
ConnessioniTCP
end-system
attivo
end-system
passivo
3) Quando l’applicazione del passivo finalmente legge dal buffer
l’end-of-file (con una read() che restituisce 0), deve effettuare per quel
socket la chiamata alla funzione close(). La close() ordina al modulo
TCP di inviare a sua volta all’end-systemattivo un segmento FIN,
col numero di sequenza (N) del FIN, cioè l’ultimo byte trasmesso più 1.
4) il modulo TCP dell’attivo, quando riceve il FIN spedisce un ACK
con Ack number N+1, cioè il numero di sequenza del successivo al
FIN, cioè il FIN più uno, poichè 1 è per convenzione la dimensione del
FIN. Terminato questo passo viene conclusa anche la funzione close()
dell’attivo.
• Chiusura attiva o passiva non dipendono dall’essere client o server,
ma solo da chi per primo effettua la chiamata alla funzione close().
• Notare che i due segmenti dal passivo all’attivo degli step 2.1 e 2.2
(ACK M+1 e FIN N rispettivamente) potrebbero essere combinati in un
solo messaggio a seconda del comportamento del passivo.
• Un’ulteriore variazione, di carattere opposta alla precedente, è che tra
gli step 2.1 e 2.2 il passivo ha ancora la possibilità di inviare dei dati
verso l’attivo, perchè al momento dello step 2.1 è stata effettuata,
mediante il FIN, una chiusura del flusso solo nella direzione dall’attivo
al passivo, chiusura che viene detta half-close.
Terminazione delle (2)
ConnessioniTCP
90
Esempio di connessione TCP (1)
93
Vediamo i segmenti scambiati e gli stati assunti da client e server in una
connessione TCP in cui il client chiede un servizio ed il server risponde.
Il client inizia e specifica l’opzione Maximum Segment Size di 1460
byte, il server risponde e specifica una diversa richiesta di MSS di 1024.
La MSS può essere diversa nelle due direzioni. Stabilita la connessione
il client spedisce una richiesta al server nei dati di un solo segmento. Il
server risponde spedendo la risposta nei dati di un solo segmento.
Notare che, per diminuire il numero di segmenti scambiati, assieme alla
risposta il server spedisce nel segmento anche l’ACK per il segmento
ricevuto. Tale tecnica, detta piggybacking, viene utilizzata quando il
ritardo nella risposta è inferiore ai 200 msec. Infine vengono utilizzati
quattro segmenti per effettuare la terminazione della connessione.
Il compito del livello transport (livello 4) è di fornire un trasporto
efficace dall'host di origine a quello di destinazione,
indipendentemente dalla rete utilizzata.
Questo è il livello in cui si gestisce per la prima volta (dal basso
verso l'alto) una conversazione diretta, cioè senza intermediari, fra
una transport entity su un host e la sua peer entity su un
altro host.
.
Naturalmente, tali servizi sono realizzati dal livello transport per
mezzo dei servizi ad esso offerti dal livello network.
Così come ci sono due tipi di servizi di livello network, ce ne
sono due anche a livello transport:
• servizi affidabili orientati alla connessione, detti di tipo
stream, offerti dal TCP (Transmission Control Protocol);
• servizi senza connessione detti di tipo datagram offerti
dall’ UDP (User Datagram Protocol).
Il livello di Trasporto del TCP/IP
67
TSAP
address
NSAP
address
Liv.
Applic.
Liv.
Transport
Liv.
Network
Transport
Entity
Transport
Entity
TPDU
Quando si vuole trasferire una o più TPDU (Transport Protocol Data
Unit) da una sorgente ad una destinazione di livello 4, occorre
specificare mittente e destinatario di livello 4. Il protocollo di livello 4
deve quindi decidere come deve essere fatto l'indirizzo di livello
transport, detto TSAP address (Transport Service Access Point
address).
Poichè l’indirizzo di livello 4 serve a trasferire informazioni tra
applicazioni che lavorano su hosts diversi, deve poter individuare un
host e una entità contenuta nell’host. Per questo motivo i protocolli di
livello 4 tipicamente definiscono come indirizzo di livello 4 una coppia
formata da un indirizzo di livello network che identifica l’host, e da
un’altra informazione che identifica un punto di accesso in quell’host
(NSAP address, informazione supplementare).
Ad esempio, in TCP/IP un TSAP address (ossia un indirizzo TCP o
UDP) ha la forma:
( IP address : port number )
Port number è un intero a 16 bit, che identifica un servizio o un punto
di accesso e/o smistamento di livello 4.
Ad es. la coppia (137.204.72.49 : 23) indica la porta 23 dell’host
poseidon.csr.unibo.it, cioè l’entry point per il demone telnet, cioè il
punto di accesso all’applicazione che permette ad un utente di
collegarsi mediante telnet all’host poseidon.
• Anche se l’indirizzo di livello trasporto è formato da questa coppia, il
TCP/IP, per ridurre l’overhead causato dagli header dei vari livelli,
nella trasmissione effettua una violazione della stratificazione tra i
livelli 4 e 3 (Trasporto e Network). Infatti, come vedremo TCP e UDP
contengono nei loro header solo i numeri delle porte e riutilizzano
gli indirizzi IP di mittente e destinaz. contenuti nel pacchetto IP.
Indirizzi a livello di Trasporto
per il TCP/IP
68
• Gli identificatori di porta (port number) permettono di
effettuare la demultiplazione dei pacchetti di livello 4, ovvero di
discriminare l’applicazione destinazione dei pacchetti in funzione
del port number contenuto nell’header del pacchetto di livello
transport, sia esso di tipo TCP che UDP.
• E’ ovvio che mittente e destinatario devono essere d’accordo sul
valore della porta del destinatario per poter effettuare la
trasmissione.
• Il mittente scrive questo numero di porta come indirizzo del
destinatario, ed il destinatario si deve mettere in attesa dei
pacchetti che giungono all’host del destinatario e che posseggono
come identificatore proprio quel port number.
• Alcune primitive fornite dall’interfaccia socket permettono di
specificare il numero di porta di cui interessa ricevere i pacchetti
(stream=flussi di dati nel caso TCP). E’ quindi il sistema operativo
che si fa carico di effettuare le operazioni di demultiplexing dei
pacchetti ricevuti dal livello network.
Multiplexing a livello Trasporto
69
• Il livello transport fornisce un protocollo per il trasporto di
blocchi di dati non connesso e non affidabile, detto UDP (User
Datagram Protocol), che utilizza l’IP per trasportare messaggi,
che è molto simile all’IP in termini di risultato del trasporto, ed
offre in più rispetto all’IP la capacità di distinguere tra più
destinazioni all’interno di uno stesso host, mediante il meccanismo
delle porte.
•Ogni Datagram UDP viene incapsulato in un datagram IP, quindi
la dimensione del datagram UDP non può superare la dimensione
massima della parte dati del datagram IP. Il datagram IP può
essere frammentato se la MTU è piccola.
• L’UDP non usa dei riscontri per verificare se un messaggio è
arrivato a destinazione, non ordina i messaggi arrivati, e non fornisce
nessun tipo di controllo sulla velocità di trasmissione dei dati. Quindi i
datagram UDP possono essere persi, duplicati o arrivare fuori ordine.
• Utilizzano UDP alcuni protocolli standard, a cui sono riservati dei
numeri di porte predefiniti, in modo da poter essere rintracciati nello
stesso punto (punto d’accesso) su tutti gli hosts. Ricordiamo tra gli
altri: nameserver (server di nomi di dominio, porta 53), bootps
(server del protocollo di bootstrap, 67), tftp (Trivial File Transfer, 69),
ntp (Network Time protocol, 123).
Il protocollo di Trasporto UDP
(User Datagram Protocol)
70
IP header UDP header UDP data
20 bytes 8 bytes
UDP datagram
IP datagram
Il pacchetto UDP è costituito da un header e da una parte dati.
L’header UDP è composto da 4 campi:
• i primi due sono i numeri di porta del mittente e del destinatario
del datagram, ciascuno di 16 bit.
• il terzo è la lunghezza dei dati del datagram UDP, in byte.
• l’ultimo è un checksum per il controllo d’errore, che però è
opzionale. Un valore zero in questo campo indica che la chesksum
non è stata calcolata
Si noti che non ci sono gli indirizzi IP di mittente e destinatario.
A differenza dell’header IP, il checksum contenuto nell’header
UDP considera anche la parte dati UDP.
Inoltre il calcolo della checksum viene effettuato ponendo in testa
al datagram UDP una pseudointestazione (che non viene
trasmessa), con gli indirizzi IP di provenienza e destinazione
ricavata dall’header IP in cui l’UDP viene trasportato,
pseudointestazione fatta in questo modo:
Il motivo di questo modo di computare la checksum è verificare a
livello UDP, che il datagram UDP abbia raggiunto la corretta
destinazione IP, che non compare nell’header UDP.
Formato del Datagram UDP
71
0 15 16 31
source port number destination port number
checksum UDP length UDP
UDP data
UDP
header
0 15 16 31
source IP address
UDP datagram length
UDP
pseudo
header
destination IP address
8-bit protocol (17)
zero
esempio di trasmissione
di datagram UDP
72
Senza per ora entrare nei dettagli riguardanti i socket, vediamo un
semplice esempio di programma che sfrutta i socket per trasmettere un
datagram UDP contenente una stringa di testo “pippo” da un host
sender 137.204.72.49 ad un host receiver 130.136.2.7 .
Il punto di accesso stabilito dal programmatore è nel receiver, nella porta
UDP caratterizzata dal numero 3001.
Il receiver si mette in attesa sulla porta 3001 fino a che il sender invia
un datagram all’host receiver su quella porta, e stampa il contenuto del
datagram ricevuto.
Quindi sender e receiver devono essersi messi d’accordo sulla porta da
usare, ed il sender deve conoscere l’indirizzo IP del receiver.
Il codice completo (con la gestione degli errori) dei due programmi
che realizzano l’esempio qui mostrato è disponibile allo indirizzo
http://www.cs.unibo.it/~ghini/didattica/sistemi3/UDP1/UDP1.html
data link
physical
network
transport
application
3001
receiver
130.136.2.7
media
data link
physical
network
transport
application
sender
137.204.72.49
data
punto
d’accesso
esempio: receiver di datagram UDP
73
/* eseguito sull’host 130.136.2.7 */
#define SIZEBUF 10000
void main(void) {
struct sockaddr_in Local, From; short int local_port_number=3001;
char string_remote_ip_address[100]; short int remote_port_number;
int socketfd, OptVal, msglen, Fromlen; char msg[SIZEBUF];
/* prende un socket per datagram UDP */
socketfd = socket (AF_INET, SOCK_DGRAM, 0);
/* impedisce l'errore di tipo EADDRINUSE nella bind() */
OptVal = 1;
setsockopt (socketfd, SOL_SOCKET, SO_REUSEADDR,
(char *)&OptVal, sizeof(OptVal) );
/* assegna l'indirizzo IP locale e una porta UDP locale al socket */
Local.sin_family = AF_INET;
Local.sin_addr.s_addr = htonl(INADDR_ANY);
Local.sin_port = htons(local_port_number);
bind ( socketfd, (struct sockaddr*) &Local, sizeof(Local));
/* wait for datagram */
Fromlen=sizeof(struct sockaddr);
msglen = recvfrom ( socketfd, msg, (int)SIZEBUF, 0,
(struct sockaddr*)&From, &Fromlen);
sprintf((char*)string_remote_ip_address,"%s",inet_ntoa(From.sin_addr);
remote_port_number = ntohs(From.sin_port);
printf("ricevuto msg: \"%s\" len %d, from host %s, port %d\n",
msg, msglen, string_remote_ip_address, remote_port_number);
}
esempio: sender di datagram UDP
74
/* eseguito sull’host 137.204.72.49 */
int main(void) {
struct sockaddr_in Local, To; char msg[]=“pippo";
char string_remote_ip_address[]="130.136.2.7";
short int remote_port_number = 3001;
int socketfd, OptVal, addr_size;
/* prende un socket per datagram UDP */
socketfd = socket(AF_INET, SOCK_DGRAM, 0);
/* impedisce l'errore di tipo EADDRINUSE nella bind() */
OptVal = 1;
setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR,
(char *)&OptVal, sizeof(OptVal));
/* assegna l'indirizzo IP locale e una porta UDP locale al socket */
Local.sin_family = AF_INET;
/* il socket verra' legato all'indirizzo IP dell'interfaccia che verrà
usata per inoltrare il datagram IP, e ad una porta a scelta del s.o. */
Local.sin_addr.s_addr = htonl(INADDR_ANY);
Local.sin_port = htons(0); /* il s.o. decide la porta locale */
bind( socketfd, (struct sockaddr*) &Local, sizeof(Local));
/* assegna la destinazione */
To.sin_family = AF_INET;
To.sin_addr.s_addr = inet_addr(string_remote_ip_address);
To.sin_port = htons(remote_port_number);
addr_size = sizeof(struct sockaddr_in);
/* send to the address */
sendto(socketfd, msg, strlen(msg) , 0,
(struct sockaddr*)&To, addr_size);
}
Il protocollo di Trasporto TCP
(Transmission Control Protocol)
75
Il protocollo TCP è stato progettato per fornire un flusso di byte da
sorgente a destinazione full-duplex, affidabile, su una rete non
affidabile.
Dunque, offre un servizio reliable e connection oriented, e si
occupa di:
• stabilire la connessione full duplex tra due punti di accesso a
livello trasporto;
• accettare dati dal livello application eventualmente
bufferizzando in input;
• a richiesta, forzare l’invio interrompendo la bufferizzazione;
• spezzare o accorpare i dati in segment, il nome usato per i TPDU
(Transport Protocol Data Unit) aventi dimensione massima 64
Kbyte, ma tipicamente di circa 1.500 byte;
• consegnarli al livello network, per effettuare la trasmissione
all’interno di singoli datagram IP, eventualmente ritrasmettendoli;
• ricevere segmenti dal livello network;
• rimetterli in ordine, eliminando buchi e doppioni;
• consegnare i dati, in ordine, al livello application.
• chiudere la connessione.
Il servizio effettua internamente la gestione di ack, il controllo del
flusso e il controllo della congestione.
Il servizio del TCP è di tipo orientato allo stream, ovvero
trasporta un flusso di byte, il che significa che se anche la sorgente
spedisce (scrive sul device) i dati a blocchi (es 1 KB poi 3 KB poi
ancora 2 KB) la connessione non informa la destinazione su come
sono state effettuate individualmente le scritture; la destinazione
potrebbe ad es. leggere i dati a blocchi di 20 byte per volta.
• Come l’UDP, il TCP impiega numeri di porta di protocollo per
identificare la destinazione finale all’interno di un host.
• La situazione è però molto diversa rispetto all’UDP, in cui
possiamo immaginare ogni porta come una sorta di coda a cui
arrivano dei datagram delimitati, provenienti ciascuno da un mittente
eventualmente diverso.
• Per il TCP si vuole invece che una connessione sia dedicata in
esclusiva ad una coppia di applicazioni risiedenti su macchine
diverse. Il TCP impiega la connessione (virtuale), non la porta di
protocollo, come sua astrazione fondamentale;
• le connessioni sono identificate da una coppia di punti
d’accesso (endpoint) detti socket, uno su ciascuna macchina.
• Ogni socket è caratterizzato da una coppia ( IP address: Port
number) che può essere utilizzata da più processi simultaneamente.
Questa è ad esempio la situazione prodotta dal processo demone del
telnet (telnetd) che consente agli utenti di una macchina unix A di
collegarsi ad A da un’altro host accedendo tutti alla stessa porta TCP
numero 23 di quell’host A. Tutte le connessioni condividono quindi
la stessa coppia (IP_A, 23).
• Però ciascuna connessione viene univocamente individuata
dalla coppia di socket dei due host A e B implicati nella
connessione, ovvero dalla terna:
( IP address A: Port number A , IP address B: Port number B )
Più utenti che effettuano tutti il telnet da una stessa macchina B
verso una stessa macchina A instaurano connessioni identificate da
terne del tipo ( IP_A : 23 , IP_B : tcp_port_B ) con tcp_port_B
tutti diversi, e quindi con connessioni univocamente
determinate.
Indirizzamento nel TCP (1)
76
Come per il caso UDP, anche per il TCP esistono dei port number
che sono riservati ad uso di protocolli standard, e vengono detti
well-know-port. Queste porte sono quelle con valore inferiore a 256.
Port Number Service
20 Ftp (control)
21 Ftp (data)
23 Telnet
25 Smtp
80 Http
I segmenti TCP
• L’unità di trasferimento del TCP è detta segmento, e viene usato
per stabilire connessioni, per trasferire dati, per inviare riscontri (una
sorta di ricevuta di ritorno), per dimensionare le finestre scorrevoli e
per chiudere le connessioni.
• TCP usa un meccanismo di sliding window (finestre scorrevoli) di
tipo go-back-n con timeout. Se questo scade, il segmento si
ritrasmette. Si noti che le dimensioni della finestra scorrevole e i
valori degli ack sono espressi in numero di byte, non in
numero di segmenti.
• ogni byte del flusso TCP è numerato con un numero d'ordine a 32
bit, usato sia per il controllo di flusso che per la gestione degli ack;
• ogni segmento TCP non può superare i 65.535 byte, e viene
incluso in un singolo datagram IP;
• un segmento TCP è formato da:
uno header, a sua volta costituito da:
- una parte fissa di 20 byte;
- una parte opzionale;
i dati da trasportare;
Well-Know-Port nel TCP
77
Formato del segmento TCP
78
Source port, destination port: identificano gli end point (locali ai
due host) della connessione. Essi, assieme ai corrispondenti numeri
IP, identificano la connessione a cui appartiene il segmento;
Sequence number: la posizione del primo byte contenuto nel
campo dati all’interno dello stream di byte che il trasmettitore
del segmento invia (si possono inviare quindi al max 4 miliardi di
byte circa in uno stesso stream).
Ack. number: la posizione del prossimo byte aspettato all’interno
del segmento inviato dal ricevitore del presente segmento.
TCP header length: lunghezza del segmento misurata in parole di
32 bit (necessario perché il campo options ha dimensione variabile).

Destination port
Sequence number
32 bit
Source port
Ack. number
TCP
header len
U
R
G
A
C
K
P
S
H
R
S
T
S
Y
N
F
I
N Window size
Options (zero o più parole di 32 bit)
Urgent pointer Checksum
Dati (opzionali)
reserved
Checksum, simile a quello di UDP, verifica che sia l’header che i
dati del segmento siano arrivati a destinazione senza errori. Come
per UDP il calcolo del checksum prevede che sia aggiunto in testa al
segmento uno pseudoheader contenente gli indirizzi IP di sorgente e
destinazione, la lunghezza del segmento, estratti dall’header del
datagram IP che contiene il segmento.
Nell’header del segmento TCP sono presenti inoltre 6 flags di un bit
ciascuno che servono per assegnare validità ad alcuni campi
dell’header o per segnalare richieste o conferme:
URG 1 se il campo urgent pointer è usato, 0 altrimenti.
ACK 1 se l'ack number è valido (cioè se si trasporta un ack), 0
altrimenti.
PSH indica che questo segmento contiene dati urgenti (pushed
data), da consegnare senza aspettare che il buffer si riempia.
RST richiesta di reset della connessione (ci sono problemi!).
SYN usato nella fase di setup della connessione:
SYN=1 ACK=0 richiesta connessione;
SYN=1 ACK=1 accettata connessione.
FIN usato per rilasciare una connessione.
Formato del segmento TCP (2)
79
0 15 16 31
source IP address
TCP segment length
TCP
pseudo
header
destination IP address
8-bit protocol (=6)
0
altri campi presenti nel segmento TCP sono:
Window size: il controllo di flusso è di tipo sliding window di
dimensione variabile. Window size dice quanti byte possono
essere spediti a partire da quello (compreso) che viene
confermato con l'ack number. Un valore zero significa: fermati
per un pò, riprenderai quando ti arriverà un uguale ack number con
un valore di window size diverso da zero. Questo valore serve a
ridurre la velocità di trasmissione dei dati nel flusso di byte che va
dal ricevente al trasmettitore del presente segmento. Viene usato
quando ci si accorge di una congestione della rete, per non
incrementare ancora la congestione, oppure quando i buffer di
ricezione sono ormai pieni e non si vuole rischiare di perdere dei
dati .
Urgent pointer puntatore ai dati urgenti, indica la posizione in
cui terminano i dati urgenti nello stream inviati dal trasmettitore.
Options contiene alcune opzioni appliccabili al flusso. Le più
importante sono negoziabili durante il setup della connessione, e
sono:
• dimensione massima dei segmenti da spedire (MSS: Maximum
Segment Size), serve se uno degli host ha dei buffer molto limitati
ma soprattutto per adattare il segmento alla MTU della rete che
collega i due host, in modo che ogni segmento venga incluso in un
datagram IP che non debba essere frammentato;
• uso di selective repeat invece che go-back-n (un diverso algoritmo
di controllo del flusso);
• uso di NAK.
Formato del segmento TCP (3)
80
L’affidabilità del TCP si basa sulla combinazione di alcune tecniche
che adesso analizzeremo separatamente. La tecnica più semplice per
garantire che tutti i byte trasmessi in un flusso giungano a
destinazione è nota come riscontro positivo con ritrasmissione.
Quando un ricevitore riceve un pacchetto risponde al pacchetto
inviando un riscontro (acknowledgment, ACK) al trasmettitore
per confermare di averlo ricevuto.
Quando il trasmettitore deve inviare un pacchetto, nel momento
in cui lo spedisce ne mantiene una copia e fa partire un timer.
Se allo scadere del timer non ha ancora ricevuto il relativo ACK
ritrasmette il pacchetto.
Trasmissione affidabile: riscontro
positivo con ritrasmissione (1)
81
Poichè può capitare che la rete duplichi un pacchetto, può capitare di
ricevere un ACK per un pacchetto che era già stato riscontrato in
precedenza, e di scambiarlo per un riscontro di un successivo
pacchetto per cui attendevamo un ACK.
Per ovviare al problema dei riscontri duplicati, i pacchetti vengono
numerati sequenzialmente, e l’ACK contiene il numero
sequenziale del pacchetto che vuole riscontrare.
Le finestre Scorrevoli (1)
• Il meccanismo di riscontro positivo rallenta la trasmissione, perchè
il trasmettitore deve attendere il riscontro di ciascun pacchetto
prima di inviare il successivo, quindi i pacchetti viaggiano in rete
in una sola direzione per volta, e la rete è sottoutilizzata mentre gli
end system attendono le risposte, che possono ritardare.
• La tecnica delle finestre scorrevoli (Sliding Windows) invece
permette al trasmettitore di poter continuare ad inviare un certo
numero N (dimensione della finestra) di pacchetti successivi
all’ultimo per cui ha ricevuto il riscontro, ovvero permette di
trasmettere fino ad altri N pacchetti mentre si è in attesa di ricevere
il riscontro di un pacchetto precedentemente inviato. In tal modo la
rete viene utilizzata anche nei periodi di attesa.
In figura vengono
trasmessi 3 pacchetti
prima di ricevere un
riscontro.
Trasmissione affidabile: riscontro
positivo con ritrasmissione (2)
82
• definiamo non riscontrato un pacchetto trasmesso per il quale non
è ancora stato ricevuto il riscontro.
• La finestra del trasmettitore inizia col primo pacchetto non
riscontrato. I pacchetti successivi, che stanno dentro alla finestra,
possono essere trasmessi, mentre i pacchetti che seguono la finestra
non possono essere trasmessi.
• Quando un pacchetto P (trasmesso) nella finestra viene riscontrato
(il pacchetto 6 in figura) (e sono riscontrati anche i suoi precedenti)
allora la finestra avanza fino al pacchetto successivo a P,
consentendo di trasmettere i pacchetti che sono entrati nella finestra.
(in figura la dimensione della finestra è 8)
Le finestre Scorrevoli (2)
83
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
pacchetti
trasmessi
e riscontrati
primo pacchetto
non riscontrato
pacchetti trasmissibili
pacchetti
non ancora
trasmissibili
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
pacchetti
trasmessi
e riscontrati
primo pacchetto
non riscontrato
pacchetti trasmissibili
pacchetti
non ancora
trasmissibili
pacchetti
riscontrati
nuovi pacchetti
trasmissibili
• le finestre continuano a scorrere finchè si ricevono riscontri,
permettendo di trasmettere i nuovi pacchetti che entrano nella
finestra.
• Le prestazioni dei protocolli a finestra scorrevole dipendono dalla
dimensione della finestra e dalla velocità della rete.
• se la dimensione della finestra è 1, si ritorna all’algoritmo di
riscontro positivo.
• all’aumentare della dimensione della finestra diminuisce il periodo
di non utilizzo della rete.
• con un opportuna scelta della dimensione della finestra è possibile
mantenere la rete satura di pacchetti senza congestionarla.
• Per ogni pacchetto trasmesso viene comunque ancora fatto partire
un timer, allo scadere del quale, se non è ancora stato ricevuto il
riscontro, il pacchetto deve essere ritrasmesso.
• Il meccanismo delle finestre scorrevoli è stato spiegato riferendosi
alla trasmissione di pacchetti, ma vale ugualmente se:
1) consideriamo lo stesso meccanismo (la finestra) replicato nei
due end system, perchè essendo la connessione TCP bidirezionale,
ogni host funge da trasmettitore;
2) al posto dei pacchetti consideriamo i byte del flusso realizzato
dalla connessione TCP, e utilizziamo la posizione dei byte nel flusso
come indicatore per sapere nel trasmettitore:
- qual’è il primo byte della finestra (da riscontrare)
- qual’è il primo byte che segue la finestra (da non trasmettere)
- qual’è il primo byte ancora da trasmettere dentro la finestra.
Le finestre Scorrevoli (3)
84
4 5 6 7 8 9 10 11 12 13 14 15
primo pacchetto
non riscontrato
pacchetti non
ancora trasmissibili
primo pacchetto
da trasmettere
• Poichè il TCP invia i dati in segmenti di lunghezza variabile, e
poichè i segmenti ritrasmessi possono includere più dati
dell’originale, i riscontri non possono fare riferimento facilmente ai
datagrammi o ai segmenti, ma fanno riferimento alla posizione dei
byte nello stream.
• Il ricevitore raccoglie i byte di dati dei segmenti in arrivo e li
riordina, ma poichè i segmenti viaggiano in datagram IP possono
essere persi o consegnati disordinatamente, generando dei buchi
nella ricostruzione.
• Il ricevitore riscontra solo i byte che precedono il primo buco,
o meglio, specifica il valore sequenziale (la posizione) del primo
byte che ancora gli manca e che si aspetta di ricevere, il primo
byte del primo buco, anche se ha già ricevuto qualcosa di
successivo.
• Il vantaggio è che questi riscontri cumulativi sono facili da
generare, e che la perdita di un riscontro non costringe alla
ritrasmissione se arriva un riscontro per un byte successivo.
• Uno svantaggio è che il trasmettitore, se un riscontro per un byte
non arriva, non è in grado di capire se manca solo un pezzetto (un
buco anche di quel solo byte) o manca tutto da quel byte in avanti, e
ricomincia a trasmettere tutto da quel byte.
I Riscontri (1)
85
4 5 6 7 8 9 10 11 12 13 14 15
primo byte
non riscontrato
il trasmettitore ritrasmette tutto, da 7 a 13,
anche se il ricevitore ha già tutto da 8 a 13
byte gia’ ricevuti
ma non riscontrati
Socket per TCP:
Fondamenti
Molte applicazioni di rete sono formate da due programmi distinti
(che lavorano su due diversi host) uno detto server ed uno detto client.
Il server si mette in attesa di una
richiesta da servire, il client
effettua tale richiesta.
Tipicamente il client comunica con
un solo server, mentre un server
usualmente comunica con più
client contemporaneamente (su
connessioni diverse nel caso tcp).
Inoltre spesso client e server sono
processi utente, mentre i protocolli
della suite TCP/IP fanno solitamente parte del sistema operativo.
Nel seguito faremo riferimento al termine IP nel senso di IPv4.
Unix Standards
Posix = Portable Operating System Interface è una famiglia di
standard (vedi http://www.pasc.org/standing/sd11.html) sviluppata da
IEEE e adottata da ISO. Posix comprende IEEE Std 1003.1 (1996)
(una raccolta di alcune specifiche precedenti) che contiene al suo
interno una parte detta “Part1: System Application Program Interface
(API)” che specifica l’interfaccia C per le chiamate di sistema del
kernel Unix, relative a processi (fork, exec, signal, timer, user ID,
gruppi), files e directory (I/Ofunction), I/O da terminale, password, le
estensioni per il realtime, execution scheduling, semaphores, shared
memory, clock, message queues. In particolare comprende IEEE Std
1003.1g: Protocol Independent Interface (PII) che è lo standard per
l’interfaccia di programmazione delle reti, e definisce due standard
chiamati DNI (Detailed Network Interfaces):
1) DNI/Socket basato sulle API socket del 4.4BSD, di cui ci
occuperemo
2) DNI/XTI, basato sulle specifiche XPG4 del consorzio X/Open
Network Applications
86
Per primo viene fatto partire il server, poi viene fatto partire il client che
chiede la connessione al server e la connessione viene instaurata.
Nell’esempio (ma non è obbligatorio) il client spedisce una richiesta al
server, questo risponde trasmettendo alcuni dati. Questa trasmissione
bidirezionale continua fino a che uno dei due (il client nell’esempio)
decide di interrompere la connessione,
e tramite la close() chiude la
connessione. Infine il server
chiude a sua volta la connessione.
Interazioni tra Client e Server TCP
104
ephemeral
port
Vediamo un semplice esempio di programma che sfrutta i socket TCP
per instaurare una connessione tra un client e un server, trasmettere dal
client al server una stringa di caratteri, aspettare che il server modifichi
questi caratteri (tranne l’ultimo, lo’\0’ che delimita la stringa)
shiftandoli di due posizioni (es: ‘a’ diventa ‘c’, ‘2’ diventa ‘4’) e li
rispedisca indietro così traslati, infine stampare il risultato.
Il server è l’host 130.136.2.7, mentre il client è l’host 137.204.72.49.
Il punto di accesso del servizio di traslazione è la porta TCP 5001.
Il codice completo (con la gestione degli errori) dei due programmi
che realizzano l’esempio qui mostrato è disponibile allo indirizzo
http://www.cs.unibo.it/~ghini/didattica/sistemi3/TCP1/TCP1.html
esempio di trasmissione con TCP
114
data link
physical
network
TCP
transport
application
5001
server
130.136.2.7
media
data link
physical
network
TCP
transport
application
client
137.204.72.49
write
traslazione
caratteri
buf[i]=buf[i]+2
read
write read
/* servTCP.c eseguito sull’host 130.136.2.7 */
void main(void) {
struct sockaddr_in Local, Client; short int local_port_number=5001;
char buf[SIZEBUF]; int sockfd, newsockfd, n, nread, nwrite, len;
/* prende un socket per streamTCP */
sockfd = socket (AF_INET, SOCK_STREAM, 0);
/* collega il socket ad un indirizzo IP locale e una porta TCP locale */
memset ( &Local, 0, sizeof(Local) );
Local.sin_family = AF_INET;
Local.sin_addr.s_addr = htonl(INADDR_ANY);
Local.sin_port = htons(local_port_number);
bind ( sockfd, (struct sockaddr*) &Local, sizeof(Local));
/* accetta max 10 richieste simultanee di inizio conness., da adesso */
listen(sockfd, 10 );
/* accetta la prima conness. creando un nuovo socket per la conness. */
newsockfd = accept(sockfd, (struct sockaddr*) &Cli, &len);
/* riceve la stringa dal client */
nread=0;
while( (n=read(newsockfd, &(buf[nread]), MAXSIZE )) >0) {
nread+=n;
if(buf[nread-1]=='\0') break; /* fine stringa */
}
/* converte i caratteri della stringa */
for( n=0; n<nread -1 ; n++) buf[n] = buf[n]+2;
/* spedisce la stringa traslata al client */
nwrite=0;
while((n=write ( newsockfd, &(buf[nwrite]),nread-nwrite)) >0 )
nwrite+=n;
/* chiude i socket */
close(newsocketfd); close(socketfd);
}
server TCP per l’esempio
115
/* cliTCP.c eseguito sull’host 137.204.72.49 */
void main(void) {
struct sockaddr_in Local, Serv; short int remote_port_number=5001;
char msg[]="012345ABCD"; int sockfd , n, nread, nwrite, len
/* prende un socket per streamTCP */
sockfd = socket (AF_INET, SOCK_STREAM, 0);
/* collega il socket senza specificare indirizzo IP e porta TCP locali */
memset ( &Local, 0, sizeof(Local) );
Local.sin_family = AF_INET;
Local.sin_addr.s_addr = htonl(INADDR_ANY);
Local.sin_port = htons(0);
bind ( sockfd, (struct sockaddr*) &Local, sizeof(Local));
/* specifica l’indirizzo del server, e chiede la connessione */
memset ( &Serv, 0, sizeof(Serv) );
Serv.sin_family = AF_INET;
Serv.sin_addr.s_addr = inet_addr ( string_remote_ip_address);
Serv.sin_port = htons(remote_port_number);
connect ( sockfd, (struct sockaddr*) &Serv, sizeof( Serv));
/* spedisce la stringa al server */
len = strlen(msg)+1; nwrite=0;
while((len>nwrite)&&(n=write(sockfd,&(msg[nwrite]),len-nwrite))>0))
nwrite+=n;
nread=0; /* riceve la stringa traslata dal server */
while( (n=read(sockfd, &( msg[nread]), MAXSIZE )) >0) {
nread+=n;
if(buf[nread-1]=='\0') break; /* fine stringa */
}
printf(“%s\n”, msg); /* stampa la stringa traslata */
/* chiude i socket e termina*/
close(sockfd);
}
client TCP per l’esempio
116
Cominciamo la descrizione delle Socket API (Application program
Interface) dalla descrizione delle strutture usate per trasferire
indirizzi dall’applicazione al kernel (nelle funzioni bind, connect,
sendto) e dal kernel alle applicazioni (nelle funzioni accept, recvfrom,
getsockname e getpeername).
• I dati definiti per Posix.1g sono quelli della seguente tabella:
int8_t signed 8-bit integer <sys/types.h>
uint8_t unsigned 8-bit integer <sys/types.h >
int16_t signed 16-bit integer <sys/types.h >
uint16_t unsigned 16-bit integer <sys/types.h>
int32_t signed 32-bit integer <sys/types.h>
uint32_t unsigned 32-bit integer <sys/types.h>
sa_family_t famiglia di indirizzi socket <sys/socket.h>
AF_INET per IPv4, AF_INET6 per IPv6,
AF_LOCAL per indir. locali unix (per pipe ecc..)
socklen_t lunghezza della struttura che
contiene l’indirizzo,
di solito è un uint32_t <sys/socket.h>
in_addr_t indirizzo IPv4, = uint32 <netinet/in.h>
in_port_t porta TCP o UDP, = uint16 <netinet/in.h>
• Poichè i socket devono fornire un’interfaccia per diverse famiglie di
protocolli (IPv4, IPv6 e Unix), e poichè tali strutture vengono passate
per puntatore, le funzioni di libreria presentano un argomento che è il
puntatore alla generica struttura (struct sockaddr*), ma essendo
diversa la struttura passata a seconda della famiglia di indirizzi usata,
l’argomento passato deve essere convertito mediante il cast alla struttura
(struct sockaddr*), ad es:
struct sockaddr_in server; /* IPv4 socket address structure */
memset ( &server, 0, sizeof(server) ); /* azzero tutta la struttura */
... riempimento dei dati della struttura server ...
bind ( socketfd, ( (struct sockaddr struct sockaddr *) *)&server, sizeof(server) );
Socket Address Structures (1)
96
La generica struttura dell’indirizzo è dunque cosi definita:
struct sockaddr {
uint8_t sa_len;
sa_family_t sa_family;
char sa_data[14];
};
La famiglia di indirizzi Ipv4 (sa_family=AF_INET) usa la struttura:
struct sockaddr_in {
uint8_t sin_len; /* lunghezza struttura */
sa_family_t sin_family; /* = AF_INET */
in_port_t sin_port; /*16-bit TCP UDP port, network byte ordered */
struct in_addr sin_addr; /* 32-bit IPv4 address, network byte ordered */
char sin_zero[8]; /* unused */
};
con
struct in_addr { /* e’ una struttura per ragioni storiche */
in_addr_t s_addr ; /* 32-bit IPv4 address network byte ordered */
};
• sa_len e sa_family si sovrappongono perfettamente a sin_len e
sin_family rispettivamente, permettendo di leggere la costante di tipo
sa_family_t e di capire che tipo di struttura si sta utilizzando.
• il campo sin_len non è richiesto espressamente da Posix.1g, , e anche
quando è presente non è necessario settarlo, se non per applicazioni di
routing, in quanto le principali funzioni in cui si passano indirizzi
prevedono già un argomento in cui si passa (o riceve) la lunghezza della
struttura indirizzo.
• Il campo sin_zero non è usato, ma va sempre settato tutto a zero prima
di passare una struttura che lo contiene. Di più, per convenzione,
bisogna sempre settare TUTTA la struttura indirizzo tutta a zero
prima di riempire i vari campi, usando la funzione memset().
• memset ( &server, 0, sizeof(server) );
Socket Address Structures (2)
97
Confrontiamo alcune delle strutture usate per gli indirizzi:
Socket Address Structure (3)
98
Poichè alcuni campi delle strutture di indirizzo (i numeri di porta o gli
indirizzi IPv4 ad esempio) devono essere memorizzati secondo l’ordine
per i bytes stabilito per la rete (network byte order), prima di assegnare
alla struttura un valore di porta (16-bit) o un indirizzo IPv4 (32-bit) è
necessario convertirlo dall’ordine dei byte per l’host all’ordine per la
rete, utilizzando delle funzioni di conversione, i cui prototipi sono
definiti nell’include <netinet/in.h>:
uint16_t htons (uint16_t host16bitvalue); /* Host TO Network Short */
uint32_t htonl (uint32_t host32bitvalue); /* Host TO Network Long */
Viceversa, per convertire il valore di una porta o di un indirizzo IPv4,
preso da una struttura di indirizzo, in un valore intero secondo
l’ordinamento dell’host si devono utilizzare le funzioni:
uint16_t ntohs (uint16_t net16bitvalue); /* Network TO Host Short */
uint32_t ntohl (uint32_t net32bitvalue); /* Network TO Host Long */
Se l’ordinamento dell’host è corrispondente all’ordinamento di rete,
queste funzioni sono implementate con delle macro nulle, cioè non
modificano il dato.
Funzioni di Manipolazione dei Byte
Vediamo solo le funzioni portabili ovunque perche sono ANSI C.
void *memset (void *dest, int c, size_t n_bytes);
setta al valore c un numero len di byte a partire da dest
void *memcpy (void *dest, const void *src, size_t n_bytes);
copia n_bytes byte da src a dest, problemi se c’e’ sovrapposizione,
nel caso usare memmove. Resituisce dest.
void *memcmp (const void ptr1, const void *ptr2, size_t n_bytes);
confronta due vettori di n_bytes ciascuno, restituisce 0 se sono
uguali, diverso da zero se diversi.
Funzioni di Ordinamento dei Byte
99
Queste funzioni sono definite in <arpa/inet.h>
Le funzioni inet_aton e inet_addr convertono gli indirizzi IP da una
forma di stringa di caratteri ASCII decimali separati da punti del tipo
“255.255.255.255”, nella forma di interi a 32-bit ordinati secondo
l’ordinamento di rete.
int inet_aton (const char *str, struct in_addr *addrptr);
scrive nella locazione puntata da addrptr il valore a 32-bit,
nell’ordine di rete, ottenuto dalla conversione della stringa zero-
terminata puntata da str. Restituisce zero in caso di errore, 1 se
tutto va bene.
in_addr_t inet_addr (const char *str); NON VA USATA
restituisce il valore a 32-bit, nell’ordine di rete, ottenuto dalla
conversione della stringa zero-terminata puntata da str.
In caso di errori restituisce INADDR_NONE, e questo è un casino,
perchè INADDR_NONE è un intero a 32 bit di tutti 1, che sarebbe
ottenuto come risultato della chiamata di inet_addr passandogli la
stringa “255.255.255.255” che è l’indirizzo valido di broadcast.
Per evitare confusione non deve essere usata.
Infine c’e’ una funzione che effettua la conversione inversa, da interi a
32-bit network ordered verso stringhe ASCII decimali separate da punti.
char *inet_ntoa (struct in_addr addr);
scrive in una locazione di memoria statica (di cui restituisce un
puntatore) la stringa ASCII null-terminata di caratteri decimali
separati da punti corrispondeni all’indirizzo IP a 32-bit, nell’ordine
di rete, contenuto nella struttura addr (che stranamente non è un
puntatore). Occhio, questa funzione non è rientrante, perchè
memorizza il risultato in una locazione statica.
Funzioni di Conversione di Indirizzi IP
dalla forma dotted-decimal ASCII string
alla forma 32-bit network byte ordered
100
La prima azione per fare dell’I/O da rete è la chiamata alla funziona
socket() specificando il tipo di protocollo di comunicazione da utilizzare
(TCP con IPv4, UDP con IPv6, Unix domain stream protocol per usare
le pipe).
#include <sys/socket.h>
int socket (int family, int type, int protocol);
restituisce un descrittore di socket maggiore o uguale a zero, oppure -1
in caso di errore, e setta errno.
L’argomento family specifica la famiglia di protocolli da utilizzare.
family descrizione
AF_INET IPv4 protocol
AF_INET6 IPv6 protocol
AF_LOCAL Unix domain protocols (ex AF_UNIX)
AF_ROUTE Routing socket
AF_ROUTE Key socket (sicurezza in IPv6)
L’argomento type specifica quale tipo di protocollo vogliamo utilizzare
all’interno della famiglia di protocolli specificata da family.
type descrizione
SOCK_STREAM socket di tipo stream(connesso affidabile)
SOCK_DGRAM socket di tipo datagram
SOCK_DRAW socket di tipo raw (livello network)
L’argomento protocol di solito è settato a 0, tranne che nel caso dei
socket raw.
Non tutte le combinazioni di family e type sono valide. Quelle valide
selezionano un protocollo che verrà utilizzato.
AF_KEY
AF_INET AF_INET6 AF_LOCAL AF_ROUTE
SOCK_STREAM TCP TCP esiste
SOCK_DGRAM UDP UDP esiste
SOCK_DRAW IPv4 IPv6 esiste
funzione socket()
105
La funzione connect() è usata dal client TCP per stabilire la connessione
con un server TCP.
#include <sys/socket.h>
int connect (int socketfd, const struct sockaddr *servaddr,
socklen_t addrlen);
restituisce 0 se la connessione viene stabilita, -1 in caso di errore.
• L’argomento socketfd è un descrittore socket ottenuto da una chiamata
alla funzione socket().
• L’argomento servaddr come visto in precedenza è in realtà per IPv4
un puntatore alla struttura sockaddr_in, e deve specificare l’indirizzo
IP e il numero di porta del server da connettere.
• L’argomento addrlen specifica la dimensione della struttura dati che
contiene l’indirizzo del server servaddr, viene di solito assegnata
mediante la sizeof(servaddr).
• Il client non deve di solito specificare il proprio indirizzo IP e la
propria porta, perchè queste informazioni non servono a nessuno.
Quindi può chiedere al sistema operativo di assegnargli una porta TCP
qualsiasi, e come indirizzo IP l’indirizzo della sua interfaccia di rete, o
dell’interfaccia di rete usata se ne ha più di una. Quindi NON SERVE la
chiamata alla bind() prima della connect().
• Nel caso di connessione TCP la connect inizia il protocollo three way
handshake spedendo un segmento SYN. La funzione termina o quando
la connessione è stabilita o in caso di errore.
• In caso di errore la connect restituisce -1 e la variabile errno è settata a:
- ETIMEDOUT nessuna risposta al segmento SYN
- ECONNREFUSED il server risponde con un segmento RST (reset) ad
indicare che nessun processo server è in attesa
(stato LISTEN) su quella porta
- EHOSTUNREACH o ENETUNREACH host non raggiungibile
- ed altri ancora.
funzione connect()
106
La funzione bind() collega al socket un indirizzo locale. Per TCP e
UDP ciò significa assegnare un indirizzo IP ed una porta a 16-bit.
#include <sys/socket.h>
int bind (int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
restituisce 0 se tutto OK, -1 in caso di errore.
• L’argomento sockfd è un descrittore ottenuto da una socket().
• L’argomento myaddr è un puntatore alla struttura sockaddr_in, e
specifica l’eventuale indirizzo IP locale e l’eventuale numero di porta
locale a cui il sistema operativo deve collegare il socket.
• L’argomento addrlen specifica la dimensione della struttura myaddr.
• L‘applicazione può collegarsi o no ad una porta.
• Di solito il server si collega ad una porta nota (well know port).
Fa eccezione il meccanismo delle RPC.
• I client di solito non si collegano ad una porta con la bind.
• In caso non venga effettuato il collegamento con una porta, il
kernel effettua autonomamente il collegamento con una porta
qualsiasi (ephemeral port) al momento della connect (per il client)
o della listen (per il server).
• L’applicazione può specificare (con la bind) per il socket un indirizzo
IP di un’interfaccia dell’host stesso.
• Per un TCP client ciò significa assegnare il source IP address che
verrà inserito negli IP datagram, spediti dal socket.
• Per un TCP server ciò significa che verranno accettate solo le
connessioni per i client che chiedono di connettersi proprio a
quell’IP address.
• Se il TCP client non fa la bind() o non specifica un IP address
nella bind(), il kernel sceglie come source IP address, nel momento
in cui il socket si connette, quello della interfaccia di rete usata.
• Se il server non fa il bind con un IP address, il kernel assegna al
socket come indirizzo IP locale quello contenuto nell’IP destination
address del datagramIP che contiene il SYN segment ricevuto.
107
funzione bind() (1)
Chiamando la bind() si può specificare o no l’indirizzp IP e la porta,
assegnando valori ai due campi sin_addr e sin_port della struttura
sockaddr_in passata alla bind come secondo argomento.
A seconda del valore otteniamo risultati diversi, che sono qui elencati,
nella tabella che si riferisce solo al caso:
IP_address port
sin_addr sin_port Risultato
wildcard 0 il kernel sceglie IP address e porta
wildcard nonzero il kernel sceglie IP address, porta fissata
Local IP Address 0 IP address fissato, kernel sceglie la porta
Local IP Address non zero IP address e porta fissati dal processo
Specificando il numero di porta 0 il kernel sceglie collega il socket ad
un numero di porta temporaneo nel momento in cui la bind() è
chiamata.
Specificando la wildcard (mediante la costante INADDR_ANYper
IPv4) il kernel non sceglie l’indirizzo IP locale fino a che o il socket è
connesso (se TCP) o viene inviato il primo datagramper quel socket (se
UDP).
L’assegnazione viene fatta con le istruzioni:
struct sockaddr_in localaddr;
localaddr.sin_addr.s_addr = htonl(INADDR_ANY);
localaddr.sin_port = htons(port_number);
Se con la bind si lascia al kernel la scelta di IP address locale o port
number locale, una volta che il kernel avrà scelto, si potrà sapere quale
IP address e quale port number è stato scelto mediante la funzione
getsockname().
108
funzione bind() (2)
La funzione listen è chiamata solo dal TCP server e esegue due azioni:
1) ordina al kernel di far passare il socket dallo stato iniziale CLOSED
allo stato LISTEN, e di accettare richieste di inizio connessione per quel
socket, accodandole in delle code del kernel.
2) specifica al kernel quante richieste di inizio connessione può
accodare al massimo per quel socket.
#include <sys/socket.h>
int listen (int socketfd, int backlog );
restituisce 0 se tutto OK, -1 in caso di errore.
• L’argomento socketfd è un descrittore ottenuto da una socket().
• L’argomento backlog è un intero che specifica quante richieste di
inizio connessione (sia connessioni non ancora stabilite, cioè che non
hanno ancora raggiunto lo stato ESTABLISHED, sia connessioni
stabilite) il kernel può mantenere in attesa nelle sue code.
• Quando un segmento SYN arriva da un client, se il TCP verifica che
c’è un socket per quella richiesta, crea una nuova entry in una coda
delle connessioni incomplete, e risponde con il suo FIN+ACK secondo
il 3-way handshake. L’entry rimane nella coda fino a che il 3-way è
terminato o scade il timeout.
• Quando il 3-way termina normalmente, la connessione viene
instaurata, e la entry viene spostata in una coda delle connessioni
completate.
• Quando il server chiama la accept, la prima delle entry nella coda
delle connessioni completate viene consegnata alla accept() che ne
restituisce l’indice come risultato, ovvero restituisce un nuovo socket
che identifica la nuova connessione.
• Se quando il server chiama la accept(), la coda delle connessioni
completate è vuota, la accept resta in attesa.
• L’argomento backlog specifica il numero totale di entry dei due tipi di
code.
• Solitamente si usa 5, per http daemon si usano valori molto grandi.
109
funzione listen()
La funzione accept è chiamata solo dal TCP server e restituisce la
prima entry nella coda delle connessioni già completate per quel
socket. Se la coda è vuota la accept resta in attesa.
#include <sys/socket.h>
int accept (int socketfd, struct sockaddr *cli_addr,
socklen_t *ptraddrlen);
restituisce un descrittore socket >=0 se tutto OK, -1 in caso di errore.
L’argomento socketfd è un descrittore ottenuto da una socket() e in
seguito processato da bind() e listen(). E’ il cosiddetto listening socket,
ovvero il socket che si occupa di insturare le connessioni con i client che
lo richiedono, secondo le impostazioni definite dalla bind() e dalla
listen(). Tale listening socket viene utilizzato per accedere alla coda
delle connessioni instaurate come visto per la listen().
• L’argomento cli_addr è un puntatore alla struttura sockaddr_in, su cui
la funzione accept scrive l’indirizzo IP del client e il numero di porta
del client, con cui è stata instaurata la connessione a cui si riferisce il
socket che viene restituito come risultato .
• L’argomento ptraddrlen è un puntatore alla dimensione della struttura
cli_addr che viene restituita.
Se accept termina correttamente restituisce un nuovo descrittore di
socket che è il connected socket, cioè si riferisce ad una connessione
instaurata con un certo client secondo le regole del listening socket
socketfd passato come input. Il connected socket verrà utilizzato per
scambiare i dati nella nuova connessione.
Il listening socket socketfd (il primo argomento) mantiene anche dopo
la accept le impostazioni originali, e può essere riutilizzato in una nuova
accept per farsi affidare dal kernel una nuova connessione.
110
funzione accept()
La funzione close è utilizzata normalmente per chiudere un descrittore
di file, è utilizzata per chiudere un socket e terminare una connessione
TCP.
int close (int socketfd);
restituisce 0 se tutto OK, -1 in caso di errore.
L’argomento socketfd è un descrittore di socket.
• Normalmente la chiamata alla close() fa marcare “closed” il socket, e
la funzione ritorna il controllo al chiamante. Il socket allora non può più
essere usato dal processo, ovvero non può più essere usato come
argomento di read e write.
• Però il TCP continua ad utilizzare il socket trasmettendo i dati che
eventualmente stanno nel suo buffer interno, fino a che non sono stati
trasmessi tutti. In caso di errore (che impedisce questa trasmissione)
successivo alla close l’applicazione non se ne accorge e l’altro end
systemnon riceverà alcuni dei dati.
• Esiste un’opzione però (la SO_LINGER socket option) che modifica
il comportamento della close, facendo in modo che la close restituisca
il controllo al chiamante solo dopo che tutti i dati nei buffer sono
stati correttamente trasmessi e riscontrati.
• Se un socket connesso sockfd è condiviso da più processi (padre e
figlio ottenuto da una fork), il socket mantiene il conto di quanti sono
i processi a cui appartiene. In tal caso la chiamata alla close(sockfd)
per prima cosa decrementa di una unità questo contatore, e non
innesca la sequenza FIN+ACK+FIN+ACK di terminazione della
connessione fino a che tale contatore è maggiore di zero, perchè
esiste ancora un processo che tiene aperta la connessione.
• Per innescare veramente la sequenza di terminazione, anche se ci sono
ancora processi per quella connessione si usa la funzione shutdown().
111
funzione close()
La funzione getsockname serve a conoscere l’indirizzo di protocollo
(IP e port number) dell’host locale associato ad un certo descrittore di
socket connesso.
int getsockname ( int socketfd, struct sockaddr *Localaddr,
socklen_t *ptr_addrlen );
restituisce 0 se tutto OK, -1 in caso di errore.
Il primo argomento socketfd è un descrittore di socket connesso.
Il secondo argomento Localaddr è un puntatore ad una struttura di tipo
sockaddr, in cui la funzione metterà l’indirizzo locale della connessione.
Il terzo argomento ptr_addrlen è un puntatore ad intero in cui la
funzione metterà la dimensione della struttura scritta.
Questa funzione viene utilizzata in varie situazioni:
In un client, dopo una connect se non è stata effettuata la bind, e
quindi non si è specificato nessun indirizzo: in tal caso getsockname
permette di conoscere l’indirizzo IP e la porta assegnati dal kernel alla
connessione.
In un client dopo una bind in cui come port number è stato
specificato il valore 0, con il quale si è informato il kernel di scegliere
lui la porta. In tal caso la getsockname restituisce il numero di porta
locale assegnato dal kernel.
In un server multihomed, dopo una accept preceduta da una bind in
cui come indirizzo IP LOCALE è stata messa la wildcard
INADD_ANY, ciòè una volta che si sia stabilita una connessione, la
getsockname permette al server di sapere quale indirizzo IP ha la propria
interfaccia di rete utilizzata per la connessione.
112
funzione getsockname()
La funzione getpeername serve a conoscere l’indirizzo di protocollo (IP
e port number) dell’host remoto associato ad un certo descrittore di
socket connesso.
int getpeername ( int socketfd, struct sockaddr *Remoteaddr,
socklen_t *ptr_addrlen );
restituisce 0 se tutto OK, -1 in caso di errore.
Il primo argomento socketfd è un descrittore di socket connesso.
Il secondo argomento Remoteaddr è un puntatore ad una struttura di
tipo sockaddr, in cui la funzione metterà l’indirizzo remoto della
connessione.
Il terzo argomento ptr_addrlen è un puntatore ad intero in cui la
funzione metterà la dimensione della struttura scritta.
113
funzione getpeername()
I socket TCP, una volta che la connessione TCP sia stata instaurata,
sono accedibili come se fossero dei file, mediante un descrittore di file
(un intero) ottenuto tramite una socket() o una accept() o una connect().
Con questo descrittore è possibile effettuare letture tramite la funzione
read, che restituisce i byte letti dal flusso in entrata, e scritture tramite le
funzioni write e send, che spediscono i byte formando il flusso in uscita.
ssize_t read (int fd, void *buf, size_t count);
cerca di leggere count byte dal file descriptor fd, scrivendoli nel
buffer puntato da buf. Se count è zero la read restituisce zero.
Se count è maggiore di zero viene effettuata la lettura e viene
restituito il numero di byte letti (maggiore di zero, se tutto è OK).
- Se viene restituito 0 (zero) significa end-of-file (fine stream),
ovvero significa che l’altro end system ha volutamente chiuso la
connessione e quindi il socket non potrà essere più utilizzato per
leggere.
- Se viene restituito -1 è accaduto un errore e viene settata la
variabile globale errno definita in <errno.h> con un valore che
indica quale errore è avvenuto. In particolare, se errno vale EINTR
significa che il sistema operativo ha dovuto interrompere la system
call read, ma il socket non è in stato di errore, quindi la read può
essere ripetuta immediatamente con gli stessi parametri. Altro caso
particolare è quando il socket è stato definito non bloccante nel
qual caso se non ci sono byte disponibili viene restituito EAGAIN,
ed il socket rimane utilizzabile. Se invece errno ha un altro valore
(EBADF, EINVAL, EFAULT, …) il socket viene invalidato.
La funzione read, applicata ad un socket, presenta una particolarità. Può
accadere che la read() restituisca meno byte di quanti richiesti, anche se
lo stream è ancora aperto. Ciò accade se il buffer a disposizione del
socket nel kernel è stato esaurito. Sarà necessario ripetere la read
(richiedendo il numero dei byte mancanti) fino ad ottenerli tutti. Oss.:
ssize_t è definito in <unistd.h> ed è un long.
I/O su Socket TCP: read()
101a
I/O su Socket TCP: write() (1)
101b
ssize_t write (int fd, const void *buf, size_t count);
cerca di scrivere fino a count byte nel buffer di sistema
corrispondente al file descriptor fd perché siano poi trasmessi. I
byte vengono letti dal buffer puntato da buf.
- Se count è zero la write restituisce zero e non scrive nulla.
- Se count è maggiore di zero viene effettuata la scrittura e viene
restituito il numero di byte scritti.
- Se viene restituito -1 è accaduto un errore e viene settata la
variabile errno con un valore che indica l’errore.
- Se il socket è configurato in modalità bloccante (è il default) la
write è bloccante, cioè attende fino a che tutti i byte sono stati
passati al buffer di sistema. Ciò significa che se non c’è abbastanza
spazio nel buffer di sistema la write attende fino a che non sono
stati spediti abbastanza byte da permettere la scrittura di tutti i byte
passati. Durante questa attesa può capitare che al processo arrivi un
segnale (es SIGUSR1) che deve essere gestito dal processo stesso;
in tal caso la write termina restituendo il numero di byte scritti
sul buffer di sistema. Se invece nessun byte è stato ancora scritto
viene restituito -1 e indicato l’errore EINTR.
- Se invece il socket è configurato in modalità non bloccante la
write scrive sul buffer di sistema il maggior numero di byte
possibile senza produrre attesa e poi termina restituendo il numero
di byte scritti. Se non è stato possibile scrivere nulla la write
restituisce -1 senza attendere e setta errno al valore EAGAIN.
Altri possibili errori sono EBADF (file descriptor non valido),
EINVAL (file descriptor non permette scritture), EFAULT (il
buffer buf è fuori dallo spazio di memoria permesso),
EPIPE (il socket è stato chiuso dall’altro end system).
Indipendentemente da come i socket sono stati settati, nel momento in
cui viene invocata la write può capitare che il processo venga
terminato dall’arrivo di un segnale SIGPIPE che viene generato dalla
write stessa per indicare che il socket che si sta usando è stato chiuso in
modo anormale dall’altro end system (inviando un segmento col flag
reset) o che non è più utilizzabile.
Per impedire la terminazione del processo ho due diverse possibilità:
1) istruire il processo per far intercettare i segnali SIGPIPE, nel qual
caso la write invece di inviare il segnale restituirà -1 indicando come
errore EPIPE.
// da eseguire solo una volta, in fase di setup del processo
if( signal(SIGPIPE, SIG_IGN) == SIG_ERR )
{perror("signal SIGPIPE failed: ");exit(1);}
…..
// poi si possono fare tutte le chiamate alla write
ris=write(socketfd, buff, n);
if(ris<0){
if(errno==EPIPE) { printf(“chiusura anomala\n”);exit(1); }
……
}
Così però intercetto tutti i segnale SIGPIPE indipendentemente da quale
socket (usato dal processo) lo provoca.
2) utilizzare al posto della write la system call send, in cui può essere
specificato di non generare il segnale SIGPIPE ma di restituire -1
indicando come errore EPIPE.
In questo modo, solo il segnale SIGPIPE di quella particolare
invocazione viene intercettato.
I/O su Socket TCP: write() (2)
101c
I/O su Socket TCP: send() (3)
101d
int send (int fd, const void *buf, size_t count, int flags);
cerca di scrivere fino a count byte nel buffer di sistema
corrispondente al file descriptor fd perché siano poi trasmessi.
I byte vengono letti dal buffer puntato da buf.
- Se count è zero la send restituisce zero e non
scrive nulla.
- Se count è maggiore di zero viene effettuata
la scrittura e viene
restituito il numero di byte scritti.
- Se viene restituito -1 è accaduto un errore e
viene settata la
variabile errno con un valore che indica
l’errore.
Il comportamento viene influenzato dal valore del parametro flags,
il cui valore può essere 0 oppure viene assegnato mediante OR bit a
bit delle seguenti costanti: MSG_OOB, MSG_DONTWAIT,
MSG_NOSIGNAL.
Se il valore di flags è zero la send si comporta come la write.
Se viene specificato MSG_DONTWAIT la send non si blocca
bensì scrive il numero di byte possibili nel buffer di sistema e
termina restituendo il numero di byte scritti, eventualmente zero.
Se viene specificato MSG_NOSIGNAL la send non solleva
l’eccezione di tipo SIGPIPE e quindi non rischia di far terminare il
processo. Al contrario, in caso di chiusura anormale della
connessione, restituisce -1 e setta la variabile errno al valore
EPIPE.
I/O su Socket TCP: (2)
TCP Output
102
Ogni socket TCP possiede un buffer per l’output (send buffer) in cui
vengono collocati temporaneamente i dati che dovranno essere trasmessi
mediante la connessione instaurata. La dimensione di questo buffer può
essere configurata mediante un’opzione SO_SNDBUF.
Quando un’applicazione chiama write() per n bite sul socket TCP, il
kernel cerca di copiare n byte dal buffer dell’appl. al buffer del socket.
Se il buffer del socket e’ più piccolo di n byte, oppure è già parzialmente
occupato da dati non ancora trasmessi e non c’è spazio sufficiente,
verranno copiati solo nc<n byte, e verrà restituito dalla write il numero
nc di byte copiati.
Se il socket ha le impostazioni di default, cioè è di tipo bloccante, la fine
della routine write ci dice che sono stati scritti sul buffer del socket
quegli nc byte, e possiamo quindi riutilizzare le prime nc posizioni del
buffer dell’applicazione. Ciò non significa affatto che già i dati siano
stati trasmessi all’altro end-system.
Per attendere di ricevere almeno un byte, o leggere i byte già arrivati, si
usa la già descritta
ssize_t read (int fd, void *buf, size_t n);
Per attendere di ricevere TUTTI i byte richiesti, si implementa la
seguente funzione readn, che
-restituisce -1 in caso di errore, e setta errno
-restituisce il numero di byte letti se l’altro end system chiude la
connessione
- restituisce il numero di byte chieste (e letti) se tutto ok.
ssize_t readn (int fd, char *buf, size_t n)
{
size_t nleft; ssize_t nread;
nleft = n;
while (nleft > 0) {
if ( (nread = read(fd, buf+n-nleft, nleft)) < 0) {
if (errno != EINTR)
return(-1); // restituisco errore
}
else if (nread == 0) {
// EOF, connessione chiusa, termino
// esce e restituisco il numero di byte letti
break;
}
else // continuo a leggere
nleft -= nread;
}
return(n - nleft); // return >= 0
}
I/O su Socket TCP: utility (1)
103a
I/O su Socket TCP: utility (2)
Per attendere di consegnare al buffer di sistema TUTTI i byte richiesti
restituisce -1 in caso di errore e setta errno
restituisce il numero di byte da inviare ed inviati, se tutto OK.
ssize_t writen (int fd, const char *buf, size_t n)
{
size_t nleft; ssize_t nwritten; char *ptr;
ptr = buf; nleft = n;
while (nleft > 0)
{
if ( (nwritten = send(fd, ptr, nleft, MSG_NOSIGNAL )) < 0) {
if (errno == EINTR) nwritten = 0; /* and call write() again*/
else return(-1); /* error */
}
nleft -= nwritten; ptr += nwritten;
}
return(n);
}
Per consegnare da zero ad n byte da trasmettere, ma senza attendere:
restituisce -1 in caso di errore, se no restituisce il numero di byte scritti
ssize_t write_nowait (int fd, const char *buf, size_t n)
{
int nwritten;
do {
nwritten=send ( fd, buf, n, MSG_DONTWAIT|MSG_NOSIGNAL);
}while( (nwritten<0) && (errno==EINTR) );
return(nwritten);
}
103b
Per primo viene fatto partire il server, poi viene fatto partire il client che
chiede la connessione al server e la connessione viene instaurata.
Nell’esempio (ma non è obbligatorio) il client spedisce una richiesta al
server, questo risponde trasmettendo alcuni dati. Questa trasmissione
bidirezionale continua fino a che uno dei due (il client nell’esempio)
decide di interrompere la connessione,
e tramite la close() chiude la
connessione. Infine il server
chiude a sua volta la connessione.
Interazioni tra Client e Server TCP
104
ephemeral
port
La funzione fork è usata per duplicare un processo.
#include <unistd.h>
pid_t fork (void);
• restituisce -1 in caso di errore. Se tutto va a buon fine restituisce 0
nel processo figlio ed un valore maggiore di zero (il pid process
identifier) nel processo padre.
• Questa funzione viene chiamata nel processo (padre=parent), e
restituisce il risultato in due diversi processi (padre e figlio).
• Se il figlio vuole conoscere il pid del padre userà la funzione getppid().
• I descrittori di file e di socket aperti dal padre prima della fork
sono condivisi col figlio, e possono perciò essere usati da entrambi
per l’I/O.
• Inoltre, per come funziona la funzione close(), è possibile per uno dei
processi (padre o figlio) chiudere una connessione aperta condivisa
(dai due processi) senza con questo impedire all’altro processo di
continuare ad utilizzare la connessione.
• La fork viene usata per generare delle repliche del processo server, per
gestire in parallelo le connessioni che via via vengono instaurate.
funzione fork()
117
• Un server banale in attesa su una porta TCP serializza le varie richieste
di apertura di una connessione dei client permettendo la connessione ad
un solo client per volta.
• Server TCP più evoluti invece, come i web server, una volta risvegliati
dalla richiesta di una connessione da parte di un client, effettuano una
fork() duplicando se stessi (il processo). Il processo figlio viene dedicato
a servire la connessione appena instaurata, il processo padre attende
nuove richieste di connessione sulla stessa porta.
A livello di interfaccia socket, questa situazione si ottiene così:
• Il server chiama la accept passando come argomento il socket
listening (socket in ascolto), e subito dopo chiama la fork.
• il processo padre chiude il connected socket, e ripete la accept sul
listening socket, in attesa della prossima richiesta di connessione.
• Invece il descrittore del connected socket (socket connesso) restituito
dalla accept resta aperto per il figlio, e viene utilizzato da questo
utilizzato per gestire l’I/O con la connessione. Quando infine il figlio
termina il suo lavoro e chiude il connected socket con la close(), la
connessione viene finalmente terminata con la sequenza di FIN.
pid_t pid; int listenfd, connfd;
listenfd = socket (AF_INET, SOCK_STREAM, 0);
bind ( listenfd, (struct sockaddr*) &Local, sizeof(Local));
listen(listenfd, 10 );
for( ; ; ) {
connfd = accept ( listenfd, (struct sockaddr*) &Cli, &len);
pid = fork();
if ( pid !=0) close(connfd); /* processo padre */
else { /* processo figlio */
close(listenfd);
usa_nuova_connessione_indicata_da_newsockfd(connfd);
close(connfd); exit(0);
}
}
Server TCP Concorrenti (1)
118
Vediamo graficamente cosa capita a livello di TCP e di porte.
Entriamo un pò nei dettagli del programma appena visto, per quanto
riguarda la scelta delle porte.
Consideriamo la situazione più complicata, quella di un’applicazione
server collocata su un host con più interfacce di rete, che vuole
permettere le connessioni su una certa porta convenzionale (la 6001)
da parte di client che accedono a una qualsiasi delle due interfacce
del server.
listenfd = socket (AF_INET, SOCK_STREAM, 0);
/* collega il socket ad un indirizzo IP locale e una porta TCP locale */
memset ( &Local, 0, sizeof(Local) );
Local.sin_family = AF_INET;
Local.sin_addr.s_addr = htonl(INADDR_ANY); /* wildcard */
Local.sin_port = htons(6001);
bind ( listenfd, (struct sockaddr*) &Local, sizeof(Local));
/* accetta max 100 richieste simultanee di inizio conness., da adesso */
listen(listenfd, 100 );
/* accetta la prima conness. creando un nuovo socket per la conness. */
for( ; ; ){
connfd = accept(listenfd, (struct sockaddr*) &Cli, &len);
pid = fork();
if ( pid !=0 ) /* processo padre */
close ( connfd );
else { /* processo figlio */
close ( listenfd ); /* chiuso il listening socket */
il figlio usa il connected socket ()
close ( connfd );
exit(0);
}
}
119
Server TCP Concorrenti (2)
120
La quaterna (IP locale, Port Number locale , IP remoto, Port Number remoto)
che identifica univocamente una connessione TCP viene di solito
chiamata socket pair.
SERVER CLIENT
IP = IP_A1 IP = IP_B
IP = IP_A2 port = 2222
Server TCP Concorrenti (3)
listening socket
( *, 6001 , * , * )
connected socket
( IP_A2, 6001 , IP_B , 2222 )
IP_A2
IP_A1
listening socket
( *, 6001 , * , * )
IP_A1
IP_A2
dopo la listen(), prima della accept()
dopo la accept() , prima della fork()
121
Server TCP
Concorrenti
(4)
listening socket
( *, 6001 , * , * )
connected socket
( IP_A2, 6001 , IP_B , 2222 )
IP_A2
IP_A1
listening socket
( *, 6001 , * , * )
IP_A1
IP_A2
dopo la fork()
dopo la close(connfd) del padre, e la close(listenfd) del figlio
connected socket
( IP_A2, 6001 , IP_B , 2222 )
padre
figlio
padre
figlio
Un’applicazione di rete può avere la necessità di accedere
contemporaneamente a più tipi di input e di output, ad es. input di tipo
streamdalla tastiera, input di tipo streamda rete eventualmente da più
connessioni contemporaneamente, input di tipo datagramda rete
anch’esso eventualmente da più socket contemporaneamente.
Esistono vari modelli di I/O disponibili in ambiente Unix:
I/O Bloccante
I/O Non Bloccante
I/O tramite Multiplexing
I/O guidato da signal
I/O asincrono (per ora poco implementato)
Consideriamo per ora il modello di I/O standard, per cui quando viene
effettuata una richiesta di I/O mediante una chiamata ad una primitiva di
tipo read() o write(), la primitiva non restituisce il controllo al chiamante
fino a che l’operazione di I/O non è stata effettuata, ovvero o la read() ha
letto da un buffer del kernel dei dati, o la write() ha scritto dei dati
dell’utente in un buffer del kernel.
Le funzioni di I/O finora analizzate sono state descritte nel loro
funzionamento proprio secondo la modalità standard (bloccante).
• Il problema è che, quando l’applicazione effettua una read su un certo
descrittore di file o di socket, se i dati non sono già presenti nella coda
del kernel dedicata a quel descrittore, l’applicazione rimane bloccata
fino a che i dati non sono disponibili, ed è impossibile leggere dati
eventualmente già pronti sugli altri descrittori di file o socket.
Analogamente per la write() se i buffer del kernel in cui si deve scrivere
il dato è già occupato.
• Un problema analogo, caratteristico dei socket, si ha quando
l’applicazione effettua una chiamata alla funzione accept(), che
restituisce il controllo solo quando una richiesta di inizio connessione è
disponibile (o meglio è già stata soddisfatta ed è nella coda delle
connessioni stabilite).
I/O Multiplexing
122
Quello che serve è un modo di ordinare al kernek di avvertirci quando,
in un insieme di canali di I/O, si verifica una condizione di
disponibilità all’I/O, che può essere così definita:
1) o dei dati sono pronti alla lettura in una coda del kernel, e si può
accedere mediante una read che restituirà immediatamente il controllo al
chiamante con i dati letti,
2) o una coda di output del kernel si è svuotata ed è pronta ad accettare
dati in scrittura mediante una write,
3) o si è verificato un errore in uno dei dispositivi di I/O e quindi una
read() o write() restituirebbe il valore -1,
4) o quando un socket listening è disponibile a fornire immediatamente
un connected socket in risposta ad una chiamata di tipo accept(), perchè
ha ricevuto una richiesta di connessione da un client,
Posix.1g mette a disposizione una primitiva, detta select(), che:
1) permette di effettuare attesa contemporaneamente su più tipi di canali
di I/O in modo da essere risvegliati quando uno di questi canali è
disponibile all’I/O in lettura o scrittura o ha verificato un errore, o
ancora nel caso dei socket quando sono disponibili i cosiddetti dati fuori
banda (usati solo in casi particolarissimi perchè meno utili di quanto il
nome farebbe presupporre),
2) e permette di fissare un limite all’attesa, in modo da essere risvegliati
se non accade nulla allo scadere di un certo tempio limite. Quest’ultima
possibilità può collassare in un’attesa di durata nulla, ovvero permette di
non effettuare attesa alcuna, ma solo di controllare lo stato istantaneo
dei vari canali e restituire subito il controllo al chiamante.
I/O Multiplexing
123
124
La funzione select permette di chiedere al kernel informazioni sullo
stato di descrittori di tutti i tipi, riguardo a loro disponibilità in lettura
scrittura o condizioni eccezionali, e di specificare quanto tempo al
massimo aspettare.
#include <sys/select.h>
int select ( int maxfdp1, fd_set *readset, fd_set *writeset,
fd_set *exceptset, const struct timeval *timeout);
La funzione restituisce -1 in caso di errore,
0 se il timeout fissato è scaduto,
altrimenti restituisce il numero di descrittori che hanno raggiunto la
condizione di disponibilità loro richiesta.
L’ultimo argomento timeout dice al kernel quanto aspettare al massimo,
ed è una struttura così fatta:
struct timeval {
long tv_sec; /* secondi */
long tv_usec; /* microsecondi */
}
con questa struttura noi possiamo specificare alla select:
attesa infinita: attesa fino a che una delle condizioni si è verificata. Si
passa un puntatore timeout nullo.
attesa limitata: attesa il numero di secondi e microsecondi specificati
nella struttura puntata dal puntatore timeout passato. In caso di timeout
la select restituisce 0.
attesa nulla: ritorna subito al chiamante dopo avere fotografato la
situazione dei descrittori. SI specifica settando a zero tv_sec e tv_usec.
NB: la timeval specifica microsecondi, ma i kernel non riescono a
discriminare solitamente sotto i 10 msec.
funzione select()
125
int select ( int maxfdp1, fd_set *readset, fd_set *writeset,
fd_set *exceptset, const struct timeval *timeout);
I tre argomenti centrali di tipo fd_set*, readset writeset exceptset
specificano i descrittori che si vuole controllare rispetivamente per
verificare disponibilità alla lettura scrittura o eccezioni (out of band data
only).
Il tipo fd_set (descriptor set) è un array di interi, in cui ogni bit
corrisponde ad un descrittore. Se il bit è settato il descrittore viene
considerato appartenente al set, altrimenti non vi appartiene.
Esistono delle macro per settare o resettare gli fd_set.
void FD_ZERO (fd_set *fdset); clear di tutti i bit di fd_set
void FD_SET ( int fd, fd_set *fdset); setta il bit fd in fd_set
void FD_CLR ( int fd, fd_set *fdset); clear del bit fd in fd_set
int FD_ISSET ( int fd, fd_set *fdset); !=0 se il bit fd è settato in fd_set
0 se il bit fd non è settato
Con queste macro posso settare pulire e controllare l’appartenenza o
meno di un descrittore all’insieme. Es.:
fd_set readset; dichiaro la variabile fd_set
FD_ZERO ( &readset ); inizializzo, azzero tutto,
insieme vuoto
FD_SET ( 1, &readset ); 1 appartiene all’insieme
FD_SET ( 4, &readset ); 4 “
FD_SET ( 7, &readset );
FD_ISSET ( 4, &readset ) restituisce != 0
FD_ISSET ( 3, &readset ) restituisce 0
Ricordarsi di inizializzare il set (FD_ZERO) altrimenti risultati
impredicibili.
funzione select() (2)
int select ( int maxfdp1, fd_set *readset, fd_set *writeset,
fd_set *exceptset, const struct timeval *timeout);
Il primo argomento maxfdp1, specifica quali descrittori controllare, nel
senso che deve avere il valore più alto tra i descrittori settati + 1.
Es.: se i descrittori settati sono 1, 4 , 7 maxfdp1 deve essere 8 = 7+1
I 3 descrittori di set passati per argomento contengono quindi i
descrittori da controllare, e vengono passati per puntatore perchè la
select li modifica scrivendoci sopra il risultato.
Quando la select termina si controllano ciascuno dei 3 fd_set, chiedendo
tramite la macro FD_ISSET() quali descrittori sono settati.
Se un descrittore (es. 4) non è settato (es. FD_ISSET(4, &readset )== 0)
significa che non è pronto.
Se invece il descrittore è settato (es. FD_ISSET(4, &readset ) != 0)
significa che è pronto.
• Il valore restituito dalla select dice quanti descrittori sono stati
settati.
• se la select restituisce 0 significa che è scaduto il timeout.
• se la select restituisce -1 c’e’ stato un errore o è avvenuta una
signal.
N.B. esiste una define che specifica la costante FD_SETSIZE ovvero il
numero di descrittori che può contenere la struttura fd_set.
Vediamo ora un esempio di uso della select, con cui implementiamo un
web server, che lavora in parallelo senza dover fare delle fork().
126
funzione select() (3)
Prima parte del server, inizializzazione:
typedef SA struct sockaddr ;
int main(int argc, char **argv)
{
int i, maxi, maxfd, listenfd, connfd, sockfd;
int nready, client [FD_SETSIZE];
ssize_t n;
fd_set rset, allset;
char line[MAXLINE];
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
listen(listenfd, LISTENQ);
maxfd = listenfd; /* initialize */
maxi = -1; /* index into client[] array */
for (i = 0; i < FD_SETSIZE; i++)
client[i] = -1; /* -1 indicates available entry */
FD_ZERO(&allset);
FD_SET(listenfd, &allset);
127
esempio d’uso della select()
server che non utilizza la fork() (1)
for ( ; ; ) {
rset = allset; /* structure assignment */
nready = select(maxfd+1, &rset, NULL, NULL, NULL);
if ( FD_ISSET( listenfd, &rset) ) { /* new client connection */
clilen = sizeof(cliaddr);
connfd = accept( listenfd, (SA *) &cliaddr, &clilen);
for (i = 0; i < FD_SETSIZE; i++)
if (client[i] < 0) {
client[i] = connfd; /* save descriptor */
break;
}
if (i == FD_SETSIZE) err_quit("too many clients");
FD_SET(connfd, &allset); /* add new descriptor to set */
if (connfd > maxfd) maxfd = connfd; /* for select */
if (i > maxi) maxi = i; /* max index in client[] array */
if (--nready <= 0)
continue; /* no more readable descriptors */
}
for (i = 0; i <= maxi; i++) { /* check all clients for data */
if ( (sockfd = client[i]) < 0)
continue;
if (FD_ISSET(sockfd, &rset)) {
if ( (n = Readline(sockfd, line, MAXLINE)) == 0) {
/*connection closed by client */
close(sockfd);
FD_CLR(sockfd, &allset);
client[i] = -1;
} else
writen(sockfd, line, n);
if (--nready <= 0)
break; /* no more readable descriptors */
}
}
}
}
128
server che non utilizza la fork() (1)
Advanced TCP Socket
130
Le opzioni per i socket sono controllate mediante tre tipi di primitive:
1) le funzioni getsockopt() e setsockopt(), che permettono di
configurare alcune caratteristiche proprie solo dei socket, quali
dimensioni dei segmenti, controlli sulla funzionalità della connessione,
modalita’ di utilizzo degli indirizzi;
2) la funzione fcntl(), che invece consente di settare caratteristiche
comuni a tutti i descrittori di I/O, quali comportamento bloccante o
non bloccante e I/O guidato dai signal.
3) la funzione ioctl(), che ripete operazioni delle fcntl ed inoltre
effettua operazioni riguardanti ARP e routing.
Le opzioni tipiche per i socket possono essere settate solo quando il
socket è reso disponibile all’interfaccia di programmazione.
Consideriamo ad es. il caso di un connected socket ottenuto da un
server in risposta ad una chiamata alla accept() a partire da un socket
listening. Il connected socket viene creato dopo la listen() quando arriva
una richiesta di connessione dal client, e tutto il procedimento di
instaurazione della connessione avviene senza che il programmatore
possa intervenire, in quanto il socket creato verrà reso disponibile al
programmatore solo quando questo farà eseguire la accept().
Per rendere possibile configurare il socket anche in questa situazione
temporanea, l’interfaccia socket implementa la politica seguente: il
connected socket eredita dal listening socket alcune opzioni, invece
di assumere le opzioni di default. Queste opzioni sono:
SO_DEBUG, SO_DONTROUTE, SO_KEEPALIVE, SO_LINGER,
SO_OOBINLINE, SO_RCVBUF e SO_SNDBUF. In tal modo se
vogliamo che il connected socket abbia queste opzioni gia’ durante la
fase del thee-way-handshake dobbiamo settare in quel modo il listening
socket.
In http://www.cs.unibo.it/~ghini/didattica/sistemi3/SOCKOPTS/checkopts.c
e’ implementato un esempio di lettura delle opzioni di default di un socket.
Le Opzioni per i Socket
Il formato di queste due funzioni è il seguente:
#include <sys/socket.h>
int getsockopt ( int sockfd, int level, int optname,
void *optval, socklen_t *optlen );
int setsockopt ( int sockfd, int level, int optname,
const void *optval, socklen_t optlen );
restituiscono 0 se tutto OK, -1 in caso di errore.
- Il primo argomento socketfd è un descrittore di socket aperto con una
socket(), su cui operano le funzioni.
- L’argomento level indica a che livello di protocollo deve agire
l’opzione: a livello di socket generale (level=SOL_SOCKET), a livello
IPv4(IPPROTO_IP), IPv6(IPPROTO_IPV6), ICMPv6
(IPPROTO_ICMPV6), a livello TCP (level=IPPROTO_TCP).
- optname specifica l’opzione da leggere /settare.
- optval è un puntatore ad una variabile di tipo dipendente dall’opzione
specificata, che contiene il valore nuovo da settare dell’opzione da
configurare (caso setsockopt) o che conterrà il valore attuale
dell’opzione (caso getsockopt).
- optlen contiene la dimensione della variabile puntata da optval.
Le opzioni possono effettuare due diverse operazioni:
- settare o resettare un flag,
- assegnare o leggere un valore più complesso.
Nel caso dei flags, il valore restituito o passato (optval) punta ad un
intero, che vale zero se l’opzione e’ disabilitata, vale nonzero se
l’opzione e’ abilitata.
Nel caso non flags optval punta ad un dato di tipo diverso, come
indicato nella seguente tabella:
131
funzioni getsockopt() e setsockopt()
T
a
b
e
l
l
a

d
e
l
l
e

O
p
z
i
o
n
i

S
o
c
k
e
t
G
e
n
e
r
i
c
h
e
,

c
i
o
è

d
i

l
i
v
e
l
l
o

S
o
c
k
e
t
,

p
e
r

g
e
t
s
o
c
k
o
p
t
(
)

e

s
e
t
s
o
c
k
o
p
t
(
)
1
3
2
P
e
r

q
u
e
s
t
e

o
p
z
i
o
n
i
,

n
e
l
l
e

f
u
n
z
i
o
n
i

g
e
t
s
o
c
k
o
p
t
(
)

e

s
e
t
s
o
c
k
o
p
t
(
)


d
e
v
e

e
s
s
r
e
u
t
i
l
i
z
z
a
t
o

c
o
m
e

s
e
c
o
n
d
o

a
r
g
o
m
e
n
t
o

l
e
v
e
l
=
S
O
L
_
S
O
C
K
E
T
.
Consideriamo le principali opzioni caratteristiche di tutti i socket,
quelle identificate dal livello SOL_SOCKET.
SO_BROADCASTquesta opzione abilita o disabilita la possibilità
per un socket di spedire messaggi broadcast. Viene applicato solo ai
socket datagram(DGRAM) e solo se la rete sottostante lo permette (es:
ethernet, non punto a punto). Per default questa opzione è
disabilitata, per impedire ad un processo di spedire accidentalmente
un datagramin broadcast, ad es. se l’indirizzo IP di destinazione viene
preso a linea di comando e si digita per errore un indirizzo di
broadcast. In tal caso il kernel si accorge di avere a che fare con un
datagramdi broadcast il cui invio è disabilitato, e restituisce un errore
di tipo EACCES.
SO_DEBUG questa opzione è supportata solo da TCP, e ordina al
kernel di mantenere informazioni su tutti i pacchetti spediti o ricevuti
da/a un certo socket, in una coda circolare. Il programma trcp
esaminera’ questa coda.
SO_DONTROUTE questa opzione e’ applicata per bypassare il
normale meccanismo di routing dei pacchetti in uscita, ad es: per farli
uscire da un’interfaccia di rete diversa da quella prevista dalle
tabelle interne di routing. Viene usata ad es. dai processi daemon del
routing (routed o gated) per instradare un pacchetto sull’interfaccia
giusta quando le tabelle di routing sono sbagliate.
SO_KEEPALIVE questa opzione è applicata solo agli streamTCP,
per verificare se una connessione che da molto tempo non scambia
dati debba essere chiusa o no. Il motivo per cui una connessione
deve essere chiusa da un end systeme’ che l’altro end systema) e’
down, b) non e’ raggiungibile (rete partizionata o problema nel
routing), c) non e’ piu’ interessato a quella connessione.
Opzioni Socket Generiche (1)
133
SO_KEEPALIVE (continuazione) Quando un socket TCP ha questa
opzione settata, se nessun dato viene scambiato in una delle due
direzioni della connessione per 2 ore, il TCP spedisce un segmento,
detto keepalive probe, all’altro end-system, per verificarne la
situazione, e si aspetta di ricevere un ACK.
1) se il peer risponde con un ACK tutto e’ OK, ed il TCP mandera’ un
nuovo probe dopo altre due ore di inattivita’.
2) se il peer risponde con un segmento RST (reset), significa che e’
andato in crash e poi ha effettuato il reboot. Il socket allora viene
chiuso, ed la variabile d’errore del socket settata a ECONNRESET.
3) se non c’e’ risposta dal peer, il TCP riprova a mandare altri 8
segmenti keepalive probe, ogni 75 secondi, aspettando risposta.
3.1) Se non viene ricevuta alcuna risposta il socket e’ chiuso e la var.
d’errore settata a ETIMEOUT.
3.2) se invece viene ricevuta un ICMP error in risposta ad uno dei
keepalive probe, il socket viene chiuso, e viene restituito l’errore
indicato dall’ICMP, che sara’ di tipo EHOSTUNREACH, ovvero
l’host non e’ raggiungibile.
La specifica Posix.1g stabilisce anche le modalita’ per settare
lintervallo di attesa (le 2 ore) ad un valore diverso, ma tale opzione e’
implementata raramente.
Questa Opzione SO_KEEPALIVE serve a stabilire se il peer host e’
andato in crash. Invece il crash dell’applicazione peer viene
individuato e notificato dal TCP peer. Cioe’ quando nell’altro end
systemil processo che gestiva il socket va in crash, il TCP di
quell’host spedisce un segmento FIN per chiudere la connessione, e
questa chiusura può essere individuata con una read() o una
select() settata per verificare la possibilita’ di leggere da quel
socket. Non esiste altro modo di accorgersi di un crash se non
cercando di fare un test per lettura.
Opzioni Socket Generiche (2)
134
SO_RCVBUF e SO_SNDBUF Queste due opzioni servono a
modificare la dimensione dei buffer di ricezione e trasmissione del
TCP e dell’UDP.
Per il TCP la dimensione del buffer di ricezione viene mandata all’atto
dell’instaurazione della connessione. E’ quindi necessario che per il
server questa opzione sia settata prima della chiamata alla listen(),
mentre per il client deve essere settata prima della chiamata alla
connect().
Invece per UDP il buffer di ricezione determina la dimensione
massima dei datagramche possono essere accettati.
SO_REUSEADDR e SO_REUSEPORTQueste due opzioni servono
a permettere di effettuare la bind() su porte e indirizzi IP gia’ utilizzati
da qualche altro processo. La SO_REUSEADDR ad es. puo’ essere
utile nei seguenti casi:
a) si cerca di fare la bind per un listening socket che e’ stato chiuso e si
vuol fare ripartire, quando ancora esiste un connected socket nato dal
listening socket appena chiuso.
b) ci sono piu connected socket che lavorano sulla stessa porta di un
host ma con IP diversi. E’ il caso dei web server che devono lavorare
sulla stessa well know port 80 ma su interfacce diverse.
SO_TYPE Questa opzione puo’ essere usata solo in lettura, e
restituisce il tipo del socket, SOCK_STREAM o SOCK_DGRAM.
Opzioni Socket Generiche (3)
135
SO_LINGERQuesta opzione determina le modalita’ di chiusura
realizzate dalla funzione close(). Viene usato come argomento optval
della setsockopt() un puntatore ad una struttura di tipo:
struct linger { int l_onoff; /* 0=off , nonzero=on */
int l_linger; /* linger time, Posix.1g vuole secondi */
}
Per default la close() restituisce subito il controllo al chiamante, ma se
alcuni dati rimangono nei buffer di spedizione il TCP tenta di spedirli.
1) se l_onoff==0 l’opzione SO_LINGER e’ disabilitata, quindi viene
settato il modo di default appena visto.
• In questo modo non sappiamo se il TCP peer ha ricevuto tutti i dati, e
a maggior ragione non sappiamo se l’application peer li ha ricevuti.
2) se l_onoff != 0 e l_linger==0 quando un socket chiama la close(), il
TCP chiude la connessione in modo traumatico, non spedendo i dati
eventualmente bufferizzati per la spedizione, e mandando un segment
RST (reset) all’altro end-system. Non si va nello stato TIME_WAIT e
si rischia di danneggiare l’apertura di una nuova connessione con gli
stessi indirizzi IP e di porta.
• Anche in questo caso non sappiamo se il TCP peer ha ricevuto
tutti i dati, e nemmeno se li ha ricevuti l’ application peer.
Opzioni Socket Generiche (4)
136
SO_LINGER(continuazione)
3) se l_onoff != 0 e l_linger !=0 quando un socket chiama la close(),
se il socket e’ di tipo bloccante (e’ il default) il TCP tenta di spedire i
dati eventualmente bufferizzati per la spedizione, fino a che si verifica
una di queste condizioni:
3.1) tutti i dati sono trasmessi e riscontrati dal TCP peer, ed allora la
funzione close() restituisce il controllo al chiamante con risultato 0,
passando dallo stato TIME_WAIT.
Anche in questo caso però, anche se sappiamo che il TCP ha ricevuto i
dati non abbiamo garanzie che l’application peer riceva i dati. Puo’
capitare infatti che dopo che il TCP peer ha spedito l’ACK per i dati ed
il FIN, e quindi la close() e’ terminata, l’application peer vada in crash
e non riesca a leggere dalla coda del TCP peer.
3.2) oppure scade il tempo assegnato di attesa l_linger e la funzione
close restituisce -1 mandando un segment RST (reset) all’altro
end-system, e non passa dallo stato TIME_WAIT.
Opzioni Socket Generiche (5)
137
La funzione shutdown() e’ utilizzata per chiudere una connessione in
modo differente rispetto alla close().Infatti:
- mentre la close() decrementa solo il contatore dei processi che
utilizzano quel socket e quando il contatore e’ zero spedisce il
segmento FYN, la shutdown spedisce subito il segmento di FIN
(anche se il contatore e’ maggiore di zero) ovviamente senza passare
avanti agli altri dati bufferizzati per la spedizione.
- mentre la close() chiude entrambe le direzioni della connessione, e
quindi impedisce di usare con quel socket sia primitive di input sia di
output (no read no write dopo close() ), la shutdown da’ la possibilita’ di
effettuare chiusure asimmetriche di una connessione, specificando quale
direzione deve essere interrotta.
int shutdown (int sockfd, int howto);
restituisce 0 se tutto OK, -1 in caso di errore.
L’argomento socketfd è un descrittore di socket.
L’argomento howto specifica l’azione che deve essere effettuata sul
socket sockfd, ed e’ una delle seguenti:
- SHUT_WR l’applicazione chiude il socket sia in scrittura che in
lettura,, senza badare al contatore di processi per quel socket. Tutti i dati
eventualmente presenti nelle code di output verranno spediti, e poi
verra’ spedito un SYN segment, per terminare il lato di output della
connessione. Rimane possibile effettuare delle read() fino a che l’altro
end-systemnon effettua a suo volta un\a close() che fa inviare il SYN
segment verso chi aveva effettuato la chiamata alla shutdown(). Questo
tipo di chiusura viene detta half-close.
- SHUT_RD l’applicazione chiude il socket in lettura, resta possibile
effettuare le write(). Non è piu’ possibile effettuare le letture, e tutti i
dati eventualmente gia’ ricevuti dal TCP e presenti nelle code per l’input
vengono scartati. Vengono scartati anche eventuali dati giunti
dopo la shutdown(). 138
funzione shutdown() (1)
- SHUT_RDWR l’applicazione chiude il socket sia in scrittura che in
lettura, come se effettuasse due chiamate alla shutdown, con parametro
SHUT_RD e poi con parametro SHUT_WR.
vediamo qui una rappresentazione di una half close (SHUT_WR).
139
funzione shutdown() (2)
Garanzie di
Trasmissione Completata (1)
140
Garanzia per il Client che il Server ha ricevuto tutti i dati.
Un modo sicuro per sapere se l’applicazione dell’altro end system(non
solo il TCP) ha ricevuto i dati è sostituire la chiamata alla close() con
una chiamata alla shutdown() usando come secondo argomento la
costante SHUT_WR, e procedere poi con una chiamata alla read().
In questo modo la shutdown manda il segmento di FIN, ma lascia
aperto il socket in lettura, permettendo di effettuare la chiamata alla
read() che altrimenti restituirebbe immediatamente un errore. La read()
rimane bloccata fino a che l’application peer termina la lettura di tutti i
dati e legge il FIN, quindi effettua la close() che manda il segmento
FIN di risposta.
La read() allora riceve l’end-of-file e termina restituendo 0. Si ha
allora la garanzia che la applicazione peer ha ricevuto tutti i dati.
Garanzia per il Client che il Server ha ricevuto tutti i dati.
Un altro modo per garantire che l’application peer ha ricevuto tutti i
dati e’ usare il cosiddetto application-level acknowledgment o
application ACK.
Il client dopo avere spedito tutti i suoi dati si blocca su una read
aggiuntiva, che rappresenta l’attesa per un ACK a livello di
applicazione. Il server, dopo avere ricevuto tutti i dati effettua una
write di un byte, che rappresenta l’ACK a livello di applicazione.
Quando il client ritorna dalla read() effettua la close() che manda il
FIN segment. Solo allora l’application peer effettua la close().
In questo modo si ha la garanzia che il processo server ha letto i dati
che gli sono stati inviati.
141
Garanzie di
Trasmissione Completata (2)
T
a
b
e
l
l
a

d
e
l
l
e

O
p
z
i
o
n
i

S
o
c
k
e
t
p
e
r

T
C
P
,

c
i
o
è

d
i

l
i
v
e
l
l
o

I
P
P
R
O
T
O
_
T
C
P
,

p
e
r

g
e
t
s
o
c
k
o
p
t
(
)

e

s
e
t
s
o
c
k
o
p
t
(
)
1
4
2
P
e
r

q
u
e
s
t
e

o
p
z
i
o
n
i
,

n
e
l
l
e

f
u
n
z
i
o
n
i

g
e
t
s
o
c
k
o
p
t
(
)

e

s
e
t
s
o
c
k
o
p
t
(
)


d
e
v
e

e
s
s
e
r
e

u
t
i
l
i
z
z
a
t
o

c
o
m
e

s
e
c
o
n
d
o

a
r
g
o
m
e
n
t
o

l
e
v
e
l
=
I
P
P
R
O
T
O
_
T
C
P
.
143
Opzioni Socket per TCP
Queste opzioni si usano specificando il livello IPPROTO_TCP.
TCP_KEEPALIVE Questa opzione specifica il tempo (in sec.) di
inattivita’ della connessione prima che venga fatto partire il segmento
di keepalive probe. Il valore di default e’ di 7200 sec. (2 ore). Questa
opzione e’ realmente attivata solo quando l’opzione SO_KEEPALIVE
e’ abilitata.
TCP_MAXSEG Questa opzione restituisce o setta il maximum
segment size (MSS) per la connessione TCP.
TCP_NODELAY Per default il TCP abilita il cosiddetto Algoritmo di
Nagle (Nagle Algorithm) il cui scopo e’ di diminuire il numero di
segmenti piccoli trasmessi, come nel caso di client telnet che
prevedono l’ACK per ogni singolo carattere battuto a tastiera. Questo
algoritmo è legato all’algoritmo dell’ACK ritardato (delayed ACK
algorithm) che fa aspettare il TCP per circa 50-200 msec) prima di dare
l’ack ad un segmento, sperando di poter accodare l’ACK ad un
segmento di dati.
Questi due algoritmi assieme cercano di minimizzare il numero di
segmenti trasmessi, ma producono ritardo per applicazioni che
scambiano piccoli dati e quasi solo in una direzione.
L’opzione TCP_NODELAY disabilita l’uso di questi algoritmi.
Nagle algorithmDISABLED
Nagle algorithm Enabled settata TCP_NODELAY opt.
Consente di inviare uno stesso datagram UDP a piu’ destinatari, i quali
devono essersi preventivamente registrati come membri del gruppo di
multicast caratterizzato da un certo indirizzo di multicast.
Indirizzi di multicast:
Classe D, 224.0.0.0 - 239.255.255.255
Indirizzi di Multicast Speciali:
224.0.0.1 (all-host group) vi si devono registrare tutti gli host di
una sottorete, che implementano il multicast.
224.0.0.2 (all-router group) vi si devono registrare tutti gli host di
una sottorete, che implementano il multicast.
Operazioni necessarie per ricevere datagram UDP via multicast:
creazione di un socket UDP
collegamento ad una porta del protocollo UDP (bind)
join ad un indirizzo di multicast (setsockopt).
Operazioni necessarie per smettere di ricevere via multicast:
leave del gruppo multicast (setsockopt).
N.B. Se con la bind non specifico un indirizzo IP, posso ricevere
datagram UDP sia originati da multicast che da unicast, per la
porta UDP specificata.
N.B. Se con la bind specifico l’indirizzo IP di multicast a cui faro’ il
join, potro’ ricevere solo datagram UDP di multicast per quello
indirizzo di multicast.
Operazioni opzionali per spedire datagram UDP via multicast:
Specificare il TTL dei datagram UDP multicast.
Se non si specifica, il default e’ 1, non si esce dalla subnet.
Specificare se il mittente deve ricevere copia del datagram(se fa
parte del gruppo di multicast.
144
Opzioni Socket per IP: Multicast (1)
indica se i datagram
di multicast spediti
devono andare anche
al mittente, se questo
appartiene
al gruppo
u_char IP_MULTICAST_LOOP
specifica il TTL dei
datagramdi
multicast da spedire
u_char IP_MULTICAST_TTL
indica l’interfaccia
di rete da usare per
spedire i datagram di
multicast
struct in_addr IP_MULTICAST_IF
toglie il socket UDP
dal gruppo di
multicast
struct ip_mreq IP_DROP_MEMBERSHIP
collega il socket
UDP ad uno
specifico gruppo di
multicast
struct ip_mreq IP_ADDR_MEMBERSHIP
uso tipo di dato opzione
Opzioni di livello IPPROTO_IP
Le opzioni utilizzate per configurare un socket UDP per la
trasmissione/ricezione di datagram per un certo indirizzo di multicast,
vengono settate mediante la primitiva setsockoption, utilizzando il
livello IPPROTO_IP, ed i comandi e le strutture dati specificati in
tabella.
145
Opzioni per il Multicast in IPv4
146
esempio: receiver di datagram UDP
per gruppo di Multicast
struct ip_mreq Mreq;
.....
socketfd = socket (AF_INET, SOCK_DGRAM, 0);
OptVal = 1;
setsockopt (socketfd, SOL_SOCKET, SO_REUSEADDR,
(char *)&OptVal, sizeof(OptVal) );
Local.sin_family = AF_INET;
Local.sin_addr.s_addr = htonl(INADDR_ANY);
Local.sin_port = htons( 6001); // local_port_number
bind ( socketfd, (struct sockaddr*) &Local, sizeof(Local));
/* join the multicast group. */
Mreq.imr_multiaddr.s_addr = inet_addr("234.5.6.7");
Mreq.imr_interface.s_addr = INADDR_ANY;
setsockopt(socketfd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
(char *)&Mreq, sizeof(Mreq) );
for (j=1;j<=1000;j++) {
Fromlen=sizeof(struct sockaddr);
msglen = recvfrom( socketfd, msg, (int)SIZEBUF, 0,
(struct sockaddr*)&From, &Fromlen);
sprintf(string_remote_ip_address,"%s",inet_ntoa(From.sin_addr);
remote_port_number = ntohs(From.sin_port);
printf("ricevuto msg: \"%s\" len %d, from host %s, port %d\n",
msg, msglen, string_remote_ip_address, remote_port_number);
}
147
esempio: sender di datagramUDP
per gruppo di Multicast
struct ip_mreq Mreq; int ttl, int loopback;
.....
socketfd = socket (AF_INET, SOCK_DGRAM, 0);
OptVal = 1;
/* set TTL to traverse up to multiple routers */
ttl = TTL_VALUE; // per default 1
setsockopt(socketfd, IPPROTO_IP, IP_MULTICAST_TTL,
(char *)&ttl, sizeof(ttl));
/* join the multicast group, NON NECESSARIO per SPEDIRE */
Mreq.imr_multiaddr.s_addr = inet_addr("234.5.6.7");
Mreq.imr_interface.s_addr = INADDR_ANY;
setsockopt(socketfd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
(char *)&Mreq, sizeof(Mreq) );
/* disable loopback , NON NECESSARIO se non si fa join */
loopback =0;
setsockopt(socketfd, IPPROTO_IP, IP_MULTICAST_LOOP,
(char *)&loopback, sizeof(loopback));
for (j=1;j<=1000;j++) {
To.sin_family = AF_INET;
To.sin_addr.s_addr = inet_addr("234.5.6.7" );
To.sin_port = htons( 6001 ); // remote_port_number
addr_size = sizeof(struct sockaddr_in);
/* send to the address */
sendto ( socketfd, msg, strlen(msg) , 0,
(struct sockaddr*)&To, addr_size);
}
La funzione fcntl significa “file control” e serve ad effettuare alcune
operazioni su vari descrittori non solo di socket ma in generale di file.
In particolare la funzione fcntl permette di:
• configurare l’I/O di un descrittore come non bloccante o bloccante
usando nella fcntl il comando F_SETFL con argomento
O_NONBLOCK (O_BLOCK),
• configurare l’I/O di un descrittore come guidato dai signal usando
nella fcntl il comando F_SETFL con argomento O_ASYNC, ottenendo
che in corrispondenza di ogni modifica della situazione del socket
venga scatenato un SIGIO signal.
• settare un proprietario per il descrittore (comando F_SETOWN),
ovvero definire qual’e’ il processo che viene avvisato con il signal
SIGIO quando il descrittore e’ disponibile all’I/O, ma questo solo se e’
stato settato il socket come guidato dai signal. Si ricorda che un socket
appena creato con la socket() non ha proprietario, un connected socket
ha lo stesso proprietario del listenung socket
• conoscere il proprietario corrente per il descrittore (F_GETOWN).
#include <fcntl.h>
int fcntl (int fd, int cmd, ... /* int arg */ );
restituisce -1 in caso di errore,
un altro valore dipendente dal comando in caso tutto OK.
Le proprieta’ di un descrittore di file (e di socket) sono definite
mediante alcuni flags, che la funzione fcntl permette di leggere (usando
come secondo argomento cmd=F_GETFL) e di settare (usando come
secondo argomento cmd=F_SETFL).
I due flags che ci interessano per i socket sono:
O_NONBLOCK non blocking I/O
O_ASYNC signal-driven I/O notification
148
Opzioni Socket tramite fcntl (1)
In generale la procedura corretta per settare un flags consiste nel
leggere i flags del descrittore mediante il comando F_GETFL,
modificare solo il flag che interessa con un OR logico, settare i nuovi
flags per il socket mediante il comando F_SERFL.
Nel seguente esempio viene settato il flag O_NONBLOCK, quindi
il socket diventa “non bloccante”.
Procedura corretta
int flags, sockfd;
sockfd = socket(....);
if ( (flags=fcntl(sockfd,F_GETFL,0)) <0 )
exit(1);
flags |= O_NONBLOCK;
if ( fcntl(sockfd,F_SETFL,flags) <0 )
exit(1);
Procedura errata, vengono azzerati tutti gli altri flags.
int sockfd;
sockfd = socket(....);
if ( fcntl(sockfd,F_SETFL, O_NONBLOCK )<0)
exit(1);
Infine in questo esempio viene resettato il flag O_NONBLOCK, quindi
il socket diventa “bloccante”.
int flags, sockfd;
sockfd = socket(....);
if ( (flags=fcntl(sockfd,F_GETFL,0)) <0 )
exit(1);
flags &= ∼O_NONBLOCK;
if ( fcntl(sockfd,F_SETFL,flags) <0 )
exit(1);
149
Opzioni Socket tramite fcntl (2)
Se per un socket viene settato il flags O_ASYNC, quando il socket
diventa disponibile all’I/O, o e’ in errore, viene lanciato un signal
SIGIO al processo proprietario del socket, o ai processi del gruppo
proprietario del socket, se il proprietario esiste.
Per settare il proprietario si usa la fcntl con secondo argomento
F_SETOWN passando come terzo parametro o il numero positivo pid
del processo proprietario, o il numero negativo ottenuto cambiando di
segno il numero di gruppo.
Per conoscere il proprietario si usa la fcntl con secondo argomento
F_GETOWN. La funzione restituisce un intero positivo (il pid del
proprietario) o un numero negativo diverso da -1, che e’ l’identificatore
del gruppo cambiato di segno.
settare il proprietario di un socket
int sockfd,pid;
sockfd = socket(....);
if ( fcntl(sockfd,O_SETOWN, pid ) <0 )
exit();
settare il gruppo di un socket
int sockfd, gid;
sockfd = socket(....);
if ( fcntl(sockfd,O_SETOWN , -gid ) <0 )
exit();
150
Opzioni Socket tramite fcntl (3)
Per default un socket e’ bloccante. Cio’ significa che quando il socket
deve effettuare un’operazione di I/O, se questa non puo’ essere
terminata immediatamente il processo si mette in attesa aspettando la
fine dell’operazione. Se invece, mediante la fcntl vista prima, il socket
viene settato Nonblocking il comportamento delle funzioni cambia:
Operazioni di input (read, recvfrom). Con un socket Non
Bloccante, se l’operazione di input non puo’ essere terminata (cioe’
non c’e’ nemmeno un byte per il socket TCP o non c’e’ nemmeno un
datagramper il socket UDP) la funzione ritorna immediatamente al
chiamante restituendo il codice d’errore EWOULDBLOCK.
Operazioni di output (write, sendto ).
• Con un socket Non Bloccante di tipo TCP, se l’operazione di
scrittura non puo’ essere effettuata nemmeno in parte perche’ manca
spazio nei buffer del TCP in cui andare a scrivere i dati, la funzione
ritorna immediatamente al chiamante restituendo il codice d’errore
EWOULDBLOCK. Se invece e’ rimasto spazio, viene effettuata una
scrittura di una porzione di dati minore o uguale alla dimensione del
buffer libero, e la write restituisce il numero di byte scritti.
• Con un socket Non Bloccante di tipo UDP invece il problema non
sussiste perche’ nessun datagramviene mantenuto in un buffer perche’
non c’e’ ritrasmissione, quindi il datagram UDP viene immediatamente
spedito e vada come vada. Quindi una primitiva di output di un socket
UDP non blocca mai il processo che l’effettua.
Operazioni di Accettazione Richiesta Connessione (accept).
Con un socket Non Bloccante, se l’operazione di accept non puo’
restituire immediatamente una nuova connessione perche’ la coda delle
connessioni accettate e’ vuota, la funzione ritorna immediatamente al
chiamante restituendo il codice d’errore EWOULDBLOCK.
151
I/O Non Bloccante (1)
Operazioni di Richiesta Connessione (connect).
Con un socket UDP la funzione connect non e’ realmente bloccante,
perche’ deve solo scrivere nel kernel l’indirizzo IP e il numero di porta
dell’altro end-system, quindi anche nel caso di socket Non Bloccante la
connect per l’UDP effettua sempre l’operazione interamente e
restituisce il controllo al chiamante.
Invece per il TCP la chiamata del client alla connect() di un socket
bloccante deve aspettare che, all’interno del three way handshake, il
client abbia ricevuto l’ACK per il SYN segment, sia stato ricevuto, e
quindi puo’ veramente bloccare il processo.
Se il socket TCP e’ di tipo Non Bloccante, la chiamata del client alla
connect() fa iniziare il procedimento di inizio connessione, cioe’ fa
spedire il primo SYN segment, ma se la connessione non puo’
immediatamente essere instaurata la connect() termina con un codice
d’errore EINPROGRESS ad indicare che l’operazione continua a
livello inferiore.
Con una select() sara’ possibile accorgersi di quando il socket
impegnato nella richiesta di connessione ha stabilitpo la connessione, e
quindi puo’ essere ripetuta la connect().
Si noti che puo’ capitare che la connect() restituisca OK perche’ e ‘
riuscita ad avere immediatamente risposta ed ha instaurato la
connessione richiesta.
152
I/O Non Bloccante (2)
socketfd = socket(AF_INET, SOCK_STREAM, 0);
/* SETTO IL SOCKET COME NON BLOCCANTE */
set_socket_non_blocking(socketfd); /* ora socket e' non bloccante */
... qui va messo il necessario per la bind .......
bind(socketfd, (struct sockaddr*) &Local, sizeof(Local));
memset ( &Serv, 0, sizeof(Serv) );
Serv.sin_family = AF_INET;
Serv.sin_addr.s_addr = inet_addr(string_remote_ip_address);
Serv.sin_port = htons(remote_port_number);
/* connection request, NON BLOCCANTE */
ris = connect(socketfd, (struct sockaddr*) &Serv, sizeof(Serv));
if (ris != SOCKET_ERROR) { /* connessione riuscita subito */
set_socket_blocking(socketfd); /* ora socket e' bloccante */
.....................
}
else { /* (ris == SOCKET_ERROR) */
if(errno!=EINPROGRESS) exit(1); /* conness. non terminata */
FD_ZERO(&fdr); FD_SET(socketfd,&fdr);
fdw=fdr;
.....................
ris=select(socketfd+1,&fdr,&fdw,NULL,NULL);
if ((FD_ISSET(socketfd,&fdr))||(FD_ISSET(socketfd,&fdw))) {
ris=connect(socketfd,(struct sockaddr*) &Serv, sizeof(Serv));
if (ris == SOCKET_ERROR) {
if(errno==EISCONN) {
printf ("connessione gia' esistente, OK\n");
set_socket_blocking(socketfd); /* socket e‘ bloc*/
}
else exit(1); /* connessione NON riuscita */
}
}
}
153
Connect Non Bloccante (1)
/* SETTO IL SOCKET COME NON BLOCCANTE */
int set_non_blocking(int socketfd) {
int flags;
if ( (flags=fcntl(socketfd,F_GETFL,0)) <0 ) return(0); /* errore */
flags |= O_NONBLOCK;
if ( fcntl(socketfd,F_SETFL,flags) <0 ) return(0); /* errore */
return(1);
}
/* SETTO IL SOCKET COME BLOCCANTE */
int set_blocking(int socketfd) {
int flags;
if ( (flags=fcntl(socketfd,F_GETFL,0)) <0 ) return(0); /* errore */
flags &= (~O_NONBLOCK);
if ( fcntl(socketfd,F_SETFL,flags) <0 ) return(0); /* errore */
return(1);
}
N.B.
per l’esempio completo vedere nella home page
154
Connect Non Bloccante (2)

Un esempio di Wan: la rete dell’Università di Bologna

Bologna
Ozzano

Forlì
Bertinoro

Ravenna

Monte Calderaro M. Maggio

Cesena
Rimini

3

Caratteristiche delle Reti
Le RETI LOCALI (LAN). • Una rete locale è un mezzo di trasporto equamente condiviso tra tutte le stazioni che vi si collegano, ad alta velocità e basso tasso d’errore, limitato ad un ambito locale. • Le velocità trasmissive sono comprese nell’intervallo 4 Mb/s 155 Mb/s. Accanto alle vecchie reti Ethernet e Token Ring si sono aggiunte reti più moderne quali FastEthernet a 100 Mb/s e ATM a 34 e 155 Mb/s. • Tutte queste tecnologie adottano tipicamente come supporto trasmissivo preferenziale il doppino di rame e la fibra ottica sulle dorsali. Il cavo coassiale è stato (per fortuna) quasi del tutto abbandonato. Le RETI METROPOLITANE (MAN). Sono recenti estensioni delle reti locali in ambito urbano. Sfruttano una grande varietà di tecnologie, che vanno dai ponti radio ad alta frequenza (es ponti radio punto-punto a 38 GHz per velocità da 2 Mb/s a 34 Mb/s, o punto-multipunto di tipo Spread Spectrum) alle fibre ottiche. Le prestazioni classiche raggiunte sono comprese tra 2 Mb/s e 155 Mb/s. Le RETI GEOGRAFICHE (WAN). Si basano solitamente su servizi offerti dai gestori nazionali di telecomunicazione, che rendono disponibili canali per trasmissione digitale punto a punto, i cosiddetti CDN (Canali Diretti Numerici). A causa della liberalizzazione del mercato 4 l’offerta è in continua evoluzione.

quali lo Stack TCP/IP. L’ISO doveva: • standardizzare la terminologia. • fornire un modello rispetto a cui confrontare le architetture di reti proprietarie. L’OSI ha cercato di diventare più di un modello di riferimento. • servire come base comune da cui far partire lo sviluppo di standard per l’interconnessione di sistemi informatici. l’OSI ha adottato un approccio a livelli (layers): il problema della comunicazione tra due applicazioni è stato spezzato in un insieme di 7 livelli. Infatti l’ISO ha standardizzato per OSI una serie di protocolli. 5 . garantendo l’interoperabilità dei prodotti. I protocolli di livello superiore incontrano più difficoltà a causa dell’alto impatto che la loro adozione avrebbe sui dispositivi di instradamento e sul software dei sistemi informativi. Per gestire la complessità dei problemi. I livelli 1 (Fisico) e 2 (Data Link) sono stati accettati e sono oggi degli standard. • definire quali sono le funzionalità di una rete. ciascuno dei quali esegue funzioni ben specifiche.L’OSI (Open System Interconnections) L’ OSI è un progetto di ampio respiro formulato dall’ISO (International Standard Organization) alla fine degli anni ‘70 con lo scopo di fungere da modello di riferimento per le reti di calcolatori. da collocare nei vari livelli del modello. per formare una vera architettura di rete concorrenziale con altre.

Il modello ISO/OSI Architettura a Livelli Per ridurre la complessità del progetto. . ciascun sistema è decomposto in un insieme ordinato di livelli. Tale approccio di progettazione a livelli è comune a tutte le 6 moderne architetture di rete. La figura seguente rappresenta i livelli che compongono il modello di riferimento ISO-OSI. rappresentati per convenienza come una pila verticale. OSI introduce un’architettura a livelli (layered architecture) i cui componenti principali sono: • i livelli (layers) • le entità (entities) • i punti di accesso al servizio (SAP: Service Access Points) • le connessioni (connections) In tale architettura.

che effettua la trasmissione verso l’altro sistema. • L’interfaccia di un livello definisce quali operazioni primitive e quali servizi sono forniti da un livello ai livelli superiori. con il messaggio che viene passato via via ai livelli inferiori (che aggiungono i loro header).Il modello ISO/OSI Comunicazioni tra livelli su sistemi diversi (2) • Anche se è definito un protocollo di livello N. sino a quando si giunge al livello Fisico. e giunto al sistema di destinazione risale liberandosi degli header aggiunti. • Infatti ogni livello passa dati e informazioni di controllo a quello sottostante. fino ad arrivare al livello stabilito. In figura è rappresentato il meccanismo di comunicazione tra due entità di livello N= 4. 9 . viene trasmesso attraverso il mezzo fisico. nessun dato è trasferito direttamente da un livello N all’altro su un diverso sistema.

7 . Esso verifica la presenza di errori aggiungendo delle FCS (Frame Control Sequence) e può gestire meccanismi di correzione di tali errori tramite ritrasmissione. che gestisce l’instradamento dei messaggi. ed è il primo livello (a partire dal basso) che gestisce informazioni sulla topologia della rete. A questo livello si specificano ad esempio le tensioni che rappresentano 0 e 1 e le caratteristiche dei cavi e dei connettori. Deve quindi gestire delle tabelle di instradamento e provvedere ad instradamenti alternativi in caso di guasti (fault tollerance). Il livello ha come scopo la trasmissione sufficientemente affidabile di pacchetti detti frame tra due sistemi contigui. Il livello 1 del modello OSI si occupa di trasmettere sequenze binarie sul canale di comunicazione. • Il livello 3: NETWORK. Tale livello determina se e quali sistemi intermedi devono essere attraversati dal messaggio per giungere a destinazione.Il modello ISO/OSI I Livelli (1) • Il livello 1: FISICO. Accetta come input dei pacchetti di livello 3 (tipicamente poche centinaia di bit) e li trasmette sequenzialmente. • Il livello 2: DATA LINK. Il livello 3 è il livello Network.

Il modello ISO/OSI
I Livelli (2)
• Il livello 4: TRASPORTO. Il livello 4 del modello OSI fornisce un servizio di trasferimento trasparente dei dati tra entità del livello 5 (sessione). Si occupa di garantire un servizio affidabile. Deve quindi effettuare la frammentazione dei dati, la correzione degli errori e la prevenzione della congestione della rete. Il livello 4 è il livello più basso, a partire dall’alto, a trascurare la topologia della rete e la presenza di sistemi intermedi di instradamento, ed è quindi detto livello end-to-end. • Il livello 5: SESSIONE. Organizza il dialogo tra due programmi applicativi, consentendo di aggiungere a connessioni end-to-end servizi più avazati. • Il livello 6: PRESENTAZIONE. Definisce formalmente i dati che gli applicativi si scambiano, come questi dati sono rappresentati localmente sul sistema, e come vengono codificati durante il trasferimento. • Il livello 7: APPLICAZIONE. Il livello 7 è il livello dei programmi applicativi, facenti parte del sistema operativo oppure scritti dall’utente, attraverso i quali l’utente utilizza la rete.
8

Principali Architetture di Rete
• L’insieme dei livelli, dei protocolli e delle interfacce definisce un’architettura di rete. Le architetture di rete più note sono: • SNA (System Network Architecture) architettura della rete IBM; • DNA (Digital Network Architecture), meglio nota come DECnet, la rete della Digital Equipment Corporation; • Internet protocol Suite, meglio nota con il nome TCP/IP, è la rete degli elaboratori UNIX e rappresenta uno standard “de facto” attualmente impiegato per la rete Internet. •OSI (Open System Architecture), che è lo standard “de iure” prodotto dall’ISO.

10

Incapsulamento dei Pacchetti
La trasmissione dei dati avviene quindi: • attraverso una serie di passaggi da livelli superiori a livelli inferiori nel sistema che trasmette, • poi attraverso mezzi fisici di comunicazione, • infine attraverso un’altra serie di passaggi, questa volta da livelli inferiori a livelli superiori. Notare come a livello 2, sia necessario aggiungere in coda un campo che identifica la fine del pacchetto prima di passare lo stesso al livello che utilizza il mezzo trasmissivo.

11

Può anche implicare l’attraversamento di alcuni sistemi intermedi (IS: Intermediate Systems). router a livello 3 ed infine gateway a livello 7. sarà approfondito 12 analizzando lo stack TCP/IP . Si parla allora di repeater a livello 1. bridge a livello 2.Sistemi Intermedi (1) Non sempre lo scambio delle informazioni avviene direttamente tra i due sistemi finali che contengono le applicazioni (ES: End Systems). Accenniamo per ora solo all’azione svolta dai Repeater. In questi Intermediate Systems esistono delle entità che assumono la funzione di Router (in senso esteso). ovvero entità che instradano le informazioni. Tali entità possono essere collocate a diversi livelli del modello OSI. mentre l’instradamento a livelli superiori. ed allora gli Intermediate Systems assumono nomi diversi a seconda del livello in cui avviene l’instradamento dei dati. Qui di seguito è rappresentata la collocazione di un Router nel modello OSI. che comporta anche la rigenerazione digitale del segnale.

• Il segnale che il Repeater riceve da un lato viene amplificato e propagato all’altro lato. • Di conseguenza il livello Fisico del sistema B riceve un segnale afflitto dalla somma dei rumori che si sono prodotti durante la trasmissione sui due tratti di percorso. quindi viene propagato anche il rumore che può essersi prodotto durante la trasmissione dal sistema A al Repeater. e a tale rumore si aggiungerà il rumore prodotto nella trasmissione dal Repeater al sistema B. 13 . e quindi aumenta la probabilità che si riscontri un errore nei dati trasmessi. • E’ per questo motivo che bisogna ridurre al minimo il numero di repeater in un percorso.Sistemi Intermedi (2) Qui di seguito è rappresentata la collocazione di un Repeater nel modello OSI. compito che invece spetta al livello Data Link. Il livello Fisico non effettua correzione dei dati ricevuti. • La correzione potrà essere effettuata solo quando il segnale giungerà al sistema 2. ma l’eventuale ritrasmissione dei dati dovrà attraversare nuovamente i due tratti di rete.

Architettura TCP/IP e confronto con OSI 14 .

Interfaccia Socket 15 .

che è stato riconosciuto anche da OSI. Per quanto riguarda le reti in ambito locale (LAN).Lo Stack TCP/IP: Le Basi I livelli TCP/IP hanno questa relazione con i livelli di OSI. Risulta necessario perciò soffermarsi brevemente sulla strutturazione30 delle LAN. • connectionless. nel seguito considereremo proprio la situazione di una rete locale. Il livello più basso (corrispondente ai livelli 1 e 2 di OSI) non è specificato dall’architettura. che prevede di utilizzare quelli disponibili per le varie piattaforme HW e conformi agli standard. . lo standard riconosciuto per i livelli 1 e 2 è rappresentato dal progetto IEEE 802. Lo stack di protocolli TCP/IP implementa un livello network (livello 3) di tipo: • packet-switched. Per capire il funzionamento dei protocolli TCP/IP.

• Le LAN hanno quindi sempre XQ VROR FDQDOH WUDVPLVVLYR ad alta velocità condiviso nel tempo da tutti i sistemi collegati. Questo è ottenibile abbastanza facilmente in un'area delimitata usando mezzi trasmissivi di buona qualità.Le LAN (Local Area Network) DEFINIZIONE: una LAN è un sistema di comunicazione che permette ad apparecchiature indipendenti di comunicare tra loro. ma impone anche alcune complicazioni: è QHFHVVDULD OD SUHVHQ]D GL LQGLUL]]L GL OLYHOOR  per stabilire chi sono il reale destinatario e il mittente della trasmissione e occorre arbitrare l'accesso all'unico mezzo trasmissivo tra tutti i sistemi che hanno necessità di trasmettere. essendo intrinsecamente affidabili. non hanno la necessità di correggere gli errori a livello 2 OSI e quindi normalmente utilizzano SURWRFROOL GL OLYHOOR  FRQQHFWLRQOHVV ad alte 31 prestazioni. . • L'unico canale trasmissivo presente deve anche essere caratterizzato da un EDVVR WDVVR GL HUURUH. L'effetto ottenuto è quello che le LAN. Tale organizzazione ha enormi vantaggi. Quando un sistema trasmette diventa proprietario temporaneamente (per la durata di uno o pochi pacchetti) dell'intera capacità trasmissiva della rete. entro un DUHD GHOLPLWDWD. utilizzando XQ canale fisico a YHORFLWj HOHYDWD e con EDVVR WDVVR G¶HUURUH. • La trasmissione è sempre di tipo EURDGFDVW. ovvero un sistema trasmette e tutti gli altri ricevono.

il progetto IEEE 802 suddivide il livello Data Link in due sottolivelli: • //& /RJLFDO /LQN &RQWURO. pur utilizzando tecnologie trasmissive differenziate. IEEE 802 introduce l’idea che OH /$1 H OH 0$1 GHYRQR IRUQLUH XQ LQWHUIDFFLD XQLILFDWD YHUVR LO OLYHOOR 1HWZRUN (livello rete). Per ottenere tale risultato. complessivamente raccolti nel progetto IEEE 802.Il Progetto IEEE 802 Quando le prime LAN cominciarono a diffondersi l’IEEE decise di costituire sei comitati per studiare il problema della standardizzazione delle LAN e delle MAN.

 • 0$& 0HGLD $FFHVV &RQWURO.

mentre il 0$& q FDUDWWHULVWLFR GL FLDVFXQD /$1. Il VRWWROLYHOOR //& q FRPXQH D WXWWH OH /$1. mentre i vari MAC sono descritti negli standard specifici di ogni rete locale (ad esempio il MAC CSMA/CD è descritto nello standard IEEE 802. ed il MAC Fast Ethernet (100BaseT) è descritto in IEEE 802. Il sottolivello LLC è l'interfaccia unificata verso il livello Network ed è descritto nell'apposito standard IEEE 802. FRVu FRPH LO OLYHOOR ILVLFR DO TXDOH q VWUHWWDPHQWH DVVRFLDWR.2. 32 .3.3u).

3HU WUDVPHWWHUH LQ EURDGFDVW. • WUDVPLVVLRQL SXQWRJUXSSR. • LQ ULFH]LRQH GHWHUPLQDUH D TXDOL VLVWHPL q HIIHWWLYDPHQWH GHVWLQDWR LO PHVVDJJLR H TXDOH VLVWHPD OR KD JHQHUDWR. basati su principi diversi. Il MAC è indispensabile in quanto a livello 2 (Data Link) le LAN implementano sempre una sottorete trasmissiva di tipo broadcast in cui ogni sistema riceve tutti i frame inviati dagli altri. quali la contesa. se l'indirizzo di 33 destinazione indica tutti i sistemi. La soluzione del secondo problema QHFHVVLWD GHOOD SUHVHQ]D GL LQGLUL]]L D OLYHOOR 0$& (quindi nella MAC-PDU) che identifichino ciascun sistema e trasformino trasmissioni broadcast in: • WUDPLVVLRQL SXQWRSXQWR. bisogna risolvere due problemi: • LQ WUDVPLVVLRQH YHULILFDUH FKH LO FDQDOH VLD OLEHUR SULPD GL WUDVPHWWHUH H ULVROYHUH HYHQWXDOL FRQIOLWWL GL SL VLVWHPL FKH YRJOLDQR XWLOL]]DUH FRQWHPSRUDQHDPHQWH LO FDQDOH. la prenotazione e il round-robin.MAC: Media Access Control (1) Il sottolivello MAC è specifico di ogni LAN e risolve il SUREOHPD GHOOD FRQGLYLVLRQH GHO PH]]R WUDVPLVVLYR. La soluzione del primo problema è data dai vari algoritmi di MAC. . il token. • WUDVPLVVLRQL HIIHWWLYDPHQWH EURDGFDVW. cioè IDU FRQGLYLGHUH XQ XQLFR FDQDOH WUDVPLVVLYR D WXWWL L VLVWHPL. se l'indirizzo di destinazione indica un gruppo di sistemi. se l'indirizzo di destinazione indica un singolo sistema.

il pacchetto viene passato al LLC solo se il MAC-DSAP è uguale all'indirizzo hardware della scheda o a quello caricato da software nell'apposito buffer. • se il MAC-DSAP è multicast. quali servizi questi mettono a disposizione e. per effettuare una serie di controlli. si usano delle tecniche di hash per mantenere la lista dei gruppi abilitati. . cioè se la scheda appartiene al gruppo indirizzato. perchè tutti i sistemi devono riceverlo. si verifica se la ricezione di quel multicast è stata abilitata dal software di livello superiore. Per prima cosa il MAC verifica che il pacchetto sia integro (cioè abbia una FCS Frame Control Sequence corretta) e di dimensioni ammesse. le relazioni 36 esistenti tra gli indirizzi MAC e gli indirizzi di livello 3. questo non viene passato automaticamente al livello superiore (LLC) ma viene analizzato a livello MAC.Ricezione dei Pacchetti a livello 2 Quando una scheda di rete locale riceve un pacchetto. Gli indirizzi di gruppo servono principalmente per scoprire quali altri sistemi sono collegati alla rete locale. il pacchetto viene sempre passato al LLC. Poiché non è noto a priori a quanti gruppi possa appartenere una scheda. molto importante. cioè di livello 3. Si possono porre tre casi: • se l’indirizzo MAC di destinazione (il MAC-DSAP) è EURDGFDVW. • se il MAC-DSAP è single. Successivamente il livello MAC analizza l'indirizzo di destinazione (MAC-DSAP).

Ricezione dei Pacchetti a livello 2 Un’eccezione: il modo PROMISCUO Nel modo normale visto nella precedente slide. detto “PRGR SURPLVFXR” (SURPLVFXRXV PRGH) in cui tutti i pacchetti ricevuti a livello MAC sono passati ai livelli superiori. Tale modalità di funzionamento viene utilizzata da applicazioni (ad es. ecc. In tal modo si ha la garanzia che i pacchetti destinati ad un host vengano letti solo da quell’host. lo passa al livello superiore (LLC) solo se il pacchetto di livello MAC è un pacchetto broadcast. 37 . Ovviamente tale modalità rappresenta un problema di sicurezza perchè rende possibile vedere dati riservati trasmessi via rete. da chi. .. impedendo ad altri di accedere alla porta attraverso la quale si configura la scheda di rete. il “tcpdump” per Linux) per effettuare il monitoraggio della rete. se una scheda di rete inserita in una LAN riceve un pacchetto. In realtà esiste un’altra modalità di funzionamento delle schede di rete per LAN. o è un pacchetto single con indirizzo uguale a quello della scheda di rete. ovvero per controllare quanti e quali pacchetti vengono trasmessi in rete. o è un pacchetto multicast con indirizzo con cui un’indirizzo dell’host è in corrispondenza. In tal caso l’analisi sulla destinazione dei pacchetti dovrà essere effettuata da protocolli di livello superiore. Per tale motivo i sistemi operativi moderni permettono solo all’amministratore del sistema (root) di configurare la scheda di rete perchè funzioni in modo promiscuo.

l’indirizzo MAC dell’host destinazione stesso. una richiesta del tipo: "chi ha l'indirizzo IP uguale a IP_D ?”. in data link broadcast. e i MAC la passano a livello 3. duplicandosi opportunamente quando i percorsi si dividono. indirizzo che servirà per inviare il frame che incapsulerà il pacchetto destinato all’host di destinazione. !!! Il protocollo ARP opera in questo modo: • l’host S invia a tutte le stazioni della LAN. che permette ad un host S su una certa LAN. è rappresentato dal protocollo ARP di (livello 3) del TCP/IP. In tali applicazioni un singolo pacchetto inviato dal sorgente raggiunge molti hosts destinatari. per eventuali trasmissioni successive.Utilizzo del Broadcast. di scoprire a partire dall’indirizzo IP dell’host di destinazione D (se questo sta nella stessa LAN). • solo l'host che ha quell'indirizzo IP IP_D risponde. • tutti gli host ricevono la risposta. che sono sfruttati ad es. multicast e broadcast sono implementati sia a livello MAC che a livello IP. in applicazioni di videoconferenza. inserendo nella risposta il proprio indirizzo MAC. Un’applicazione importantissima del meccanismo di broadcast realizzato a livello MAC. quali ad es. 38 . I concetti di indirizzi single. • l'host S mantiene in memoria il MAC address di D per alcuni minuti. • quando S riceve la risposta. sa a che MAC address deve inviare il pacchetto. MBone. A livello IP il multicast è utilizzato per realizzare con semplicità dei flussi di comunicazione punto-multipunto.

Notare come ogni livello specifichi due indirizzi di quel livello. un mittente (*-SSAP. •Ogni livello LLC può gestire un solo livello MAC: questo significa che un livello LLC non può avere funzionalità di "relaying" (non può inoltrare pacchetti) tra più MAC. 39 . S come Source) ed un destinatario (*-DSAP. •Il livello MAC è implementato nell'hardware della scheda di rete locale. In figura vediamo le relazioni tra le Protocol Data Unit di livello 3 (Network). mentre il livello LLC è di solito realizzato in software.Relazione tra il Livello 3. Su tale livello MAC si appoggia un livello LLC. Qualche considerazione: •Ogni interfaccia di rete locale è gestita da un suo livello MAC. Logical Link Control e Media Access Control. incapsulate una dentro l’altra. le LLC-PDU e le MAC-PDU. •Tale funzionalità di instradamento dei pacchetti è delegata al livello 3. D come Destination) per i pacchetti/frame passati al livello inferiore.

e di IRUQLUH XQ VXSSRUWR VWDQGDUG DOOD FRQYLYHQ]D GL SL SURWRFROOL GL OLYHOOR VXSHULRUH VXOOD VWHVVD /$1.LLC: Logical Link Control LLC ha lo scopo di fornire un’interfaccia unificata con il livello network. Destination Address Source Address Control L3-Information La PDU di LLC Supporto multiprotocollo offerto da LLC 40 . e TXHVWL LQGLUL]]L UDSSUHVHQWDQR JOL LGHQWLILFDWRUL GHO SURWRFROOR GL OLYHOOR VXSHULRUH D FXL LO OLYHOOR //& GHYH FRQVHJQDUH LO SDFFKHWWR FKH JOL q DUULYDWR [o da cui il pacchetto è arrivato] . Proprio per questo scopo la Protocol Data Unit di LLC contiene anch’essa due indirizzi. una destinazione e una sorgente.

Ciò può essere utile per effettuare dei test sulla rete. Le opzioni di Instradamento di Provenienza. indica il numero massimo di hop che vuole memorizzare e crea spazio sufficiente nel campo opzioni per memorizzare tali hop. ma consente 53 di attraversare più reti tra due indirizzi consecutivi. la prima. Essenzialmente le opzioni sono classificabili in 3 categorie: ) 2S]LRQH GL UHJLVWUD]LRQH GHO SHUFRUVR. detta LQVWUDGDPHQWR GL SURYHQLHQ]D VHYHUR specifica una sequenza di salti consecutivi. Evitiamo di addentrarci su come sono organizzati i sottocampi del campo Opzioni IP.HEADER IPv4 (5) Il campo 2SWLRQV non è necessario in tutti i datagram IP. • Quando il trasmettitore setta l’opzione di registrazione del percorso.QVWUDGDPHQWR GL 3URYHQLHQ]D. per implementare l’applicazione detta “traceroute” che visualizza i router toccati da un pacchetto. Esistono due modalità per l’instradamento di provenienza. ogni router toccato dal datagram IP aggiunge il proprio indirizzo IP alla lista di registrazione del percorso. e causa errore se due router non sono consecutivi nella rete. cioè non stanno sulla stessa rete fisica o se il router non può seguire quel percorso. •Questa opzione viene utilizzata ad es. il protocollo IP. può estrarre la lista dei router toccati dal pacchetto. Le opzioni sono utilizzate allo scopo di testare la funzionalità della rete sottostante. ) 2S]LRQL GL . anche se i router normalmente sceglierebbero un percorso diverso. • Quando il pacchetto IP giunge alla destinazione finale. consentono al trasmettitore di imporre ad un pacchetto IP un certo percorso attraverso la rete. 32 bit per ogni indirizzo IP da memorizzare. nel qual caso il router si limita ad inoltrare il messaggio. . e analizziamone soltanto le funzionalità previste. La seconda detta LQVWUDGDPHQWR GL SURYHQLHQ]D SHUPLVVLYR specifica una sequenza di indirizzi IP. • Quando il pacchetto IP viaggia per la rete. almeno fino a che tale lista non è piena. se vuole. Naturalmente per imporre l’instradamento è necessario conoscere la topologia della rete.

perchè essendo ogni frammento gestito separatamente. si vuole che l’opzione sia copiata in uno solo dei frammenti. • (ODERUD]LRQH GHOOH 2S]LRQL GXUDQWH OD IUDPPHQWD]LRQH. espresso secondo l’ora del meridiano di Greenwich. che l’opzione deve essere copiata in tutti gli eventuali frammenti del pacchetto IP. Si avrebbero così più liste di registrazione del percorso potenzialmente diverse. Questo diverso comportamento viene configurato in modo diverso per le diverse Opzioni IP. ma aggiunge all’indirizzo IP di ogni router attraversato anche la data e l’ora in cui il router gestisce il datagram IP. • Al contrario. e l’opzione copiata in uno solo dei frammenti. viene identificata mediante un byte nel campo Opzioni. e quindi il flag COPIA viene posto ad uno. 54 . Il flag COPIA viene perciò posto a zero. • Per l’opzione di registrazione del percorso. potrebbe seguire percorsi diversi verso la destinazione. quando posto ad 1. In caso contrario l’opzione verrà copiata solo in uno dei frammenti.HEADER IPv4 (6) ) 2S]LRQH GL FRQWUDVVHJQR WHPSRUDOH E’ simile all’opzione di registrazione del percorso. Il primo bit (detto bit COPIA) di questo byte stabilisce. Ciascuna Opzione IP. per l’opzione di instradamento di provenienza si vuole che tutti i frammenti seguano lo stesso percorso.

3759 frammentazione iniziale DATI: 430x8=3440 4 5 999 254 17 130.204 137.204.204.2. inviato da S=130.136.49 2960 .72.2.2.136.72.136. tranne uno che ha MTU pari a 500 byte pacchetti invariati.Esempio di Frammentazione 4 1 UDP Un datagram di 3760 byte.204 137.204.136.204.204.136.204.49 DATI: 2960.204 a D=137.72.2959 999 255 17 185x8=1480 0 0 1479 1480 1480 1479 1479 1480 2959 2960 0 0 1480 1480 800 S 130 .204.3439 340 0 430 chsum5 17 1 370 5 500 MTU 1500 MTU 500 MTU 1500 4 5 999 255 17 UDP 0 riassemblaggio dei frammenti 370 chsum3 130.72. tutti i tratti di rete hanno MTU di 1500 byte.49 .204 137.2.49 1480 1480 480 320 MTU 1 500 4 999 254 chsum4 130.204 820 370x8=2960 D 137. tranne TTL decrementato 5 999 0x8=0 255 17 130.2.2.3759 frammentazione del frammento 49b 3759 .72.204 137.2.204 137.49 DATI: 4 5 1 UDP 1500 0 chsum1 0 -1479 1500 185 chsum2 2959 2960 2960 3439 3439 3440 3759 3759 MTU 1500 MTU 1500 130.49 DATI: 3440 .49 DATI: 1480 .136.136.72.72.

l’RFC 791 specifica il protocollo IP. Si tratta di un protocollo semplice. Ad es. • VHQ]D FRQQHVVLRQH: ogni pacchetto viene trattato in maniera indipendente dagli altri. ma il protocollo IP non informerà nè il trasmettitore nè il ricevitore. pacchetti diversi aventi stesso mittente e stesso destinatario possono seguire percorsi diversi. che si posizionano nello stack dei protocolli a partire dal livello 3 (network). di tipo datagram ovvero VHQ]D FRQQHVVLRQH e QRQ DIILGDELOH. Se le risorse della rete lo consentono il pacchetto viene portato a destinazione. Il protocollo IP (versione 4) L’architettura TCP/IP (il cui nome più preciso è . alcuni possono essere consegnati ed altri no. • QRQ DIILGDELOH: il pacchetto inviato può essere perso.QWHUQHW 3URWRFRO) è il protocollo principale del livello 3 dell’architettura TCP/IP. in caso contrario 42 verrà scartato. I protocolli appartenenti a questa architettura sono specificati tramite standard denominati RFC (Request For Comments) disponibili in rete. . Il protocollo IP (. duplicato ritardato o consegnato fuori sequenza.QWHUQHW 3URWRFRO 6XLWH) è formata da diversi componenti.Il livello Network del TCP/IP.

S. Hinden. che IPv6 vuole correggere: • QXPHUR GHJOL LQGLUL]]L . che però non viene utilizzato. • VLFXUH]]D: in IPv6 saranno rese standard e disponibili alcune primitive per l’autenticazione e la cifratura dei dati. IP versione 4 versione soffre di almeno 3 problemi principali. Version 6 (IPv6) Specification. December 1995 R. Esistono a tuttoggi. che rappresenta ancora lo standard più diffuso. Si vuole anche gestire la comunicazione con un meccanismo simile ad un protocollo con connessione. nota col nome di IP versione 6 (RFC 1883: Internet Protocol. Nel seguito parleremo di IPv4. Nonostante queste prospettive. solo delle LVROH in cui si parla IPv6. in un oceano IPv4. ma già dal 1995 è stata proposta una nuova versione del protocollo IP.Il protocollo IP versione 6 Attualmente lo standard per il livello network dello stack TCP/IP è rappresentato dal protocollo IP versione 4.3 GLVSRQLELOL RUPDL LQVXIILFLHQWH: gli indirizzi IP sono composti da 4 byte (32 bit) e a causa del grande incremento del numero degli hosts nel mondo la disponibilità degli indirizzi è in forte calo. cioè implementando un flusso di dati. Deering ). e rimane ancora a livello di sperimentazione. con un grande dispendio di denaro. Il protocollo IPv6 prevede invece indirizzi formati da 16 byte (128 bit) e quindi rende disponibili un numero enorme di indirizzi. • WUDIILFR JHVWLWR HVFOXVLYDPHQWH LQ PRGR EHVWHIIRUW: in IPv4 tutti i pacchetti sono trattati allo stesso modo dai router. il protocollo IP versione 6 è ancora poco diffuso. 43 . anche se esiste nell’header dei pacchetti IPv4 un campo priorità. Con IPv6 si vuole definire delle classi di servizio a cui assegnare priorità diverse. forse perchè l’adozione del nuovo protocollo costringerebbe a modificare fortemente gli apparati di rete esistenti.

3. che può essere diverso dall’ordine 44 in cui sono partiti. cioè anche se non effettua un servizio di instradamento per pacchetti destinati ad altri hosts. ULDVVHPEOD L IUDPPHQWL ricostruendo i pacchetti originali. DJJLXQJHQGRYL XQ SURSULR KHDGHU (o intestazione). • ULFHYH L GDWL (una sequenza di PDU) GDO OLYHOOR WUDVSRUWR (4). Un host può comunque disporre di più schede di rete anche senza essere un router. detto LQGLUL]]R .Funzioni del protocollo IPv4 Il protocollo IP svolge le seguenti funzioni: • GLVWLQJXH ogni hosts. Un indirizzo IP di tipo single o XQLFDVW identifica un unico host. ma uno stesso host può avere più indirizzi IP unicast. perchè dovendo fungere da centri di smistamento dispongono di più schede di rete. • HIIHWWXD OD ULOHYD]LRQH. per inserirli nei frame di livello 2. . Si parla allora di MultiHomed Systems. • DOOD GHVWLQD]LRQH. non la correzione. • FRQVHJQD DO OLYHOOR WUDVSRUWR L GDWL QHOO¶RUGLQH LQ FXL VRQR DUULYDWL D GHVWLQD]LRQH. ma dovrà prevedere una politica che definisca quale scheda di rete utilizzare per inviare i dati. o meglio RJQL VFKHGD GL UHWH PHGLDQWH XQ LGHQWLILFDWRUH. • HVWUDH GDL SDFFKHWWL L GDWL (PDU) GHO OLYHOOR WUDVSRUWR. • LQFDSVXOD FLDVFXQD 3'8 ULFHYXWD LQ SDFFKHWWL di dimensione massima 64 Kbyte (normalmente circa 1500 byte). Ad es. i router hanno più indirizzi. • LQVWUDGD L SDFFKHWWL sulla rete. header del Datagram IP area dati del Datagram IP (PDU del livello trasporto) • eventualmente IUDPPHQWD L SDFFKHWWL all’inizio o durante il trasporto. GHJOL HUURUL. tanti quante sono le schede di rete che possiede. se necessario.

L’header IP ha: • una prima parte fissa di 20 bye. Se HLEN vale 7 l’header è lungo 4*7=28 byte. ad es. 4 4 8 1 1 1 header del Datagram IP area dati del Datagram IP (PDU del livello trasporto) byte 0-3 byte 4-7 byte 8-11 byte 12-15 byte 16-19 byte .. • Il campo +/(1 (anch’esso di 4 bit) indica la lunghezza dell’Header IP misurata in ZRUG GL  E\WH.. . il passaggio da IPv4 (Version=4) ad IPv6 (version=6) il ricevente capisce da questo campo come deve trattare il pacchetto. di lunghezza variabile. che rappresenta la porzione di dati del livello trasporto da trasferire. di lunghezza variabile. Tutti i campi dell’header hanno lunghezza fissa.Il pacchetto IPv4: lo HEADER Un pacchetto IP è costituito da un header e da una parte dati. tranne il campo Options.. opzionale. Se lo standard cambia. Serve al ricevente per capire il formato del pacchetto che stà ricevendo. ed il corrispondente campo PAD (Riempimento) che serve a 45 rendere l’header lungo un multiplo di 4 byte. Version HLEN Type of service Identification Time to live Protocol Source IP address D M F F Total length Fragment offset Header checksum Destination IP address Options Pad (Riempimento) • Il campo 9HUVLRQ (di 4 bit) indica la versione del protocollo IP che ha generato il pacchetto. ma sempre multiplo di 4 byte 32 bit ed è strutturato come segue.. • una seconda parte.

Quindi se Total Length = 50 il pacchetto IP è lungo 50 byte. . il router potrebbe decidere di instradarlo su un percorso attraverso una rete ATM a cui chiedere garanzie sul ritardo massimo piuttosto che attraverso una rete con servizio di tipo Best Effort che non può offrire garanzie.3 GRYUHEEH VSHULPHQWDUH. • I bit 6 e 7 non sono usati. Di solito però i router non tengono conto delle preferenze espresse mediante il campo Type of Service. al crescere del quale cresce l’importanza del pacchetto IP. Attualmente si studiano delle politiche (note come 'LIIHUHQWLDWHG 6HUYLFHV) che cercano di realizzare routing e buffering basandosi sulla classificazione 46 delle applicazioni proprio in base a indicazioni di questo tipo. e l’instradamento. T settato chiede un elevato throughtput (ampia banda di trasmissione). e comprende sia l’header che il campo dati. Il router dovrebbe basarsi anche su queste indicazioni per decidere la precedenza dei pacchetti nelle sue code. e quindi maggiore importanza. Il valore 0 indica precedenza normale. • I bit 3 (D=Delay).HEADER IPv4 (2) • Il campo 7RWDO /HQJWK (di 16 bit) indica la lunghezza totale del pacchetto IP. 4 (T=Throughput) e 5 (R=Reliability) indicano il tipo di trasporto preferito: D settato (D=1) indica richiesta di basso ritardo. Poichè il campo è lungo 16 bit la massima lunghezza possibile per un pacchetto IP è di 216-1=65535 byte. HVSUHVVD LQ E\WH. il valore 7 un pacchetto di controllo della rete. Ad es. 0 1 2 3 4 5 6 7 Precedenza D T R Non Usati • I primi 3 bit definiscono un campo 3UHFHGHQ]D con valori da 0 a 7. • Il campo 7\SH RI 6HUYLFH (di 8 bit) rappresenta un’LQGLFD]LRQH DL URXWHU VXOOD TXDOLWj GHO WUDVSRUWR FKH SRVVLELOPHQWH LO SDFFKHWWR . se il pacchetto richiede un certo ritardo massimo. R settato indica richiesta di massima affidabilità.

La dimensione massima di dati di livello 3 che possono essere trasportati in un frame del Data Link viene chiamata 0DVVLPD 8QLWj GL 7UDVIHULPHQWR (078: 0D[LPXP 7UDQVIHU 8QLW). il datagram IP potrà essere inserito completamente in un frame di livello 2 e inviato a destinazione. ed è caratteristico di ogni tipologia di rete. Nell’esempio un pacchetto IP con 3260 byte di dati frammentato per una MTU di 1500 byte. e ciascun frammento dovrà essere inserito in un frame diverso e verrà spedito separatamente dagli altri verso la destinazione finale. Header Datagram IP Area Dati Datagram IP Header Frame Area Dati Frame • Se invece la porzione dati del datagram IP ( più la dimensione dell’header IP) è più grande della MTU della rete sottostante. dove il protocollo IP provvederà a rimettere assieme i diversi frammenti e a ricostituire il datagram originale. OD SRU]LRQH GDWL dovrà essere spezzata in più pezzi che verranno incapsulati in datagram IP (detti frammenti) più piccoli della MTU. header datagram IP (20 byte) header frammento 1 (20 byte) header frammento 2 (20 byte) header frammento 3 (20 byte) Dati 1 (1480 byte) Dati 1 (1480 byte) Dati 2 (1480 byte) Dati 3 (300 byte) Dati 2 (1480 byte) Dati 3 (300 byte) frammento 1 (offset 0) (MF=1) frammento 1 (offset 1480)( MF=1) frammento 3 (offset 2960) (MF=0) 47 . Per Ethernet MTU=1500 bytes. e quindi anche alla quantità di dati di livello 3 che possono essere trasportati in un unico frame a livello 2.La Frammentazione dei pacchetti IP L’hardware di ogni tipo di rete impone un limite superiore alla dimensione del frame di livello 2. • Se la porzione dati di un datagram IP (più la dimensione dell’header IP) è più piccola della MTU della rete sottostante.

)UDJPHQW 2IIVHW (15 bit) e il flag 0RUH )UDJPHQW (0)). Il protocollo IP usa tre campi dell’header per controllare il meccanismo della frammentazione e permettere il riassemblaggio dei datagram frammentati. L’ultimo pezzo in genere sarà il più corto e verrà identificato come ultimo settandovi a zero il flag 0RUH)UDJPHQW. perchè così è definito il campo offset dell’header IP. Il router preleva allora la porzione dati del datagram IP e lo spezza in più porzioni. 32 bit 4 4 8 1 1 1 Version HLEN Type of service Identification Time to live Protocol Source IP address D M F F Total length Fragment offset Header checksum Destination IP address Options Pad (Riempimento) 48 . e in modo che ogni frammento dei dati. sempre se la dimensione dei pacchetti IP è maggiore della MTU più piccola. in modo che ciascuna (aggiungendovi l’header) stia in un frame. si deve attraversare una porzione di rete avente una MTU minore della porzione di rete precedentemente attraversata. abbia dimensione multipla di 8 byte. ad indicare che è l’ultimo frammento. tranne l’ultimo. Questi campi sono .GHQWLILFDWLRn (16 bit). Negli altri frammenti 0) è settato a 1.La Frammentazione dei pacchetti IP il problema della frammentazione si propone ogni volta che nel percorso seguito dai pacchetti IP.

Tale offset è pensato come un multiplo di 8 byte. 185. 49 . Identification) rende univocamente identificabile un certo datagram IP. Se l’offset indica ad es. e tutti i suoi frammenti.GHQWLILFDWLRQ) del datagram originale.La Frammentazione dei pacchetti IP /¶KHDGHU GHO GDWDJUDP RULJLQDOH YHUUj FRSLDWR LQWHJUDOPHQWH QHL IUDPPHQWL (con qualche modifica per il campo Options) e in più verrà cambiato il campo )UDJPHQW 2IIVHW FKH LQGLFD LO SXQWR GHOO¶DUHD GDWL GHO GDWDJUDP RULJLQDOH LQ FXL FRPLQFLD OD SRU]LRQH GL GDWL WUDVSRUWDWD QHO IUDPPHQWR. • 7XWWL L IUDPPHQWL sono caratterizzati dall’avere lo stesso identificatore (il campo . e la coppia (IP Provenienza. il frammento porta la porzione di dati che inizia nella posizione 185*8=1480 byte. Tale numero è assegnato univocamente dal trasmettitore (che mantiene un contatore dei datagram IP trasmessi).

ma ha il flag 0RUH )UDJPHQW VHWWDWR DG XQR (è il primo frammento). Solo alla fine del viaggio avrà luogo il riassemblaggio dei frammenti. è impossibile ricostruire il datagram IP originale. Per evitare di aspettare inutilmente un frammento perso. Il ricevente non conosce la dimensione del datagram originale perchè ogni frammento mantiene nel campo Total Length la lunghezza del frammento stesso. . • il pacchetto IP ricevuto ha un RIIVHW GLYHUVR GD ]HUR (è un frammento successivo). e non quella del datagram originale. Se un solo frammento viene perso.3 WUDVPHWWLWRUH . Se il timer scade prima che tutti i frammenti siano giunti a 50 destinazione il ricevitore butta via tutti i frammenti. Se il More Fragment è zero è l’ultimo frammento. si potrà capire la dimensione totale del datagram originale sommando all’offset dell’ultimo frammento la lunghezza dei dati trasportati nell’ultimo frammento. ogni frammento viaggia separatamente dagli altri fino alla destinazione finale.GHQWLILFDWLRQ GHO GDWDJUDP). il ricevitore nel momento in cui riceve un primo frammento inizializza un timer. Il protocollo IP del ricevente identifica univocamente i frammenti di uno stesso datagram mediante la coppia (.Il Riassemblaggio dei Frammenti Dopo la frammentazione. Solo quando riceverà il frammento con il flag 0RUH )UDJPHQW VHWWDWR D ]HUR(che indica l’ultimo frammento del datagram originale). nel tentativo di ricostruire il datagram Originale. Il ricevitore riconosce di avere ricevuto un frammento e non un datagram intero in due modi: • il pacchetto IP ricevuto ha un RIIVHW XJXDOH D ]HUR.

Quando il contatore raggiunge il valore zero. in secondi. i pacchetti IP non possono viaggiare in eterno nella rete anche se per un errore i router li instradano in un percorso ciclico. e si evitano così congestioni nella rete. tutti i suoi frammenti vengono incapsulati con il TTL residuo del pacchetto. ovvero indica qual’è il protocollo di livello 4 (o 3) che ha generato i dati trasportati dal pacchetto IP.25 AX. tunneling) •6 TCP Transmission Control Protocol •17 UDP User Datagram Protocol •29 ISO-TP4 ISO Transport Protocol Class 4 51 •93 AX. -Quando il pacchetto deve essere spedito si calcola il tempo (in secondi) trascorso in coda e si decrementa di questo tempo il contatore TTL. Ogni volta che un router processa quel pacchetto IP. In tal modo il livello Network sa a quale protocollo dovrà consegnare i dati trasportati. che un pacchetto IP può rimanere all’interno di una rete prima di essere scartato. salva il valore corrente dell’orologio. Tra i protocolli che possono essere trasportati nell’area dati ricordiamo: •0 ICMP Internet Control Message Protocol •4 IP IP in IP (incapsulamento.25 frames . di 8 bit) indica in modo approssimato il tempo. -Quando un pacchetto IP viene frammentato durante il percorso.HEADER IPv4 (continua 3) • Il campo 7LPH 7R /LYH (TTL. tempo di vita. -Per ovviare al problema della congestione della rete che causa lunghe attee in coda. il router scarta il pacchetto. quando il router riceve un pacchetto e lo mette in coda in attesa per spedirlo. -Grazie al contatore TTL. • Il campo 3URWRFRO (di 8 bit) indica di quale tipo è il dato trasportato nell’area dati del pacchetto IP. -Tale valore viene inizializzato a 255 dall’host che spedisce il pacchetto IP. decrementa di una unità questo contatore.

che di solito è più piccolo dei dati trasportati. e comunque venga frammentato. . lo svantaggio di avere a livello IP una checksum solo per l’header IP impedisce al livello 3 di accorgersi di eventuali errori sui dati di livello 4. Però il livello 2 potrebbe avere già riconosciuto un errore e scartato il frame. Viene codificato utilizzando i byte del solo header. perchè devono processare solo l’header. viene introdotto ove necessario un ultimo campo 3DGGLQJ di riempimento. • i router diminuiscono il tempo necessario a calcolare la checksum. la checksum risulta diversa e il protocollo IP capisce che c’è stato un errore. è di lunghezza variabile. fino a che tali dati non sono giunti a destinazione finale.3 DGGUHVV contengono gli indirizzi IPv4 a 32 bit della provenienza originale e della destinazione finale del datagramma IP. Il YDQWDJJLR GL DYHUH XQD FKHFNVXP VHSDUDWD SHU KHDGHU e dati è che: • i protocolli di livello superiore possono scegliere una loro codifica per il controllo degli errori. • I campi 6RXUFH . e poichè l’header IP deve avere lunghezza pari ad un multiplo di 4 byte. Notare che la checksum verifica l’integrità del solo header. 52 per arrivare alla giusta lunghezza.3 DGGUHVV e 'HVWLQDWLRQ .HEADER IPv4 (4) • Il campo +HDGHU &KHFNVXP (di 16 bit) serve a verificare che OR KHDGHU . • Il campo 2SWLRQV. Tali indirizzi quindi non cambiano mai durante tutto il percorso. Di contro. e solo allora il protocollo di livello 4 effettuerà il controllo sui dati con delle proprie checksum.3 VLD DUULYDWR LQWHJUR D GHVWLQD]LRQH. Se durante il trasporto l’header subisce delle modifiche. comunque venga instradato il pacchetto. e non dei dati trasportati.

servono ad individuare singole interfacce di rete e differiscono per il numero di host che ciascuna rete può indirizzare. mentre le altre due classi D ed E sono utilizzate per servizi assai differenti. le reti dell’ Università di Bologna (130.INDIRIZZAMENTO IP Gli indirizzi IP.* ) sono di classe B. di cui le prime 3. •Gli indirizzi IP sono suddivisi in cinque classi. . 137. denominate A. Quindi se un nodo ha tre interfacce. Ad es. •Si noti che non sono gli hosts ad avere un indirizzo IP. B e C.Classe A.* . la seconda (se presente) quello della sottorete (subnet) e la terza quello dell'host. I bit che indicano la rete sono 7 e quelli che indicano l'host 24. parlando di indirizzo IP di un host si fa riferimento all’ indirizzo della sola interfaccia di rete presente. Gli indirizzi di classe A sono riconoscibili in quanto il il bit più significativo del primo byte è posto a zero. sono lunghi 32 bit (quattro byte) e sono tradizionalmente visualizzati scrivendo i valori decimali di ciascun byte separati dal carattere punto. e quindi il primo campo dell'indirizzo è compreso tra 0 e 127.*.*. Quindi si possono avere al massimo 128 reti di classe A. 55 . ciascuna con una dimensione massima di circa 16 milioni di indirizzi. esso ha tre indirizzi IP. Poichè la maggior parte degli hosts ha una sola interfaccia di rete.204. bensì le interfacce di rete. •Gli indirizzi IP comprendono due o tre parti. La prima parte indica l'indirizzo della rete (network). che devono essere univoci sulla rete.136. Sono concepiti per poche reti di dimensioni molto grandi.

. ciascuna con circa 64000 indirizzi.Classe C. .INDIRIZZAMENTO IP (2) .Classe E. Gli indirizzi di classe C hanno i primi 3 bit settati a 110 e quindi il primo campo dell'indirizzo è compreso tra 192 e 223. Si possono avere circa 16000 reti di classe B. Sono indirizzi usati per applicazioni di multicast . e quindi il primo campo dell'indirizzo è compreso tra 240 e 255.Classe B. . I bit che indicano la rete sono 21 e quelli che indicano l'host 8. Questi indirizzi sono riservati per usi futuri. Gli indirizzi di classe D si riconoscono perchè i primi 4 bit del primo byte sono settati a 1110. ciascuna con una dimensione massima di 256 indirizzi. quindi il primo campo dell'indirizzo è compreso tra 128 e 191. 56 .Classe D. Sono pensati per un numero medio reti di dimensioni medio-grandi. e quindi il primo campo dell'indirizzo è compreso tra 224 e 239. Gli indirizzi di classe B si riconosconno perchè i 2 bit più significativi del primo byte sono posti a 10. Gli indirizzi di classe E hanno i 4 bit più significativi settati a 1111. Sono concepiti per identificare molte reti di dimensioni piccole. Quindi si possono avere al massimo 2 milioni di reti di classe C. I bit che indicano la rete sono 14 e quelli che indicano l'host 16.

0) broadcast su questa network 0 1 2 7 8 tutti 1 31 (non ammesso come indi_ rizzo di provenienza) (suggerimento: provare il comando ping 255.204) 0 1 2 Broadcast per la rete specificata (non ammesso come indi_ rizzo di provenienza) 15 16 rete tutti 1 31 0 1 2 loopback 127 7 8 qualunque numero 31 (suggerimento:staccare il cavo di rete e provare il comando ping 127.5.7) Quando si utilizza il loopback.0. 31 (suggerimento: provare su s.0.6.o. .INDIRIZZAMENTO IP 0 1 2 questo host 7 8 tutti 0 (3) Esistono inoltre indirizzi IP con significato particolare. il pacchetto non viene inviato sulla rete ma viene passato ai moduli IP di ricezione.255) 0 1 2 l’ host specificato in questa rete tutti 0 15 16 host 31 (non ammesso come indi_ rizzo di destinazione) (serve solo all’ avviamento) (suggerimento: provare il comando ping 0. Linux il comando ping 0. ed elaborato come se fosse in arrivo: ciò serve ad es. per effettuare localmente 57 dei test su un software di rete in fase di sviluppo.0. ad esempio per gli indirizzi di broadcast e per il loopback.255.255.0.

L’ ORDINE dei BYTE in RETE E’necessario creare un insieme di protocolli di rete che non dipendano dall’ architettura del computer o della scheda di rete. cioè col byte più significativo più vicino all’ inizio del pacchetto. Le librerie socket forniscono per le conversioni delle funzioni inizio fine pacchetto pacchetto che sono: ntohs. ovvero per evitare che un indirizzo IP possa venire scritto in due modi diversi. prima di scrivere ad es. byte 3 byte 2 byte 1 byte 0 58 direzione di trasmissione htons. prima di valutare l’ indirizzo IP contenuto nel campo provenienza del pacchetto IP. . ovviamente non per la parte dati.ntohl. In particolare gli interi di 32 bit (le dimensioni dell’ indirizzo IP) sono memorizzati in due modi diversi: le macchine Little Endian mantengono il byte meno significativo nell’ indirizzo di memoria più basso. un’ indirizzo IP nel campo destinazione del pacchetto IP. le macchine Big Endian mantengono il byte più significativo Big Endian byte nell’ indirizzo di memoria più basso. •In ricezione. deve convertire tale campo dal formato di rete standard nel formato interno della macchina. Non tutte le architetture di calcolatori memorizzano i dati nello stesso modo. quello che prevede che i byte più significativi siano trasmessi per primi (stile Big Endian). address byte 3 byte 2 byte 1 byte 0 •Per evitare confusione. Guardando viaggiare i dati da una macchina all’ altra. i protocolli TCP/IP definiscono un ordine di byte standard della rete. l’ host deve convertire il numero IP dalla sua propria rappresentazione alla rappresentazione standard per la rete.htonl. un intero a 32 bit comincia ad essere trasmesso dal byte più significativo. che tutte le macchine devono usare per i campi dei protocolli IP. •In trasmissione. •Internet stabilisce come ordine standard per gli interi a 32 bit.

•Se la destinazione sta sulla stessa sottorete dell’ host. e lo invia in rete. •Se invece la destinazione non sta sulla stessa sottorete si usa la consegna indiretta. e lo invia in rete. cioè l’ host deve inviare il pacchetto IP al router di default. vede il proprio indirizzo MAC_D e carica il pacchetto passandolo al livello IP ed il gioco è fatto. estrae il pacchetto IP. L’ host cerca (ancora con il protocollo ARP) l’ indirizzo di livello 2 del default router (MAC_R). Il router vede passare il pacchetto con il proprio MAC address. incapsula il pacchetto IP in un pacchetto di livello 2 con quell’ indirizzo MAC_D come destinazione. vede che l’ indirizzo IP_D non è 59 il suo e lo instrada con lo stesso meccanismo già visto. ovvero l’ instradamento di un pacchetto IP verso la destinazione specificata nell’ header del pacchetto IP viene effettuato secondo le seguenti modalità: •Un certo host deve inviare un pacchetto IP verso una destinazione IP_D (oppure un router ha ricevuto un pacchetto e deve instradarlo verso la destinazione IP_D). lo carica. incapsula il pacchetto IP in un pacchetto di livello 2 con quell’ indirizzo MAC_R come destinazione. si usa la consegna diretta. . •La destinazione MAC_D a livello 2 vede passare il pacchetto. •l’ host controlla l’ indirizzo IP di destinazione per capire se l’ indirizzo IP_D appartiene alla sua stessa sottorete cioè se la destinazione è raggiungibile in modo diretto mediante il MAC address cioè sfruttando il solo livello 2.Approccio al ROUTING IP (1) Il routing IP. cioè l’ host trova (col protocollo ARP) l’ indirizzo di livello 2 di destinazione (MAC_D). ovvero un router che l’ host conosce e che sta sulla stessa sottorete dell’ host. ovvero se per raggiungere la destinazione non è necessario attraversare router (livello 3) ma al massimo si attraversano dei bridge (vedi subnetting).

3.136.10 MAC_D 60 . e non gli indirizzi IP S cagnina 130.203 bridge (quindi livello 2).2.136.204 sangiovese D 130.3. instrada i pacchetti tramite il MAC address Consegna Indiretta (via IP address) Sorgente S e destinazione D non stanno sulla stessa sottorete in mezzo c’ e’un router.8 MAC_S ROUTER 130.unibo.3.136. invisibile al livello IP.252 130.3.204 BRIDGE sangiovese D 130.3.Approccio al ROUTING IP (2) Consegna Diretta (via MAC address) Sorgente S e destinazione D appartengono alla stessa sottorete anche se in mezzo c’ e’un bridge. perchè lavorando a livello 2 instrada i pacchetti mediante i MAC address.3.136.203 S cagnina 130. che lavora a livello 3.it 130.cs.2.136.253 MAC_R3 MAC_R2 D sarastro. necessario routing a livello IP S cagnina 130.136.136.136.

0 (o in esadecimale ffffff00). a seconda della sua topologia. configurato uguale per tutti gli host della stessa subnet. la subnet e l’ host. •Però una rete fisica. e a zero in corrispondenza del campo host. si possono ottenere molte subnet contenenti ciascuna pochi host. può essere intrinsecamente costituita da più parti. •Il meccanismo consiste nel considerare la parte host del’ indirizzo IP di un host. come formato da due parti. indica che il campo host coincide con l’ ultimo byte dell’ indirizzo IP 61 . •L’ ampiezza dei campi subnet e host viene definita mediante un parametro. A seconda dell’ ampiezza del campo dedicato alla subnet. connesse in varia maniera. più comunemente scritta come indirizzo IP 255. o poche subnet contenenti molti hosts. detto netmask. Ad es. La netmask contiene i bit ad uno in corrispondenza dei campi network e subnet dell’ indirizzo IP. •Si è cercato di indirizzare ciascuna di queste porzioni di rete in modo separato. come appartenenti ad una stessa rete.255. •Il meccanismo del subneting serve proprio a descrivere la situazione in cui alcuni host sono in grado di comunicare tra loro in modo diretto via livello 2. una netmask 11111111111111111111111100000000. e si dice allora che gli host appartengono alla stessa sottorete.255. e di criteri di opportunità vari. senza essere costretti ad utilizzare per ciascuna di esse una diversa rete (non sono poi così tante le reti disponibili). ciascuna delle quali costituisce una rete broadcast a livello 2. della disposizione degli apparati di routing e bridging.SUBNET IP (1) •Un certo indirizzo di rete di classe A. B o C permette di mappare un certo numero di hosts. in modo da dar loro la possibilità di scambiarsi direttamente i pacchetti IP senza utilizzare un router.

136.3.3.3.255. Ad es. .136. altrimenti stanno su sottoreti diverse. Dato un indirizzo IP di un host.* con netmask 255.0 . dato un altro indirizzo IP (ad.136.*.255. ottenendo due valori che sono la parte network e subnet dei due indirizzi. es. i due indirizzi 130.0. quindi i due IP sono nella stessa subnet. 012 78 15 16 23 24 31 SUBNET IP 10 network 012 78 (2) indirizzo di classe B host 15 16 23 24 31 netmask 11111111111111111111111100000000 netmask 012 10 78 15 16 23 24 31 subnetting network subnet host Con il subnetting dell’ esempio qui sopra. allora i due indirizzi IP stanno sulla stessa sottorete. ogni indirizzo viene messo in AND bit a bit con la netmask.255. di un host a cui il primo deve spedire un pacchetto) per capire se i due indirizzi appartengono alla stessa sottorete.255.204 e 130. a partire da uno stesso indirizzo di classe B. e la netmask per la sua sottorete. in modo che la parte host viene partizionata in una parte subnet ed una host di 8 bit ciascuna. Se questi due valori sono uguali. ciascuna con 254 indirizzi disponibili per gli host (256 -broadcast(1) -questo(0)).. otteniamo 256 diverse sottoreti.136. a cui viene applicata una netmask 255. Algoritmo di Riconoscimento di Appartenenza a stessa subnet. perchè dall’ AND bit a bit di indirizzi e netmask si 62 ottiene 130.0. una rete (classe B) 130.203 stanno sulla stessa sotorete.Vediamo un esempio di indirizzo di una classe B.

e si permette che: •una stessa rete fisica (una LAN) (o più reti fisiche interconnesse da bridge) possa contenere più subnet IP.136.136. in tecnologia IP una subnet deve coincidere con una rete fisica (*).2.136. e quindi può coincidere anche con un insieme di più reti fisiche connesse mediante bridges (livello 2). che però non è chiaro a quale livello debbano essere collocate. (**) (**) Anche questa affermazione è messa in forse dall’ avvento di concetti quali le LAN virtuali.2.2.136. Quindi per ora ci teniamo il concetto di subnet IP come rete capace di comunicare per mezzo del 63 broadcast di livello 2.7 Bridge sarastro 130.136. o meglio deve coincidere con l’ insieme degli host che possono comunicare tra loro via broadcast di livello2 . il concetto di subnet è stato rilassato.204 130.3.253 130. Quindi.3.0 130.SUBNET IP (3) Come abbiamo visto poco fa (slide 59) l’ importanza di capire se due host stanno sulla stessa sottorete risiede nel fatto che in IP il routing minimo (consegna diretta) è realizzabile solo se i due hosts stanno sulla stessa sottorete. sangio cagnina vese 130.136.10 (*) In realtà.0 130. cioè una subnet IP non può mai attraversare dei router.136.2. .0 130.3.136.2. qualora sia necessario partizionare una rete fisica in modo logico.251 130.136.3. •Invece una subnet IP non può mai contenere più reti fisiche interconnesse da router.203 Router timur 130.

cioè hanno stesso indirizzo di rete (sia essa di classe A B o C). che l’ amministratore di rete assegna ad ogni host. 4) hanno indirizzo IP e netmask tali che l’ AND bit a bit tra indirizzo IP e netmask da’lo stesso risultato per tutti gli IP della sottorete (criterio di appartenenza ad una stessa 64 sottorete). che legge nella sua scheda di rete. che può essere assegnato e salvato su file o gli può essere assegnato al boot (per i sistemi diskless. 3) hanno avuto assegnata la stessa netmask. Tale criterio è costituito dalla cosiddetta netmask. . per permettere ad es. •un criterio per sapere quali hosts stanno nella sua sottorete.Informazioni per Routing in un Host Per come viene effettuato il routing in IP. le informazioni che un host deve conoscere per poter instradare correttamente i pacchetti che vuole spedire sono: •il proprio indirizzo MAC. La sottorete a cui un host (un IP address) appartiene è costituita da quegli host che: 1) appartengono alla stessa rete IP. a cui consegnare tutti i pacchetti IP per indirizzi che non stanno nella sua stessa sottorete. la stessa per tutti gli hosts della sottorete. •l’ indirizzo IP di un router di default. in modo da poter decidere se spedire i pacchetti direttamente (previa richiesta ARP) o se instradarli via default router. di comunicare tramite livello 2 direttamente e di effettuare la chiamata al protocollo ARP. mediante il protocollo RARP a partire dal MAC address). 2) appartengono alla stessa rete fisica (o a reti fisiche diverse ma separate solo da bridge. •il proprio indirizzo IP. che individua l’ appartenenza ad una stessa sottorete. che inoltrano via MAC address) e quindi propagano i broadcast di livello 2.

il livello IP riconosce nell’ indirizzo IP di destinazione del pacchetto IP il proprio indirizzo IP e consegna il pacchetto IP al protocollo superiore. e decide di instradarlo. ed una volta con il proprio indirizzo IP. una volta con l’ indirizzo di destinazione. •Nell’ host di destinazione.Ogni host deve mantenere le seguenti informazioni: •il proprio indirizzo MAC. il MAC address della destinazione.Un host per inviare un datagram IP. . •il proprio indirizzo IP. il prossimo router sceglie il successivo hop. toglie l’ incapsulamento e passa al livello IP. e l’ host usa l’ instradamento diretto. •Qualunque sia la destinazione a livello 2 (il router per il primo salto o l’ host destinazione in caso di consegna diretta) il MAC address verrà ottenuto con chiamata ARP o alla cache ARP. •l’ indirizzo IP di un router di default nella stessa sottorete. •In caso contrario l’ host invia il pacchetto IP (che ha nell’ header l’ indirizzo di destinazione IP) incapsulato in un frame destinato al router di default cioè avente MAC address del router sulla stessa rete. interface)) e se l’ destinazione del pacchetto sta’nella IP sottorete di una interfaccia instrada in modo diretto. •fa l’ AND bit a bit della netmask. come indirizzo di livello 2. •La destinazione a livello 2 vede transitare il proprio indirizzo MAC. consulta delle tabelle di instradamento (coppie (subnet. •Invece il router si accorge che il pacchetto IP non è per lui. altrimenti 65 sceglie (tra i router a lui direttamente collegati) quale usare per instradare il pacchetto. •la netmask della sua sottorete (riconoscere gli IP della sottorete).Il Routing in Internet Protocol --. inviando il datagram in un frame di livello 2 avente. --. •Se i due risultati sono uguali l’ indirizzo di destinazione appartiene alla sua stessa sottorete. ed il salto successivo lo deciderà il router (Next Hop). carica il frame.

I pacchetti broadcast sono invece propagati a tutti. l’ Univ. Il routing tra autonomous systems risponde più ad esigenze 66 amministrative che non tecniche. (*) In realtà la faccenda può complicarsi se si vuole minimizzare lo scambio di pacchetti in reti fisiche costituite da più sottoreti fisiche collegate da bridge. di Bologna). Prot. i cosiddetti autonomous systems (es. ed instradano i pacchetti MAC ad essi destinati solo in quella direzione.Routing Gerarchico Il concetto di subnet introduce quindi un livello di gerarchia nelle reti TCP/IP. Il routing tra le subnet. memorizzando gli indirizzi MAC contenuti nei campi provenienza dei pacchetti di livello 2 che li attraversano. Il routing si scinde in: •un routing all’ interno della subnet. Tra i più recenti ricordiamo OSPF (Open Shortest Path First) che effettua il bilanciamento di carico tra percorsi paralleli ed e’standard. In tal modo capiscono automaticamente in che direzione sono collocati gli hosts con quei MAC address. Serve solo mappare indirizzi IP in indirizzi MAC con ARP e RARP.). mediante tabelle di instradamento che possono essere scritte (in toto o in parte) manualmente dal gestore della rete o calcolate automaticamente con algoritmi dei cosiddetti tipi distance vector o link state packet. che consente la consegna diretta tra le stazioni mediante una trasmissione broadcast. •un routing tra subnet diverse ma gestite da una stessa autorità. . Il routing all’ interno della subnet è banale (* in teoria) perchè la subnet coincide con una rete fisica. cioè all’ interno di un autonomous system è gestito dai router (detti Interior Router). quali BGP (Border Gat. ed è gestito con algoritmi detti Exterior Gateway Protocol. •un routing tra diversi autonomous systems. Alcuni bridge intelligenti monitorano il traffico a livello 2.

Setup delle ConnessioniTCP Una connessione TCP viene instaurata con le seguenti fasi. Il segmento presenta inoltre nel campo Ack number il valore J+1 che indica che si aspetta di ricevere J+1. e dal sequence number ricevuto K capisce che i dati del server inizieranno da K+1. mediante le chiamate a socket. quindi risponde con un segmento ACK (flag syn settato a zero e flag ack settato a 1) con Ack number K+1.. Il segmento viene incapsulato in un datagram IP. listen e infine accept che realizza una apertura passiva (passive open) cioè senza trasmissione di dati.. 1) il server si predispone ad accettare una richiesta di connessione. e termina la connect. che formano il Three-Way Handshake (perchè formato da almeno J+3 J+2 J+1 K+1 K+2 K+3 K+. Il segmento contiene un header TCP con i numeri di porta ed eventuali opzioni su cui accordarsi. a zero il flag ack. 3) Il server deve rispondere al segmento SYN del client spedendogli un segmento SYN (flag syn settato ad 1) con il numero di sequenza iniziale (K) dei dati che il server vuole mandare al client in quella connessione. 2) il client effettua le chiamate a socket. per validare il campo Ack number.. e di solito non contiene dati. 4) il client. bind. ricevendo il SYN del server con l’Ack numer J+1 sa che la sua richiesta di connessione è stata accettata. 3 pacchetti trasmessi): J+. bind ed infine alla connect che realizza una apertura attiva (active open) mediante la spedizione di un segmento TCP detto SYN segment (synchronize) in cui è settato ad 1 il flag syn. e presenta il flag ack settato ad 1. . e che trasporta un numero di sequenza iniziale (J) che è il numero di sequenza iniziale dei dati che il client vuole mandare al server. 87 5) al ricevimento dell’ACK K+1 il server termina la accept..

si setta questa opzione che indica di considerare il campo Window Size dopo averlo shiftato a sinistra di un numero di posizione compreso tra 0 e 14. 88 . Per situazioni in cui il collegamento è a larghissima banda ma con grande ritardo di trasmissione (es. L’opzione SO_RECVBUF resa disponibile dall’interfaccia socket. ovvero in modo da raggiungere valori dell’ordine del GigaByte. L’opzione TCP_MAXSEG resa disponibile dall’interfaccia socket.Opzioni TCP nel Setup Ogni segmento di tipo SYN può contenere delle opzioni. Per identificare finestre più grandi nell’header TCP. consente di settare questa opzione. perchè il campo Window Size occupa 16 bit. la più grande dimensione di segmento che è in grado di ricevere. 2) Windows scale options: la finestra scorrevole più grande che due TCP possono concordare è 65535. che servono a stabilire alcune caratteristiche della connessione che si sta instaurando. Tra le più importanti ricordiamo: 1) MSS options: con questa opzione il TCP che manda il proprio SYN annuncia il maximum segment size. via satellite) una dimensione di finestra più grande rende più veloce la trasmissione di grandi quantità di dati. consente di settare questa opzione. in modo da moltiplicare la Window Size di un fattore fino a 2 elevato alla 14.

2) Poi il TCP passivo trasmette all’applicazione padrona di quella connessione il segnale FIN. 2) l’end system che riceve il FIN (chiamiamolo passivo) effettua la chiusura passiva (passive close) all’insaputa dell’applicazione. 89 . 2. con un numero di sequenza M pari all’ultimo dei byte trasmessi in precedenza più uno.1) (2.2) (3) (4) Una connessione TCP viene chiusa mediante un protocollo composto da quattro messaggi trasmessi: 1) una delle applicazioni su un end-system (chiamiamola attiva) effettua la chiusura attiva (active close) chiamando la funzione close() che spedisce un segmento FIN (flag FIN settato a 1). Poiche la ricezione del FIN significa che non si riceverà nessun altro dato. come riscontro per il FIN ricevuto. Con ciò si indica che viene trasmesso un ulteriore dato. che è il FIN stesso. Per convenzione il FIN è pensato avere dimensione pari ad 1 byte. sotto forma di end-of-file che viene accodato ai dati non ancora letti dall’applicazione.1) Per prima cosa il modulo TCP del passivo spedisce all’end-system attivo un segmento ACK con Ack number pari a M+1.end-system (1) attivo Terminazione delle ConnessioniTCP (1) end-system passivo (2. 2. quindi l’end-system attivo si aspetta di ricevere per il FIN un ACK con Ack Number pari a M+1. con l’end-of-file il TCP comunica all’applicazione che lo stream di input è chiuso.

1 e 2.1 è stata effettuata. poichè 1 è per convenzione la dimensione del FIN. cioè l’ultimo byte trasmesso più 1.2 il passivo ha ancora la possibilità di inviare dei dati verso l’attivo. • Notare che i due segmenti dal passivo all’attivo degli step 2. perchè al momento dello step 2. di carattere opposta alla precedente. è che tra gli step 2.2 (ACK M+1 e FIN N rispettivamente) potrebbero essere combinati in un solo messaggio a seconda del comportamento del passivo. ma solo da chi per primo effettua la chiamata alla funzione close(). La close() ordina al modulo TCP di inviare a sua volta all’end-system attivo un segmento FIN. cioè il numero di sequenza del successivo al FIN.3) Quando l’applicazione del passivo finalmente legge dal buffer l’end-of-file (con una read() che restituisce 0). mediante il FIN. col numero di sequenza (N) del FIN. cioè il FIN più uno. 90 . 4) il modulo TCP dell’attivo. chiusura che viene detta half-close. quando riceve il FIN spedisce un ACK con Ack number N+1. una chiusura del flusso solo nella direzione dall’attivo al passivo.1 e 2. deve effettuare per quel socket la chiamata alla funzione close(). Terminazione delle ConnessioniTCP (2) • Un’ulteriore variazione. • Chiusura attiva o passiva non dipendono dall’essere client o server. Terminato questo passo viene conclusa anche la funzione close() dell’attivo.

Il client inizia e specifica l’opzione Maximum Segment Size di 1460 byte. . Stabilita la connessione il client spedisce una richiesta al server nei dati di un solo segmento. detta piggybacking. assieme alla risposta il server spedisce nel segmento anche l’ACK per il segmento ricevuto. il server risponde e specifica una diversa richiesta di MSS di 1024. La MSS può essere diversa nelle due direzioni. Infine vengono utilizzati 93 quattro segmenti per effettuare la terminazione della connessione.Esempio di connessione TCP (1) Vediamo i segmenti scambiati e gli stati assunti da client e server in una connessione TCP in cui il client chiede un servizio ed il server risponde. Tale tecnica. viene utilizzata quando il ritardo nella risposta è inferiore ai 200 msec. Notare che. per diminuire il numero di segmenti scambiati. Il server risponde spedendo la risposta nei dati di un solo segmento.

TSAP address Liv. Questo è il livello in cui si gestisce per la prima volta (dal basso verso l'alto) una conversazione diretta. Applic. indipendentemente dalla rete utilizzata. tali servizi sono realizzati dal livello transport per mezzo dei servizi ad esso offerti dal livello network. •servizi senza connessione detti di tipo datagram offerti 67 dall’UDP (User Datagram Protocol). ce ne sono due anche a livello transport: •servizi affidabili orientati alla connessione. Transport Transport Entity TPDU Transport Entity Liv. . Liv.Il livello di Trasportodidel TCP/IP Il compito del livello transport (livello 4) è fornire un trasporto efficace dall'host di origine a quello di destinazione. offerti dal TCP (Transmission Control Protocol). Così come ci sono due tipi di servizi di livello network. cioè senza intermediari. . fra una transport entity su un host e la sua peer entity su un altro host. detti di tipo stream. Network NSAP address Naturalmente.

che identifica un servizio o un punto di accesso e/o smistamento di livello 4.unibo. informazione supplementare). occorre specificare mittente e destinatario di livello 4. •Anche se l’ indirizzo di livello trasporto è formato da questa coppia. e da un’ altra informazione che identifica un punto di accesso in quell’ host (NSAP address. come vedremo TCP e UDP contengono nei loro header solo i numeri delle porte e riutilizzano 68 gli indirizzi IP di mittente e destinaz.csr. Per questo motivo i protocolli di livello 4 tipicamente definiscono come indirizzo di livello 4 una coppia formata da un indirizzo di livello network che identifica l’ host.204.it. Infatti. detto TSAP address (Transport Service Access Point address). cioè l’ entry point per il demone telnet. in TCP/IP un TSAP address (ossia un indirizzo TCP o UDP) ha la forma: ( IP address : port number ) Port number è un intero a 16 bit. cioè il punto di accesso all’ applicazione che permette ad un utente di collegarsi mediante telnet all’ host poseidon.72. contenuti nel pacchetto IP. deve poter individuare un host e una entità contenuta nell’ host. Poichè l’ indirizzo di livello 4 serve a trasferire informazioni tra applicazioni che lavorano su hosts diversi. Il protocollo di livello 4 deve quindi decidere come deve essere fatto l'indirizzo di livello transport. nella trasmissione effettua una violazione della stratificazione tra i livelli 4 e 3 (Trasporto e Network). . per ridurre l’ overhead causato dagli header dei vari livelli.Indirizzi a livello di Trasporto per il TCP/IP Quando si vuole trasferire una o più TPDU (Transport Protocol Data Unit) da una sorgente ad una destinazione di livello 4. il TCP/IP. Ad es. la coppia (137. Ad esempio.49 : 23) indica la porta 23 dell’ host poseidon.

•Il mittente scrive questo numero di porta come indirizzo del destinatario. •Alcune primitive fornite dall’ interfaccia socket permettono di specificare il numero di porta di cui interessa ricevere i pacchetti (stream=flussi di dati nel caso TCP). E’quindi il sistema operativo che si fa carico di effettuare le operazioni di demultiplexing dei pacchetti ricevuti dal livello network. sia esso di tipo TCP che UDP. ed il destinatario si deve mettere in attesa dei pacchetti che giungono all’ host del destinatario e che posseggono come identificatore proprio quel port number. 69 .Multiplexing a livello Trasporto •Gli identificatori di porta (port number) permettono di effettuare la demultiplazione dei pacchetti di livello 4. •E’ovvio che mittente e destinatario devono essere d’ accordo sul valore della porta del destinatario per poter effettuare la trasmissione. ovvero di discriminare l’ applicazione destinazione dei pacchetti in funzione del port number contenuto nell’ header del pacchetto di livello transport.

porta 53). Quindi i datagram UDP possono essere persi. Il datagram IP può essere frammentato se la MTU è piccola.Il protocollo di Trasporto UDP (User Datagram Protocol) •Il livello transport fornisce un protocollo per il trasporto di blocchi di dati non connesso e non affidabile. ntp (Network Time protocol. che utilizza l’ per trasportare messaggi. •Utilizzano UDP alcuni protocolli standard. a cui sono riservati dei numeri di porte predefiniti. e non fornisce nessun tipo di controllo sulla velocità di trasmissione dei dati. Ricordiamo tra gli altri: nameserver (server di nomi di dominio. quindi la dimensione del datagram UDP non può superare la dimensione massima della parte dati del datagram IP. ed IP offre in più rispetto all’ la capacità di distinguere tra più IP destinazioni all’ interno di uno stesso host. tftp (Trivial File Transfer. IP che è molto simile all’ in termini di risultato del trasporto. 67). bootps (server del protocollo di bootstrap. • Ogni Datagram UDP viene incapsulato in un datagram IP. 69). IP datagram UDP datagram IP header 20 bytes UDP header 8 bytes UDP data •L’ UDP non usa dei riscontri per verificare se un messaggio è arrivato a destinazione. duplicati o arrivare fuori ordine. in modo da poter essere rintracciati nello stesso punto (punto d’ accesso) su tutti gli hosts. 123). mediante il meccanismo delle porte. 70 . detto UDP (User Datagram Protocol). non ordina i messaggi arrivati.

Inoltre il calcolo della checksum viene effettuato ponendo in testa al datagram UDP una pseudointestazione (che non viene trasmessa). pseudointestazione fatta in questo modo: 0 15 16 31 source IP address destination IP address zero 8-bit protocol (17) UDP datagram length UDP pseudo header Il motivo di questo modo di computare la checksum è verificare a livello UDP.Formato del Datagram UDP Il pacchetto UDP è costituito da un header e da una parte dati. •l’ ultimo è un checksum per il controllo d’ errore. L’ header UDP è composto da 4 campi: •i primi due sono i numeri di porta del mittente e del destinatario del datagram. A differenza dell’ header IP. che però è opzionale. Un valore zero in questo campo indica che la chesksum non è stata calcolata 0 15 16 31 source port number length UDP destination port number checksum UDP UDP data UDP header Si noti che non ci sono gli indirizzi IP di mittente e destinatario. ciascuno di 16 bit. che il datagram UDP abbia raggiunto la corretta 71 destinazione IP. in byte. che non compare nell’ header UDP. il checksum contenuto nell’ header UDP considera anche la parte dati UDP. . •il terzo è la lunghezza dei dati del datagram UDP. con gli indirizzi IP di provenienza e destinazione ricavata dall’ header IP in cui l’ UDP viene trasportato.

49 ad un host receiver 130. nella porta UDP caratterizzata dal numero 3001.204. Quindi sender e receiver devono essersi messi d’ accordo sulla porta da usare.7 .cs. application receiver sender data application transport punto 3001 d’ accesso 137.2.esempio di trasmissione di datagram UDP Senza per ora entrare nei dettagli riguardanti i socket.204. e stampa il contenuto del datagram ricevuto.it/~ghini/didattica/sistemi3/UDP1/UDP1.7 data link physical media Il codice completo (con la gestione degli errori) dei due programmi che realizzano l’ esempio qui mostrato è disponibile allo indirizzo 72 http://www.136. vediamo un semplice esempio di programma che sfrutta i socket per trasmettere un datagram UDP contenente una stringa di testo “pippo” da un host sender 137.unibo.49 transport network data link physical network 130.72.html .72. Il punto di accesso stabilito dal programmatore è nel receiver. Il receiver si mette in attesa sulla porta 3001 fino a che il sender invia un datagram all’ host receiver su quella porta.136.2. ed il sender deve conoscere l’ indirizzo IP del receiver.

} 73 . msg. short int local_port_number=3001. from host %s.2. (char *)&OptVal. /* assegna l'indirizzo IP locale e una porta UDP locale al socket */ Local. Local. /* wait for datagram */ Fromlen=sizeof(struct sockaddr). msglen. msglen = recvfrom ( socketfd. Local. SO_REUSEADDR. port %d\n".inet_ntoa(From. sizeof(Local)). (int)SIZEBUF. 0. string_remote_ip_address. From. int socketfd. msglen.136. Fromlen. printf("ricevuto msg: \"%s\" len %d. &Fromlen). char string_remote_ip_address[100]. SOCK_DGRAM. remote_port_number). bind ( socketfd.sin_port). sizeof(OptVal) ). /* prende un socket per datagram UDP */ socketfd = socket (AF_INET. sprintf((char*)string_remote_ip_address. SOL_SOCKET. /* impedisce l'errore di tipo EADDRINUSE nella bind() */ OptVal = 1. short int remote_port_number. msg. OptVal."%s". (struct sockaddr*)&From. (struct sockaddr*) &Local.sin_family = AF_INET. setsockopt (socketfd.7 */ #define SIZEBUF 10000 void main(void) { struct sockaddr_in Local.sin_addr. remote_port_number = ntohs(From. char msg[SIZEBUF].esempio: receiver di datagram UDP /* eseguito sull’ host 130. 0).sin_addr).sin_port = htons(local_port_number).s_addr = htonl(INADDR_ANY).

7".s_addr = htonl(INADDR_ANY). e ad una porta a scelta del s. (struct sockaddr*)&To.sin_port = htons(remote_port_number). addr_size = sizeof(struct sockaddr_in). decide la porta locale */ bind( socketfd.sin_family = AF_INET.sin_family = AF_INET. addr_size.sin_port = htons(0). short int remote_port_number = 3001. /* il s. sizeof(Local)). /* assegna la destinazione */ To.sin_addr. /* prende un socket per datagram UDP */ socketfd = socket(AF_INET. char string_remote_ip_address[]="130.72. */ Local. SO_REUSEADDR.o. char msg[]=“pippo".136. strlen(msg) . To. msg. To.sin_addr. sizeof(OptVal)).esempio: sender di datagram UDP /* eseguito sull’ host 137.2. addr_size).49 */ int main(void) { struct sockaddr_in Local. setsockopt(socketfd.204. (struct sockaddr*) &Local. SOCK_DGRAM. Local.s_addr = inet_addr(string_remote_ip_address). To. } 74 . /* assegna l'indirizzo IP locale e una porta UDP locale al socket */ Local. (char *)&OptVal. 0).o. OptVal. /* impedisce l'errore di tipo EADDRINUSE nella bind() */ OptVal = 1. SOL_SOCKET. /* send to the address */ sendto(socketfd. /* il socket verra' legato all'indirizzo IP dell'interfaccia che verrà usata per inoltrare il datagram IP. int socketfd. 0.

su una rete non affidabile. Il servizio effettua internamente la gestione di ack. eventualmente ritrasmettendoli. al livello application. per effettuare la trasmissione all’ interno di singoli datagram IP. •consegnare i dati. •consegnarli al livello network. leggere i dati a blocchi di 20 byte per volta. •a richiesta. forzare l’ invio interrompendo la bufferizzazione. •chiudere la connessione. •spezzare o accorpare i dati in segment.Il protocollo di Trasporto TCP (Transmission Control Protocol) Il protocollo TCP è stato progettato per fornire un flusso di byte da sorgente a destinazione full-duplex. Il servizio del TCP è di tipo orientato allo stream. eliminando buchi e doppioni.500 byte. ovvero trasporta un flusso di byte. il che significa che se anche la sorgente spedisce (scrive sul device) i dati a blocchi (es 1 KB poi 3 KB poi ancora 2 KB) la connessione non informa la destinazione su come sono state effettuate individualmente le scritture. affidabile. •accettare dati dal livello application eventualmente bufferizzando in input. il nome usato per i TPDU (Transport Protocol Data Unit) aventi dimensione massima 64 Kbyte. •rimetterli in ordine. la destinazione 75 potrebbe ad es. in ordine. Dunque. ma tipicamente di circa 1. il controllo del flusso e il controllo della congestione. e si occupa di: •stabilire la connessione full duplex tra due punti di accesso a livello trasporto. . •ricevere segmenti dal livello network. offre un servizio reliable e connection oriented.

provenienti ciascuno da un mittente eventualmente diverso. •Ogni socket è caratterizzato da una coppia ( IP address: Port number) che può essere utilizzata da più processi simultaneamente. Indirizzamento nel TCP (1) . •le connessioni sono identificate da una coppia di punti d’ accesso (endpoint) detti socket. come sua astrazione fondamentale. il TCP impiega numeri di porta di protocollo per identificare la destinazione finale all’ interno di un host. in cui possiamo immaginare ogni porta come una sorta di coda a cui arrivano dei datagram delimitati.•Come l’ UDP. IP address B: Port number B ) Più utenti che effettuano tutti il telnet da una stessa macchina B verso una stessa macchina A instaurano connessioni identificate da terne del tipo ( IP_A : 23 . •Però ciascuna connessione viene univocamente individuata dalla coppia di socket dei due host A e B implicati nella connessione. 23). Questa è ad esempio la situazione prodotta dal processo demone del telnet (telnetd) che consente agli utenti di una macchina unix A di collegarsi ad A da un’ altro host accedendo tutti alla stessa porta TCP numero 23 di quell’ host A. uno su ciascuna macchina. non la porta di protocollo. IP_B : tcp_port_B ) con tcp_port_B tutti diversi. Tutte le connessioni condividono quindi la stessa coppia (IP_A. •La situazione è però molto diversa rispetto all’ UDP. Il TCP impiega la connessione (virtuale). •Per il TCP si vuole invece che una connessione sia dedicata in esclusiva ad una coppia di applicazioni risiedenti su macchine diverse. e quindi con connessioni univocamente 76 determinate. ovvero dalla terna: ( IP address A: Port number A .

a sua volta costituito da: . Se questo scade.535 byte. e viene usato per stabilire connessioni. •TCP usa un meccanismo di sliding window (finestre scorrevoli) di tipo go-back-n con timeout. per inviare riscontri (una sorta di ricevuta di ritorno). Queste porte sono quelle con valore inferiore a 256. non in numero di segmenti. per trasferire dati. e vengono detti well-know-port. . il segmento si ritrasmette. per dimensionare le finestre scorrevoli e per chiudere le connessioni. usato sia per il controllo di flusso che per la gestione degli ack. e viene incluso in un singolo datagram IP. . anche per il TCP esistono dei port number che sono riservati ad uso di protocolli standard. Si noti che le dimensioni della finestra scorrevole e i valori degli ack sono espressi in numero di byte. •ogni byte del flusso TCP è numerato con un numero d'ordine a 32 bit.una parte fissa di 20 byte.Well-Know-Port nel TCP Come per il caso UDP. Port Number Service 20 Ftp (control) 21 Ftp (data) 23 Telnet 25 Smtp 80 Http I segmenti TCP •L’ unità di trasferimento del TCP è detta segmento. •un segmento TCP è formato da: uno header. 77 i dati da trasportare.una parte opzionale. •ogni segmento TCP non può superare i 65.

destination port: identificano gli end point (locali ai due host) della connessione. Ack. Essi. assieme ai corrispondenti numeri IP. identificano la connessione a cui appartiene il segmento. Sequence number: la posizione del primo byte contenuto nel campo dati all’ interno dello stream di byte che il trasmettitore del segmento invia (si possono inviare quindi al max 4 miliardi di byte circa in uno stesso stream). number TCP header len reserved Checksum U A P R S F R C S S Y I G K H T N N Window size Urgent pointer Options (zero o più parole di 32 bit) Dati (opzionali) Source port. TCP header length: lunghezza del segmento misurata in parole di 32 bit (necessario perché il campo options ha dimensione variabile).Formato del segmento TCP 32 bit Source port Destination port Sequence number Ack. number: la posizione del prossimo byte aspettato all’ interno del segmento inviato dal ricevitore del presente segmento. 78 .

SYN=1 ACK=1 accettata connessione. RST richiesta di reset della connessione (ci sono problemi!). Come per UDP il calcolo del checksum prevede che sia aggiunto in testa al segmento uno pseudoheader contenente gli indirizzi IP di sorgente e destinazione. la lunghezza del segmento. ACK 1 se l'ack number è valido (cioè se si trasporta un ack). 0 altrimenti. estratti dall’ header del datagram IP che contiene il segmento.Formato del segmento TCP (2) Checksum. simile a quello di UDP. SYN usato nella fase di setup della connessione: SYN=1 ACK=0 richiesta connessione. 0 altrimenti. 79 . FIN usato per rilasciare una connessione. 0 15 16 31 source IP address destination IP address 0 8-bit protocol (=6) TCP segment length TCP pseudo header Nell’ header del segmento TCP sono presenti inoltre 6 flags di un bit ciascuno che servono per assegnare validità ad alcuni campi dell’ header o per segnalare richieste o conferme: URG 1 se il campo urgent pointer è usato. verifica che sia l’ header che i dati del segmento siano arrivati a destinazione senza errori. PSH indica che questo segmento contiene dati urgenti (pushed data). da consegnare senza aspettare che il buffer si riempia.

. Un valore zero significa: fermati per un pò. Window size dice quanti byte possono essere spediti a partire da quello (compreso) che viene confermato con l'ack number. oppure quando i buffer di ricezione sono ormai pieni e non si vuole rischiare di perdere dei dati . serve se uno degli host ha dei buffer molto limitati ma soprattutto per adattare il segmento alla MTU della rete che collega i due host. 80 •uso di NAK. Questo valore serve a ridurre la velocità di trasmissione dei dati nel flusso di byte che va dal ricevente al trasmettitore del presente segmento. •uso di selective repeat invece che go-back-n (un diverso algoritmo di controllo del flusso). Options contiene alcune opzioni appliccabili al flusso. riprenderai quando ti arriverà un uguale ack number con un valore di window size diverso da zero. indica la posizione in cui terminano i dati urgenti nello stream inviati dal trasmettitore. Urgent pointer puntatore ai dati urgenti. in modo che ogni segmento venga incluso in un datagram IP che non debba essere frammentato. e sono: •dimensione massima dei segmenti da spedire (MSS: Maximum Segment Size). Le più importante sono negoziabili durante il setup della connessione.Formato del segmento TCP (3) altri campi presenti nel segmento TCP sono: Window size: il controllo di flusso è di tipo sliding window di dimensione variabile. per non incrementare ancora la congestione. Viene usato quando ci si accorge di una congestione della rete.

81 . La tecnica più semplice per garantire che tutti i byte trasmessi in un flusso giungano a destinazione è nota come riscontro positivo con ritrasmissione. Quando un ricevitore riceve un pacchetto risponde al pacchetto inviando un riscontro (acknowledgment. nel momento in cui lo spedisce ne mantiene una copia e fa partire un timer. ACK) al trasmettitore per confermare di averlo ricevuto. Se allo scadere del timer non ha ancora ricevuto il relativo ACK ritrasmette il pacchetto.Trasmissione affidabile: riscontro positivo con ritrasmissione (1) L’ affidabilità del TCP si basa sulla combinazione di alcune tecniche che adesso analizzeremo separatamente. Quando il trasmettitore deve inviare un pacchetto.

•La tecnica delle finestre scorrevoli (Sliding Windows) invece permette al trasmettitore di poter continuare ad inviare un certo numero N (dimensione della finestra) di pacchetti successivi all’ ultimo per cui ha ricevuto il riscontro. può capitare di ricevere un ACK per un pacchetto che era già stato riscontrato in precedenza. 82 Le finestre Scorrevoli . e l’ ACK contiene il numero sequenziale del pacchetto che vuole riscontrare. e la rete è sottoutilizzata mentre gli end system attendono le risposte. Per ovviare al problema dei riscontri duplicati. e di scambiarlo per un riscontro di un successivo pacchetto per cui attendevamo un ACK. che possono ritardare. In figura vengono trasmessi 3 pacchetti prima di ricevere un riscontro. ovvero permette di trasmettere fino ad altri N pacchetti mentre si è in attesa di ricevere il riscontro di un pacchetto precedentemente inviato.Trasmissione affidabile: riscontro positivo con ritrasmissione (2) Poichè può capitare che la rete duplichi un pacchetto. perchè il trasmettitore deve attendere il riscontro di ciascun pacchetto prima di inviare il successivo. i pacchetti vengono numerati sequenzialmente. In tal modo la rete viene utilizzata anche nei periodi di attesa. (1) •Il meccanismo di riscontro positivo rallenta la trasmissione. quindi i pacchetti viaggiano in rete in una sola direzione per volta.

•La finestra del trasmettitore inizia col primo pacchetto non riscontrato. mentre i pacchetti che seguono la finestra non possono essere trasmessi. I pacchetti successivi. che stanno dentro alla finestra. Le finestre Scorrevoli 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 pacchetti trasmessi pacchetti trasmissibili e riscontrati primo pacchetto non riscontrato pacchetti riscontrati pacchetti non ancora trasmissibili 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 pacchetti trasmessi e riscontrati primo pacchetto non riscontrato pacchetti non ancora pacchetti trasmissibili trasmissibili nuovi pacchetti trasmissibili 83 (in figura la dimensione della finestra è 8) .(2) •definiamo non riscontrato un pacchetto trasmesso per il quale non è ancora stato ricevuto il riscontro. consentendo di trasmettere i pacchetti che sono entrati nella finestra. possono essere trasmessi. •Quando un pacchetto P (trasmesso) nella finestra viene riscontrato (il pacchetto 6 in figura) (e sono riscontrati anche i suoi precedenti) allora la finestra avanza fino al pacchetto successivo a P.

2) al posto dei pacchetti consideriamo i byte del flusso realizzato dalla connessione TCP. •se la dimensione della finestra è 1. •Il meccanismo delle finestre scorrevoli è stato spiegato riferendosi alla trasmissione di pacchetti. e utilizziamo la posizione dei byte nel flusso come indicatore per sapere nel trasmettitore: . il pacchetto deve essere ritrasmesso. ogni host funge da trasmettitore.qual’ il primo byte della finestra (da riscontrare) è . allo scadere del quale. ma vale ugualmente se: 1) consideriamo lo stesso meccanismo (la finestra) replicato nei due end system. si ritorna all’ algoritmo di riscontro positivo.qual’ il primo byte ancora da trasmettere dentro la finestra. •all’ aumentare della dimensione della finestra diminuisce il periodo di non utilizzo della rete. perchè essendo la connessione TCP bidirezionale.qual’ il primo byte che segue la finestra (da non trasmettere) è . se non è ancora stato ricevuto il riscontro. permettendo di trasmettere i nuovi pacchetti che entrano nella finestra. è Le finestre Scorrevoli 4 5 6 7 8 9 10 11 12 13 14 15 primo pacchetto non riscontrato primo pacchetto da trasmettere 84 pacchetti non ancora trasmissibili .(3) •le finestre continuano a scorrere finchè si ricevono riscontri. •Per ogni pacchetto trasmesso viene comunque ancora fatto partire un timer. •Le prestazioni dei protocolli a finestra scorrevole dipendono dalla dimensione della finestra e dalla velocità della rete. •con un opportuna scelta della dimensione della finestra è possibile mantenere la rete satura di pacchetti senza congestionarla.

•Il ricevitore riscontra solo i byte che precedono il primo buco. il primo byte del primo buco. non è in grado di capire se manca solo un pezzetto (un buco anche di quel solo byte) o manca tutto da quel byte in avanti. e ricomincia a trasmettere tutto da quel byte. anche se ha già ricevuto qualcosa di successivo. ma poichè i segmenti viaggiano in datagram IP possono essere persi o consegnati disordinatamente. i riscontri non possono fare riferimento facilmente ai datagrammi o ai segmenti. se un riscontro per un byte non arriva. da 7 a 13.(1) •Poichè il TCP invia i dati in segmenti di lunghezza variabile. e che la perdita di un riscontro non costringe alla ritrasmissione se arriva un riscontro per un byte successivo. •Il ricevitore raccoglie i byte di dati dei segmenti in arrivo e li riordina. ma fanno riferimento alla posizione dei byte nello stream. •Il vantaggio è che questi riscontri cumulativi sono facili da generare. specifica il valore sequenziale (la posizione) del primo byte che ancora gli manca e che si aspetta di ricevere. generando dei buchi nella ricostruzione. primo byte non riscontrato byte gia’ricevuti ma non riscontrati I Riscontri 4 5 6 7 8 9 10 11 12 13 14 15 85 il trasmettitore ritrasmette tutto. anche se il ricevitore ha già tutto da 8 a 13 . e poichè i segmenti ritrasmessi possono includere più dati dell’ originale. •Uno svantaggio è che il trasmettitore. o meglio.

Socket per TCP: Fondamenti .

gruppi).Molte applicazioni di rete sono formate da due programmi distinti (che lavorano su due diversi host) uno detto server ed uno detto client.html) sviluppata da IEEE e adottata da ISO. signal. password.pasc.org/standing/sd11. I/O da terminale. Inoltre spesso client e server sono processi utente. Posix comprende IEEE Std 1003. execution scheduling. semaphores.4BSD. basato sulle specifiche XPG4 del consorzio X/Open . files e directory (I/O function). Tipicamente il client comunica con un solo server.1 (1996) (una raccolta di alcune specifiche precedenti) che contiene al suo interno una parte detta “Part1: System Application Program Interface (API)” che specifica l’interfaccia C per le chiamate di sistema del kernel Unix. user ID.1g: Protocol Independent Interface (PII) che è lo standard per l’interfaccia di programmazione delle reti. e definisce due standard chiamati DNI (Detailed Network Interfaces): 1) DNI/Socket basato sulle API socket del 4. shared memory. mentre i protocolli della suite TCP/IP fanno solitamente parte del sistema operativo. Network Applications Unix Standards Posix = Portable Operating System Interface è una famiglia di standard (vedi http://www. In particolare comprende IEEE Std 1003. timer. relative a processi (fork. di cui ci occuperemo 86 2) DNI/XTI. Nel seguito faremo riferimento al termine IP nel senso di IPv4. clock. il client effettua tale richiesta. Il server si mette in attesa di una richiesta da servire. mentre un server usualmente comunica con più client contemporaneamente (su connessioni diverse nel caso tcp). le estensioni per il realtime. exec. message queues.

ephemeral port 104 . poi viene fatto partire il client che chiede la connessione al server e la connessione viene instaurata. Infine il server chiude a sua volta la connessione. Questa trasmissione bidirezionale continua fino a che uno dei due (il client nell’esempio) decide di interrompere la connessione. questo risponde trasmettendo alcuni dati. e tramite la close() chiude la connessione. Nell’esempio (ma non è obbligatorio) il client spedisce una richiesta al server.Interazioni tra Client e Server TCP Per primo viene fatto partire il server.

unibo. lo’\0’ che delimita la stringa) shiftandoli di due posizioni (es: ‘a’ diventa ‘c’. infine stampare il risultato.7.72.cs.7 data link physical media Il codice completo (con la gestione degli errori) dei due programmi che realizzano l’esempio qui mostrato è disponibile allo indirizzo http://www. aspettare che il server modifichi questi caratteri (tranne l’ultimo.49 TCP transport network data link physical network 130.2.204.49.204.72.136. ‘2’ diventa ‘4’) e li rispedisca indietro così traslati. trasmettere dal client al server una stringa di caratteri. mentre il client è l’host 137.2. Il punto di accesso del servizio di traslazione è la porta TCP 5001.esempio di trasmissione con TCP Vediamo un semplice esempio di programma che sfrutta i socket TCP per instaurare una connessione tra un client e un server.136. Il server è l’host 130.it/~ghini/didattica/sistemi3/TCP1/TCP1. server traslazione caratteri buf[i]=buf[i]+2 application write read write client read application TCP transport 5001 137.html 114 .

(struct sockaddr*) &Cli. newsockfd.sin_port = htons(local_port_number).2.c eseguito sull’host 130. Local.136. SOCK_STREAM.sin_addr.s_addr = htonl(INADDR_ANY). /* accetta max 10 richieste simultanee di inizio conness. n. bind ( sockfd. sizeof(Local) ).server TCP per l’esempio /* servTCP. MAXSIZE )) >0) { nread+=n. char buf[SIZEBUF]. short int local_port_number=5001. while( (n=read(newsockfd. 115 } . /* riceve la stringa dal client */ nread=0. (struct sockaddr*) &Local. len. Local. /* chiude i socket */ close(newsocketfd). while((n=write ( newsockfd. /* prende un socket per stream TCP */ sockfd = socket (AF_INET. &(buf[nwrite]).. Client. da adesso */ listen(sockfd. n++) buf[n] = buf[n]+2. &(buf[nread]). /* spedisce la stringa traslata al client */ nwrite=0.7 */ void main(void) { struct sockaddr_in Local. /* fine stringa */ } /* converte i caratteri della stringa */ for( n=0. sizeof(Local)). 0). n<nread -1 . nwrite.nread-nwrite)) >0 ) nwrite+=n. 10 ). /* accetta la prima conness. /* collega il socket ad un indirizzo IP locale e una porta TCP locale */ memset ( &Local. */ newsockfd = accept(sockfd. Local. &len).sin_family = AF_INET. nread. 0. creando un nuovo socket per la conness. close(socketfd). int sockfd. if(buf[nread-1]=='\0') break.

while ((len>nwrite)&&(n=write(sockfd. char msg[]="012345ABCD" . short int remote_port_number=5001. MAXSIZE )) >0) { nread+=n. n. (struct sockaddr*) &Serv.&(msg[nwrite]). nwrite=0. e chiede la connessione */ memset ( &Serv. int sockfd . sizeof(Local)). nread=0.204.49 */ void main(void) { struct sockaddr_in Local./* cliTCP.sin_addr. Local. /* fine stringa */ } printf(“%s\n”. msg).sin_port = htons(0). connect ( sockfd. SOCK_STREAM. len /* prende un socket per stream TCP */ sockfd = socket (AF_INET. /* stampa la stringa traslata */ /* chiude i socket e termina*/ close (sockfd).len-nwrite))>0)) nwrite+=n. if(buf[nread-1]=='\0') break.72. Serv. /* spedisce la stringa al server */ len = strlen(msg)+1. Serv.sin_addr. Local. /* collega il socket senza specificare indirizzo IP e porta TCP locali */ memset ( &Local. sizeof(Local) ). 0. 0. bind ( sockfd. sizeof( Serv)). nread.s_addr = htonl(INADDR_ANY). Serv.sin_family = AF_INET. /* specifica l’indirizzo del server. 0). &( msg[nread]). nwrite. /* riceve la stringa traslata dal server */ while ( (n=read(sockfd.sin_family = AF_INET.sin_port = htons(remote_port_number). Serv.s_addr = inet_addr ( string_remote_ip_address). sizeof(Serv) ). (struct sockaddr*) &Local.c eseguito sull’host 137. Local. 116 } client TCP per l’esempio .

h> indirizzo IPv4. sizeof(server) ). l’argomento passato deve essere convertito mediante il cast alla struttura (struct sockaddr*). locali unix (per pipe ecc.h > uint16_t unsigned 16-bit integer <sys/types.Socket Address Structures (1) Cominciamo la descrizione delle Socket API (Application program Interface) dalla descrizione delle strutture usate per trasferire indirizzi dall’applicazione al kernel (nelle funzioni bind. connect. sizeof(server) ).. = uint16 <netinet/in.. AF_LOCAL per indir. getsockname e getpeername). ma essendo diversa la struttura passata a seconda della famiglia di indirizzi usata. recvfrom. le funzioni di libreria presentano un argomento che è il puntatore alla generica struttura (struct sockaddr*). e poichè tali strutture vengono passate per puntatore.h> int32_t signed 32-bit integer <sys/types.h> AF_INET per IPv4. (struct sockaddr *)&server.h> • Poichè i socket devono fornire un’interfaccia per diverse famiglie di protocolli (IPv4.h> uint8_t unsigned 8-bit integer <sys/types. 0.. ad es: struct sockaddr_in server.. riempimento dei dati della struttura server .h> sa_family_t famiglia di indirizzi socket <sys/socket. IPv6 e Unix). = uint32 <netinet/in. di solito è un uint32_t <sys/socket.h > int16_t signed 16-bit integer <sys/types.1g sono quelli della seguente tabella: int8_t signed 8-bit integer <sys/types.h> porta TCP o UDP.h> uint32_t unsigned 32-bit integer <sys/types. /* azzero tutta la struttura */ . AF_INET6 per IPv6. *) . • I dati definiti per Posix. sendto) e dal kernel alle applicazioni (nelle funzioni accept.. 96 bind ( socketfd.) socklen_t in_addr_t in_port_t lunghezza della struttura che contiene l’indirizzo. /* IPv4 socket address structure */ memset ( &server.

• sa_len e sa_family si sovrappongono perfettamente a sin_len e sin_family rispettivamente. Socket Address Structures (2) . La famiglia di indirizzi Ipv4 (sa_family=AF_INET) usa la struttura: struct sockaddr_in { uint8_t sin_len. /* unused */ }. /* 32-bit IPv4 address network byte ordered */ }. permettendo di leggere la costante di tipo sa_family_t e di capire che tipo di struttura si sta utilizzando. /* lunghezza struttura */ sa_family_t sin_family. Di più. e anche quando è presente non è necessario settarlo. • Il campo sin_zero non è usato. /* 32-bit IPv4 address. • il campo sin_len non è richiesto espressamente da Posix. ma va sempre settato tutto a zero prima di passare una struttura che lo contiene. per convenzione. usando la funzione memset(). network byte ordered */ struct in_addr sin_addr. char sa_data[14]. sa_family_t sa_family. con struct in_addr { /* e’ una struttura per ragioni storiche */ in_addr_t s_addr . bisogna sempre settare TUTTA la struttura indirizzo tutta a zero prima di riempire i vari campi. /* = AF_INET */ in_port_t sin_port. 97 • memset ( &server. 0. }.La generica struttura dell’indirizzo è dunque cosi definita: struct sockaddr { uint8_t sa_len.1g. /*16-bit TCP UDP port. se non per applicazioni di routing. sizeof(server) ). in quanto le principali funzioni in cui si passano indirizzi prevedono già un argomento in cui si passa (o riceve) la lunghezza della struttura indirizzo. network byte ordered */ char sin_zero[8].

Confrontiamo alcune delle strutture usate per gli indirizzi:

Socket Address Structure (3)

98

Poichè alcuni campi delle strutture di indirizzo (i numeri di porta o gli indirizzi IPv4 ad esempio) devono essere memorizzati secondo l’ordine per i bytes stabilito per la rete (network byte order), prima di assegnare alla struttura un valore di porta (16-bit) o un indirizzo IPv4 (32-bit) è necessario convertirlo dall’ordine dei byte per l’host all’ordine per la rete, utilizzando delle funzioni di conversione, i cui prototipi sono definiti nell’include <netinet/in.h>: uint16_t htons (uint16_t host16bitvalue); /* Host TO Network Short */ uint32_t htonl (uint32_t host32bitvalue); /* Host TO Network Long */ Viceversa, per convertire il valore di una porta o di un indirizzo IPv4, preso da una struttura di indirizzo, in un valore intero secondo l’ordinamento dell’host si devono utilizzare le funzioni: uint16_t ntohs (uint16_t net16bitvalue); /* Network TO Host Short */ uint32_t ntohl (uint32_t net32bitvalue); /* Network TO Host Long */ Se l’ordinamento dell’host è corrispondente all’ordinamento di rete, queste funzioni sono implementate con delle macro nulle, cioè non modificano il dato.

Funzioni di Ordinamento dei Byte

Funzioni di Manipolazione dei Byte
Vediamo solo le funzioni portabili ovunque perche sono ANSI C. void *memset (void *dest, int c, size_t n_bytes); setta al valore c un numero len di byte a partire da dest void *memcpy (void *dest, const void *src, size_t n_bytes); copia n_bytes byte da src a dest, problemi se c’e’ sovrapposizione, nel caso usare memmove. Resituisce dest. void *memcmp (const void ptr1, const void *ptr2, size_t n_bytes); confronta due vettori di n_bytes ciascuno, restituisce 0 se sono uguali, diverso da zero se diversi. 99

Funzioni di Conversione di Indirizzi IP dalla forma dotted-decimal ASCII string alla forma 32-bit network byte ordered
Queste funzioni sono definite in <arpa/inet.h> Le funzioni inet_aton e inet_addr convertono gli indirizzi IP da una forma di stringa di caratteri ASCII decimali separati da punti del tipo “255.255.255.255”, nella forma di interi a 32-bit ordinati secondo l’ordinamento di rete. int inet_aton (const char *str, struct in_addr *addrptr); scrive nella locazione puntata da addrptr il valore a 32-bit, nell’ordine di rete, ottenuto dalla conversione della stringa zeroterminata puntata da str. Restituisce zero in caso di errore, 1 se tutto va bene. in_addr_t inet_addr (const char *str); NON VA USATA restituisce il valore a 32-bit, nell’ordine di rete, ottenuto dalla conversione della stringa zero-terminata puntata da str. In caso di errori restituisce INADDR_NONE, e questo è un casino, perchè INADDR_NONE è un intero a 32 bit di tutti 1, che sarebbe ottenuto come risultato della chiamata di inet_addr passandogli la stringa “255.255.255.255” che è l’indirizzo valido di broadcast. Per evitare confusione non deve essere usata. Infine c’e’ una funzione che effettua la conversione inversa, da interi a 32-bit network ordered verso stringhe ASCII decimali separate da punti. char *inet_ntoa (struct in_addr addr); scrive in una locazione di memoria statica (di cui restituisce un puntatore) la stringa ASCII null-terminata di caratteri decimali separati da punti corrispondeni all’indirizzo IP a 32-bit, nell’ordine di rete, contenuto nella struttura addr (che stranamente non è un puntatore). Occhio, questa funzione non è rientrante, perchè 100 memorizza il risultato in una locazione statica.

e setta errno. int type. int protocol). family descrizione AF_INET IPv4 protocol AF_INET6 IPv6 protocol AF_LOCAL Unix domain protocols (ex AF_UNIX) AF_ROUTE Routing socket AF_ROUTE Key socket (sicurezza in IPv6) L’argomento type specifica quale tipo di protocollo vogliamo utilizzare all’interno della famiglia di protocolli specificata da family. Unix domain stream protocol per usare le pipe). restituisce un descrittore di socket maggiore o uguale a zero. UDP con IPv6.h> int socket (int family. #include <sys/socket.funzione socket() La prima azione per fare dell’I/O da rete è la chiamata alla funziona socket() specificando il tipo di protocollo di comunicazione da utilizzare (TCP con IPv4. oppure -1 in caso di errore. L’argomento family specifica la famiglia di protocolli da utilizzare. Quelle valide selezionano un protocollo che verrà utilizzato. tranne che nel caso dei socket raw. type descrizione SOCK_STREAM socket di tipo stream (connesso affidabile) SOCK_DGRAM socket di tipo datagram SOCK_DRAW socket di tipo raw (livello network) L’argomento protocol di solito è settato a 0. Non tutte le combinazioni di family e type sono valide. AF_KEY AF_INET AF_INET6 AF_LOCAL AF_ROUTE SOCK_STREAM SOCK_DGRAM SOCK_DRAW TCP UDP IPv4 TCP UDP IPv6 esiste esiste esiste 105 .

106 . const struct sockaddr *servaddr. • L’argomento socketfd è un descrittore socket ottenuto da una chiamata alla funzione socket(). restituisce 0 se la connessione viene stabilita. perchè queste informazioni non servono a nessuno.ETIMEDOUT nessuna risposta al segmento SYN . #include <sys/socket.h> int connect (int socketfd.funzione connect() La funzione connect() è usata dal client TCP per stabilire la connessione con un server TCP. • L’argomento addrlen specifica la dimensione della struttura dati che contiene l’indirizzo del server servaddr. • Nel caso di connessione TCP la connect inizia il protocollo three way handshake spedendo un segmento SYN.ECONNREFUSED il server risponde con un segmento RST (reset) ad indicare che nessun processo server è in attesa (stato LISTEN) su quella porta . e deve specificare l’indirizzo IP e il numero di porta del server da connettere. • Il client non deve di solito specificare il proprio indirizzo IP e la propria porta. -1 in caso di errore. e come indirizzo IP l’indirizzo della sua interfaccia di rete. socklen_t addrlen). Quindi NON SERVE la chiamata alla bind() prima della connect(). Quindi può chiedere al sistema operativo di assegnargli una porta TCP qualsiasi. • L’argomento servaddr come visto in precedenza è in realtà per IPv4 un puntatore alla struttura sockaddr_in.EHOSTUNREACH o ENETUNREACH host non raggiungibile . viene di solito assegnata mediante la sizeof(servaddr).ed altri ancora. La funzione termina o quando la connessione è stabilita o in caso di errore. o dell’interfaccia di rete usata se ne ha più di una. • In caso di errore la connect restituisce -1 e la variabile errno è settata a: .

• L’argomento sockfd è un descrittore ottenuto da una socket(). • Per un TCP server ciò significa che verranno accettate solo le connessioni per i client che chiedono di connettersi proprio a quell’IP address. il kernel effettua autonomamente il collegamento con una porta qualsiasi (ephemeral port) al momento della connect (per il client) o della listen (per il server). #include <sys/socket. • L’argomento myaddr è un puntatore alla struttura sockaddr_in.(1) La funzione bind() collega al socket un indirizzo locale. Fa eccezione il meccanismo delle RPC. • Per un TCP client ciò significa assegnare il source IP address che verrà inserito negli IP datagram. nel momento in cui il socket si connette. • L’applicazione può specificare (con la bind) per il socket un indirizzo IP di un’interfaccia dell’host stesso. il kernel assegna al socket come indirizzo IP locale quello contenuto nell’IP destination address del datagram IP che contiene il SYN segment ricevuto. il kernel sceglie come source IP address. • L’argomento addrlen specifica la dimensione della struttura myaddr.h> int bind (int sockfd. -1 in caso di errore. e specifica l’eventuale indirizzo IP locale e l’eventuale numero di porta locale a cui il sistema operativo deve collegare il socket. 107 funzione bind() . quello della interfaccia di rete usata. • L‘applicazione può collegarsi o no ad una porta. • Se il TCP client non fa la bind() o non specifica un IP address nella bind(). spediti dal socket. Per TCP e UDP ciò significa assegnare un indirizzo IP ed una porta a 16-bit. socklen_t addrlen). • In caso non venga effettuato il collegamento con una porta. const struct sockaddr *myaddr. • I client di solito non si collegano ad una porta con la bind. restituisce 0 se tutto OK. • Di solito il server si collega ad una porta nota (well know port). • Se il server non fa il bind con un IP address.

sin_addr.sin_port = htons(port_number). Se con la bind si lascia al kernel la scelta di IP address locale o port number locale.(2) Chiamando la bind() si può specificare o no l’indirizzp IP e la porta. A seconda del valore otteniamo risultati diversi. Specificando la wildcard (mediante la costante INADDR_ANY per IPv4) il kernel non sceglie l’indirizzo IP locale fino a che o il socket è connesso (se TCP) o viene inviato il primo datagram per quel socket (se UDP). porta fissata IP address fissato. si potrà sapere quale IP address e quale port number è stato scelto mediante la funzione getsockname(). nella tabella che si riferisce solo al caso: IP_address sin_addr wildcard wildcard Local IP Address Local IP Address port sin_port 0 nonzero 0 non zero funzione bind() Risultato il kernel sceglie IP address e porta il kernel sceglie IP address. che sono qui elencati.s_addr = htonl(INADDR_ANY). una volta che il kernel avrà scelto. localaddr. L’assegnazione viene fatta con le istruzioni: struct sockaddr_in localaddr. kernel sceglie la porta IP address e porta fissati dal processo Specificando il numero di porta 0 il kernel sceglie collega il socket ad un numero di porta temporaneo nel momento in cui la bind() è chiamata. 108 . localaddr. assegnando valori ai due campi sin_addr e sin_port della struttura sockaddr_in passata alla bind come secondo argomento.

sia connessioni stabilite) il kernel può mantenere in attesa nelle sue code. cioè che non hanno ancora raggiunto lo stato ESTABLISHED. • L’argomento backlog specifica il numero totale di entry dei due tipi di code. #include <sys/socket. • Quando il 3-way termina normalmente.h> int listen (int socketfd. 2) specifica al kernel quante richieste di inizio connessione può accodare al massimo per quel socket.La funzione listen è chiamata solo dal TCP server e esegue due azioni: 1) ordina al kernel di far passare il socket dallo stato iniziale CLOSED allo stato LISTEN. int backlog ). e la entry viene spostata in una coda delle connessioni completate. e di accettare richieste di inizio connessione per quel socket. • L’argomento socketfd è un descrittore ottenuto da una socket(). • Se quando il server chiama la accept(). la accept resta in attesa. L’entry rimane nella coda fino a che il 3-way è terminato o scade il timeout. • Quando il server chiama la accept. se il TCP verifica che c’è un socket per quella richiesta. la connessione viene instaurata. -1 in caso di errore. crea una nuova entry in una coda delle connessioni incomplete. 109 funzione listen() . • Quando un segmento SYN arriva da un client. e risponde con il suo FIN+ACK secondo il 3-way handshake. la coda delle connessioni completate è vuota. • Solitamente si usa 5. la prima delle entry nella coda delle connessioni completate viene consegnata alla accept() che ne restituisce l’indice come risultato. ovvero restituisce un nuovo socket che identifica la nuova connessione. restituisce 0 se tutto OK. per http daemon si usano valori molto grandi. accodandole in delle code del kernel. • L’argomento backlog è un intero che specifica quante richieste di inizio connessione (sia connessioni non ancora stabilite.

Se la coda è vuota la accept resta in attesa. L’argomento socketfd è un descrittore ottenuto da una socket() e in seguito processato da bind() e listen().h> int accept (int socketfd. socklen_t *ptraddrlen).funzione accept() La funzione accept è chiamata solo dal TCP server e restituisce la prima entry nella coda delle connessioni già completate per quel socket. con cui è stata instaurata la connessione a cui si riferisce il socket che viene restituito come risultato . Il connected socket verrà utilizzato per scambiare i dati nella nuova connessione. Il listening socket socketfd (il primo argomento) mantiene anche dopo la accept le impostazioni originali. secondo le impostazioni definite dalla bind() e dalla listen(). E’ il cosiddetto listening socket. cioè si riferisce ad una connessione instaurata con un certo client secondo le regole del listening socket socketfd passato come input. -1 in caso di errore. Tale listening socket viene utilizzato per accedere alla coda delle connessioni instaurate come visto per la listen(). Se accept termina correttamente restituisce un nuovo descrittore di socket che è il connected socket. e può essere riutilizzato in una nuova accept per farsi affidare dal kernel una nuova connessione. struct sockaddr *cli_addr. • L’argomento cli_addr è un puntatore alla struttura sockaddr_in. restituisce un descrittore socket >=0 se tutto OK. 110 . • L’argomento ptraddrlen è un puntatore alla dimensione della struttura cli_addr che viene restituita. ovvero il socket che si occupa di insturare le connessioni con i client che lo richiedono. #include <sys/socket. su cui la funzione accept scrive l’indirizzo IP del client e il numero di porta del client.

• Esiste un’opzione però (la SO_LINGER socket option) che modifica il comportamento della close. il socket mantiene il conto di quanti sono i processi a cui appartiene. e la funzione ritorna il controllo al chiamante.funzione close() La funzione close è utilizzata normalmente per chiudere un descrittore di file. fino a che non sono stati trasmessi tutti. 111 . anche se ci sono ancora processi per quella connessione si usa la funzione shutdown(). In caso di errore (che impedisce questa trasmissione) successivo alla close l’applicazione non se ne accorge e l’altro end system non riceverà alcuni dei dati. -1 in caso di errore. L’argomento socketfd è un descrittore di socket. • Se un socket connesso sockfd è condiviso da più processi (padre e figlio ottenuto da una fork). facendo in modo che la close restituisca il controllo al chiamante solo dopo che tutti i dati nei buffer sono stati correttamente trasmessi e riscontrati. • Per innescare veramente la sequenza di terminazione. è utilizzata per chiudere un socket e terminare una connessione TCP. In tal caso la chiamata alla close(sockfd) per prima cosa decrementa di una unità questo contatore. ovvero non può più essere usato come argomento di read e write. int close (int socketfd). perchè esiste ancora un processo che tiene aperta la connessione. • Normalmente la chiamata alla close() fa marcare “closed” il socket. Il socket allora non può più essere usato dal processo. • Però il TCP continua ad utilizzare il socket trasmettendo i dati che eventualmente stanno nel suo buffer interno. e non innesca la sequenza FIN+ACK+FIN+ACK di terminazione della connessione fino a che tale contatore è maggiore di zero. restituisce 0 se tutto OK.

Il primo argomento socketfd è un descrittore di socket connesso. e quindi non si è specificato nessun indirizzo: in tal caso getsockname permette di conoscere l’indirizzo IP e la porta assegnati dal kernel alla connessione. -1 in caso di errore. In un client dopo una bind in cui come port number è stato specificato il valore 0. In un server multihomed.funzione getsockname() La funzione getsockname serve a conoscere l’indirizzo di protocollo (IP e port number) dell’host locale associato ad un certo descrittore di socket connesso. In tal caso la getsockname restituisce il numero di porta locale assegnato dal kernel. int getsockname ( int socketfd. in cui la funzione metterà l’indirizzo locale della connessione. dopo una connect se non è stata effettuata la bind. restituisce 0 se tutto OK. Il secondo argomento Localaddr è un puntatore ad una struttura di tipo sockaddr. ciòè una volta che si sia stabilita una connessione. struct sockaddr *Localaddr. dopo una accept preceduta da una bind in cui come indirizzo IP LOCALE è stata messa la wildcard INADD_ANY. 112 . Questa funzione viene utilizzata in varie situazioni: In un client. Il terzo argomento ptr_addrlen è un puntatore ad intero in cui la funzione metterà la dimensione della struttura scritta. socklen_t *ptr_addrlen ). la getsockname permette al server di sapere quale indirizzo IP ha la propria interfaccia di rete utilizzata per la connessione. con il quale si è informato il kernel di scegliere lui la porta.

in cui la funzione metterà l’indirizzo remoto della connessione. 113 . int getpeername ( int socketfd. Il primo argomento socketfd è un descrittore di socket connesso. socklen_t *ptr_addrlen ).funzione getpeername() La funzione getpeername serve a conoscere l’indirizzo di protocollo (IP e port number) dell’host remoto associato ad un certo descrittore di socket connesso. Il terzo argomento ptr_addrlen è un puntatore ad intero in cui la funzione metterà la dimensione della struttura scritta. Il secondo argomento Remoteaddr è un puntatore ad una struttura di tipo sockaddr. -1 in caso di errore. restituisce 0 se tutto OK. struct sockaddr *Remoteaddr.

una volta che la connessione TCP sia stata instaurata. cerca di leggere count byte dal file descriptor fd. scrivendoli nel buffer puntato da buf. ovvero significa che l’altro end system ha volutamente chiuso la connessione e quindi il socket non potrà essere più utilizzato per leggere. La funzione read. applicata ad un socket. Oss.: 101a ssize_t è definito in <unistd. se errno vale EINTR significa che il sistema operativo ha dovuto interrompere la system call read. Se count è maggiore di zero viene effettuata la lettura e viene restituito il numero di byte letti (maggiore di zero. sono accedibili come se fossero dei file. Se count è zero la read restituisce zero. Se invece errno ha un altro valore (EBADF. size_t count). ed il socket rimane utilizzabile. anche se lo stream è ancora aperto. Altro caso particolare è quando il socket è stato definito non bloccante nel qual caso se non ci sono byte disponibili viene restituito EAGAIN.I/O su Socket TCP: read() I socket TCP. EINVAL. . …) il socket viene invalidato. void *buf. se tutto è OK). Con questo descrittore è possibile effettuare letture tramite la funzione read.Se viene restituito 0 (zero) significa end-of-file (fine stream). Può accadere che la read() restituisca meno byte di quanti richiesti. ma il socket non è in stato di errore. EFAULT. presenta una particolarità. . . quindi la read può essere ripetuta immediatamente con gli stessi parametri. che restituisce i byte letti dal flusso in entrata. Ciò accade se il buffer a disposizione del socket nel kernel è stato esaurito. mediante un descrittore di file (un intero) ottenuto tramite una socket() o una accept() o una connect().h> ed è un long. che spediscono i byte formando il flusso in uscita. In particolare. ssize_t read (int fd. Sarà necessario ripetere la read (richiedendo il numero dei byte mancanti) fino ad ottenerli tutti.Se viene restituito -1 è accaduto un errore e viene settata la variabile globale errno definita in <errno. e scritture tramite le funzioni write e send.h> con un valore che indica quale errore è avvenuto.

const void *buf. . . Ciò significa che se non c’è abbastanza spazio nel buffer di sistema la write attende fino a che non sono stati spediti abbastanza byte da permettere la scrittura di tutti i byte passati. EPIPE (il socket è stato chiuso dall’altro end system). Se non è stato possibile scrivere nulla la write restituisce -1 senza attendere e setta errno al valore EAGAIN. EFAULT (il buffer buf è fuori dallo spazio di memoria permesso). . Durante questa attesa può capitare che al processo arrivi un segnale (es SIGUSR1) che deve essere gestito dal processo stesso. size_t count).Se count è maggiore di zero viene effettuata la scrittura e viene restituito il numero di byte scritti. . (1) cerca di scrivere fino a count byte nel buffer di sistema corrispondente al file descriptor fd perché siano poi trasmessi. in tal caso la write termina restituendo il numero di byte scritti sul buffer di sistema.Se count è zero la write restituisce zero e non scrive nulla.Se il socket è configurato in modalità bloccante (è il default) la write è bloccante.Se invece il socket è configurato in modalità non bloccante la write scrive sul buffer di sistema il maggior numero di byte possibile senza produrre attesa e poi termina restituendo il numero di byte scritti.I/O su Socket TCP: write() ssize_t write (int fd. cioè attende fino a che tutti i byte sono stati passati al buffer di sistema. Altri possibili errori sono EBADF (file descriptor non valido). . 101b . I byte vengono letti dal buffer puntato da buf. Se invece nessun byte è stato ancora scritto viene restituito -1 e indicato l’errore EINTR.Se viene restituito -1 è accaduto un errore e viene settata la variabile errno con un valore che indica l’errore. EINVAL (file descriptor non permette scritture).

// poi si possono fare tutte le chiamate alla write ris=write(socketfd. nel momento in cui viene invocata la write può capitare che il processo venga terminato dall’arrivo di un segnale SIGPIPE che viene generato dalla write stessa per indicare che il socket che si sta usando è stato chiuso in modo anormale dall’altro end system (inviando un segmento col flag reset) o che non è più utilizzabile. // da eseguire solo una volta.I/O su Socket TCP: write() (2) Indipendentemente da come i socket sono stati settati. nel qual caso la write invece di inviare il segnale restituirà -1 indicando come errore EPIPE.exit(1).} …. 2) utilizzare al posto della write la system call send. Per impedire la terminazione del processo ho due diverse possibilità: 1) istruire il processo per far intercettare i segnali SIGPIPE. if(ris<0){ if(errno==EPIPE) { printf(“chiusura anomala\n”).. in fase di setup del processo if( signal(SIGPIPE. n). in cui può essere specificato di non generare il segnale SIGPIPE ma di restituire -1 indicando come errore EPIPE. } …… } Così però intercetto tutti i segnale SIGPIPE indipendentemente da quale socket (usato dal processo) lo provoca. solo il segnale SIGPIPE di quella particolare 101c invocazione viene intercettato. SIG_IGN) == SIG_ERR ) {perror("signal SIGPIPE failed: ").exit(1). . In questo modo. buff.

Il comportamento viene influenzato dal valore del parametro flags. Se viene specificato MSG_NOSIGNAL la send non solleva l’eccezione di tipo SIGPIPE e quindi non rischia di far terminare il processo. il cui valore può essere 0 oppure viene assegnato mediante OR bit a bit delle seguenti costanti: MSG_OOB. Se il valore di flags è zero la send si comporta come la write. .Se count è maggiore di zero viene effettuata la scrittura e viene restituito il numero di byte scritti. eventualmente zero. const void *buf.Se count è zero la send restituisce zero e non scrive nulla. . (3) cerca di scrivere fino a count byte nel buffer di sistema corrispondente al file descriptor fd perché siano poi trasmessi. size_t count. Se viene specificato MSG_DONTWAIT la send non si blocca bensì scrive il numero di byte possibili nel buffer di sistema e termina restituendo il numero di byte scritti. .Se viene restituito -1 è accaduto un errore e viene settata la variabile errno con un valore che indica l’errore. MSG_NOSIGNAL. MSG_DONTWAIT. int flags). I byte vengono letti dal buffer puntato da buf. 101d .I/O su Socket TCP: send() int send (int fd. restituisce -1 e setta la variabile errno al valore EPIPE. Al contrario. in caso di chiusura anormale della connessione.

la fine della routine write ci dice che sono stati scritti sul buffer del socket quegli nc byte. e verrà restituito dalla write il numero nc di byte copiati. Quando un’applicazione chiama write() per n bite sul socket TCP.I/O su Socket TCP: TCP Output (2) Ogni socket TCP possiede un buffer per l’output (send buffer) in cui vengono collocati temporaneamente i dati che dovranno essere trasmessi mediante la connessione instaurata. Se il buffer del socket e’ più piccolo di n byte. cioè è di tipo bloccante. e possiamo quindi riutilizzare le prime nc posizioni del buffer dell’applicazione. oppure è già parzialmente occupato da dati non ancora trasmessi e non c’è spazio sufficiente. al buffer del socket. il kernel cerca di copiare n byte dal buffer dell’appl. 102 . verranno copiati solo nc<n byte. La dimensione di questo buffer può essere configurata mediante un’opzione SO_SNDBUF. Se il socket ha le impostazioni di default. Ciò non significa affatto che già i dati siano stati trasmessi all’altro end-system.

si implementa la seguente funzione readn.restituisce il numero di byte chieste (e letti) se tutto ok. connessione chiusa. termino // esce e restituisco il numero di byte letti break. ssize_t nread. // restituisco errore } else if (nread == 0) { // EOF. size_t n). } else // continuo a leggere nleft -= nread.nleft). nleft = n. } return(n . void *buf. ssize_t readn (int fd. // return >= 0 } 103a . char *buf. e setta errno -restituisce il numero di byte letti se l’altro end system chiude la connessione . che -restituisce -1 in caso di errore. nleft)) < 0) { if (errno != EINTR) return(-1). Per attendere di ricevere TUTTI i byte richiesti. o leggere i byte già arrivati. si usa la già descritta ssize_t read (int fd. size_t n) { size_t nleft. while (nleft > 0) { if ( (nread = read(fd.I/O su Socket TCP : utility (1) Per attendere di ricevere almeno un byte. buf+n-nleft.

ptr += nwritten. } return(n). n. se tutto OK. MSG_NOSIGNAL )) < 0) { if (errno == EINTR) nwritten = 0. char *ptr. 103b } . buf. /* and call write() again*/ else return(-1). MSG_DONTWAIT|MSG_NOSIGNAL). size_t n) { int nwritten. return(nwritten). ptr. } Per consegnare da zero ad n byte da trasmettere. nleft. }while( (nwritten<0) && (errno==EINTR) ). ptr = buf. while (nleft > 0) { if ( (nwritten = send(fd. size_t n) { size_t nleft. ssize_t nwritten. do { nwritten=send ( fd. const char *buf. nleft = n. const char *buf. /* error */ } nleft -= nwritten. se no restituisce il numero di byte scritti ssize_t write_nowait (int fd.I/O su Socket TCP : utility (2) Per attendere di consegnare al buffer di sistema TUTTI i byte richiesti restituisce -1 in caso di errore e setta errno restituisce il numero di byte da inviare ed inviati. ssize_t writen (int fd. ma senza attendere: restituisce -1 in caso di errore.

Questa trasmissione bidirezionale continua fino a che uno dei due (il client nell’esempio) decide di interrompere la connessione.Interazioni tra Client e Server TCP Per primo viene fatto partire il server. questo risponde trasmettendo alcuni dati. Infine il server chiude a sua volta la connessione. ephemeral port 104 . Nell’esempio (ma non è obbligatorio) il client spedisce una richiesta al server. e tramite la close() chiude la connessione. poi viene fatto partire il client che chiede la connessione al server e la connessione viene instaurata.

Se tutto va a buon fine restituisce 0 nel processo figlio ed un valore maggiore di zero (il pid process identifier) nel processo padre.h> pid_t fork (void). per gestire in parallelo le connessioni che via via vengono instaurate. è possibile per uno dei processi (padre o figlio) chiudere una connessione aperta condivisa (dai due processi) senza con questo impedire all’altro processo di continuare ad utilizzare la connessione. • I descrittori di file e di socket aperti dal padre prima della fork sono condivisi col figlio. • restituisce -1 in caso di errore. #include <unistd. • Questa funzione viene chiamata nel processo (padre=parent). per come funziona la funzione close().funzione fork() La funzione fork è usata per duplicare un processo. e restituisce il risultato in due diversi processi (padre e figlio). • Se il figlio vuole conoscere il pid del padre userà la funzione getppid(). • La fork viene usata per generare delle repliche del processo server. e possono perciò essere usati da entrambi per l’I/O. • Inoltre. 117 .

listen(listenfd. e ripete la accept sul listening socket. usa_nuova_connessione_indicata_da_newsockfd(connfd). una volta risvegliati dalla richiesta di una connessione da parte di un client. listenfd = socket (AF_INET. il processo padre attende nuove richieste di connessione sulla stessa porta. sizeof(Local)). if ( pid !=0) close(connfd). • Invece il descrittore del connected socket (socket connesso) restituito dalla accept resta aperto per il figlio. ) { connfd = accept ( listenfd. in attesa della prossima richiesta di connessione. e subito dopo chiama la fork. connfd. exit(0). /* processo padre */ else { /* processo figlio */ close(listenfd). effettuano una fork() duplicando se stessi (il processo). for( . (struct sockaddr*) &Cli. come i web server. 118 } } Server TCP Concorrenti . int listenfd. Il processo figlio viene dedicato a servire la connessione appena instaurata. e viene utilizzato da questo utilizzato per gestire l’I/O con la connessione. questa situazione si ottiene così: • Il server chiama la accept passando come argomento il socket listening (socket in ascolto). pid_t pid.(1) • Un server banale in attesa su una porta TCP serializza le varie richieste di apertura di una connessione dei client permettendo la connessione ad un solo client per volta. &len). A livello di interfaccia socket. bind ( listenfd. close(connfd). 0). la connessione viene finalmente terminata con la sequenza di FIN. (struct sockaddr*) &Local. • il processo padre chiude il connected socket. . Quando infine il figlio termina il suo lavoro e chiude il connected socket con la close(). 10 ). • Server TCP più evoluti invece. pid = fork(). SOCK_STREAM.

. /* chiuso il listening socket */ il figlio usa il connected socket () close ( connfd ). exit(0).. per quanto riguarda la scelta delle porte. SOCK_STREAM. ){ connfd = accept(listenfd. if ( pid !=0 ) /* processo padre */ close ( connfd ). quella di un’applicazione server collocata su un host con più interfacce di rete.sin_port = htons(6001).sin_addr. Local. 0). (struct sockaddr*) &Local. &len). che vuole permettere le connessioni su una certa porta convenzionale (la 6001) da parte di client che accedono a una qualsiasi delle due interfacce del server. listenfd = socket (AF_INET. sizeof(Local)). /* accetta max 100 richieste simultanee di inizio conness. creando un nuovo socket per la conness.sin_family = AF_INET. da adesso */ listen(listenfd. (struct sockaddr*) &Cli. pid = fork(). 0.s_addr = htonl(INADDR_ANY). 100 ). */ for( . Entriamo un pò nei dettagli del programma appena visto. /* wildcard */ Local.Server TCP Concorrenti (2) Vediamo graficamente cosa capita a livello di TCP e di porte. Local. } 119 } . else { /* processo figlio */ close ( listenfd ). bind ( listenfd. /* accetta la prima conness. Consideriamo la situazione più complicata. /* collega il socket ad un indirizzo IP locale e una porta TCP locale */ memset ( &Local. sizeof(Local) ).

SERVER CLIENT IP = IP_A1 IP = IP_B IP = IP_A2 port = 2222 dopo la listen(). Port Number locale . 6001 . IP_B . * . prima della fork() IP_A1 listening socket ( *. 6001 . 6001 . prima della accept() IP_A1 listening socket ( *. IP remoto.Server TCP Concorrenti (3) La quaterna (IP locale. * ) IP_A2 connected socket ( IP_A2. * . 2222 ) 120 . Port Number remoto) che identifica univocamente una connessione TCP viene di solito chiamata socket pair. * ) IP_A2 dopo la accept() .

IP_B . * . 6001 .dopo la fork() IP_A1 listening socket ( *. * ) IP_A2 Server TCP Concorrenti (4) connected socket ( IP_A2. * . 6001 . 6001 . 6001 . e la close(listenfd) del figlio IP_A1 IP_A2 listening socket ( *. * ) padre figlio connected socket ( IP_A2. 2222 ) 121 . IP_B . 2222 ) padre figlio dopo la close(connfd) del padre.

• Il problema è che.I/O Multiplexing Un’applicazione di rete può avere la necessità di accedere contemporaneamente a più tipi di input e di output. si ha quando l’applicazione effettua una chiamata alla funzione accept(). input di tipo datagram da rete anch’esso eventualmente da più socket contemporaneamente. l’applicazione rimane bloccata fino a che i dati non sono disponibili. se i dati non sono già presenti nella coda del kernel dedicata a quel descrittore. Le funzioni di I/O finora analizzate sono state descritte nel loro funzionamento proprio secondo la modalità standard (bloccante). Analogamente per la write() se i buffer del kernel in cui si deve scrivere il dato è già occupato. quando l’applicazione effettua una read su un certo descrittore di file o di socket. o la write() ha scritto dei dati dell’utente in un buffer del kernel. input di tipo stream da rete eventualmente da più connessioni contemporaneamente. ovvero o la read() ha letto da un buffer del kernel dei dati. Esistono vari modelli di I/O disponibili in ambiente Unix: I/O Bloccante I/O Non Bloccante I/O tramite Multiplexing I/O guidato da signal I/O asincrono (per ora poco implementato) Consideriamo per ora il modello di I/O standard. . per cui quando viene effettuata una richiesta di I/O mediante una chiamata ad una primitiva di tipo read() o write(). che restituisce il controllo solo quando una richiesta di inizio connessione è disponibile (o meglio è già stata soddisfatta ed è nella coda delle 122 connessioni stabilite). ed è impossibile leggere dati eventualmente già pronti sugli altri descrittori di file o socket. la primitiva non restituisce il controllo al chiamante fino a che l’operazione di I/O non è stata effettuata. input di tipo stream dalla tastiera. caratteristico dei socket. ad es. • Un problema analogo.

Quest’ultima possibilità può collassare in un’attesa di durata nulla. perchè ha ricevuto una richiesta di connessione da un client. detta select(). che può essere così definita: 1) o dei dati sono pronti alla lettura in una coda del kernel.I/O Multiplexing Quello che serve è un modo di ordinare al kernek di avvertirci quando. 123 . che: 1) permette di effettuare attesa contemporaneamente su più tipi di canali di I/O in modo da essere risvegliati quando uno di questi canali è disponibile all’I/O in lettura o scrittura o ha verificato un errore. 2) e permette di fissare un limite all’attesa.1g mette a disposizione una primitiva. 4) o quando un socket listening è disponibile a fornire immediatamente un connected socket in risposta ad una chiamata di tipo accept(). 3) o si è verificato un errore in uno dei dispositivi di I/O e quindi una read() o write() restituirebbe il valore -1. si verifica una condizione di disponibilità all’I/O. ma solo di controllare lo stato istantaneo dei vari canali e restituire subito il controllo al chiamante. in modo da essere risvegliati se non accade nulla allo scadere di un certo tempio limite. o ancora nel caso dei socket quando sono disponibili i cosiddetti dati fuori banda (usati solo in casi particolarissimi perchè meno utili di quanto il nome farebbe presupporre). Posix. ovvero permette di non effettuare attesa alcuna. 2) o una coda di output del kernel si è svuotata ed è pronta ad accettare dati in scrittura mediante una write. in un insieme di canali di I/O. e si può accedere mediante una read che restituirà immediatamente il controllo al chiamante con i dati letti.

h> int select ( int maxfdp1. ed è una struttura così fatta: struct timeval { long tv_sec. 124 . const struct timeval *timeout). Si passa un puntatore timeout nullo. /* microsecondi */ } con questa struttura noi possiamo specificare alla select: attesa infinita: attesa fino a che una delle condizioni si è verificata. fd_set *readset. #include <sys/select. L’ultimo argomento timeout dice al kernel quanto aspettare al massimo. /* secondi */ long tv_usec. altrimenti restituisce il numero di descrittori che hanno raggiunto la condizione di disponibilità loro richiesta. SI specifica settando a zero tv_sec e tv_usec. attesa nulla: ritorna subito al chiamante dopo avere fotografato la situazione dei descrittori. In caso di timeout la select restituisce 0.funzione select() La funzione select permette di chiedere al kernel informazioni sullo stato di descrittori di tutti i tipi. ma i kernel non riescono a discriminare solitamente sotto i 10 msec. e di specificare quanto tempo al massimo aspettare. fd_set *exceptset. NB: la timeval specifica microsecondi. La funzione restituisce -1 in caso di errore. 0 se il timeout fissato è scaduto. riguardo a loro disponibilità in lettura scrittura o condizioni eccezionali. fd_set *writeset. attesa limitata: attesa il numero di secondi e microsecondi specificati nella struttura puntata dal puntatore timeout passato.

fd_set *fdset). fd_set *fdset). readset writeset exceptset specificano i descrittori che si vuole controllare rispetivamente per verificare disponibilità alla lettura scrittura o eccezioni (out of band data only). FD_SET ( 4. clear di tutti i bit di fd_set void FD_SET ( int fd. 125 .: fd_set readset. in cui ogni bit corrisponde ad un descrittore. azzero tutto. altrimenti non vi appartiene. !=0 se il bit fd è settato in fd_set 0 se il bit fd non è settato Con queste macro posso settare pulire e controllare l’appartenenza o meno di un descrittore all’insieme. const struct timeval *timeout). &readset ) restituisce 0 Ricordarsi di inizializzare il set (FD_ZERO) altrimenti risultati impredicibili. fd_set *readset. fd_set *writeset. dichiaro la variabile fd_set inizializzo. Es. setta il bit fd in fd_set void FD_CLR ( int fd. Se il bit è settato il descrittore viene considerato appartenente al set. FD_ZERO ( &readset ). FD_SET ( 1. &readset ). clear del bit fd in fd_set int FD_ISSET ( int fd. void FD_ZERO (fd_set *fdset). insieme vuoto 1 appartiene all’insieme 4 “ funzione select() FD_ISSET ( 4. fd_set *exceptset. &readset ). Il tipo fd_set (descriptor set) è un array di interi. fd_set *fdset). FD_SET ( 7. I tre argomenti centrali di tipo fd_set*. &readset ). Esistono delle macro per settare o resettare gli fd_set. &readset ) restituisce != 0 FD_ISSET ( 3.(2) int select ( int maxfdp1.

nel senso che deve avere il valore più alto tra i descrittori settati + 1. che lavora in parallelo senza dover fare delle fork(). 4) non è settato (es. esiste una define che specifica la costante FD_SETSIZE ovvero il numero di descrittori che può contenere la struttura fd_set. Vediamo ora un esempio di uso della select. specifica quali descrittori controllare. fd_set *readset. FD_ISSET(4. chiedendo tramite la macro FD_ISSET() quali descrittori sono settati. Se un descrittore (es.: se i descrittori settati sono 1. e vengono passati per puntatore perchè la select li modifica scrivendoci sopra il risultato. con cui implementiamo un web server. const struct timeval *timeout). Quando la select termina si controllano ciascuno dei 3 fd_set. • se la select restituisce -1 c’e’ stato un errore o è avvenuta una signal. 4 . • Il valore restituito dalla select dice quanti descrittori sono stati settati.(3) int select ( int maxfdp1. funzione select() 126 . fd_set *writeset. fd_set *exceptset. Es. 7 maxfdp1 deve essere 8 = 7+1 I 3 descrittori di set passati per argomento contengono quindi i descrittori da controllare. FD_ISSET(4. N.B. &readset ) != 0) significa che è pronto. Il primo argomento maxfdp1. Se invece il descrittore è settato (es. &readset )== 0) significa che non è pronto. • se la select restituisce 0 significa che è scaduto il timeout.

sockfd. char line[MAXLINE].s_addr = htonl(INADDR_ANY). listenfd. maxfd. servaddr. i < FD_SETSIZE. struct sockaddr_in cliaddr. for (i = 0. LISTENQ).sin_addr. listenfd = socket(AF_INET. connfd. listen(listenfd.sin_family = AF_INET. inizializzazione: typedef SA struct sockaddr . servaddr. (SA *) &servaddr. FD_ZERO(&allset). fd_set rset. client[FD_SETSIZE]. sizeof(servaddr)). /* initialize */ /* index into client[] array */ /* -1 indicates available entry */ (1) 127 . FD_SET(listenfd. servaddr. sizeof(servaddr)). char **argv) { int i. maxfd = listenfd. i++) client[i] = -1. servaddr. socklen_t clilen. SOCK_STREAM. allset.esempio d’uso della select() server che non utilizza la fork() Prima parte del server. maxi.sin_port = htons(SERV_PORT). int nready. int main(int argc. ssize_t n. bzero(&servaddr. 0). &allset). maxi = -1. bind(listenfd.

for (i = 0. if ( FD_ISSET( listenfd. (SA *) &cliaddr. NULL). NULL. i < FD_SETSIZE. ) { rset = allset. if (--nready <= 0) break. &rset)) { if ( (n = Readline(sockfd. if (FD_ISSET(sockfd. FD_CLR(sockfd. line. line. . &clilen). client[i] = -1. n). /* structure assignment */ nready = select(maxfd+1. /* no more readable descriptors */ } } } } (1) 128 . connfd = accept( listenfd. } if (i == FD_SETSIZE) err_quit("too many clients"). FD_SET(connfd. &allset). NULL. /* for select */ if (i > maxi) maxi = i. i++) { /* check all clients for data */ if ( (sockfd = client[i]) < 0) continue. i <= maxi. &rset) ) { /* new client connection */ clilen = sizeof(cliaddr). /* max index in client[] array */ if (--nready <= 0) continue. &rset. &allset). MAXLINE)) == 0) { /*connection closed by client */ close(sockfd). /* save descriptor */ break. i++) if (client[i] < 0) { client[i] = connfd. } else writen(sockfd. /* add new descriptor to set */ if (connfd > maxfd) maxfd = connfd.server che non utilizza la fork() for ( . /* no more readable descriptors */ } for (i = 0.

Advanced TCP Socket .

Il connected socket viene creato dopo la listen() quando arriva una richiesta di connessione dal client. Queste opzioni sono: SO_DEBUG. 130 . SO_RCVBUF e SO_SNDBUF. SO_OOBINLINE. Le opzioni tipiche per i socket possono essere settate solo quando il socket è reso disponibile all’interfaccia di programmazione. l’interfaccia socket implementa la politica seguente: il connected socket eredita dal listening socket alcune opzioni.c e’ implementato un esempio di lettura delle opzioni di default di un socket.unibo. Consideriamo ad es. 3) la funzione ioctl(). quali comportamento bloccante o non bloccante e I/O guidato dai signal.it/~ghini/didattica/sistemi3/SOCKOPTS/checkopts. 2) la funzione fcntl(). modalita’ di utilizzo degli indirizzi. SO_LINGER. quali dimensioni dei segmenti. controlli sulla funzionalità della connessione. il caso di un connected socket ottenuto da un server in risposta ad una chiamata alla accept() a partire da un socket listening. in quanto il socket creato verrà reso disponibile al programmatore solo quando questo farà eseguire la accept(). che ripete operazioni delle fcntl ed inoltre effettua operazioni riguardanti ARP e routing.cs.Le Opzioni per i Socket Le opzioni per i socket sono controllate mediante tre tipi di primitive: 1) le funzioni getsockopt() e setsockopt(). SO_KEEPALIVE. SO_DONTROUTE. Per rendere possibile configurare il socket anche in questa situazione temporanea. invece di assumere le opzioni di default. e tutto il procedimento di instaurazione della connessione avviene senza che il programmatore possa intervenire. In http://www. che permettono di configurare alcune caratteristiche proprie solo dei socket. In tal modo se vogliamo che il connected socket abbia queste opzioni gia’ durante la fase del thee-way-handshake dobbiamo settare in quel modo il listening socket. che invece consente di settare caratteristiche comuni a tutti i descrittori di I/O.

int level. vale nonzero se l’opzione e’ abilitata.assegnare o leggere un valore più complesso. . IPv6(IPPROTO_IPV6). .settare o resettare un flag.optlen contiene la dimensione della variabile puntata da optval. . int optname.funzioni getsockopt() e setsockopt() Il formato di queste due funzioni è il seguente: #include <sys/socket. int optname. a livello IPv4(IPPROTO_IP). socklen_t *optlen ). restituiscono 0 se tutto OK. a livello TCP (level=IPPROTO_TCP). const void *optval.optval è un puntatore ad una variabile di tipo dipendente dall’opzione specificata.optname specifica l’opzione da leggere /settare. socklen_t optlen ). -1 in caso di errore. che contiene il valore nuovo da settare dell’opzione da configurare (caso setsockopt) o che conterrà il valore attuale dell’opzione (caso getsockopt). Nel caso non flags optval punta ad un dato di tipo diverso. su cui operano le funzioni.Il primo argomento socketfd è un descrittore di socket aperto con una socket(). . che vale zero se l’opzione e’ disabilitata. Nel caso dei flags.h> int getsockopt ( int sockfd. come indicato nella seguente tabella: 131 . . .L’argomento level indica a che livello di protocollo deve agire l’opzione: a livello di socket generale (level=SOL_SOCKET). void *optval. int level. Le opzioni possono effettuare due diverse operazioni: . ICMPv6 (IPPROTO_ICMPV6). int setsockopt ( int sockfd. il valore restituito o passato (optval) punta ad un intero.

nelle funzioni getsockopt() e setsockopt() deve essre utilizzato come secondo argomento level=SOL_SOCKET.Tabella delle Opzioni Socket Generiche. cioè di livello Socket. 132 . per getsockopt() e setsockopt() Per queste opzioni.

(1) Consideriamo le principali opzioni caratteristiche di tutti i socket, quelle identificate dal livello SOL_SOCKET. SO_BROADCAST questa opzione abilita o disabilita la possibilità per un socket di spedire messaggi broadcast. Viene applicato solo ai socket datagram (DGRAM) e solo se la rete sottostante lo permette (es: ethernet, non punto a punto). Per default questa opzione è disabilitata, per impedire ad un processo di spedire accidentalmente un datagram in broadcast, ad es. se l’indirizzo IP di destinazione viene preso a linea di comando e si digita per errore un indirizzo di broadcast. In tal caso il kernel si accorge di avere a che fare con un datagram di broadcast il cui invio è disabilitato, e restituisce un errore di tipo EACCES. SO_DEBUG questa opzione è supportata solo da TCP, e ordina al kernel di mantenere informazioni su tutti i pacchetti spediti o ricevuti da/a un certo socket, in una coda circolare. Il programma trcp esaminera’ questa coda. SO_DONTROUTE questa opzione e’ applicata per bypassare il normale meccanismo di routing dei pacchetti in uscita, ad es: per farli uscire da un’interfaccia di rete diversa da quella prevista dalle tabelle interne di routing. Viene usata ad es. dai processi daemon del routing (routed o gated) per instradare un pacchetto sull’interfaccia giusta quando le tabelle di routing sono sbagliate. SO_KEEPALIVE questa opzione è applicata solo agli stream TCP, per verificare se una connessione che da molto tempo non scambia dati debba essere chiusa o no. Il motivo per cui una connessione deve essere chiusa da un end system e’ che l’altro end system a) e’ down, b) non e’ raggiungibile (rete partizionata o problema nel 133 routing), c) non e’ piu’ interessato a quella connessione.

Opzioni Socket Generiche

(2) SO_KEEPALIVE (continuazione) Quando un socket TCP ha questa opzione settata, se nessun dato viene scambiato in una delle due direzioni della connessione per 2 ore, il TCP spedisce un segmento, detto keepalive probe, all’altro end-system, per verificarne la situazione, e si aspetta di ricevere un ACK. 1) se il peer risponde con un ACK tutto e’ OK, ed il TCP mandera’ un nuovo probe dopo altre due ore di inattivita’. 2) se il peer risponde con un segmento RST (reset), significa che e’ andato in crash e poi ha effettuato il reboot. Il socket allora viene chiuso, ed la variabile d’errore del socket settata a ECONNRESET. 3) se non c’e’ risposta dal peer, il TCP riprova a mandare altri 8 segmenti keepalive probe, ogni 75 secondi, aspettando risposta. 3.1) Se non viene ricevuta alcuna risposta il socket e’ chiuso e la var. d’errore settata a ETIMEOUT. 3.2) se invece viene ricevuta un ICMP error in risposta ad uno dei keepalive probe, il socket viene chiuso, e viene restituito l’errore indicato dall’ICMP, che sara’ di tipo EHOSTUNREACH, ovvero l’host non e’ raggiungibile. La specifica Posix.1g stabilisce anche le modalita’ per settare lintervallo di attesa (le 2 ore) ad un valore diverso, ma tale opzione e’ implementata raramente. Questa Opzione SO_KEEPALIVE serve a stabilire se il peer host e’ andato in crash. Invece il crash dell’applicazione peer viene individuato e notificato dal TCP peer. Cioe’ quando nell’altro end system il processo che gestiva il socket va in crash, il TCP di quell’host spedisce un segmento FIN per chiudere la connessione, e questa chiusura può essere individuata con una read() o una select() settata per verificare la possibilita’ di leggere da quel socket. Non esiste altro modo di accorgersi di un crash se non 134 cercando di fare un test per lettura.

Opzioni Socket Generiche

Opzioni Socket Generiche

(3)

SO_RCVBUF e SO_SNDBUF Queste due opzioni servono a modificare la dimensione dei buffer di ricezione e trasmissione del TCP e dell’UDP. Per il TCP la dimensione del buffer di ricezione viene mandata all’atto dell’instaurazione della connessione. E’ quindi necessario che per il server questa opzione sia settata prima della chiamata alla listen(), mentre per il client deve essere settata prima della chiamata alla connect(). Invece per UDP il buffer di ricezione determina la dimensione massima dei datagram che possono essere accettati. SO_REUSEADDR e SO_REUSEPORT Queste due opzioni servono a permettere di effettuare la bind() su porte e indirizzi IP gia’ utilizzati da qualche altro processo. La SO_REUSEADDR ad es. puo’ essere utile nei seguenti casi: a) si cerca di fare la bind per un listening socket che e’ stato chiuso e si vuol fare ripartire, quando ancora esiste un connected socket nato dal listening socket appena chiuso. b) ci sono piu connected socket che lavorano sulla stessa porta di un host ma con IP diversi. E’ il caso dei web server che devono lavorare sulla stessa well know port 80 ma su interfacce diverse. SO_TYPE Questa opzione puo’ essere usata solo in lettura, e restituisce il tipo del socket, SOCK_STREAM o SOCK_DGRAM.

135

e mandando un segment RST (reset) all’altro end-system. /* linger time. nonzero=on */ int l_linger. non spedendo i dati eventualmente bufferizzati per la spedizione. . • Anche in questo caso non sappiamo se il TCP peer ha ricevuto 136 tutti i dati. 1) se l_onoff==0 l’opzione SO_LINGER e’ disabilitata.1g vuole secondi */ } Per default la close() restituisce subito il controllo al chiamante. Posix. ma se alcuni dati rimangono nei buffer di spedizione il TCP tenta di spedirli. e a maggior ragione non sappiamo se l’application peer li ha ricevuti. Opzioni Socket Generiche • In questo modo non sappiamo se il TCP peer ha ricevuto tutti i dati. 2) se l_onoff != 0 e l_linger==0 quando un socket chiama la close(). quindi viene settato il modo di default appena visto. Viene usato come argomento optval della setsockopt() un puntatore ad una struttura di tipo: struct linger { int l_onoff. il TCP chiude la connessione in modo traumatico. /* 0=off . Non si va nello stato TIME_WAIT e si rischia di danneggiare l’apertura di una nuova connessione con gli stessi indirizzi IP e di porta. e nemmeno se li ha ricevuti l’ application peer.(4) SO_LINGER Questa opzione determina le modalita’ di chiusura realizzate dalla funzione close().

passando dallo stato TIME_WAIT. anche se sappiamo che il TCP ha ricevuto i dati non abbiamo garanzie che l’application peer riceva i dati. 3. fino a che si verifica una di queste condizioni: 3. Puo’ capitare infatti che dopo che il TCP peer ha spedito l’ACK per i dati ed il FIN. e quindi la close() e’ terminata. e non passa dallo stato TIME_WAIT. ed allora la funzione close() restituisce il controllo al chiamante con risultato 0.Opzioni Socket Generiche (5) SO_LINGER (continuazione) 3) se l_onoff != 0 e l_linger !=0 quando un socket chiama la close(). .1) tutti i dati sono trasmessi e riscontrati dal TCP peer. l’application peer vada in crash e non riesca a leggere dalla coda del TCP peer. se il socket e’ di tipo bloccante (e’ il default) il TCP tenta di spedire i dati eventualmente bufferizzati per la spedizione.2) oppure scade il tempo assegnato di attesa l_linger e la funzione close restituisce -1 mandando un segment RST (reset) all’altro 137 end-system. Anche in questo caso però.

Vengono scartati anche eventuali dati giunti dopo la shutdown(). int howto).SHUT_RD l’applicazione chiude il socket in lettura. restituisce 0 se tutto OK.. la shutdown spedisce subito il segmento di FIN (anche se il contatore e’ maggiore di zero) ovviamente senza passare avanti agli altri dati bufferizzati per la spedizione. e poi verra’ spedito un SYN segment. e tutti i dati eventualmente gia’ ricevuti dal TCP e presenti nelle code per l’input vengono scartati.mentre la close() decrementa solo il contatore dei processi che utilizzano quel socket e quando il contatore e’ zero spedisce il segmento FYN. Tutti i dati eventualmente presenti nelle code di output verranno spediti. la shutdown da’ la possibilita’ di effettuare chiusure asimmetriche di una connessione. specificando quale direzione deve essere interrotta.SHUT_WR l’applicazione chiude il socket sia in scrittura che in lettura. .mentre la close() chiude entrambe le direzioni della connessione.(1) La funzione shutdown() e’ utilizzata per chiudere una connessione in modo differente rispetto alla close(). -1 in caso di errore. L’argomento howto specifica l’azione che deve essere effettuata sul socket sockfd. resta possibile effettuare le write(). 138 funzione shutdown() . Non è piu’ possibile effettuare le letture. L’argomento socketfd è un descrittore di socket.Infatti: . Questo tipo di chiusura viene detta half-close. ed e’ una delle seguenti: . senza badare al contatore di processi per quel socket. e quindi impedisce di usare con quel socket sia primitive di input sia di output (no read no write dopo close() ). Rimane possibile effettuare delle read() fino a che l’altro end-system non effettua a suo volta un\a close() che fa inviare il SYN segment verso chi aveva effettuato la chiamata alla shutdown(). . int shutdown (int sockfd. per terminare il lato di output della connessione.

vediamo qui una rappresentazione di una half close (SHUT_WR). come se effettuasse due chiamate alla shutdown. con parametro SHUT_RD e poi con parametro SHUT_WR.SHUT_RDWR l’applicazione chiude il socket sia in scrittura che in lettura.funzione shutdown() (2) . 139 .

140 . e procedere poi con una chiamata alla read(). ma lascia aperto il socket in lettura. La read() allora riceve l’end-of-file e termina restituendo 0. Un modo sicuro per sapere se l’applicazione dell’altro end system (non solo il TCP) ha ricevuto i dati è sostituire la chiamata alla close() con una chiamata alla shutdown() usando come secondo argomento la costante SHUT_WR. quindi effettua la close() che manda il segmento FIN di risposta. permettendo di effettuare la chiamata alla read() che altrimenti restituirebbe immediatamente un errore. Si ha allora la garanzia che la applicazione peer ha ricevuto tutti i dati. In questo modo la shutdown manda il segmento di FIN. La read() rimane bloccata fino a che l’application peer termina la lettura di tutti i dati e legge il FIN.Garanzie di Trasmissione Completata (1) Garanzia per il Client che il Server ha ricevuto tutti i dati.

dopo avere ricevuto tutti i dati effettua una write di un byte. In questo modo si ha la garanzia che il processo server ha letto i dati che gli sono stati inviati. Un altro modo per garantire che l’application peer ha ricevuto tutti i dati e’ usare il cosiddetto application-level acknowledgment o application ACK.Garanzie di Trasmissione Completata (2) Garanzia per il Client che il Server ha ricevuto tutti i dati. Il server. Quando il client ritorna dalla read() effettua la close() che manda il FIN segment. che rappresenta l’attesa per un ACK a livello di applicazione. 141 . Il client dopo avere spedito tutti i suoi dati si blocca su una read aggiuntiva. che rappresenta l’ACK a livello di applicazione. Solo allora l’application peer effettua la close().

Tabella delle Opzioni Socket per TCP. 142 . nelle funzioni getsockopt() e setsockopt() deve essere utilizzato come secondo argomento level=IPPROTO_TCP. cioè di livello IPPROTO_TCP. per getsockopt() e setsockopt() Per queste opzioni.

Opzioni Socket per TCP Queste opzioni si usano specificando il livello IPPROTO_TCP. Il valore di default e’ di 7200 sec. ma producono ritardo per applicazioni che scambiano piccoli dati e quasi solo in una direzione. TCP_MAXSEG Questa opzione restituisce o setta il maximum segment size (MSS) per la connessione TCP. TCP_NODELAY Per default il TCP abilita il cosiddetto Algoritmo di Nagle (Nagle Algorithm) il cui scopo e’ di diminuire il numero di segmenti piccoli trasmessi. Nagle algorithm Enabled Nagle algorithm DISABLED settata TCP_NODELAY opt. 143 . sperando di poter accodare l’ACK ad un segmento di dati. Questo algoritmo è legato all’algoritmo dell’ACK ritardato (delayed ACK algorithm) che fa aspettare il TCP per circa 50-200 msec) prima di dare l’ack ad un segmento. TCP_KEEPALIVE Questa opzione specifica il tempo (in sec. (2 ore). Questi due algoritmi assieme cercano di minimizzare il numero di segmenti trasmessi. come nel caso di client telnet che prevedono l’ACK per ogni singolo carattere battuto a tastiera. Questa opzione e’ realmente attivata solo quando l’opzione SO_KEEPALIVE e’ abilitata.) di inattivita’ della connessione prima che venga fatto partire il segmento di keepalive probe. L’opzione TCP_NODELAY disabilita l’uso di questi algoritmi.

255.B. Operazioni necessarie per ricevere datagram UDP via multicast: creazione di un socket UDP collegamento ad una porta del protocollo UDP (bind) join ad un indirizzo di multicast (setsockopt).1 (all-host group) vi si devono registrare tutti gli host di una sottorete. Se con la bind specifico l’indirizzo IP di multicast a cui faro’ il join. il default e’ 1.0.0. N. Operazioni opzionali per spedire datagram UDP via multicast: Specificare il TTL dei datagram UDP multicast.0. Indirizzi di multicast: Classe D.0 . 224. 224. Se con la bind non specifico un indirizzo IP. non si esce dalla subnet.0. per la porta UDP specificata. che implementano il multicast.255.Opzioni Socket per IP: Multicast (1) Consente di inviare uno stesso datagram UDP a piu’ destinatari. i quali devono essersi preventivamente registrati come membri del gruppo di multicast caratterizzato da un certo indirizzo di multicast. Se non si specifica.0. Operazioni necessarie per smettere di ricevere via multicast: leave del gruppo multicast (setsockopt).2 (all-router group) vi si devono registrare tutti gli host di una sottorete.B. potro’ ricevere solo datagram UDP di multicast per quello indirizzo di multicast. N.255 Indirizzi di Multicast Speciali: 224.239. Specificare se il mittente deve ricevere copia del datagram (se fa parte del gruppo di multicast.0. che implementano il multicast. 144 . posso ricevere datagram UDP sia originati da multicast che da unicast.

utilizzando il livello IPPROTO_IP. se questo appartiene 145 al gruppo IP_MULTICAST_TTL u_char IP_MULTICAST_LOOP u_char . vengono settate mediante la primitiva setsockoption. ed i comandi e le strutture dati specificati in tabella.Opzioni per il Multicast in IPv4 Opzioni di livello IPPROTO_IP Le opzioni utilizzate per configurare un socket UDP per la trasmissione/ricezione di datagram per un certo indirizzo di multicast. opzione IP _ADDR_MEMBERSHIP tipo di dato uso struct ip_mreq collega il socket UDP ad uno specifico gruppo di multicast IP_DROP_MEMBERSHIP struct ip_mreq toglie il socket UDP dal gruppo di multicast IP_MULTICAST_IF struct in_addr indica l’interfaccia di rete da usare per spedire i datagram di multicast specifica il TTL dei datagram di multicast da spedire indica se i datagram di multicast spediti devono andare anche al mittente.

j++) { Fromlen=sizeof(struct sockaddr).7"). SOCK_DGRAM.imr_multiaddr.imr_interface. SOL_SOCKET. // local_port_number bind ( socketfd. (struct sockaddr*)&From.sin_family = AF_INET. OptVal = 1. remote_port_number = ntohs(From.sin_port). */ Mreq. sprintf(string_remote_ip_address. IP_ADD_MEMBERSHIP. sizeof(OptVal) ). &Fromlen)..s_addr = inet_addr("234. sizeof(Mreq) ).6. setsockopt (socketfd. (char *)&OptVal.s_addr = htonl(INADDR_ANY).s_addr = INADDR_ANY. string_remote_ip_address. msg. IPPROTO_IP.sin_addr. (struct sockaddr*) &Local. SO_REUSEADDR. Local. setsockopt(socketfd. Local..inet_ntoa(From. msg. for (j=1.sin_addr). 0. (char *)&Mreq. msglen = recvfrom ( socketfd. sizeof(Local)). printf("ricevuto msg: \"%s\" len %d. Local. Mreq.5. socketfd = socket (AF_INET. port %d\n".j<=1000. } 146 . remote_port_number). msglen.esempio: receiver di datagram UDP per gruppo di Multicast struct ip_mreq Mreq.sin_port = htons( 6001). 0). (int)SIZEBUF.. /* join the multicast group."%s". from host %s.. .

6.5. IPPROTO_IP.sin_port = htons( 6001 ). /* disable loopback .. setsockopt(socketfd.esempio: sender di datagram UDP per gruppo di Multicast struct ip_mreq Mreq. To. /* send to the address */ sendto ( socketfd. socketfd = socket (AF_INET. int ttl. // remote_port_number addr_size = sizeof(struct sockaddr_in). SOCK_DGRAM. sizeof(loopback)). strlen(msg) . (char *)&ttl.j<=1000. IPPROTO_IP. (char *)&loopback. (struct sockaddr*)&To. /* set TTL to traverse up to multiple routers */ ttl = TTL_VALUE.imr_interface. sizeof(Mreq) ).6.s_addr = inet_addr("234. IP_ADD_MEMBERSHIP. // per default 1 setsockopt(socketfd. IP_MULTICAST_TTL. OptVal = 1.imr_multiaddr.j++) { To.s_addr = inet_addr("234.. int loopback..5. IP_MULTICAST_LOOP.sin_family = AF_INET. . NON NECESSARIO se non si fa join */ loopback =0.sin_addr. 0). msg. (char *)&Mreq. IPPROTO_IP.7"). sizeof(ttl)). setsockopt(socketfd. addr_size). Mreq.s_addr = INADDR_ANY. /* join the multicast group.. 0. } 147 .7" ). NON NECESSARIO per SPEDIRE */ Mreq. To. for (j=1.

. • configurare l’I/O di un descrittore come guidato dai signal usando nella fcntl il comando F_SETFL con argomento O_ASYNC. ma questo solo se e’ stato settato il socket come guidato dai signal. un connected socket ha lo stesso proprietario del listenung socket • conoscere il proprietario corrente per il descrittore (F_GETOWN). /* int arg */ ).h> int fcntl (int fd.. restituisce -1 in caso di errore. I due flags che ci interessano per i socket sono: O_NONBLOCK non blocking I/O O_ASYNC signal-driven I/O notification 148 . int cmd.. ovvero definire qual’e’ il processo che viene avvisato con il signal SIGIO quando il descrittore e’ disponibile all’I/O. Si ricorda che un socket appena creato con la socket() non ha proprietario. In particolare la funzione fcntl permette di: • configurare l’I/O di un descrittore come non bloccante o bloccante usando nella fcntl il comando F_SETFL con argomento O_NONBLOCK (O_BLOCK). un altro valore dipendente dal comando in caso tutto OK. Le proprieta’ di un descrittore di file (e di socket) sono definite mediante alcuni flags. che la funzione fcntl permette di leggere (usando come secondo argomento cmd=F_GETFL) e di settare (usando come secondo argomento cmd=F_SETFL). #include <fcntl. • settare un proprietario per il descrittore (comando F_SETOWN).Opzioni Socket tramite fcntl (1) La funzione fcntl significa “file control” e serve ad effettuare alcune operazioni su vari descrittori non solo di socket ma in generale di file. ottenendo che in corrispondenza di ogni modifica della situazione del socket venga scatenato un SIGIO signal.

O_NONBLOCK )<0) exit(1). settare i nuovi flags per il socket mediante il comando F_SERFL. flags |= O_NONBLOCK.). sockfd = socket(. flags &= ∼O_NONBLOCK. quindi il socket diventa “non bloccante”.0)) <0 ) exit(1).F_SETFL.F_GETFL.0)) <0 ) exit(1).Opzioni Socket tramite fcntl (2) In generale la procedura corretta per settare un flags consiste nel leggere i flags del descrittore mediante il comando F_GETFL.flags) <0 ) 149 exit(1). int sockfd.F_SETFL. sockfd = socket(.. Procedura errata.F_SETFL. if ( fcntl(sockfd.. sockfd = socket(... if ( fcntl(sockfd. int flags.. quindi il socket diventa “bloccante”. if ( (flags=fcntl(sockfd. Procedura corretta int flags. Nel seguente esempio viene settato il flag O_NONBLOCK.. vengono azzerati tutti gli altri flags.F_GETFL... Infine in questo esempio viene resettato il flag O_NONBLOCK. sockfd.). sockfd.. if ( (flags=fcntl(sockfd.).flags) <0 ) exit(1). modificare solo il flag che interessa con un OR logico. . if ( fcntl(sockfd.

. o il numero negativo ottenuto cambiando di segno il numero di gruppo. pid ) <0 ) exit().). La funzione restituisce un intero positivo (il pid del proprietario) o un numero negativo diverso da -1.. sockfd = socket(.Opzioni Socket tramite fcntl Se per un socket viene settato il flags O_ASYNC.O_SETOWN. che e’ l’identificatore del gruppo cambiato di segno. -gid ) <0 ) exit(). o e’ in errore.O_SETOWN . 150 .. settare il proprietario di un socket int sockfd. viene lanciato un signal SIGIO al processo proprietario del socket. se il proprietario esiste... settare il gruppo di un socket int sockfd. o ai processi del gruppo proprietario del socket. quando il socket diventa disponibile all’I/O. if ( fcntl(sockfd. sockfd = socket(.pid. if ( fcntl(sockfd.).. gid. (3) Per settare il proprietario si usa la fcntl con secondo argomento F_SETOWN passando come terzo parametro o il numero positivo pid del processo proprietario. Per conoscere il proprietario si usa la fcntl con secondo argomento F_GETOWN.

• Con un socket Non Bloccante di tipo UDP invece il problema non sussiste perche’ nessun datagram viene mantenuto in un buffer perche’ non c’e’ ritrasmissione. il socket viene settato Nonblocking il comportamento delle funzioni cambia: I/O Non Bloccante Operazioni di input (read. Con un socket Non Bloccante. Quindi una primitiva di output di un socket UDP non blocca mai il processo che l’effettua. Operazioni di Accettazione Richiesta Connessione (accept). se l’operazione di input non puo’ essere terminata (cioe’ non c’e’ nemmeno un byte per il socket TCP o non c’e’ nemmeno un datagram per il socket UDP) la funzione ritorna immediatamente al chiamante restituendo il codice d’errore EWOULDBLOCK. Se invece. Con un socket Non Bloccante. se l’operazione di accept non puo’ restituire immediatamente una nuova connessione perche’ la coda delle connessioni accettate e’ vuota. Operazioni di output (write. 151 . recvfrom ).(1) Per default un socket e’ bloccante. Se invece e’ rimasto spazio. Cio’ significa che quando il socket deve effettuare un’operazione di I/O. se questa non puo’ essere terminata immediatamente il processo si mette in attesa aspettando la fine dell’operazione. la funzione ritorna immediatamente al chiamante restituendo il codice d’errore EWOULDBLOCK. e la write restituisce il numero di byte scritti. se l’operazione di scrittura non puo’ essere effettuata nemmeno in parte perche’ manca spazio nei buffer del TCP in cui andare a scrivere i dati. • Con un socket Non Bloccante di tipo TCP. quindi il datagram UDP viene immediatamente spedito e vada come vada. mediante la fcntl vista prima. viene effettuata una scrittura di una porzione di dati minore o uguale alla dimensione del buffer libero. sendto ). la funzione ritorna immediatamente al chiamante restituendo il codice d’errore EWOULDBLOCK.

(2) Con un socket UDP la funzione connect non e’ realmente bloccante. Invece per il TCP la chiamata del client alla connect() di un socket bloccante deve aspettare che. la chiamata del client alla connect() fa iniziare il procedimento di inizio connessione. ma se la connessione non puo’ immediatamente essere instaurata la connect() termina con un codice d’errore EINPROGRESS ad indicare che l’operazione continua a livello inferiore. Si noti che puo’ capitare che la connect() restituisca OK perche’ e ‘ riuscita ad avere immediatamente risposta ed ha instaurato la connessione richiesta. 152 . e quindi puo’ essere ripetuta la connect(). cioe’ fa spedire il primo SYN segment. quindi anche nel caso di socket Non Bloccante la connect per l’UDP effettua sempre l’operazione interamente e restituisce il controllo al chiamante. perche’ deve solo scrivere nel kernel l’indirizzo IP e il numero di porta dell’altro end-system. all’interno del three way handshake. il client abbia ricevuto l’ACK per il SYN segment. sia stato ricevuto. Se il socket TCP e’ di tipo Non Bloccante. e quindi puo’ veramente bloccare il processo. Con una select() sara’ possibile accorgersi di quando il socket impegnato nella richiesta di connessione ha stabilitpo la connessione.I/O Non Bloccante Operazioni di Richiesta Connessione (connect).

.. sizeof(Serv)). } ris=select(socketfd+1....s_addr = inet_addr(string_remote_ip_address). (struct sockaddr*) &Local. .&fdw))) { ris=connect(socketfd. (struct sockaddr*) &Serv.... Serv..... /* SETTO IL SOCKET COME NON BLOCCANTE */ set_socket_non_blocking(socketfd). sizeof(Serv) ). if (ris == SOCKET_ERROR) { if(errno==EISCONN) { printf ("connessione gia' esistente...... SOCK_STREAM. memset ( &Serv.sin_addr.Connect Non Bloccante (1) socketfd = socket(AF_INET. OK\n")... /* socket e‘ bloc*/ } else exit(1). if ((FD_ISSET(socketfd. 0). Serv. non terminata */ FD_ZERO(&fdr). /* ora socket e' non bloccante */ ... 0..(struct sockaddr*) &Serv...&fdr.... sizeof(Serv))..NULL)... /* conness.&fdr).NULL..... /* connessione NON riuscita */ } } 153 . FD_SET(socketfd. } else { /* (ris == SOCKET_ERROR) */ if(errno!=EINPROGRESS) exit(1). sizeof(Local)).... /* ora socket e' bloccante */ . /* connection request...&fdw. qui va messo il necessario per la bind ...sin_port = htons(remote_port_number).sin_family = AF_INET.. Serv..&fdr))||(FD_ISSET(socketfd.. fdw=fdr. bind(socketfd.... set_socket_blocking(socketfd). NON BLOCCANTE */ ris = connect(socketfd.. if (ris != SOCKET_ERROR) { /* connessione riuscita subito */ set_socket_blocking(socketfd).

F_SETFL. } /* SETTO IL SOCKET COME BLOCCANTE */ int set_blocking(int socketfd) { int flags. /* errore */ flags &= (~O_NONBLOCK).F_SETFL.0)) <0 ) return(0). if ( (flags=fcntl(socketfd. } N.flags) <0 ) return(0). if ( fcntl(socketfd. if ( fcntl(socketfd.F_GETFL. /* errore */ return(1).B.F_GETFL. per l’esempio completo vedere nella home page 154 .0)) <0 ) return(0).Connect Non Bloccante (2) /* SETTO IL SOCKET COME NON BLOCCANTE */ int set_non_blocking(int socketfd) { int flags. /* errore */ return(1). /* errore */ flags |= O_NONBLOCK.flags) <0 ) return(0). if ( (flags=fcntl(socketfd.