Autunno 2002 Prof. Roberto De Prisco -04: I/O multiplexing Università degli studi di alerno Laurea e Diploma in Informatica Problema 04.2 Un programma deve gestire due input simultaneamente tandard input (leggere da tastiera) Un socket (leggere dal socket) Abbiamo visto un esempio in cui il client era bloccato a leggere da standard input Non poteva leggere il FIN sul socket Normalmente una funzione di I/O si blocca se non ci sono dati da leggere erve un modo per poter aspettare da più canali di input Il primo che produce dati viene letto 1
Modelli di I/O 04.3 Vari modelli di Input/Output 1. Blocking 2. Nonblocking 3. I/O multiplexing 4. Guidato dai segnali 5. Asincrono sincroni incrono: il processo si blocca (quando chiama l operazione di lettura) fino alla conclusione dell operazione In una operazione di lettura da un canale di I/O possiamo distinguere due fasi 1. Attesa per i dati da parte del kernel 2. opia dei dati dal kernel al processo che deve usarli Blocking I/O 04.4 applicazione kernel Recvfrom ystem call Non ci sono datagram pronti FAE 1: attesa BLOATA datagram pronto opia datagram FAE 2: copia Processo continua (elabora il datagram) Ritorna OK opia completata 2
Nonblocking I/O 04.5 applicazione kernel Recvfrom ystem call Non ci sono datagram pronti Recvfrom Recvfrom EWOULDBLOK EWOULDBLOK ystem call ystem call datagram pronto FAE 1: attesa opia datagram BLOATA FAE 2: copia Processo continua (elabora il datagram) Ritorna OK opia completata I/O multiplexing 04.6 applicazione kernel select ystem call Non ci sono datagram pronti BLOATA Recvfrom Ritorna pronto ystem call datagram pronto opia datagram FAE 1: attesa BLOATA FAE 2: copia Processo continua (elabora il datagram) Ritorna OK opia completata 3
I/O guidato dai segnali 04.7 applicazione signal ystem call kernel Non ci sono datagram pronti FAE 1: attesa GETORE EGNALE Recvfrom EGNALE ystem call datagram pronto opia datagram BLOATA FAE 2: copia Processo continua (elabora il datagram) Ritorna OK opia completata I/O asincrono 04.8 applicazione aio_read ystem call kernel Non ci sono datagram pronti FAE 1: attesa datagram pronto opia datagram FAE 2: copia GETORE EGNALE Processo continua (elabora il datagram) EGNALE opia completata 4
Funzione select 04.9 #include <sys/select.h> #include <sys/time.h> int select(int maxfd, fd_set readset, fd_set writeset, fdset exceptionset, const struct timeval *timeout); Valore di ritorno: se errore, 0 se timeout, numero di descrittori pronti Permette di aspettare che uno o più file descriptor siano pronti per essere letti Il timeout è dato dalla struttura struct timeval { long tv_sec; long tv_usec; Parametri select 04.10 Timeout 1. Puntatore nullo: aspetta senza timeout (fino a che un descrittore è pronto) 2. truttura con un timeout non zero: aspetta fino al timeout, poi ritorna anche se non ci sono descrittori pronti Anche se possiamo specificare i microsecondi alcuni kernel arrotondano a multipli di 10 microsecondi Alcuni sistemi Linux modificano la struttura timeout (vale il tempo rimanente al momento del ritorno) 3. truttura con un timeout pari a 0: non aspettare, ritorna immediatamente (polling) File descriptor da controllare Readset: pronti per la lettura Writeset: pronti per la scrittura Exceptionset: condizioni particolari Arrivo di dati fuori banda su un socket Informazioni di controllo da uno pseudo terminale 5
Parametri select 04.11 Per descriveri gli insiemi si usa la struttura fd_set che è un insieme di bit void FD_ZERO(fd_set *fdset); Azzera la struttura fdset void FD_ET(int fd, fd_set *fdset); Mette a 1 il bit relativo al file descriptor fd void FD_LR(int fd, fd_set *fdset); Mette a 0 il bit relativo al file descriptor fd int FD_IET(int fd, fd_set *fdset); ontrolla se il bit relativo al file descriptor fd è a 1 La costante FD_ETIZE (select.h) è il numero di descrittori in fd_set (solitamente 1024) maxfd: è il numero massimo di descrittori effetivamente usati Usato per efficienza dal kernel Es. se siamo interessati ai descrittori 1,4,7,9 maxfd deve valere 10 (i file descriptor iniziano da 0) Descrittori pronti 04.12 Quando un socket descriptor è pronto per essere usato? ocket in lettura Quando c è almeno un byte da leggere La soglia si può cambiare con le opzioni dei socket Il socket è stato chiuso in lettura Es. è stato ricevuto il FIN L operazione di lettura ritorna EOF Il socket è un listening socket e ci sono delle connessioni completate è un errore L operazione di lettura ritornerà e errno specificherà l errore 6
Descrittori pronti 04.13 ocket in scrittura Il numero di byte di spazio disponibile nel buffer del kernel è maggiore di 2048 La soglia si può cambiare con le opzioni dei socket L operazione di scrittura ritorna il numero di byte effettivamente passati al livello di trasporto Il socket è stato chiuso in scrittura Un operazione di scrittura genera IGPIPE è un errore L operazione di scrittura ritornerà e errno specificherà l errore Eccezioni per socket Arrivo di dati fuori banda echoclient versione select (1) 04.14 void client_echo_select(file *fp, int sockfd) { int maxfdl; fd_set rset; char sendline[maxline], recvline[maxline]; int n; echocli-slct.c FD_ZERO(&rset); for( ; ; ) { FD_ET(fileno(fp), &rset); FD_ET(sockfd, &rset); maxfdl = MAX(fileno(fp), sockfd) + 1; if( select(maxfdl, &rset, NULL, NULL, NULL) < 0 ) err_sys("select error"); if( FD_IET(sockfd, &rset) ) { if ( (n = reti_readline(sockfd, recvline, MAXLINE)) < 0) { if( errno == EPIPE ) { err_msg( %s [%d]: server disconnesso, FILE, LINE ); break; else err_sys("readline error"); 7
echoclient versione select (2) 04.15 if (n == 0) err_quit( %s [%d]: server disconnesso, FILE, LINE ); fputs(recvline, stdout); if( FD_IET(fileno(fp), &rset) ) { if( fgets(sendline, MAXLINE, fp) == NULL) return; if( (reti_writen(sockfd, sendline, strlen(sendline))) < 0) err_sys("write error"); Il client riesce a gestire sia l input da tastiera che l input dal socket e il server termina viene spedito un EOF sul socket Il client lo riceve e termina la connessione enza select il client se ne sarebbe accorto dopo echoclient e select 04.16 ondizioni gestite da select in lettura su stdin ed un socket Data o EOF stdin socket lient TP RT data FIN 8
top-and-wait 04.17 Il client opera in modalità stop-and-wait: pedisce una linea di input e si blocca in attesa della risposta del server echo Tempo 0 dati Tempo 4 echo Tempo 1 dati Tempo 5 echo Tempo 2 dati Tempo 6 echo Tempo 3 dati Tempo 7 echo Batch input 04.18 i spediscono le richieste consecutivamente senza aspettare le risposte, che arriveranno dopo Tempo 0 d1 Tempo 4 d5 d4 d3 d2 r1 Tempo 1 d2 d1 Tempo 5 d6 d5 d4 d3 r1 r2 Tempo 2 d3 d2 d1 Tempo 6 d7 d6 r1 d5 r2 d4 r3 Tempo 3 d4 d3 d2 d1 Tempo 7 d8 r1 d7 r2 d6 r3 d5 r4 9
hutdown della connessione 04.19 Quando il client finisce di spedire non può chiudere il socket i possono esser ancora dati in arrivo i deve chiudere il socket solo in scrittura e lasciarlo aperto in lettura pedire il FIN solo in una direzione #include <sys/socket.h> int shutdown(int sockfd, int howto); Valore di ritorno: se errore, 0 se OK howto = HUT_RD, HUT_WR, HUT_RDWR echoclient versione shutdown 04.20 void client_echo_shutdown(file *fp, int sockfd) { int maxfdl, stdineof; fd_set rset; char sendline[maxline], recvline[maxline]; int n; echocli-shtd.c FD_ZERO(&rset); for( ; ; ) { FD_ET(fileno(fp), &rset); FD_ET(sockfd, &rset); maxfdl = MAX(fileno(fp), sockfd) + 1; if( select(maxfdl, &rset, NULL, NULL, NULL) < 0 ) err_sys("select error"); if( FD_IET(sockfd, &rset) ) { if ( (n = reti_readline(sockfd, recvline, MAXLINE)) < 0) { if( errno == EPIPE ) { err_msg("%s [%d]: server disconnesso", FILE, LINE ); break; else err_sys("readline error"); 10
echoclient versione shutdown 04.21 if (n == 0) { if( stdineof == 1 ) return; else { err_msg("%s [%d]: server disconnesso", FILE, LINE ); exit(); fputs(recvline, stdout); if( FD_IET(fileno(fp), &rset) ) { if( fgets(sendline, MAXLINE, fp) == NULL) { stdineof = 1; shutdown(sockfd, 1); FD_LR(fileno(fp), &rset); continue; if( (reti_writen(sockfd, sendline, strlen(sendline))) < 0) err_sys("write error"); elect per il server Possiamo usare select anche nel server 04.22 Al posto di creare un figlio per ogni connessione elect può leggere da tutti i client connessi trutture dati utilizzate Array rset, contiene file descriptor dei socket utilizzati dal server (sia listening che connessi) Array client, contiene interi che indicano fd client 1 2 3 rset fd0 fd1 fd2 fd3 fd4 fd5 0 0 0 0 0 0 FD_ETIZE 11
trutture dati per il server 04.23 Il server crea il listening socket chiamando listen upponiamo che gli standard file siano aperti e che il fd ritornato da listen sia 3 Tale informazione verrà memorizzata in rset rset fd0 fd1 fd2 fd3 fd4 fd5 0 0 0 1 0 0 Il server chiama select per leggere da tutti i socket (file descriptor) aperti All inizio c è solo il il listening socket, su fd 3 Quindi il parametro maxfd di select deve essere 4 trutture dati per il server 04.24 Quando un client stabilisce una connessione verrà creato un socket per la connessione con la funzione accept upponiamo che il fd ritornato è 4 client 1 2 3 4 rset fd0 fd1 fd2 fd3 fd4 fd5 0 0 0 1 1 0 FD_ETIZE Di nuovo il server chiama select per leggere da tutti i socket (file descriptor) aperti Ora ci sono due socket, su fd 3 (listening) e fd 4 (connessione) Quindi il parametro maxfd di select deve essere 5 12
trutture dati per il server 04.25 upponiamo che un altro client si connette, verrà creato un nuovo socket upponiamo che il fd ritornato è 5 client 1 2 3 4 5 rset fd0 fd1 fd2 fd3 fd4 fd5 0 0 0 1 1 1 FD_ETIZE Di nuovo il server chiama select per leggere da tutti i socket (file descriptor) aperti Ora ci sono tre socket, su fd 3 (listening), e fd 4 e fd 5 per le due connessioni Quindi il parametro maxfd di select deve essere 6 trutture dati per il server 04.26 upponiamo ora che la prima connessione (quella che usa fd 4) venga chiusa client 1 2 3 5 rset fd0 fd1 fd2 fd3 fd4 fd5 0 0 0 1 0 1 FD_ETIZE Il server chiama select per leggere da tutti i socket (file descriptor) aperti Ora ci sono due socket, su fd 3 (listening), e fd 5 (connessione) Quindi il parametro maxfd di select deve essere 6 13
Echo server versione select (1) 04.27 int main(int argc, char **argv) { int listenfd, connfd, sockfd; int i, maxi, maxfd; int ready, client[fd_etize]; char buff[maxline]; fd_set rset, allset; ssize_t n; struct sockaddr_in servaddr, cliaddr; socklen_t cliaddr_len; echosrv-slct.c if( (listenfd = socket(af_inet, OK_TREAM, 0)) < 0) err_sys("socket error"); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(inaddr_any); servaddr.sin_port = htons(port); if( (bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr))) < 0) err_sys("bind error"); if( listen(listenfd, BAKLOG) < 0 ) err_sys("listen error"); Echo server versione select (2) 04.28 maxfd = listenfd; /* inzializza il numero di descrittori */ maxi = ; for ( i = 0; i < FD_ETIZE; i++) client[i] = ; /* inizializza l'array client a */ FD_ZERO(&allset); /* inizializza a zero tutti i descrittori */ FD_ET(listenfd, &allset); /* setta il descrittore di ascolto */ for ( ; ; ) { rset = allset; /* insieme descrittori da controllare per la lettura */ if( (ready = select(maxfd+1, &rset, NULL, NULL, NULL)) < 0 ) err_sys("select error"); if( FD_IET(listenfd, &rset) ) { /* richiesta ricevuta dal listening socket */ cliaddr_len = sizeof(cliaddr); if( (connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &cliaddr_len)) < 0) err_sys("accept error"); for(i = 0; i < FD_ETIZE; i++) if( client[i] < 0 ) { /* cerca il primo posto libero per il nuovo il descrittore */ client[i] = connfd; break; if( i == FD_ETIZE ) err_quit("troppi client"); FD_ET(connfd, &allset); /* setta connfd per la select */ if( connfd > maxfd ) maxfd = connfd; /* aggiorna maxfd */ if( i > maxi ) maxi = i; /* aggiorna maxi */ if( --ready <= 0 ) continue; /* se non ci sono altri socket pronti riprendi da select */ 14
Echo server versione select (3) 04.29 for( i = 0; i <= maxi; i++ ) { /* controlla tutti i socket attivi per controllare se sono leggibili */ if( (sockfd = client[i]) < 0 ) continue; if ( FD_IET(sockfd, &rset) ) { /* se sockfd è leggibile invoca la readline */ if ( (n = reti_readline(sockfd, buff, MAXLINE)) == 0) { /* connessione chiusa dall'altro endpoint */ close(sockfd); /* rimuovi sockfd dalla lista di socket che la select deve controllare */ FD_LR(sockfd, &allset); client[i] = ; /* cancella sockfd da client */ else reti_writen(sockfd, buff, n); if ( --ready <= 0 ) break; Denial of ervice Attack 04.30 onsideriamo la seguente situazione che può accadere con il server visto poco fa Un client si connette, spedisce un solo byte (che non sia un newline) e non fa più nulla Il server chiama readline che leggerà il singolo byte ma si bloccherà nella prossima chiamata a read in attesa di un newline Il server è bloccato e nessun altro client riceverà il servizio Denial of ervice Attack Un client riesce a far i modo che il server non risponda più ad altri client 15
Denial of ervice Attack 04.31 Il problema deriva dal fatto che il server si blocca in una funzione relativa ad un client mentre ci sono anche altri client Potrebbe andare bene se il server deve gestire un solo client Possibili soluzioni Usare un singolo processo per ogni client Utilizzare un timeout sulle operazioni di I/O Usare I/O non-blocking 16