From 24b55de696cfd8c0d7238a6b536802aba233912d Mon Sep 17 00:00:00 2001 From: Simone Piccardi Date: Sun, 17 Aug 2003 17:17:50 +0000 Subject: [PATCH] Aggiunta trattazione del crash del server, eseguite alcune correzioni alla trattazione di SIPIPE --- ChangeLog | 5 ++ gapil.tex | 2 +- ipc.tex | 2 +- listati/endian.c | 13 +++ signal.tex | 8 +- socket.tex | 61 +++++++++++++- elemtcp.tex => tcpsock.tex | 159 ++++++++++++++++++++++++++++++++++--- 7 files changed, 232 insertions(+), 18 deletions(-) create mode 100644 listati/endian.c rename elemtcp.tex => tcpsock.tex (93%) diff --git a/ChangeLog b/ChangeLog index 2d217eb..f1126aa 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2003-08-17 Simone Piccardi + + * tcpsock.tex: Questo è il nuovo nome di elemtcp.tex, più adatto + allo stato attuale della struttura del libro. + 2003-06-16 Simone Piccardi * socket.tex: Correzione nomi funzioni di conversione da Stefano diff --git a/gapil.tex b/gapil.tex index f17df27..1d18318 100644 --- a/gapil.tex +++ b/gapil.tex @@ -140,7 +140,7 @@ \include{ipc} \include{network} \include{socket} -\include{elemtcp} +\include{tcpsock} %\include{simpltcp} \appendix \include{netlayer} diff --git a/ipc.tex b/ipc.tex index bd3aa42..56caf92 100644 --- a/ipc.tex +++ b/ipc.tex @@ -112,7 +112,7 @@ essere bloccante (qualora non siano presenti dati), inoltre se si legge da una pipe il cui capo in scrittura è stato chiuso, si avrà la ricezione di un EOF (vale a dire che la funzione \func{read} ritornerà restituendo 0). Se invece si esegue una scrittura su una pipe il cui capo in lettura non è aperto il -processo riceverà il segnale \errcode{EPIPE}, e la funzione di scrittura +processo riceverà il segnale \const{SIGPIPE}, e la funzione di scrittura restituirà un errore di \errcode{EPIPE} (al ritorno del gestore, o qualora il segnale sia ignorato o bloccato). diff --git a/listati/endian.c b/listati/endian.c new file mode 100644 index 0000000..9929323 --- /dev/null +++ b/listati/endian.c @@ -0,0 +1,13 @@ +int endian(void) +{ +/* + * Variables definition + */ + short magic, test; + char * ptr; + + magic = 0xABCD; /* endianess magic number */ + ptr = (char *) &magic; + test = (ptr[1]<<8) + (ptr[0]&0xFF); /* build value byte by byte */ + return (magic == test); /* if the same is little endian */ +} diff --git a/signal.tex b/signal.tex index 855edad..296d579 100644 --- a/signal.tex +++ b/signal.tex @@ -651,13 +651,13 @@ resto del sistema. L'azione predefinita di questi segnali è di terminare il processo, questi segnali sono: \begin{basedescript}{\desclabelwidth{2.0cm}} -\item[\const{SIGPIPE}] Sta per \textit{Broken pipe}. Se si usano delle pipe o - delle FIFO è necessario che, prima che un processo inizi a scrivere su di - essa, un'altro abbia aperto la pipe in lettura (si veda +\item[\const{SIGPIPE}] Sta per \textit{Broken pipe}. Se si usano delle pipe, + (o delle FIFO o dei socket) è necessario, prima che un processo inizi a + scrivere su una di esse, che un'altro l'abbia aperta in lettura (si veda \secref{sec:ipc_pipes}). Se il processo in lettura non è partito o è terminato inavvertitamente alla scrittura sulla pipe il kernel genera questo segnale. Se il segnale è bloccato, intercettato o ignorato la chiamata che - lo ha causato fallisce restituendo l'errore \errcode{EPIPE} + lo ha causato fallisce, restituendo l'errore \errcode{EPIPE}. \item[\const{SIGLOST}] Sta per \textit{Resource lost}. Viene generato quando c'è un advisory lock su un file NFS, ed il server riparte dimenticando la situazione precedente. diff --git a/socket.tex b/socket.tex index 0d35c3e..23d9206 100644 --- a/socket.tex +++ b/socket.tex @@ -767,12 +767,37 @@ parte dal bit meno significativo \label{fig:sock_endianess} \end{figure} +Si può allora verificare quale tipo di endianess usa il proprio computer con +un programma elementare che si limita ad assegnare un valore ad una variabile +per poi ristamparne il contenuto leggendolo un byte alla volta. Il codice di +detto programma, \file{endtest.c}, è nei sorgenti allegati, allora se lo +eseguiamo su un PC otterremo: +\begin{verbatim} +[piccardi@gont sources]$ ./endtest +Using value ABCDEF01 +val[0]= 1 +val[1]=EF +val[2]=CD +val[3]=AB +\end{verbatim}%$ +mentre su di un Mac avremo: +\begin{verbatim} +piccardi@anarres:~/gapil/sources$ ./endtest +Using value ABCDEF01 +val[0]=AB +val[1]=CD +val[2]=EF +val[3]= 1 +\end{verbatim}%$ + + La \textit{endianess}\index{endianess} di un computer dipende essenzialmente dalla architettura hardware usata; Intel e Digital usano il \textit{little endian}, Motorola, IBM, Sun (sostanzialmente tutti gli altri) usano il -\textit{big endian}. Il formato della rete è anch'esso \textit{big endian}, -altri esempi di uso di questi formati sono quello del bus PCI, che è -\textit{little endian}, o quello del bus VME che è \textit{big endian}. +\textit{big endian}. Il formato dei dati contenuti nelle intestazioni dei +protocolli di rete è anch'esso \textit{big endian}; altri esempi di uso di +questi due diversi formati sono quello del bus PCI, che è \textit{little + endian}, o quello del bus VME che è \textit{big endian}. Esistono poi anche dei processori che possono scegliere il tipo di formato all'avvio e alcuni che, come il PowerPC o l'Intel i860, possono pure passare @@ -781,6 +806,36 @@ in Linux l'ordinamento resta sempre lo stesso, anche quando il processore permetterebbe di eseguire questi cambiamenti. +Per controllare quale tipo di ordinamento si ha sul proprio computer si è +scritta una piccola funzione di controllo, il cui codice è riportato +\figref{fig:sock_endian_code}, che restituisce un valore nullo (falso) se +l'architettura è \textit{big endian} ed uno non nullo (vero) se l'architettura +è \textit{little endian}. + +\begin{figure}[htb] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \includecodesample{listati/endian.c} + \end{minipage} + \normalsize + \caption{La funzione \func{endian}, usata per controllare il tipo di + architettura della macchina.} + \label{fig:sock_endian_code} +\end{figure} + +Come si vede la funzione è molto semplice, e si limita, una volta assegnato +(\texttt{\small 9}) un valore di test pari a \texttt{0xABCD} ad una variabile +di tipo \ctyp{short} (cioè a 16 bit), a ricostruirne una copia byte a byte. +Per questo prima (\texttt{\small 10}) si definisce il puntatore \var{ptr} per +accedere al contenuto della prima variabile, ed infine calcola (\texttt{\small + 11}) il valore della seconda assumendo che il primo byte sia quello meno +significativo (cioè, per quanto visto in \secref{fig:sock_endianess}, che sia +\textit{little endian}). Infine la funzione restituisce (\texttt{\small 12}) +il valore del confonto delle due variabili. + + + + \subsection{Le funzioni per il riordinamento} \label{sec:sock_func_ord} diff --git a/elemtcp.tex b/tcpsock.tex similarity index 93% rename from elemtcp.tex rename to tcpsock.tex index a49c48c..20f143e 100644 --- a/elemtcp.tex +++ b/tcpsock.tex @@ -1,4 +1,4 @@ -%% elemtcp.tex +%% tcpsock.tex %% %% Copyright (C) 2000-2003 Simone Piccardi. Permission is granted to %% copy, distribute and/or modify this document under the terms of the GNU Free @@ -2241,16 +2241,157 @@ nessun errore al ritorno di \funcd{accept} quanto un errore di -\subsection{Il crollo del server} +\subsection{La terminazione precoce del server} \label{sec:TCP_server_crash} -Un secondo caso critico è quello in cui si ha il crollo del server. In tal -caso si suppone che il processo del server termini per un errore fatale, cosa -che potremo simulare inviandogli un segnale di terminazione. La conclusione -del processo comporta la chiusura di tutti i file aperti, compresi tutti i -socket relativi a connessioni stabilite; questo significa che al momento del -crollo del servizio il client riceverà un FIN dal server in corrispondenza -della chiusura del socket. +Un secondo caso critico è quello in cui si ha una terminazione prococe del +server, ad esempio perché il programma ha un crash. In tal caso si suppone che +il processo termini per un errore fatale, cosa che potremo simulare +inviandogli un segnale di terminazione. La conclusione del processo comporta +la chiusura di tutti i file descriptor aperti, compresi tutti i socket +relativi a connessioni stabilite; questo significa che al momento del crollo +del servizio il client riceverà un FIN dal server in corrispondenza della +chiusura del socket. + +Vediamo allora cosa succede nel nostro caso, facciamo partire una connessione +con il server e scriviamo una prima riga, poi terminiamo il server con un +\texttt{C-c}. A questo punto scriviamo una seconda riga e poi un'altra riga +ancora. Il risultato finale della sessione è il seguente: +\begin{verbatim} +[piccardi@gont sources]$ ./echo 192.168.1.141 +Prima riga +Prima riga +Seconda riga dopo il C-c +Altra riga +[piccardi@gont sources]$ +\end{verbatim} + +Come si vede il nostro client, nonostante la connessione sia stata interrotta +prima dell'invio della seconda riga, non solo accetta di inviarla, ma prende +anche un'altra riga prima di terminare senza riportare nessun +errore. + +Per capire meglio cosa è successo conviene analizzare il flusso dei pacchetti +utilizzando un analizzatore di traffico come \cmd{tcpdump}. Il comando +permette di selezionare, nel treffico di rete generato su una macchina, i +pacchetti che interessano, stampando a video (o salvando su disco) il loro +conteuto. Non staremo qui ad entrare nei dettagli dell'uso del programma, che +sono spiegati dalla pagina di manuale; per l'uso che vogliamo farne quello che +ci interessa è, posizionandosi sulla macchina che fa da client, selezionare +tutti i pacchetti che sono diretti o provengono dalla macchina che fa da +server. In questo modo (posto che non ci siano altre connessioni col server, +cosa che avremo cura di evitare) tutti i pacchetti rilevati apparterranno alla +nostra sessione di interrogazione del servizio. + +Il comando \cmd{tcpdump} permette selezioni molto complesse, basate sulle +interfacce su cui passano i pacchetti, sugli indirizzi IP, sulle porte, sulle +caratteristiche ed il contenuto dei pacchetti stessi, inoltre permette di +combinare fra loro diversi criteri di selezione con degli operatori logici; +quando un pacchetto che corrisponde ai criteri di selezione scelti viene +rilevato i suoi dati vengono stampati sullo schermo (anche questi secondo un +formato configurabile in maniera molto precisa). + +Lanciando il comando prima di ripetere la sessione di lavoro mostrata +nell'esempio precedente potremo allora catturare tutti pacchetti scambiati fra +il client ed il server; i risultati\footnote{in realtà si è ridotta la + lunghezza dell'output rispetto al reale tagliando alcuni dati non necessari + alla comprensione del flusso.} prodotti in questa occasione da \cmd{tcpdump} +sono allora i seguenti: +\begin{verbatim} +[root@gont gapil]# tcpdump src 192.168.1.141 or dst 192.168.1.141 -N -t +tcpdump: listening on eth0 +gont.34559 > anarres.echo: S 800922320:800922320(0) win 5840 +anarres.echo > gont.34559: S 511689719:511689719(0) ack 800922321 win 5792 +gont.34559 > anarres.echo: . ack 1 win 5840 +gont.34559 > anarres.echo: P 1:12(11) ack 1 win 5840 +anarres.echo > gont.34559: . ack 12 win 5792 +anarres.echo > gont.34559: P 1:12(11) ack 12 win 5792 +gont.34559 > anarres.echo: . ack 12 win 5840 +anarres.echo > gont.34559: F 12:12(0) ack 12 win 5792 +gont.34559 > anarres.echo: . ack 13 win 5840 +gont.34559 > anarres.echo: P 12:37(25) ack 13 win 5840 +anarres.echo > gont.34559: R 511689732:511689732(0) win 0 +\end{verbatim} + +Le prime tre righe vengono prodotte al momento in cui lanciamo il nostro +client, e corrispondono ai tre pacchetti del three way handshake. L'output del +comando riporta anche i numeri di sequenza iniziali, mentre la lettera +\texttt{S} indica che per quel pacchetto si aveva il SYN flag attivo. Si noti +come a partire dal secondo pacchetto sia sempre attivo il campo \texttt{ack}, +seguito dal numero di sequenza per il quale si da il ricevuto; quest'ultimo, a +partire dal terzo pacchetto, viene espresso in forma relativa per maggiore +compattezza. Il campo \texttt{win} in ogni riga indica la \textit{advertising + window} di cui parlavamo in \secref{sec:TCP_TCP_opt}. Allora si può +verificare dall'output del comando come venga appunto realizzata la sequenza +di pacchetti descritta in \secref{sec:TCP_conn_cre}: prima viene inviato dal +clinet un primo pacchetto con il SYN che inizia la connessione, a cui il +server risponde dando il ricevuto con un secondo pacchetto, che a sua volta +porta un SYN, cui il client risponde con un il terzo pacchetto di ricevuto. + +Ritorniamo allora alla nostra sessione con il servizio echo: dopo le tre righe +del three way handshake non avremo nulla fin tanto che non scriveremo una +prima riga sul client; al momento in cui facciamo questo si genera una +sequenza di altri quattro pacchetti. Il primo, dal client al server, +contraddistinto da una lettera \texttt{P} che significa che il flag PSH è +impostato, contiene la nostra riga (che è appunto di 11 caratteri), e ad esso +il server risponde immediatamente con un pacchetto vuoto di ricevuto. Poi +tocca al server riscrivere indietro quanto gli è stato inviato, per cui sarà +lui a mandare indietro un terzo pacchetto con lo stesso contenuto appena +ricevuto, e a sua volta riceverà dal client un ACK nel quarto pacchetto. +Questo causerà la ricezione dell'eco nel client che lo stamperà a video. + +A questo punto noi procediamo ad interrompere l'esecuzione del server con un +\texttt{C-c} (cioè con l'invio di \const{SIGTERM}): nel momento in cui +facciamo questo vengono immediatamente generati altri due pacchetti. La +terminazione del processo infatti comporta la chiusura di tutti i suoi file +descriptor, il che comporta, per il socket che avevamo aperto, l'inizio della +sequenza di chiusura illustrata in \secref{sec:TCP_conn_term}. Questo +significa che dal server partirà un FIN, che è appunto il primo dei due +pacchetti, contraddistinto dalla lettera \texttt{F}, cui seguirà al solito un +ACK da parte del client. A questo punto la connessione dalla parte del server +è chiusa, ed infatti se usiamo \cmd{netstat} per controllarne lo stato sul +client, otterremo che essa è andata nello stato \texttt{CLOSE\_WAIT}: +\begin{verbatim} +[root@gont gapil]# netstat -ant +Active Internet connections (servers and established) +Proto Recv-Q Send-Q Local Address Foreign Address State +... ... ... ... ... ... +tcp 1 0 192.168.1.2:34582 192.168.1.141:7 CLOSE_WAIT +\end{verbatim} + +Il problema è che in questo momento il client è bloccato dentro la funzione +\texttt{ClientEcho} nella chiamata a \func{fgets}, e sta attendendo dell'input +dal terminale, per cui non è in grado di accorgersi di nulla. Solo quando +inseriremo la seconda riga il comando uscirà da \func{fgets} e proverà a +scriverla sul socket. Questo comporta la generazione degli ultimi due +pacchetti riportati da \cmd{tcpdump}: il primo, inviato dal client contenente +i 25 caratteri della riga appena letta, e ad esso la macchina server +risponderà, non essendoci più niente in ascolto sulla porta 7, con un segmento +di RST, contraddistinto dalla lettera \texttt{R}, che causa la conclusione +definitiva della connessione anche nel client, dove non comparrà più +nell'output di \cmd{netstat}. + +Non avendo controllato lo stato di uscita della nostra funzione di scrittura +(si riveda il codice illustrato in \secref{fig:TCP_client_echo_sub}) che a +questo punto riporterebbe un errore, il client proseguirà leggendo dal socket, +e dato che questo è stato chiuso avremo che, come spiegato in +\secref{sec:TCP_conn_term}, la funzione \func{read} ritorna normalmente con un +valore nullo. Questo comporta che la seguente chiamata a \func{fputs} non ha +effetto (viene stampata una stringa nulla) ed il client si blocca di nuovo +nella successiva chiamata a \func{fgets}. Per questo diventa possibile +inserire una terza riga e solo dopo averlo fatto si avrà la terminazione del +programma. + +Per capire come questa avvenga comunque, non avendo inserito nel codice nessun +controllo di errore, occorre ricordare che, a parte la bidirezionalità del +flusso dei dati, dal punto di vista del funzionamento nei confronti delle +funzioni di lettura e scrittura, i socket sono del tutto analoghi a delle +pipe. Allora, da quanto illustrato in \secref{sec:ipc_pipes}, sappiamo che +tutte le volte che si cerca di scrivere su una pipe il cui altro capo non è +aperto il lettura il processo riceve un segnale di \const{SIGPIPE}, e questo è +esattamente quello che avviene in questo caso, e siccome non abbiamo un +gestore per questo segnale, viene eseguita l'azione preimpostata, che è quella +di terminare il processo. -- 2.30.2