Alma Mater Studiorum - Universita' di Bologna
Sede di Cesena
Reti di Calcolatori
Esercitazione 4
Implementazione di TFTP in C / XDR
Copyright © 2006-2014 by D. Romagnoli & C. Salati
1
Servizio di file transfer TFTP in C e XDR su UDP
• Questa esercitazione, come la numero 3, richiede l’implementazione
di un servizio di file transfer.
• Anche in questo caso il protocollo applicativo utilizzato per fornire
questo servizio e’ derivato dal protocollo TFTP.
• In questo caso pero’ il protocollo applicativo (Layer 7) deve essere
descritto (come parte dell’esercitazione) in forma di sintassi astratta
tramite XDR.
• Il servizio e’ quindi implementato utilizzando i servizi del Layer di
Presentazione.
• Come Servizio di Trasporto si usera’ quello fornito dal protocollo UDP.
Poiche’ questo servizio e’ di tipo non affidabile occorrera’ gestire il
caso di perdita di datagram (in particolare i PDU di tipo DAT e ACK).
• Il linguaggio di programmazione da utilizzare e’ il C (oltre che l’XDR).
• Si dovra’ realizzare una coppia di programmi, un client e un server, e
il server dovra’ essere implementato come un filtro Unix interfacciato
2
alla rete dal superserver inetd dell’esercitazione 1.
Servizio applicativo TFTP vs. protocol entity TFTP
• Come nel caso dell’esercitazione 3, quello che si chiede di implementare e’
• sia il protocollo applicativo (di Layer 7) TFTP, lati client e server,
• che due moduli, client e server, di applicazioni utente TFTP.
• I due diversi layer dovrebbero essere mantenuti il piu’ possibile separati.
• In questo caso, pero’, il Layer di Presentazione (ovviamente anch’esso
presente) viene implementato utilizzando il sistema di programmazione XDR.
• La struttura delle due applicazioni client e server deve quindi essere la
seguente:
User Application
TFTP
Protocol Entity
Generato automaticamente da rpcgen
a partire dalla sintassi astratta descritta
in XDR (+XDR library)
TSAP
Presentation
Layer
Transport Layer
Layer Utente
Layer 7 - Application
Layer 6 - Presentation
3
Specifiche
.1
1. Le specifiche per l’applicazione utente, sia lato client che lato server,
devono essere derivate da quelle utilizzate per l’esercitazione 3.
2. Le specifiche per le protocol entity TFTP devono essere adattate al fatto
che in questo caso il protocollo di trasporto utilizzato e’ UDP.
3. Protocol entity TFTP, lato client:
• Non hanno senso le due operazioni, previste in esercitazione 3, di
apertura e chiusura della sessione di file transfer.
• L’interazione con il server (sessione) inizia tramite l’invio del primo
PDU di WRQ o RRQ (della sessione di file transfer).
La gestione del primo PDU di sessione dovra’ quindi essere diversa
da quella dei successivi PDU di WRQ e RRQ (vedi seguito).
• L’interazione con il server (la sessione) viene terminata in modo
fisiologico per timeout, in quanto a valle di un trasferimento di file
non ne e’ iniziato uno successivo per un tempo sufficientemente
lungo.
• L’interazione con il server puo’ anche terminare in modo patologico a
seguito di errori di comunicazione (e.g. caduta delle comunicazioni)
o di protocollo, e con l’eventuale scambio di un PDU di ERR.
• Continua 
4
Specifiche
.2
3. Protocol entity TFTP, lato client ( continua):
• Oltre ad una terminazione per timeout fisiologica dovra’ essere prevista
anche una terminazione per timeout patologica, dovuta a problemi di
comunicazione o alla morte del pari remoto.
• Deve essere gestito il protocollo di interazione con i server UDP che
prevede che l’indirizzo mittente della prima risposta ricevuta (quella
relativa al primo PDU di sessione inviato, di tipo WRQ o RRQ) debba
diventare l’indirizzo destinazione di tutte le comunicazioni future della
sessione.
4. Protocol entity TFTP, lato server:
• Deve essere gestita la terminazione per timeout della sessione di file
transfer con il client: terminato un trasferimento file il client non ne
richiede piu’ un altro.
• Oltre ad una terminazione per timeout fisiologica dovra’ essere prevista
anche una terminazione per timeout patologica, dovuta a problemi di
comunicazione o alla morte del pari remoto.
• L’interazione con il client puo’ anche terminare in modo patologico a
seguito di un errore di protocollo e per la ricezione di un PDU di ERR.
5
Specifiche per il server
.1
1. Il server deve poter essere utilizzabile come server (sia concorrente che
sequenziale) che si offre in rete tramite il superserver: deve cioe’ essere
strutturato come un filtro Unix.
2. Il servizio deve seguire lo schema di servizio UDP visto a lezione, cioe’:
• rispondere al cliente non tramite il socket well known ma tramite una
porta effimera,
• lasciare il socket well known disponibile a ricevere il primo
messaggio WRQ/RRQ di richiesta di nuovi clienti.
3. In realta’, per come funziona il superserver:
• e’ lui che determina se un servizio si comporta o meno in modo
concorrente;
• e’ lui che: (a) crea il socket effimero su cui il cliente ottiene
effettivamente il servizio, (b) lo connette al cliente, e gli trasferisce il
primo messaggio ricevuto dal cliente sulla porta well known, (c) lo
rimappa sui file descriptor 0 e 1.
4. Il server specifico si limita a interagire come filtro Unix con un singolo
cliente, il cliente cui e’ connesso il socket che gli e’ stato passato sui file
6
descriptor 0 e 1 dal superserver.
Specifiche per il server
.2
1. Prima dell’attivazione del server specifico, il superserver ha ridiretto
l’input stream e l’output stream del socket connesso sul quale il
cliente deve essere servito sui file descriptor 0 e 1.
2. Il server specifico si limita a interagire con il cliente inviandogli
messaggi tramite il file descriptor 1 e leggendo i messaggi ricevuti
da questo tramite il file descriptor 0.
3. Poiche’ il socket acceduto tramite i file descriptor 0 e 1 e’ connesso
(alla porta del client), il server puo’ operare su di essi tramite le
system call read() e write() (rispettivamente).
 N.B.: il server puo’ anche sapere l’indirizzo del client cui e’
connesso utilizzando la system call getpeername().
4. I file descriptor 0 e 1 sono accessibili nel programma C anche
tramite le variabili stdin e stdout.
7
Specifiche per il server
.3
1. Detto serverTftpUdp il file eseguibile del programma server, il file di
configurazione del superserver dovra’ contenere una linea del tipo:
./serverTftp udp 20069 nowait
2. Alla ricezione di un PDU di ERR il server (la sessione) deve
terminare.
3. In caso di terminazione di errore il server deve indicare la cosa
tracciando l’evento sul file stderr ereditato dal superserver
(presumibilmente la shell di attivazione del superserver).
4. Durante una interazione (sessione) con il client devono poter
essere trasferiti diversi file, sia in una direzione che nell’altra.
5. La sessione di file transfer viene considerata chiusa (in modo
normale) dal server se, esaurito il trasferimento di un file, passa un
tempo “abbastanza” lungo (dimensione definita staticamente)
senza che il client richieda il trasferimento di un altro file.
• In questo caso il server/filtro Unix termina la propria
esecuzione.
8
Specifiche per il server
.4
1. Notare che le specifiche per il server richiedono anche di poterlo
attivare in modo diretto, al di fuori del contesto superserver
(opzionale, per consentire il debug interattivo).
 Vedi le stringhe di attivazione previste per distinguere i due
casi in esercitazione 3.
2. Quando e’ attivato direttamente il server e’ responsabile di gestire
l’offerta del servizio sulla porta well known.
3. Poiche’ l’attivazione diretta e’ utilizzata solo allo scopo di
debugging del programma, il fatto che il servizio offerto sia
sequenziale o concorrente e’ irrilevante.
 Il server, in questo caso, deve essere realizzato in modo da
facilitare al massimo il lavoro di debugging.
 Quindi conviene (e’ necessario) realizzare un servizio
sequenziale, servizio fornito direttamente dal server (non
tramite un processo figlio).
4. Quando, essendo stato attivato in modo diretto, il server decide di
terminare una sessione di file transfer, esso ritorna in attesa del
9
prossimo cliente sulla porta well known del servizio.
Specifiche per il client
• Il client, alla ricezione della risposta al primo messaggio, deve:
• Estrarre dal datagram UDP l’indirizzo della porta mittente.
 E’ tramite questa porta che ricevera’ effettivamente il servizio
da parte del server.
• Connettere il proprio socket a questo indirizzo.
• (Di conseguenza) utilizzare questo nuovo indirizzo come
indirizzo destinatario nel seguito della sua interazione con il
server (per il trasferimento non solo di questo ma anche di altri
file).
• Notare che questo tipo di comportamento del client e’ compatibile
con qualunque comportamento/implementazione del server,
 e.g. anche se il server fosse sequenziale e fornisse
effettivamente il proprio servizio attraverso la porta well known.
• Notare che quando si dice “primo messaggio” si intende primo
messaggio in assoluto della sessione (ovviamente di tipo WRQ o
RRQ).
10
Affidabilita’ .1
• Il protocollo TFTP e’ a conferma di ricezione positiva con ritrasmissione, cioe’
gestisce la perdita dei PDU attraverso un meccanismo di ritrasmissione a
timeout.
 A differenza dell’esercitazione 3, in cui il servizio di trasporto utilizzato era
quello affidabile del TCP, e in cui il meccanismo dell’acknowledgement
serviva solo per il flow control, qui esso serve anche (soprattutto) al
recupero degli errori.
• Il mittente del file spedisce il PDU DAT #N e si mette in attesa di una risposta
usando una select() con timeout definito. Quando la select() termina:
 Se la terminazione e’ dovuta alla ricezione di un PDU (ACK o ERR,
altrimenti si tratta di un errore di protocollo) gestisce il PDU (se e’ un PDU di
ACK controlla che il numero di blocco sia corretto, altrimenti considera che
ci sia stato un errore di protocollo).
 Se la terminazione e’ dovuta all’esaurimento del timeout (cioe’ in assenza di
ACK entro il termine atteso) ritrasmette il PDU DAT #N;
 Se la terminazione e’ dovuta ad un errore (di ricezione o di protocollo) invia
un PDU di ERR al ricevitore (se possibile!) e termina.
• Si deve prevedere un limite massimo di ritrasmissioni (definito a compile time in
modo condiviso tra client e server) superato il quale il trasmettitore inviera’ 11
al ricevitore un PDU di ERR e terminera’ la sessione.
Affidabilita’ .2
• Il ricevitore del file e’ in attesa di PDU di tipo DAT. Alla ricezione del
PDU DAT #N si comportera’ nel modo seguente:
•
se l’ultimo ACK spedito era il numero N-1 allora si tratta di un
nuovo PDU DAT: ne copia il contenuto nel file ed invia l’ACK #N;
•
se l’ultimo ACK spedito era proprio il numero N allora si tratta di
una ritrasmissione del PDU DAT #N: lo scarta ed invia l’ACK #N;
•
in tutti i rimanenti casi (per qualunque altro valore del campo
block#), inviera’ un PDU di errore al mittente del file e terminera’.
• Il ricevitore del file non ritrasmette mai ACK a timeout!
 Ma in ogni caso e’ opportuno prevedere che la ricezione del
prossimo PDU di DAT avvenga sotto la supervisione di un timeout
di salvaguardia: allo scadere di questo timeout il ricevitore, se non
ha ricevuto alcun PDU di DAT, decide che il mittente del file (o la
rete) ha dei problemi e termina (eventualmente inviando al
mittente un PDU di ERR).
 Esercizio: come si correla questo timeout lato ricevitore con il
valore del timeout lato trasmettitore e il numero massimo di 12
retry? Specificare nella soluzione!
Affidabilita’ .3.1
• Un caso particolare e’ costituito dall’attesa (da parte del client, ovvio!)
della risposta ai PDU RRQ e WRQ.
Qui bisogna anche distinguere 2 sottocasi:
1. attesa della risposta al primo PDU RRQ/WRQ della sessione,
quello che apre la sessione stessa.
2. attesa della risposta a un PDU RRQ/WRQ che non e’ il primo
della sessione.
• Questa distinzione e’ significativa in generale, ma lo e’ particolarmente
nel nostro caso (quando l’architettura server e’ basata sulla presenza
di un superserver) perche’ se il client, a fronte dello scadere del
timeout, trasmette un nuovo PDU (ritrasmissione di RRQ/WRQ o PDU
di ERR)
• nel primo caso questo e’ inviato alla porta well known, quindi al
superserver,
• nel secondo caso esso e’ inviato al server specifico.
13
Affidabilita’ .3.2
• La gestione consigliata e’ la seguente:
• Nel caso scada il timeout sul primo PDU RRQ/WRQ della
sessione, abortire semplicemente la sessione senza inviare piu’
niente al server.
Esercizio: cosa succederebbe se il client ripetesse l’invio del PDU
RRQ/WRQ? Immaginare lo scenario in cui ad essere persa dalla
rete fosse stata la risposta al primo PDU inviato dal client.
Specificare nella soluzione!
• Nel caso scada il timeout su un PDU RRQ/WRQ che non e’ il primo
della sessione, mandare al server un PDU di ERR e abortire il
trasferimento file informandone l’operatore.
• E’ evidente che un server, deve gestire la ricezione di un PDU di ERR
anche come primo PDU di un trasferimento file.
N.B.: e’ quello che capiterebbe nel primo caso se, a seguito dello
scadere del timeout, il client, anziche’ limitarsi a terminare, decidesse
di inviare anche questo PDU.
14
Attesa a timeout
• L’esercitazione richiede la gestione di timeout per 3 scopi differenti:
1. Effettuare la ritrasmissione di un PDU di DAT per il recupero dei PDU persi
dalla rete.
 Quando attendiamo un PDU di ACK (timeout to1).
2. Accorgersi, lato server, della terminazione di una sessione di file transfer.
 Quando attendiamo un PDU di WRQ o RRQ (timeout to0).
 Ovviamente questo caso non e’ significativo per il PDU di WRQ o RRQ
relativo al primo trasferimento della sessione.
3. Come salvaguardia per gestire il caso di scomparsa inattesa
dell’interlocutore.
 Quando attendiamo un PDU di DAT (timeout to2).
• Useremo timeout di dimensione fissa, definita a compile time in modo
coordinato da client e server, ma differenziata per i diversi casi (to0 ≠ to1 ≠ to2).
• Infatti i diversi casi sono alternativi tra loro.
• Il dimensionamento congruente dei timeout per i diversi casi e
l’argomentazione delle scelte effettuate sono parte integrante
dell’esercitazione.
15
Attesa a timeout
• Supervisioneremo a tempo l’operazione di ricezione dalla rete
utilizzando la system call select().
• Detti fd il socket descriptor di interesse e to il timeout
int fd_set fds;
struct timeval to = {2, 0};
// to pari a 2sec
FD_ZERO(&fds); FD_SET(fd,&fds);
int ready = select(fd+1, &fd_set, 0, 0, &to);
• Il valore ritornato dalla select() sara’:
• 0< in caso di errore
 in questo caso bisogna preoccuparsi di gestire il codice di errore
EINTR?
Perche’? Commentare nella soluzione!
• 0 nel caso di timeout esaurito.
• 1 nel caso ci sia qualcosa da leggere sul socket descriptor fd.
16
PDU del protocollo TFTP: mappa di byte
read-request
(RRQ)
write-request
(WRQ)
data
(DAT)
Acknowledgment
(ACK)
error
(ERR)
opcode
string
EOS
string
EOS
01
filename
0
mode
0
2 bytes
n bytes
1 byte
n bytes
1 byte
opcode
string
EOS
string
EOS
02
filename
0
mode
0
2 bytes
n bytes
1 byte
n bytes
1 byte
opcode
03
block#
data
2 bytes
2 bytes
n bytes, 0 <= n <= 512
opcode
04
block#
2 bytes
2 bytes
opcode
string
EOS
05
errcode
errstring
0
2 bytes
2 bytes
n bytes
1 byte
17
PDU del protocollo: sintassi astratta sconsigliata .0
• Notare che non e’ piu’ presente il campo length. Perche’?
• Si presuppone che la sintassi sia descritta nel file XDR tftp.x.
• Quella che viene presentata cone esempio e’ una sintassi astratta
sconveniente e piena di errori formali:
 Cercate di definire una buona sintassi astratta.
 Tenete conto dei limiti del compilatore rpcgen.
• La struttura di PDU (il tipo dei singoli campi del PDU) descritta nella
mappa di byte non deve essere considerata vincolante:
 Siete incoraggiati a modificarla nel modo piu’ opportuno.
 Sfruttate bene i tipi primitivi e i costruttori di tipo previsti da XDR.
 Non dovete cercare una definizione XDR che produca come
sintassi di trasferimento quella descritta nella mappa di byte di
TFTP.
E’ l’informazione contenuta nei PDU TFTP che dovete
18
considerare.
PDU del protocollo: sintassi astratta sconsigliata .1
const MAX_FILE_NAME_LENGTH = 500; /* max lunghezza del nome del file */
const MAX_MODE_LENGTH
= 6;
/* max lunghezza del modi di
trasferimento */
const MAX_DAT_LENGTH
= 512; /* max lunghezza di un blocco dati */
const MAX_STRING_ERR_LEN
= 100; /* max lunghezza della stringa di
errore */
enum OpType {
RRQ = 0x01,
/* PDU di richiesta operazione get */
WRQ = 0x02,
/* PDU di richiesta operazione put */
DAT = 0x03,
/* PDU di trasferimento di un blocco dati */
ACK = 0x04,
/* PDU di ACK */
ERR = 0x05
/* PDU di errore */
};/* enumerato che identifica i 5 sottotipi di PDU del protocollo TFTP */
19
PDU del protocollo: sintassi astratta sconsigliata .2
struct RrqAndWrqMsg {
char
fileName<MAX_FILE_NAME_LENGTH>;
char
mode<MAX_MODE_LENGTH>;
}; /* tipo XDR per i pacchetti RRQ e WRQ */
struct DatMsg {
short
blkn;
char
dat<MAX_DAT_LENGTH>;
}; /* tipo XDR per il pacchetto DAT */
struct AckMsg {
short
blkn;
}; /* tipo XDR per il pacchetto ACK */
struct ErrMsg {
ErrCode
errCode;
char
errStr<MAX_STRING_ERR_LEN>;
}; /* tipo XDR per il pacchetto ERR */
20
PDU del protocollo: sintassi astratta sconsigliata .3
enum ErrCode {
NOT_DEFINED
= 0, /* errore non definito: vedi stringa di errore
se presente */
FILE_NOT_FOUND
= 1,
ACCESS_VIOLATION
= 2,
DISK_FULL
= 3, /* disco pieno o allocazione eccessiva */
ILL_OP_TFTP
= 4, /* operazione tftp illegale */
UNKNOWN_PORT
= 5,
FILE_ALREADY_EXIST
= 6,
NO_SUCH_USER
= 7
}; /* enumerato XDR che definisce i codici ci errore */
union TftpMsg switch (OpType opType) {
case RRQ:
RrqAndWrqMsg
rrqMsg;
case WRQ:
RrqAndWrqMsg
wrqMsg;
case DAT:
DatMsg
datMsg;
case ACK:
AckMsg
ackMsg;
case ERR:
ErrMsg
errMsg;
}; /* tipo XDR per generico PDU TFTP tra client e server */
21
Suggerimenti e domande
• Suggerimenti di Denis Romagnoli per rendere il codice piu’ leggibile:
1. Wrappare la parte di network UDP dentro apposite funzioni che
gestiscono l’interazione con la rete e la gestione del cambio di
indirizzo sul primo messaggio.
2. Wrappare l’utilizzo dell’XDR con opportune funzioni.
3. Definire una opportuna funzione per gestire il timeout.
• Domande di Claudio Salati:
1. Come mai nella descrizione del PDU a mappa di byte abbiamo
eliminato il campo length?
2. Fare questo esercizio utilizzando come servizio di trasporto
quello stream oriented del TCP sarebbe stato un problema:
perche’?
Vedi anche l’esercizio alla fine della lezione sui socket.
3. Perche’ la sintassi astratta data come esempio e’ sconveniente
da usare?
Perche’ dico che e’ piena di errori anche se, provando a
22
compilarla, rpcgen la accetta?
Generazione del codice eseguibile
• Per compilare la sintassi astratta dare il comando:
rpcgen –C tftp.x
che produce due file:
• tftp.h
che deve essere incluso nei file sorgente del vostro programma,
sia lato client che lato server.
• tftp_xdr.c
che deve essere compilato e linkato sia al programma client che
a quello server.
• Per la compilazione del client dare il comando :
gcc –g client.c tftp_xdr.c –o client
• Per la compilazione del server dare il comando:
gcc –g server.c tftp_xdr.c –o server
• Si e’ fatta l’ipotesi che i file tftp.x, client.c e server.c siano tutti nella
stessa directory.
23
• L’output della compilazione/link saranno i 2 eseguibili client e server.
Scarica

PDU del protocollo TFTP