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.