X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=elemtcp.tex;h=9eab58a2adf71cb073b6562dd5c17e3740f31069;hp=d3178839f56981b5b2cb2403be16ae75b37e9d97;hb=f15d1cc1ff7e1b4d15d434144862a5505bd33f0a;hpb=4826742c87d76af810c8a30e5495135fb43b8091 diff --git a/elemtcp.tex b/elemtcp.tex index d317883..9eab58a 100644 --- a/elemtcp.tex +++ b/elemtcp.tex @@ -35,7 +35,7 @@ verifica utilizzando il codice dei due precedenti esempi elementari \figref{fig:net_cli_code} e \figref{fig:net_serv_code}) che porta alla creazione di una connessione è la seguente: -\begin{itemize} +\begin{enumerate} \item Il server deve essere preparato per accettare le connessioni in arrivo; il procedimento si chiama \textsl{apertura passiva} del socket (in inglese \textit{passive open}); questo viene fatto chiamando la sequenza di funzioni @@ -71,7 +71,7 @@ creazione di una connessione \texttt{SYN} del server inviando un \texttt{ACK}. Alla ricezione di quest'ultimo la funzione \texttt{accept} del server ritorna e la connessione è stabilita. -\end{itemize} +\end{enumerate} Il procedimento viene chiamato \textit{three way handshake} dato che per realizzarlo devono essere scambiati tre segmenti. In \nfig\ si è @@ -186,7 +186,7 @@ seguente: \item Dopo un certo tempo anche il secondo processo chiamerà la funzione \texttt{close} sul proprio socket, causando l'emissione di un altro segmento FIN. - + \item L'altro capo della connessione riceverà il FIN conclusivo e risponderà con un ACK. \end{enumerate} @@ -248,21 +248,21 @@ riferimento resta (FIXME citare lo Stevens); qui ci limiteremo a descrivere brevemente un semplice esempio di connessione e le transizioni che avvengono nei due casi appena citati (creazione e terminazione della connessione). -In assenza di connessione lo stato del TCP è \textsl{CLOSED}; quando una +In assenza di connessione lo stato del TCP è \texttt{CLOSED}; quando una applicazione esegue una apertura attiva il TCP emette un SYN e lo stato -diventa \textsl{SYN\_SENT}; quando il TCP riceve la risposta del SYN$+$ACK -emette un ACK e passa allo stato \textsl{ESTABLISHED}; questo è lo stato +diventa \texttt{SYN\_SENT}; quando il TCP riceve la risposta del SYN$+$ACK +emette un ACK e passa allo stato \texttt{ESTABLISHED}; questo è lo stato finale in cui avviene la gran parte del trasferimento dei dati. Dal lato server in genere invece il passaggio che si opera con l'apertura -passiva è quello di portare il socket dallo stato \textsl{CLOSED} allo -stato \textsl{LISTEN} in cui vengono accettate le connessioni. +passiva è quello di portare il socket dallo stato \texttt{CLOSED} allo +stato \texttt{LISTEN} in cui vengono accettate le connessioni. -Dallo stato \textsl{ESTABLISHED} si può uscire in due modi; se un'applicazione +Dallo stato \texttt{ESTABLISHED} si può uscire in due modi; se un'applicazione chiama la \texttt{close} prima di aver ricevuto un end of file (chiusura -attiva) la transizione è verso lo stato \textsl{FIN\_WAIT\_1}; se invece -l'applicazione riceve un FIN nello stato \textsl{ESTABLISHED} (chiusura -passiva) la transizione è verso lo stato \textsl{CLOSE\_WAIT}. +attiva) la transizione è verso lo stato \texttt{FIN\_WAIT\_1}; se invece +l'applicazione riceve un FIN nello stato \texttt{ESTABLISHED} (chiusura +passiva) la transizione è verso lo stato \texttt{CLOSE\_WAIT}. In \nfig\ è riportato lo schema dello scambio dei pacchetti che avviene per una un esempio di connessione, insieme ai vari stati che il protocollo viene @@ -292,7 +292,7 @@ risposta. Infine si ha lo scambio dei quattro segmenti che terminano la connessione secondo quanto visto in \secref{sec:TCPel_conn_term}; si noti che il capo della connessione che esegue la chiusura attiva entra nello stato -\textsl{TIME\_WAIT} su cui torneremo fra poco. +\texttt{TIME\_WAIT} su cui torneremo fra poco. È da notare come per effettuare uno scambio di due pacchetti (uno di richiesta e uno di risposta) il TCP necessiti di ulteriori otto segmenti, se invece si @@ -339,16 +339,16 @@ pi Ogni implementazione del TCP deve scegliere un valore per la MSL (l'RFC1122 raccomanda 2 minuti, linux usa 30 secondi), questo comporta una durata dello -stato \textsl{TIME\_WAIT} che a seconda delle implementazioni può variare fra +stato \texttt{TIME\_WAIT} che a seconda delle implementazioni può variare fra 1 a 4 minuti. Lo stato \texttt{TIME\_WAIT} viene utilizzato dal protocollo per due motivi principali: -\begin{itemize} +\begin{enumerate} \item implementare in maniera affidabile la terminazione della connessione in entrambe le direzioni. \item consentire l'eliminazione dei segmenti duplicati dalla rete. -\end{itemize} +\end{enumerate} Il punto è che entrambe le ragioni sono importanti, anche se spesso si fa riferimento solo alla prima; ma è solo se si tiene conto della seconda che si @@ -634,11 +634,10 @@ cio server per specificare la porta (e gli eventuali indirizzi locali) su cui poi ci si porrà in ascolto. -Il prototipo della funzione, definito in \texttt{sys/socket.h}, è il seguente: +Il prototipo della funzione è il seguente: -\begin{itemize} -\item \texttt{int bind(int sockfd, const struct sockaddr *serv\_addr, - socklen\_t addrlen) } +\begin{prototype}{sys/socket.h} +{int bind(int sockfd, const struct sockaddr *serv\_addr, socklen\_t addrlen)} Il primo argomento è un file descriptor ottenuto da una precedente chiamata a \texttt{socket}, mentre il secondo e terzo argomento sono rispettivamente @@ -648,15 +647,14 @@ Il prototipo della funzione, definito in \texttt{sys/socket.h}, La funzione restituisce zero in caso di successo e -1 per un errore, in caso di errore. La variabile \texttt{errno} viene settata secondo i seguenti codici di errore: - \begin{itemize} + \begin{errlist} \item \texttt{EBADF} Il file descriptor non è valido. \item \texttt{EINVAL} Il socket ha già un indirizzo assegnato. \item \texttt{ENOTSOCK} Il file descriptor non è associato ad un socket. \item \texttt{EACCESS} Si è cercato di usare un indirizzo riservato senza essere root. - \end{itemize} - -\end{itemize} + \end{errlist} +\end{prototype} Con il TCP la chiamata \texttt{bind} permette di specificare l'indirizzo, la porta, entrambi o nessuno dei due. In genere i server utilizzano una porta @@ -715,12 +713,10 @@ di effettuare una assegnazione del tipo: \label{sec:TCPel_func_connect} La funzione \texttt{connect} è usata da un client TCP per stabilire la -connessione con un server TCP, il prototipo della funzione, definito in -\texttt{sys/socket.h}, è il seguente: +connessione con un server TCP, il prototipo della funzione è il seguente: -\begin{itemize} -\item \texttt{int connect(int sockfd, const struct sockaddr *serv\_addr, - socklen\_t addrlen) } +\begin{prototype}{sys/socket.h} +{int connect(int sockfd, const struct sockaddr *serv\_addr, socklen\_t addrlen)} Il primo argomento è un file descriptor ottenuto da una precedente chiamata a \texttt{socket}, mentre il secondo e terzo argomento sono rispettivamente @@ -730,7 +726,7 @@ connessione con un server TCP, il prototipo della funzione, definito in La funzione restituisce zero in caso di successo e -1 per un errore, in caso di errore. La variabile \texttt{errno} viene settata secondo i seguenti codici di errore: - \begin{itemize} + \begin{errlist} \item \texttt{EBADF} Il file descriptor non è valido. \item \texttt{EFAULT} L'indirizzo della struttura di indirizzi è al di fuori dello spazio di indirizzi dell'utente. @@ -751,8 +747,8 @@ connessione con un server TCP, il prototipo della funzione, definito in \item \texttt{EACCESS, EPERM} Si è tentato di eseguire una connessione ad un indirizzo broacast senza che il socket fosse stato abilitato per il broadcast. - \end{itemize} -\end{itemize} + \end{errlist} +\end{prototype} La struttura dell'indirizzo deve essere inizializzata con l'indirizzo IP e il numero di porta del server a cui ci si vuole connettere, come mostrato @@ -822,23 +818,179 @@ necessario effettuare una \texttt{bind}. La funzione \texttt{listen} è usata per usare un socket in modalità passiva, cioè, come dice il nome, per metterlo in ascolto di eventuali connessioni; in sostanza l'effetto della funzione è di portare il socket dallo stato -\texttt{CLOSED} a quello \texttt{LISTEN}. - -\begin{prototype}{int listen(int sockfd, int backlog)} +\texttt{CLOSED} a quello \texttt{LISTEN}. In genere si chiama la funzione in +un server dopo le chiamate a \texttt{socket} e \texttt{bind} e prima della +chiamata ad \texttt{accept}. Il prototipo della funzione come definito dalla +man page è: + +\begin{prototype}{sys/socket.h}{int listen(int sockfd, int backlog)} + La funzione pone il socket specificato da \texttt{sockfd} in modalità + passiva e predispone una coda per le connessioni in arrivo di lunghezza pari + a \texttt{backlog}. La funzione si può applicare solo a socket di tipo + \texttt{SOCK\_STREAM} o \texttt{SOCK\_SEQPACKET}. + + La funzione restituisce 0 in caso di successo e -1 in caso di errore. I + codici di errore restituiti in \texttt{errno} sono i seguenti: \begin{errlist} \item \texttt{EBADF} L'argomento \texttt{sockfd} non è un file descriptor valido. \item \texttt{ENOTSOCK} L'argomento \texttt{sockfd} non è un socket. - \item \texttt{EOPNOTSUPP} The socket is not of a type that supports the lis­ - ten operation. + \item \texttt{EOPNOTSUPP} Il socket è di un tipo che non supporta questa + operazione. \end{errlist} \end{prototype} +Il parametro \texttt{backlog} indica il numero massimo di connessioni pendenti +accettate; se esso viene ecceduto il client riceverà una errore di tipo +\texttt{ECONNREFUSED}, o se il protocollo, come nel caso del TCP, supporta la +ritrasmissione, la richiesta sarà ignorata in modo che la connessione possa +essere ritentata. + +Per capire meglio il significato di tutto ciò occorre approfondire la modalità +con cui il kernel tratta le connessioni in arrivo. Per ogni socket in ascolto +infatti vengono mantenute due code: +\begin{enumerate} +\item Una coda delle connessioni incomplete (\textit{incomplete connection + queue} che contiene una entrata per ciascun SYN arrivato per il quale si + sta attendendo la conclusione del three-way handshake. Questi socket sono + tutti nello stato \texttt{SYN\_RECV}. +\item Una coda delle connessioni complete (\textit{complete connection queue} + che contiene una entrata per ciascuna connessione per le quali il three-way + handshake è stato completato ma ancora \texttt{accept} non è ritornata. +\end{enumerate} + +Lo schema di funzionamento è descritto in \nfig, quando arriva un SYN da un +client il server crea una nuova entrata nella coda delle connessioni +incomplete, e poi risponde con il SYN$+$ACK. La entrata resterà nella coda +delle connessioni incomplete fino al ricevimento dell'ACK dal client o fino ad +un timeout. Nel caso di completamento del three-way handshake l'entrata viene +sostata nella coda delle connessioni complete. Quando il processo chiama la +funzione \texttt{accept} (vedi \secref{sec:TCPel_func_accept}) la prima +entrata nella coda delle connessioni complete è passata al programma, o, se la +coda è vuota, il processo viene posto in attesa e risvegliato all'arrivo della +prima connessione completa. + +Storicamente il valore del parametro \texttt{backlog} era corrispondente al +massimo valore della somma del numero di entrate possibili per ciascuna di +dette code. Stevens riporta che BSD ha sempre applicato un fattore di 1.5 al +valore, e provvede una tabella con i risultati ottenuti con vari kernel, +compreso linux 2.0, che mostrano le differenze fra diverse implementazioni. + +Ma in linux il significato di questo valore è cambiato a partire dal kernel +2.2 per prevenire l'attacco chiamato \texttt{syn flood}. Questo si basa +sull'emissione da parte dell'attaccante di un grande numero di pacchetti SYN +indirizzati verso una porta forgiati con indirizzo IP fasullo \footnote{con la + tecnica che viene detta \textit{ip spoofing}} così che i SYN$+$ACK vanno +perduti la coda delle connessioni incomplete viene saturata, impedendo di +fatto le connessioni. + +Per ovviare a questo il significato del \texttt{backlog} è stato cambiato a +significare la lunghezza della coda delle connessioni complete. La lunghezza +della coda delle connessioni incomplete può essere ancora controllata usando +la \texttt{sysctl} o scrivendola direttamente in +\texttt{/proc/sys/net/ipv4/tcp\_max\_syn\_backlog}. Quando si attiva la +protezione dei syncookies però (con l'opzione da compilare nel kernel e da +attivare usando \texttt{/proc/sys/net/ipv4/tcp\_syncookies}) questo valore +viene ignorato e non esiste più un valore massimo. + +La scelta storica per il valore di questo parametro è di 5, e alcuni vecchi +kernel non supportavano neanche valori superiori, ma la situazione corrente è +molto cambiata dagli anni '80 e con server web che possono sopportare diversi +milioni di connessioni al giorno un tale valore non è più adeguato. Non esiste +comunque una risposta univoca per la scelta del valore, per questo non +conviene specificare questo valore con una costante (il cui cambiamento +richiederebbe la ricompilazione del server) ma usare piuttosto una variabile +di ambiente (vedi \secref{sec:xxx_env_var}). Lo Stevens tratta accuratamente +questo argomento, con esempi presi da casi reali su web server, ed in +particolare evidenzia come non sia più vero che la ragione della coda è quella +di gestire il caso in cui il server è occupato fra chiamate successive alla +\texttt{accept} (per cui la coda più occupata sarebbe quella delle connessioni +compeltate), ma è invece necessaria a gestire la presenza di un gran numero di +SYN in attesa di completare il three-way handshake. + +Come accennato nel caso del TCP se un SYN arriva con tutte le code piene, il +pacchetto sarà ignorato. Questo viene fatto perché la condizione delle code +piene è transitoria, e se il client ristrasmette il SYN è probabile che +passato un po' di tempo possa trovare lo spazio per una nuova connessione. Se +invece si rispondesse con un RST la \texttt{connect} del client ritornerebbe +con una condizione di errore, mentre questo è il tipico caso in cui è si può +lasciare la gestione della connessione alla ritrasmissione prevista dal +protocollo TCP. + + \subsection{La funzione \texttt{accept}} \label{sec:TCPel_func_accept} +La funzione \texttt{accept} è chiamata da un server TCP per gestire la +connessione una volta che sia stato completato il three way handshake, la +funzione restituisce un nuovo socket descriptor su cui si potrà operare per +effettuare la comunicazione. Se non ci sono connessioni completate il processo +viene messo in attesa. Il prototipo della funzione è il seguente: + + +\begin{prototype}{sys/socket.h} +{int listen(int sockfd, struct sockaddr *addr, socklen\_t *addrlen)} + La funzione estrae la prima connessione completa relativa al socket + \texttt{sockfd} in attesa sulla coda delle connessioni complete che associa + nuovo socket con le stesse caratteristiche di \texttt{sockfd} (restituito + dalla funzione stessa). Il socket originale non viene toccato. Nella + struttura \texttt{addr} e nella variabile \texttt{addrlen} vengono + restituiti indirizzo e relativa lunghezza del client che si è connesso. + + La funzione restituisce un numero di socket descriptor positivo in caso di + successo e -1 in caso di errore, nel qual caso la variabile \texttt{errno} + viene settata ai seguenti valori: + + \begin{errlist} + \item \texttt{EBADF} L'argomento \texttt{sockfd} non è un file descriptor + valido. + \item \texttt{ENOTSOCK} L'argomento \texttt{sockfd} non è un socket. + \item \texttt{EOPNOTSUPP} Il socket è di un tipo che non supporta questa + operazione. + \item \texttt{EAGAIN} or \item \texttt{EWOULDBLOCK} Il socket è stato + settato come non bloccante, e non ci sono connessioni in attesa di essere + accettate. + \item \texttt{EFAULT} The addr parameter is not in a writable part of the + user address space. + \item \texttt{EPERM} Firewall rules forbid connection. + + \item \texttt{ENOBUFS, ENOMEM} Not enough free memory. This often means + that the memory allocation is limited by the socket buffer limits, not by + the system memory. + \end{errlist} +\end{prototype} + +La funzione può essere usata solo con socket che supportino la connessione +(cioè di tipo \texttt{SOCK\_STREAM}, \texttt{SOCK\_SEQPACKET} o +\texttt{SOCK\_RDM}). Per alcuni protocolli che richiedono una conferma +esplicita della connessione, (attualmenente in linux solo DECnet ha questo +comportamento), la funzione opera solo l'estrazione dalla coda delle +connessioni, la conferma della connessione viene fatta implicitamente dalla +prima chiamata ad una \texttt{read} o una \texttt{write} mentre il rifiuto +della connessione viene fatta con la funzione \texttt{close}. + +I due parametri \texttt{cliaddr} e \texttt{addrlen} (si noti che quest'ultimo +è passato per indirizzo per avere indietro il valore) sono usati per ottenere +l'indirizzo del client da cui proviene la connessione. Prima della chiamata +\texttt{addrlen} deve essere inizializzato alle dimensioni della struttura il +cui indirizzo è passato come parametro in \texttt{cliaddr}, al rientro della +funzione \texttt{addrlen} conterrà il numero di bytes scritti dentro +\texttt{cliaddr}. + +Se la funzione ha successo restituisce un nuovo socket descriptor, detto +\textit{connected socket}, su cui è agganciata la connessione che il client +TCP ha effettuato verso il socket \texttt{sockfd}. Quest'ultimo, che viene +chiamato invece \textit{listening socket}, deve essere stato creato in +precedenza e messo in ascolto con \texttt{listen}, e non viene toccato dalla +funzione. + +Questa distinzione è essenziale per capire + + \section{Una semplice implementazione del servizio \texttt{echo} su TCP} \label{sec:TCPel_echo_example} + +Veniamo ora ad una applicazione