X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=elemtcp.tex;h=964dd4f16f5cc1c0539b9338093392ae75c5905a;hp=37745f2ac447edfed010194d17c4f748abc3c718;hb=f1b2cf6ae09fb598a0b44719644ffaa94c2864f3;hpb=ace32c3911cea589e3b43cf7fa603117fd849cba diff --git a/elemtcp.tex b/elemtcp.tex index 37745f2..964dd4f 100644 --- a/elemtcp.tex +++ b/elemtcp.tex @@ -1,70 +1,1276 @@ \chapter{Socket TCP elementari} \label{cha:elem_TCP_sock} -In questo capitolo inizieremo ad approndire la conoscenza dei socket TCP, +In questo capitolo iniziamo ad approfondire la conoscenza dei socket TCP, tratteremo qui dunque il funzionamento delle varie funzioni che si sono usate -nei due esempi elementari forniti in precedenza (vedi \ref{sec:net_cli_sample} -e \ref{sec:net_cli_server}), previa una descrizione delle principali -caratteristiche del funzionamento di una connessione TCP. - -La seconda parte del capitolo sarà poi dedicata ad una riscrittura -dell'esempio precedente per trasformarlo in una prima applicazione -client/server semplice, ma completa, che implementi il servizio standard -\texttt{time} su TCP (vedremo in seguito un esempio anche con UDP). +nei due esempi elementari forniti in precedenza (vedi +\secref{sec:net_cli_sample} e \secref{sec:net_serv_sample}), previa una +descrizione delle principali caratteristiche del funzionamento di una +connessione TCP. \section{Il funzionamento di una connessione TCP} \label{sec:TCPel_connession} -Prima di entrare nei dettagli di come si usano le varie funzioni dei socket -che operano con TCP, è fondamentale capire alcune basi del funzionamento del -protocollo, ed in particolare su come si stabilisce una connessione, come la -si conclude e qual'è il significato dei vari stati del protocollo ad essa -connessi; in particolare questo ci permetterà di capire ed usare con profitto -il programma \texttt{netstat}, che è in grado di mostrare lo stato in cui si -trova ciascuna connessione attiva. +Prima di entrare nei dettagli delle funzioni usate nelle applicazioni che +utilizzano i socket TCP, è fondamentale spiegare alcune basi del funzionamento +del TCP; la conoscenza del funzionamento del protocollo è infatti essenziale +per capire il modello di programmazione ed il funzionamento delle API. + +In particolare ci concentreremo sulle modalità con le quali il protocollo dà +inizio e conclude una connessione; faremo inoltre anche un breve accenno al +significato di alcuni dei vari stati che il protocollo assume durante la vita +di una connessione, che possono essere osservati per ciascun socket attivo con +l'uso del programma \cmd{netstat}. -\subsection{Creazione: il \textit{three way handshake}} +\subsection{La creazione della connessione: il \textit{three way handshake}} \label{sec:TCPel_conn_cre} +Il processo che porta a creare una connessione TCP è chiamato \textit{three + way handshake}; la successione tipica degli eventi (la stessa che si +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{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 + \func{socket}, \func{bind} e \func{listen}. Completata l'apertura passiva il + server chiama la funzione \func{accept} e il processo si blocca in attesa di + connessioni. + +\item Il client richiede l'inizio della connessione usando la funzione + \func{connect}, attraverso un procedimento che viene chiamato + \textsl{apertura attiva}, dall'inglese \textit{active open}. La chiamata di + \func{connect} blocca il processo e causa l'invio da parte del client di un + segmento SYN,\footnote{Si ricordi che il segmento è l'unità elementare di + dati trasmessa dal protocollo TCP al livello superiore; tutti i segmenti + hanno un header che contiene le informazioni che servono allo + \textit{stack TCP} (così viene di solito chiamata la parte del kernel che + implementa il protocollo) per realizzare la comunicazione, fra questi dati + ci sono una serie di flag usati per gestire la connessione, come SYN, ACK, + URG, FIN, alcuni di essi, come SYN (che sta per \textit{syncronize}) + corrispondono a funzioni particolari del protocollo e danno il nome al + segmento, (per maggiori dettagli vedere \capref{cha:tcp_protocol}).} in + sostanza viene inviato al server un pacchetto IP che contiene solo gli + header IP e TCP (con il numero di sequenza iniziale e il flag SYN) e le + opzioni di TCP. + +\item il server deve dare ricevuto (l'\textit{acknowledge}) del SYN del + client, inoltre anche il server deve inviare il suo SYN al client (e + trasmettere il suo numero di sequenza iniziale) questo viene fatto + ritrasmettendo un singolo segmento in cui sono impostati entrambi i flag SYN + ACK. + +\item una volta che il client ha ricevuto l'acknowledge dal server la funzione + \func{connect} ritorna, l'ultimo passo è dare dare il ricevuto del SYN del + server inviando un ACK. Alla ricezione di quest'ultimo la funzione + \func{accept} del server ritorna e la connessione è stabilita. +\end{enumerate} + +Il procedimento viene chiamato \textit{three way handshake} dato che per +realizzarlo devono essere scambiati tre segmenti. In \nfig\ si è +rappresentata graficamente la sequenza di scambio dei segmenti che stabilisce +la connessione. + +% Una analogia citata da R. Stevens per la connessione TCP è quella con il +% sistema del telefono. La funzione \texttt{socket} può essere considerata +% l'equivalente di avere un telefono. La funzione \texttt{bind} è analoga al +% dire alle altre persone qual'è il proprio numero di telefono perché possano +% chiamare. La funzione \texttt{listen} è accendere il campanello del telefono +% per sentire le chiamate in arrivo. La funzione \texttt{connect} richiede di +% conoscere il numero di chi si vuole chiamare. La funzione \texttt{accept} è +% quando si risponde al telefono. + +\begin{figure}[htb] + \centering + \includegraphics[width=10cm]{img/three_way_handshake} + \caption{Il \textit{three way handshake} del TCP} + \label{fig:TCPel_TWH} +\end{figure} +Si è accennato in precedenza ai \textsl{numeri di sequenza} (che sono anche +riportati in \curfig); per gestire una connessione affidabile infatti il +protocollo TCP prevede nell'header la presenza di un numero a 32 bit (chiamato +appunto \textit{sequence number}) che identifica a quale byte nella sequenza +del flusso corrisponde il primo byte della sezione dati contenuta nel +segmento. -\subsection{Il significato delle opzioni del TCP} +Il numero di sequenza di ciascun segmento viene calcolato a partire da un +\textsl{numero di sequenza iniziale} generato in maniera casuale del kernel +all'inizio della connessione e trasmesso con il SYN; l'acknowledgement di +ciascun segmento viene effettuato dall'altro capo della connessione impostando +il flag ACK e restituendo nell'apposito campo dell'header un +\textit{acknowledge number}) pari al numero di sequenza che il ricevente si +aspetta di ricevere con il pacchetto successivo; dato che il primo pacchetto +SYN consuma un byte, nel \textit{three way handshake} il numero di acknowledge +è sempre pari al numero di sequenza iniziale incrementato di uno; lo stesso +varrà anche (vedi \nfig) per l'acknowledgement di un FIN. + +\subsection{Le opzioni TCP.} \label{sec:TCPel_TCP_opt} +Ciascun segmento SYN contiene in genere delle opzioni per il protocollo TCP +(le cosiddette \textit{TCP options}, che vengono inserite fra l'header e i +dati) che servono a comunicare all'altro capo una serie di parametri utili a +regolare la connessione. Normalmente vengono usate le seguenti opzioni: + +\begin{itemize} +\item \textit{MSS option}, dove MMS sta per \textit{maximum segment size}, con + questa opzione ciascun capo della connessione annuncia all'altro il massimo + ammontare di dati che vorrebbe accettare per ciascun segmento nella + connessione corrente. È possibile leggere e scrivere questo valore + attraverso l'opzione del socket \macro{TCP\_MAXSEG}. + +\item \textit{window scale option}; come spiegato in \capref{cha:tcp_protocol} + il protocollo TCP implementa il controllo di flusso attraverso una + \textsl{finestra annunciata} (\textit{advertized window}) con la quale + ciascun capo della comunicazione dichiara quanto spazio disponibile ha in + memoria per i dati. Questo è un numero a 16 bit dell'header, che così può + indicare un massimo di 65535 byte (anche se Linux usa come massimo 32767 per + evitare problemi con alcuni stack bacati che usano l'aritmetica con segno + per implementare lo stack TCP); ma alcuni tipi di connessione come quelle ad + alta velocità (sopra i 45Mbits/sec) e quelle che hanno grandi ritardi nel + cammino dei pacchetti (come i satelliti) richiedono una finestra più grande + per poter ottenere il massimo dalla trasmissione, per questo esiste questa + opzione che indica un fattore di scala da applicare al valore della finestra + annunciata\footnote{essendo una nuova opzione per garantire la compatibilità + con delle vecchie implementazioni del protocollo la procedura che la + attiva prevede come negoziazione che l'altro capo della connessione + riconosca esplicitamente l'opzione inserendola anche lui nel suo SYN di + risposta dell'apertura della connessione.} per la connessione corrente + (espresso come numero di bit cui shiftare a sinistra il valore della + finestra annunciata inserito nel pacchetto). + +\item \textit{timestamp option}, è anche questa una nuova opzione necessaria + per le connessioni ad alta velocità per evitare possibili corruzioni di dati + dovute a pacchetti perduti che riappaiono; anche questa viene negoziata come + la precedente. + +\end{itemize} + +La MSS è generalmente supportata da quasi tutte le implementazioni del +protocollo, le ultime due opzioni (trattate nell'RFC~1323) sono meno comuni; +vengono anche dette \textit{long fat pipe options} dato che questo è il nome +che viene dato alle connessioni caratterizzate da alta velocità o da ritardi +elevati. In ogni caso Linux supporta pienamente entrambe le opzioni. + \subsection{La terminazione della connessione} \label{sec:TCPel_conn_term} -\subsection{Il diagramma delle transizioni di stato} -\label{sec:TCPel_trans_dia} +Mentre per creare una connessione occorre un interscambio di tre segmenti, la +procedura di chiusura ne richiede quattro; ancora una volta si può fare +riferimento al codice degli esempi \figref{fig:net_cli_code} e +\figref{fig:net_serv_code}, in questo caso la successione degli eventi è la +seguente: + +\begin{enumerate} +\item Un processo ad uno dei due capi chiama la funzione \func{close}, dando + l'avvio a quella che viene chiamata \textsl{chiusura attiva} (o + \textit{active close}). Questo comporta l'emissione di un segmento FIN, che + significa che si è finito con l'invio dei dati sulla connessione. + +\item L'altro capo della connessione riceve il FIN ed esegue la + \textit{chiusura passiva} (o \textit{passive close}); al FIN, come ad ogni + altro pacchetto, viene risposto con un ACK. Inoltre il ricevimento del FIN + viene segnalato al processo che ha aperto il socket (dopo che ogni altro + eventuale dato rimasto in coda è stato ricevuto) come un end-of-file sulla + lettura, questo perché il ricevimento di un FIN significa che non si + riceveranno altri dati sulla connessione. + +\item Dopo un certo tempo anche il secondo processo chiamerà la funzione + \func{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} + +Dato che in questo caso sono richiesti un FIN ed un ACK per ciascuna direzione +normalmente i segmenti scambiati sono quattro. Questo non è vero sempre +giacché in alcune situazioni il FIN del passo 1) è inviato insieme a dei dati. +Inoltre è possibile che i segmenti inviati nei passi 2 e 3 dal capo che +effettua la chiusura passiva, siano accorpati in un singolo segmento. In +\nfig\ si è rappresentato graficamente lo sequenza di scambio dei segmenti che +stabilisce la connessione. + +\begin{figure}[htb] + \centering + \includegraphics[width=10cm]{img/tcp_close} + \caption{La chiusura di una connessione TCP} + \label{fig:TCPel_close} +\end{figure} + +Come per il SYN anche il FIN occupa un byte nel numero di sequenza, per cui +l'ACK riporterà un \textit{acknowledge number} incrementato di uno. + +Si noti che nella sequenza di chiusura fra i passi 2 e 3 è in teoria possibile +che si mantenga un flusso di dati dal capo della connessione che deve ancora +eseguire la chiusura passiva a quello che sta eseguendo la chiusura attiva. +Nella sequenza indicata i dati verrebbero persi, dato che si è chiuso il +socket dal lato che esegue la chiusura attiva; esistono tuttavia situazioni in +cui si vuole poter sfruttare questa possibilità, usando una procedura che è +chiamata \textit{half-close}; torneremo su questo aspetto e su come +utilizzarlo più avanti, quando parleremo della funzione \func{shutdown}. + +La emissione del FIN avviene quando il socket viene chiuso, questo però non +avviene solo per la chiamata della funzione \func{close} (come in +\figref{fig:net_serv_code}), ma anche alla terminazione di un processo (come +in \figref{fig:net_cli_code}). Questo vuol dire ad esempio che se un processo +viene terminato da un segnale tutte le connessioni aperte verranno chiuse. + +Infine è da sottolineare che, benché nella figura (e nell'esempio che vedremo +più avanti in \secref{sec:TCPsimp_echo}) sia stato il client ad eseguire la +chiusura attiva, nella realtà questa può essere eseguita da uno qualunque dei +due capi della comunicazione (come in fatto in precedenza nell'esempio di +\figref{fig:net_serv_code}), e anche se il caso più comune resta quello del +client, ci sono alcuni servizi, il principale dei quali è l'HTTP, per i +quali è il server ad effettuare la chiusura attiva. + + +\subsection{Un esempio di connessione} +\label{sec:TCPel_conn_dia} + +Le operazioni del TCP nella creazione e conclusione di una connessione sono +specificate attraverso il diagramma di transizione degli stati riportato in +\nfig. TCP prevede l'esistenza di 11 diversi stati per un socket ed un insieme +di regole per le transizioni da uno stato all'altro basate sullo stato +corrente e sul tipo di segmento ricevuto; i nomi degli stati sono gli stessi +che vengono riportati del comando \cmd{netstat} nel campo \textit{State}. + +Una descrizione completa del funzionamento del protocollo va al di là degli +obiettivi di questo libro; un approfondimento sugli aspetti principali si +trova in \capref{cha:tcp_protocol}, ma per una trattazione esauriente il +miglior 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 è \texttt{CLOSED}; quando una +applicazione esegue una apertura attiva il TCP emette un SYN e 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 \texttt{CLOSED} allo +stato \texttt{LISTEN} in cui vengono accettate le connessioni. + +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 \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 +ad assumere per i due lati, server e client. + +\begin{figure}[htb] + \centering + \includegraphics[width=9cm]{img/tcp_connection} + \caption{Schema dello scambio di pacchetti per un esempio di connessione} + \label{fig:TPCel_conn_example} +\end{figure} + +La connessione viene iniziata dal client che annuncia un MSS di 1460 (un +valore tipico per IPv4 su Ethernet) con Linux, il server risponde con lo +stesso valore (ma potrebbe essere anche un valore diverso). + +Una volta che la connessione è stabilita il client scrive al server una +richiesta (che assumiamo stare in un singolo segmento, cioè essere minore dei +1460 byte annunciati dal server), quest'ultimo riceve la richiesta e +restituisce una risposta (che di nuovo supponiamo stare in un singolo +segmento). Si noti che l'acknowledge della richiesta è mandato insieme alla +risposta, questo viene chiamato \textit{piggybacking} ed avviene tutte le +volte che che il server è sufficientemente veloce a costruire la risposta, in +caso contrario si avrebbe prima l'emissione di un ACK e poi l'invio della +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 +\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 +fosse usato UDP sarebbero stati sufficienti due soli pacchetti. Questo è il +costo che occorre pagare per avere l'affidabilità garantita dal TCP, se si +fosse usato UDP si sarebbe dovuto trasferire la gestione di tutta una serie di +dettagli (come la verifica della ricezione dei pacchetti) dal livello del +trasporto all'interno dell'applicazione. + +Quello che è bene sempre tenere presente è allora quali sono le esigenze che +si hanno in una applicazione di rete, perché non è detto che TCP sia la +miglior scelta in tutti i casi (ad esempio se si devono solo scambiare dati +già organizzati in piccoli pacchetti l'overhead aggiunto può essere eccessivo) +per questo esistono applicazioni che usano UDP e lo fanno perché nel caso +specifico le sue caratteristiche di velocità e compattezza nello scambio dei +dati rispondono meglio alle esigenze che devono essere affrontate. \subsection{Lo stato \texttt{TIME\_WAIT}} \label{sec:TCPel_time_wait} +Come riportato da Stevens (FIXME citare) lo stato \texttt{TIME\_WAIT} è +probabilmente uno degli aspetti meno compresi del protocollo TCP, è infatti +comune trovare nei newsgroup domande su come sia possibile evitare che +un'applicazione resti in questo stato lasciando attiva una connessione ormai +conclusa; la risposta è che non deve essere fatto, ed il motivo cercheremo di +spiegarlo adesso. + +Come si è visto nell'esempio precedente (vedi \curfig) \texttt{TIME\_WAIT} è +lo stato finale in cui il capo di una connessione che esegue la chiusura +attiva resta prima di passare alla chiusura definitiva della connessione. Il +tempo in cui l'applicazione resta in questo stato deve essere due volte la MSL +(\textit{Maximum Segment Lifetime}). + +La MSL è la stima del massimo periodo di tempo che un pacchetto IP può vivere +sulla rete; questo tempo è limitato perché ogni pacchetto IP può essere +ritrasmesso dai router un numero massimo di volte (detto \textit{hop limit}). +Il numero di ritrasmissioni consentito è indicato dal campo TTL dell'header di +IP (per maggiori dettagli vedi \secref{sec:IP_xxx}), e viene decrementato +ad ogni passaggio da un router; quando si annulla il pacchetto viene scartato. +Siccome il numero è ad 8 bit il numero massimo di ``salti'' è di 255, pertanto +anche se il TTL (da \textit{time to live}) non è propriamente un limite sul +tempo di vita, si stima che un pacchetto IP non possa restare nella rete per +più di MSL secondi. + +Ogni implementazione del TCP deve scegliere un valore per la MSL (l'RFC~1122 +raccomanda 2 minuti, Linux usa 30 secondi), questo comporta una durata dello +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{enumerate} +\item implementare in maniera affidabile la terminazione della connessione + in entrambe le direzioni. +\item consentire l'eliminazione dei segmenti duplicati dalla rete. +\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 +capisce il perché della scelta di un tempo pari al doppio della MSL come +durata di questo stato. + +Il primo dei due motivi precedenti si può capire tornando a \curfig: assumendo +che l'ultimo ACK della sequenza (quello del capo che ha eseguito la chiusura +attiva) vanga perso, chi esegue la chiusura passiva non ricevendo risposta +rimanderà un ulteriore FIN, per questo motivo chi esegue la chiusura attiva +deve mantenere lo stato della connessione per essere in grado di reinviare +l'ACK e chiuderla correttamente. Se non fosse così la risposta sarebbe un RST +(un altro tipo si segmento) che verrebbe interpretato come un errore. + +Se il TCP deve poter chiudere in maniera pulita entrambe le direzioni della +connessione allora deve essere in grado di affrontare la perdita di uno +qualunque dei quattro segmenti che costituiscono la chiusura. Per questo +motivo lo stato \texttt{TIME\_WAIT} deve essere mantenuto anche dopo l'invio +dell'ultimo ACK per poter essere in grado di poterne gestire l'eventuale +ritrasmissione in caso di perdita. + +Il secondo motivo è più complesso da capire, e necessita di spiegare meglio +gli scenari in cui accade che i pacchetti si possono perdere nella rete o +restare intrappolati, per poi riemergere. + +Il caso più comune in cui questo avviene è quello di anomalie +nell'instradamento; può accadere cioè che un router smetta di funzionare o che +una connessione fra due router si interrompa. In questo caso i protocolli di +instradamento dei pacchetti possono impiegare diverso tempo (anche dell'ordine +dei minuti) prima di trovare e stabilire un percorso alternativo per i +pacchetti. Nel frattempo possono accadere casi in cui un router manda i +pacchetti verso un'altro e quest'ultimo li rispedisce indietro, o li manda ad +un terzo router che li rispedisce al primo, si creano cioè dei circoli (i +cosiddetti \textit{routing loop}) in cui restano intrappolati i pacchetti. + +Se uno di questi pacchetti intrappolati è un segmento di TCP chi l'ha inviato, +non ricevendo risposta, provvederà alla ritrasmissione e se nel frattempo sarà +stata stabilita una strada alternativa il pacchetto ritrasmesso giungerà a +destinazione. + +Ma se dopo un po' di tempo (che non supera il limite dell'MSL, dato che +altrimenti verrebbe ecceduto il TTL) l'anomalia viene a cessare il circolo di +instradamento viene spezzato i pacchetti intrappolati potranno essere inviati +alla destinazione finale, con la conseguenza di avere dei pacchetti duplicati; +questo è un caso che il TCP deve essere in grado di gestire. + +Allora per capire la seconda ragione per l'esistenza dello stato +\texttt{TIME\_WAIT} si consideri il caso seguente: si supponga di avere una +connessione fra l'IP 195.110.112.236 porta 1550 e l'IP 192.84.145.100 porta +22, che questa venga chiusa e che poco dopo si ristabilisca la stessa +connessione fra gli stessi IP sulle stesse porte (quella che viene detta, +essendo gli stessi porte e numeri IP, una nuova \textsl{incarnazione} della +connessione precedente); in questo caso ci si potrebbe trovare con dei +pacchetti duplicati relativi alla precedente connessione che riappaiono nella +nuova. + +Ma fintanto che il socket non è chiuso una nuova incarnazione non può essere +creata, per questo un socket TCP resta sempre nello stato \texttt{TIME\_WAIT} +per un periodo di 2MSL, in modo da attendere MSL secondi per essere sicuri che +tutti i pacchetti duplicati in arrivo siano stati ricevuti (e scartati) o che +nel frattempo siano stati eliminati dalla rete, e altri MSL secondi per essere +sicuri che lo stesso avvenga le risposte nella direzione opposta. + +In questo modo il TCP si assicura che quando una viene creata una nuova +connessione tutti gli eventuali segmenti residui di una precedente connessione +che possono causare disturbi sono stati eliminati dalla rete. + + +\subsection{I numeri di porta} +\label{sec:TCPel_port_num} + +In un ambiente multitasking in un dato momento più processi possono dover +usare sia UDP che TCP, e ci devono poter essere più connessioni in +contemporanea. Per poter tenere distinte le diverse connessioni entrambi i +protocolli usano i \textsl{numeri di porta}, che fanno parte, come si può +vedere in \secref{sec:sock_sa_ipv4} e \secref{sec:sock_sa_ipv6} pure delle +strutture degli indirizzi del socket. + +Quando un client contatta un server deve poter identificare con quale dei vari +possibili server attivi intende parlare. Sia TCP che UDP definiscono un gruppo +di \textsl{porte conosciute} (le cosiddette \textit{well-known port}) che +identificano una serie di servizi noti (ad esempio la porta 22 identifica il +servizio \texttt{ssh}) effettuati da appositi server che rispondono alle +connessioni verso tali porte. + +D'altra parte un client non ha necessità di usare un numero di porta +specifico, per cui in genere vengono usate le cosiddette \textsl{porte + effimere} (o \textit{ephemeral ports}) cioè porte a cui non è assegnato +nessun servizio noto e che vengono assegnate automaticamente dal kernel alla +creazione della connessione. Queste sono dette effimere in quanto vengono +usate solo per la durata della connessione, e l'unico requisito che deve +essere soddisfatto è che ognuna di esse sia assegnata in maniera univoca. + +La lista delle porte conosciute è definita dall'RFC~1700 che contiene l'elenco +delle porte assegnate dalla IANA (\textit{Internet Assigned Number Authority}) +ma l'elenco viene costantemente aggiornato e pubblicato all'indirizzo +\texttt{ftp://ftp.isi.edu/in-notes/iana/assignements/port-numbers}, inoltre il +file \file{/etc/services} contiene un analogo elenco, con la corrispondenza +fra i numeri di porta ed il nome simbolico del servizio. I numeri sono divisi +in tre intervalli: + +\begin{enumerate} +\item \textsl{le porte conosciute}. I numeri da 0 a 1023. Queste sono + controllate e assegnate dalla IANA. Se è possibile la stessa porta è + assegnata allo stesso servizio sia su UDP che su TCP (ad esempio la porta 22 + è assegnata a ssh su entrambi i protocolli, anche se viene usata solo dal + TCP). + +\item \textsl{le porte registrate}. I numeri da 1024 a 49151. Queste porte non + sono controllate dalla IANA, che però registra ed elenca chi usa queste + porte come servizio agli utenti. Come per le precedenti si assegna una porta + ad un servizio sia per TCP che UDP anche se poi il servizio è implementato + solo su TCP. Ad esempio X Window usa le porte TCP e UDP dal 6000 al 6063 + anche se il protocollo è implementato solo tramite TCP. + +\item \textsl{le porte private} o \textsl{dinamiche}. I numeri da 49152 a + 65535. La IANA non dice nulla riguardo a queste porte che pertanto + sono i candidati naturali ad essere usate come porte effimere. +\end{enumerate} + +In realtà rispetto a quanto indicato nell'RFC~1700 i vari sistemi hanno fatto +scelte diverse per le porte effimere, in particolare in \nfig\ sono riportate +quelle di BSD, Solaris e Linux. Nel caso di Linux poi la scelta fra i due +intervalli possibili viene fatta dinamicamente a seconda della memoria a +disposizione del kernel per gestire le relative tabelle. + +\begin{figure}[!htb] + \centering + \includegraphics[width=10cm]{img/port_alloc} + \caption{Allocazione dei numeri di porta} + \label{fig:TCPel_port_alloc} +\end{figure} + +I sistemi unix hanno inoltre il concetto di \textsl{porte riservate} (che +corrispondono alle porte con numero minore di 1024 e coincidono quindi con le +porte conosciute). La loro caratteristica è che possono essere assegnate a un +socket solo da un processo con i privilegi di root, per far si che solo +l'amministratore possa allocare queste porte per far partire i relativi +servizi. + +Si tenga conto poi che ci sono alcuni client (in particolare \cmd{rsh} e +\cmd{rlogin}) che richiedono una connessione su una porta riservata anche +dal lato client come parte dell'autenticazione. Questo viene fatto tramite la +funzione \func{rresvport} assegnando al socket una porta libera +nell'intervallo fra 512 e 1023. + +Data una connessione TCP si suole chiamare \textit{socket pair} la +combinazione dei quattro numeri che definiscono i due capi della connessione e +cioè l'indirizzo IP locale e la porta TCP locale, e l'indirizzo IP remoto e la +porta TCP remota; questa combinazione, che scriveremo usando una notazione del +tipo (195.110.112.152:22, 192.84.146.100:20100), identifica univocamente una +connessione su internet. Questo concetto viene di solito esteso anche a UDP, +benché in questo caso non abbia senso parlare di connessione. L'utilizzo del +programma \cmd{netstat} permette di visualizzare queste informazioni nei campi +\textit{Local Address} e \textit{Foreing Address}. + + +\subsection{Le porte ed il modello client/server} +\label{sec:TCPel_port_cliserv} + +Per capire meglio l'uso delle porte e come vengono utilizzate quando si ha a +che fare con un'applicazione client/server (come quella che scriveremo in +\secref{sec:TCPel_cunc_serv}) esamineremo cosa accade con le connessioni nel +caso di un server TCP che deve gestire connessioni multiple. + +Se eseguiamo un \cmd{netstat} su una macchina di prova (che supponiamo avere +indirizzo 195.110.112.152) potremo avere un risultato del tipo: +\begin{verbatim} +Active Internet connections (servers and established) +Proto Recv-Q Send-Q Local Address Foreign Address State +tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN +tcp 0 0 0.0.0.0:25 0.0.0.0:* LISTEN +tcp 0 0 127.0.0.1:53 0.0.0.0:* LISTEN +\end{verbatim} +essendo presenti e attivi un server ssh, un server di posta e un DNS per il +caching locale. + +Questo ci mostra ad esempio che il server ssh ha compiuto un'apertura passiva +mettendosi in ascolto sulla porta 22 riservata a questo servizio e che si è +posto in ascolto per connessioni provenienti da uno qualunque degli indirizzi +associati alle interfacce locali; la notazione 0.0.0.0 usata da netstat è +equivalente all'asterisco utilizzato per il numero di porta ed indica il +valore generico, e corrisponde al valore \macro{INADDR\_ANY} definito in +\file{arpa/inet.h}. + +Inoltre la porta e l'indirizzo di ogni eventuale connessione esterna non sono +specificati; in questo caso la \textit{socket pair} associata al socket può +essere indicata come (*:22, *:*), usando l'asterisco anche per gli indirizzi +come carattere di \textit{wildchard}. + +In genere avendo le macchine associato un solo IP ci si può chiedere che senso +abbia l'utilizzo dell'indirizzo generico per l'indirizzo locale, ma esistono +anche macchine che hanno più di un indirizzo IP (il cosiddetto +\textit{multihoming}) in questo modo si possono accettare connessioni +indirizzate verso uno qualunque di essi. Ma come si può vedere nell'esempio +con il DNS in ascolto sulla porta 53 è anche possibile restringere l'accesso +solo alle connessioni che provengono da uno specifico indirizzo, cosa che nel +caso è fatta accettando solo connessioni che arrivino sull'interfaccia di +loopback. + +Una volta che ci si vorrà collegare a questa macchina da un'altra posta +all'indirizzo 192.84.146.100 si potrà lanciare un client \cmd{ssh} per +creare una connessione verso la precedente, e il kernel assocerà al suddetto +una porta effimera che per esempio potrà essere la 21100, la connessione +allora sarà espressa dalla socket pair (192.84.146.100:21100, +195.110.112.152.22). + +Alla ricezione della richiesta dal client il server creerà un processo figlio +per gestire la connessione, se a questo punto eseguiamo nuovamente il +programma netstat otterremo come risultato: +\begin{verbatim} +Active Internet connections (servers and established) +Proto Recv-Q Send-Q Local Address Foreign Address State +tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN +tcp 0 0 0.0.0.0:25 0.0.0.0:* LISTEN +tcp 0 0 127.0.0.1:53 0.0.0.0:* LISTEN +tcp 0 0 195.110.112.152:22 192.84.146.100:21100 ESTABLISHED +\end{verbatim} + +Come si può notare il server è ancora in ascolto sulla porta 22, però adesso +c'è un nuovo socket (con lo stato \texttt{ESTABLISHED}) che anch'esso utilizza +la porta 22, ed ha specificato l'indirizzo locale, questo è il socket con cui +il processo figlio gestisce la connessione mentre il padre resta in ascolto +sul socket originale. + +Se a questo punto lanciamo un'altra volta il client ssh per una seconda +connessione quello che otterremo usando netstat sarà qualcosa del genere: +\begin{verbatim} +Active Internet connections (servers and established) +Proto Recv-Q Send-Q Local Address Foreign Address State +tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN +tcp 0 0 0.0.0.0:25 0.0.0.0:* LISTEN +tcp 0 0 127.0.0.1:53 0.0.0.0:* LISTEN +tcp 0 0 195.110.112.152:22 192.84.146.100:21100 ESTABLISHED +tcp 0 0 195.110.112.152:22 192.84.146.100:21101 ESTABLISHED +\end{verbatim} +cioè il client effettuerà la connessione usando un'altra porta effimera, con +questa sarà aperta la connessione, ed il server creerà un'altro processo +figlio sarà creato per gestirla. + +Tutto ciò mostra come TCP, per poter gestire le connessioni con un server +concorrente, non può suddividere i pacchetti solo sulla base della porta di +destinazione, ma deve usare tutta l'informazione contenuta nella socket pair, +compresa la porta dell'indirizzo remoto. E se andassimo a vedere quali sono i +processi a cui fanno riferimento i vari socket vedremmo che i pacchetti che +arrivano dalla porta remota 21100 vanno al primo figlio e quelli che arrivano +alla porta 21101 al secondo. -\section{I numeri di porta} -\label{sec:TCPel_ports} \section{Le funzioni dei socket TCP} \label{sec:TCPel_functions} -\subsection{La funzione \texttt{connect}} -\label{sec:TCPel_func_connect} +In questa sezione descriveremo in dettaglio le varie funzioni necessarie per +l'uso dei socket TCP già citate in precedenza (e utilizzate nei due esempi +\secref{sec:net_cli_sample} e \secref{sec:net_serv_sample}) con l'eccezione +della funzione \func{socket} che è già stata esaminata in dettaglio in +\secref{sec:sock_socket}. + +In \nfig\ abbiamo un tipico schema di funzionamento di un'applicazione +client-server che usa i socket TCP: prima il server viene avviato ed in +seguito il client si connette, in questo caso, a differenza di quanto accadeva +con gli esempi elementari del \capref{cha:network} si assume che sia il +client ad effettuare delle richieste a cui il server risponde, il client +notifica poi di avere concluso inviando un end-of-file a cui il server +risponderà anche lui chiudendo la connessione per aspettarne una nuova. + +\begin{figure}[!htb] + \centering + + \caption{Struttura delle funzioni dei socket per una semplice applicazione + client/server su TCP.} + \label{fig:TCPel_cliserv_func} +\end{figure} -\subsection{La funzione \texttt{bind}} +Useremo questo schema anche per l'esempio di reimplementazione del servizio +\texttt{daytime} che illustreremo in \secref{sec:TCPel_cunc_serv}. + + +\subsection{La funzione \func{bind}} \label{sec:TCPel_func_bind} -\subsection{La funzione \texttt{listen}} -\label{sec:TCPel_func_listen} +La funzione \func{bind} assegna un indirizzo locale ad un socket. È usata +cioè per specificare la prima parte dalla socket pair. Viene usata sul lato +server per specificare la porta (e gli eventuali indirizzi locali) su cui poi +ci si porrà in ascolto. Il prototipo della funzione è il seguente: +\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 \func{socket}, mentre il secondo e terzo argomento sono rispettivamente + l'indirizzo (locale) del socket e la dimensione della struttura che lo + contiene, secondo quanto già trattato in \secref{sec:sock_sockaddr}. + + \bodydesc{La funzione restituisce zero in caso di successo e -1 per un + errore; in caso di errore la variabile \var{errno} viene impostata secondo + i seguenti codici di errore: + \begin{errlist} + \item[\macro{EBADF}] il file descriptor non è valido. + \item[\macro{EINVAL}] il socket ha già un indirizzo assegnato. + \item[\macro{ENOTSOCK}] il file descriptor non è associato ad un socket. + \item[\macro{EACCESS}] si è cercato di usare una porta riservata senza + sufficienti privilegi. + \end{errlist}} +\end{prototype} + +Con il TCP la chiamata \func{bind} permette di specificare l'indirizzo, la +porta, entrambi o nessuno dei due. In genere i server utilizzano una porta +nota che assegnano all'avvio, se questo non viene fatto è il kernel a +scegliere una porta effimera quando vengono eseguite la funzioni +\func{connect} o \func{listen}, ma se questo è normale per il client non lo è +per il server\footnote{un'eccezione a tutto ciò sono i server che usano RPC. + In questo caso viene fatta assegnare dal kernel una porta effimera che poi + viene registrata presso il \textit{portmapper}; quest'ultimo è un altro + demone che deve essere contattato dai client per ottenere la porta effimera + su cui si trova il server.} che in genere viene identificato dalla porta su +cui risponde. + +Con \func{bind} si può assegnare un IP specifico ad un socket, purché questo +appartenga ad una interfaccia della macchina. Per un client TCP questo +diventerà l'indirizzo sorgente usato per i tutti i pacchetti inviati sul +socket, mentre per un server TCP questo restringerà l'accesso al socket solo +alle connessioni che arrivano verso tale indirizzo. + +Normalmente un client non specifica mai un indirizzo ad un suo socket, ed il +kernel sceglie l'indirizzo di origine quando viene effettuata la connessione +sulla base dell'interfaccia usata per trasmettere i pacchetti, (che dipende +dalle regole di instradamento usate per raggiungere il server). +Se un server non specifica il suo indirizzo locale il kernel userà come +indirizzo di origine l'indirizzo di destinazione specificato dal SYN del +client. + +Per specificare un indirizzo generico con IPv4 si usa il valore +\macro{INADDR\_ANY}, il cui valore, come visto anche negli esempi precedenti +è pari a zero, nell'esempio \figref{fig:net_serv_code} si è usata +un'assegnazione immediata del tipo: + +\footnotesize +\begin{lstlisting}[labelstep=0,frame=,indent=1cm]{} + serv_add.sin_addr.s_addr = htonl(INADDR_ANY); /* connect from anywhere */ +\end{lstlisting} +\normalsize + +Si noti che si è usato \func{htonl} per assegnare il valore +\macro{INADDR\_ANY}; benché essendo questo pari a zero il riordinamento sia +inutile; ma dato che tutte le costanti \macro{INADDR\_} sono definite +secondo l'ordinamento della macchina è buona norma usare sempre la funzione +\macro{htonl}. + +L'esempio precedete funziona con IPv4 dato che l'indirizzo è rappresentabile +anche con un intero a 32 bit; non si può usare lo stesso metodo con IPv6, +in cui l'indirizzo è specificato come struttura, perché il linguaggio C non +consente l'uso di una struttura costante come operando a destra in una +assegnazione. -\subsection{La funzione \texttt{connect}} +Per questo nell'header \file{netinet/in.h} è definita una variabile +\type{in6addr\_any} (dichiarata come \ctyp{extern}, ed inizializzata dal +sistema al valore \macro{IN6ADRR\_ANY\_INIT}) che permette di effettuare una +assegnazione del tipo: + +\footnotesize +\begin{lstlisting}[labelstep=0,frame=,indent=1cm]{} + serv_add.sin6_addr = in6addr_any; /* connect from anywhere */ +\end{lstlisting} +\normalsize + + +\subsection{La funzione \func{connect}} \label{sec:TCPel_func_connect} -\subsection{La funzione \texttt{accept}} +La funzione \func{connect} è usata da un client TCP per stabilire la +connessione con un server TCP, il prototipo della funzione è il seguente: +\begin{prototype}{sys/socket.h} +{int connect(int sockfd, const struct sockaddr *servaddr, socklen\_t addrlen)} + + Il primo argomento è un file descriptor ottenuto da una precedente chiamata + a \func{socket}, mentre il secondo e terzo argomento sono rispettivamente + l'indirizzo e la dimensione della struttura che contiene l'indirizzo del + socket, già descritta in \secref{sec:sock_sockaddr}. + + \bodydesc{La funzione restituisce zero in caso di successo e -1 per un + errore, in caso di errore la variabile \var{errno} viene impostata secondo + i seguenti codici di errore: + \begin{errlist} + \item[\macro{ECONNREFUSED}] non c'è nessuno in ascolto sull'indirizzo remoto. + \item[\macro{ETIMEDOUT}] si è avuto timeout durante il tentativo di + connessione. + \item[\macro{ENETUNREACH}] la rete non è raggiungibile. + \item[\macro{EINPROGRESS}] il socket è non bloccante (vedi + \secref{sec:file_noblocking}) e la connessione non può essere conclusa + immediatamente. + \item[\macro{EALREADY}] il socket è non bloccante (vedi + \secref{sec:file_noblocking}) e un tentativo precedente di connessione non + si è ancora concluso. + \item[\macro{EAGAIN}] non ci sono più porte locali libere. + \item[\macro{EAFNOSUPPORT}] l'indirizzo non ha una famiglia di indirizzi + corretta nel relativo campo. + \item[\macro{EACCESS, EPERM}] si è tentato di eseguire una connessione ad un + indirizzo broadcast senza che il socket fosse stato abilitato per il + broadcast. + \end{errlist} + altri errori possibili sono: \macro{EFAULT}, \macro{EBADF}, + \macro{ENOTSOCK}, \macro{EISCONN} e \macro{EADDRINUSE}.} +\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 +nell'esempio \secref{sec:net_cli_sample} usando le funzioni illustrate in +\secref{sec:sock_addr_func}. + +Nel caso di socket TCP la funzione \func{connect} avvia il \textit{three way + handshake}, e ritorna solo quando la connessione è stabilita o si è +verificato un errore. Le possibili cause di errore sono molteplici (ed i +relativi codici riportati sopra), quelle che però dipendono dalla situazione +della rete e non da errori o problemi nella chiamata della funzione sono le +seguenti: +\begin{enumerate} +\item Il client non riceve risposta al SYN: l'errore restituito è + \macro{ETIMEDOUT}. Stevens riporta che BSD invia un primo SYN alla chiamata + di \func{connect}, un'altro dopo 6 secondi, un terzo dopo 24 secondi, se + dopo 75 secondi non ha ricevuto risposta viene ritornato l'errore. Linux + invece ripete l'emissione del SYN ad intervalli di 30 secondi per un numero + di volte che può essere stabilito dall'utente sia con una opportuna + \func{sysctl} che attraverso il filesystem \file{/proc} scrivendo il valore + voluto in \file{/proc/sys/net/ipv4/tcp\_syn\_retries}. Il valore predefinito + per la ripetizione dell'invio è di 5 volte, che comporta un timeout dopo + circa 180 secondi. +% +% Le informazioni su tutte le opzioni impostabili via /proc stanno in +% Linux/Documentation/networking/ip-sysctl.txt +% +\item Il client riceve come risposta al SYN un RST significa che non c'è + nessun programma in ascolto per la connessione sulla porta specificata (il + che vuol dire probabilmente che o si è sbagliato il numero della porta o che + non è stato avviato il server), questo è un errore fatale e la funzione + ritorna non appena il RST viene ricevuto riportando un errore + \macro{ECONNREFUSED}. + + Il flag RST sta per \textit{reset} ed è un segmento inviato direttamente + dal TCP quando qualcosa non va. Tre condizioni che generano un RST sono: + quando arriva un SYN per una porta che non ha nessun server in ascolto, + quando il TCP abortisce una connessione in corso, quando TCP riceve un + segmento per una connessione che non esiste. + +\item Il SYN del client provoca l'emissione di un messaggio ICMP di + destinazione non raggiungibile. In questo caso dato che il messaggio può + essere dovuto ad una condizione transitoria si ripete l'emissione dei SYN + come nel caso precedente, fino al timeout, e solo allora si restituisce il + codice di errore dovuto al messaggio ICMP, che da luogo ad un + \macro{ENETUNREACH}. + +\end{enumerate} + +Se si fa riferimento al diagramma degli stati del TCP riportato in +\figref{fig:TCP_state_diag} la funzione \func{connect} porta un socket +dallo stato \texttt{CLOSED} (lo stato iniziale in cui si trova un socket +appena creato) prima allo stato \texttt{SYN\_SENT} e poi, al ricevimento del +ACK, nello stato \texttt{ESTABLISHED}. Se invece la connessione fallisce il +socket non è più utilizzabile e deve essere chiuso. + +Si noti infine che con la funzione \func{connect} si è specificato solo +indirizzo e porta del server, quindi solo una metà della socket pair; essendo +questa funzione usata nei client l'altra metà contenente indirizzo e porta +locale viene lasciata all'assegnazione automatica del kernel, e non è +necessario effettuare una \func{bind}. + + +\subsection{La funzione \func{listen}} +\label{sec:TCPel_func_listen} + +La funzione \func{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}. In genere si chiama la funzione in +un server dopo le chiamate a \func{socket} e \func{bind} e prima della +chiamata ad \func{accept}. Il prototipo della funzione come definito dalla +pagina di manuale è: +\begin{prototype}{sys/socket.h}{int listen(int sockfd, int backlog)} + La funzione pone il socket specificato da \var{sockfd} in modalità + passiva e predispone una coda per le connessioni in arrivo di lunghezza pari + a \var{backlog}. La funzione si può applicare solo a socket di tipo + \macro{SOCK\_STREAM} o \macro{SOCK\_SEQPACKET}. + + \bodydesc{La funzione restituisce 0 in caso di successo e -1 in caso di + errore. I codici di errore restituiti in \var{errno} sono i seguenti: + \begin{errlist} + \item[\macro{EBADF}] l'argomento \var{sockfd} non è un file descriptor + valido. + \item[\macro{ENOTSOCK}] l'argomento \var{sockfd} non è un socket. + \item[\macro{EOPNOTSUPP}] il socket è di un tipo che non supporta questa + operazione. + \end{errlist}} +\end{prototype} + + +Il parametro \var{backlog} indica il numero massimo di connessioni pendenti +accettate; se esso viene ecceduto il client riceverà una errore di tipo +\macro{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 un ingresso per ciascun socket per il quale è arrivato + un SYN ma il three way handshake non si è ancora concluso. Questi socket + sono tutti nello stato \texttt{SYN\_RECV}. +\item Una coda delle connessioni complete (\textit{complete connection queue} + che contiene un ingresso per ciascun socket per il quale il three way + handshake è stato completato ma ancora \func{accept} non è ritornata. + Questi socket sono tutti nello stato \texttt{ESTABLISHED}. +\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 \func{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 \var{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. + +In Linux il significato di questo valore è cambiato a partire dal kernel 2.2 +per prevenire l'attacco chiamato \textit{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 e la coda delle connessioni incomplete viene saturata, impedendo di +fatto ulteriori connessioni. + +Per ovviare a questo il significato del \var{backlog} è stato cambiato a +indicare la lunghezza della coda delle connessioni complete. La lunghezza +della coda delle connessioni incomplete può essere ancora controllata usando +la \func{sysctl} o scrivendola direttamente in +\file{/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 \file{/proc/sys/net/ipv4/tcp\_syncookies}) questo valore +viene ignorato e non esiste più un valore massimo. In ogni caso in Linux il +valore di \var{backlog} viene troncato ad un massimo di \macro{SOMAXCONN} +se è superiore a detta costante (che di default vale 128). + +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 per via della presenza di server web che devono gestire un gran +numero di connessioni per cui un tale valore non è più adeguato. Non esiste +comunque una risposta univoca per la scelta del valore, per questo non +conviene specificarlo con una costante (il cui cambiamento richiederebbe la +ricompilazione del server) ma usare piuttosto una variabile di ambiente (vedi +\secref{sec:proc_environ}). + +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 il +compito principale della coda sia quello di gestire il caso in cui il server è +occupato fra chiamate successive alla \func{accept} (per cui la coda più +occupata sarebbe quella delle connessioni completate), ma piuttosto quello di +gestire la presenza di un gran numero di SYN in attesa di concludere il +three way handshake. + +Infine va messo in evidenza che nel caso di socket TCP quando un SYN arriva +con tutte le code piene, il pacchetto deve essere ignorato. Questo perché la +condizione in cui le code sono piene è ovviamente transitoria, per cui se il +client ritrasmette il SYN è probabile che passato un po' di tempo possa +trovare nella coda lo spazio per una nuova connessione. Se invece si +rispondesse con un RST per indicare l'impossibilità di effettuare la +connessione la chiamata a \func{connect} nel client ritornerebbe con una +condizione di errore, costringendo a inserire nell'applicazione la gestione +dei tentativi di riconnessione che invece può essere effettuata in maniera +trasparente dal protocollo TCP. + + +\subsection{La funzione \func{accept}} \label{sec:TCPel_func_accept} +La funzione \func{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 accept(int sockfd, struct sockaddr *addr, socklen\_t *addrlen)} + Estrae la prima connessione relativa al socket \var{sockfd} + in attesa sulla coda delle connessioni complete, che associa ad nuovo socket + con le stesse caratteristiche di \var{sockfd} (restituito dalla funzione + stessa). Il socket originale non viene toccato. Nella struttura + \var{addr} e nella variabile \var{addrlen} vengono restituiti + indirizzo e relativa lunghezza del client che si è connesso. + + \bodydesc{La funzione restituisce un numero di socket descriptor positivo in + caso di successo e -1 in caso di errore, nel qual caso la variabile + \var{errno} viene impostata ai seguenti valori: + + \begin{errlist} + \item[\macro{EBADF}] l'argomento \var{sockfd} non è un file descriptor + valido. + \item[\macro{ENOTSOCK}] l'argomento \var{sockfd} non è un socket. + \item[\macro{EOPNOTSUPP}] il socket è di un tipo che non supporta questa + operazione. + \item[\macro{EAGAIN} o \macro{EWOULDBLOCK}] il socket è stato impostato come + non bloccante (vedi \secref{sec:file_noblocking}), e non ci sono + connessioni in attesa di essere accettate. + \item[\macro{EPERM}] Le regole del firewall non consentono la connessione. + \item[\macro{ENOBUFS, ENOMEM}] questo spesso significa che l'allocazione + della memoria è limitata dai limiti sui buffer dei socket, non dalla + memoria di sistema. + \end{errlist} + Inoltre possono essere restituiti gli errori di rete relativi al nuovo + socket come: \macro{EMFILE}, \macro{EINVAL}, \macro{ENOSR}, \macro{ENOBUFS}, + \macro{EFAULT}, \macro{EPERM}, \macro{ECONNABORTED}, + \macro{ESOCKTNOSUPPORT}, \macro{EPROTONOSUPPORT}, \macro{ETIMEDOUT}, + \macro{ERESTARTSYS}.} +\end{prototype} + +La funzione può essere usata solo con socket che supportino la connessione +(cioè di tipo \macro{SOCK\_STREAM}, \macro{SOCK\_SEQPACKET} o +\macro{SOCK\_RDM}). Per alcuni protocolli che richiedono una conferma +esplicita della connessione, (attualmente 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 \func{read} o una \func{write} mentre il rifiuto della +connessione viene fatto con la funzione \func{close}. + +È da chiarire che Linux presenta un comportamento diverso nella gestione degli +errori rispetto ad altre implementazioni dei socket BSD, infatti la funzione +\func{accept} passa gli errori di rete pendenti sul nuovo socket come codici +di errore per \func{accept}. Inoltre la funzione non fa ereditare ai nuovi +socket flag come \macro{O\_NONBLOCK}, che devono essere rispecificati volta +volta, questo è un comportamento diverso rispetto a quanto accade con BSD e +deve essere tenuto in conto per scrivere programmi portabili. + +I due argomenti \var{cliaddr} e \var{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 +\var{addrlen} deve essere inizializzato alle dimensioni della struttura il +cui indirizzo è passato come argomento in \var{cliaddr}, al ritorno della +funzione \var{addrlen} conterrà il numero di byte scritti dentro +\var{cliaddr}. Se questa informazione non interessa basterà inizializzare a +\macro{NULL} detti puntatori. + +Se la funzione ha successo restituisce il descrittore di un nuovo socket +creato dal kernel (detto \textit{connected socket}) a cui viene associata la +prima connessione completa (estratta dalla relativa coda, vedi +\secref{sec:TCPel_func_listen}) che il client TCP ha effettuato verso il +socket \var{sockfd}. Quest'ultimo (detto \textit{listening socket}) è quello +creato all'inizio e messo in ascolto con \func{listen}, e non viene toccato +dalla funzione. Se non ci sono connessioni pendenti da accettare la funzione +mette in attesa il processo\footnote{a meno che non si sia imopstato il socket + per essere non bloccante (vedi \secref{sec:file_noblocking}), nel qual caso + ritorna con l'errore \macro{EAGAIN}. Torneremo su questa modalità di + operazione in \secref{sec:xxx_sock_noblock}.} fintanto che non ne arriva +una. + +Il meccanismo di funzionamento di \func{accept} è essenziale per capire il +funzionamento di un server: in generale infatti c'è sempre un solo socket in +ascolto, che resta per tutto il tempo nello stato \texttt{LISTEN}, mentre le +connessioni vengono gestite dai nuovi socket ritornati da \func{accept} che +si trovano automaticamente nello stato \texttt{ESTABLISHED} e utilizzati fino +alla chiusura della connessione che avviene su di essi. Si può riconoscere +questo schema anche nell'esempio elementare in \figref{fig:net_serv_code} dove +per ogni connessione il socket creato da \func{accept} viene chiuso dopo +l'invio dei dati. + + +\subsection{La funzione \func{close}} +\label{sec:TCPel_func_close} + +La funzione standard unix \func{close} (vedi \secref{sec:file_close}) che si +usa sui file può essere usata con lo stesso effetto anche sui socket +descriptor. + +L'azione standard di questa funzione quando applicata a socket è di marcarlo +come chiuso e ritornare immediatamente al processo. Una volta chiamata il +socket descriptor non è più utilizzabile dal processo e non può essere usato +come argomento per una \func{write} o una \func{read} (anche se l'altro +capo della connessione non avesse chiuso la sua parte). Il kernel invierà +comunque tutti i dati che ha in coda prima di iniziare la sequenza di chiusura. + +Vedremo più avanti in \secref{sec:TCPadv_so_linger} come è possibile cambiare +questo comportamento, e cosa deve essere fatto perché il processo possa +assicurarsi che l'altro capo abbia ricevuto tutti i dati. + +Come per i file anche per i socket descriptor viene mantenuto un numero di +riferimenti, per cui se più di un processo ha lo stesso socket aperto +l'emissione del FIN e la sequenza di chiusura di TCP non viene innescata +fintanto che il numero di riferimenti non si annulla. Questo è il +comportamento normale che ci si aspetta in un'applicazione client/server quale +quella che illustreremo in \secref{sec:TCPel_cunc_serv}. + +Per attivare immediatamente l'emissione del FIN e la sequenza di chiusura si +può usare la funzione \func{shutdown} su cui torneremo in seguito. + + + +\section{I server concorrenti su TCP} +\label{sec:TCPel_cunc_serv} + +Il server \texttt{daytime} dell'esempio in \secref{sec:net_cli_sample} è un +tipico esempio di server iterativo, in cui viene servita una richiesta alla +volta; in generale però, specie se il servizio è più complesso e comporta uno +scambio di dati più sostanzioso di quello in questione, non è opportuno +bloccare un server nel servizio di un client per volta; per questo si ricorre +alle capacità di multitasking del sistema. + +Il modo più immediato per creare un server concorrente è allora quello di +usare la funzione \func{fork} per far creare al server per ogni richiesta da +parte di un client un processo figlio che si incarichi della gestione della +comunicazione. + + +\subsection{Un esempio di server \textit{daytime} concorrente} +\label{sec:TCPel_cunc_daytime} + +Per illustrare il meccanismo usato in generale per creare un server +concorrente abbiamo riscritto il server \texttt{daytime} dell'esempio +precedente in forma concorrente, inserendo anche una opzione per la stampa +degli indirizzi delle connessioni ricevute. + +In \nfig\ è mostrato un estratto del codice, in cui si sono tralasciati il +trattamento delle opzioni e le parti rimaste invariate rispetto al precedente +esempio. Al solito il sorgente completo del server +\file{ElemDaytimeTCPCuncServ.c} è allegato nella directory dei sorgenti. + +\begin{figure}[!htb] + \footnotesize + \begin{lstlisting}{} +#include /* predefined types */ +#include /* include unix standard library */ +#include /* IP addresses conversion utiliites */ +#include /* socket library */ +#include /* include standard I/O library */ +#include + +int main(int argc, char *argv[]) +{ + int list_fd, conn_fd; + int i; + struct sockaddr_in serv_add, client; + char buffer[MAXLINE]; + socklen_t len; + time_t timeval; + pid_t pid; + int logging=0; + ... + /* write daytime to client */ + while (1) { + if ( (conn_fd = accept(list_fd, (struct sockaddr *)&client, &len)) + <0 ) { + perror("accept error"); + exit(-1); + } + /* fork to handle connection */ + if ( (pid = fork()) < 0 ){ + perror("fork error"); + exit(-1); + } + if (pid == 0) { /* child */ + close(list_fd); + timeval = time(NULL); + snprintf(buffer, sizeof(buffer), "%.24s\r\n", ctime(&timeval)); + if ( (write(conn_fd, buffer, strlen(buffer))) < 0 ) { + perror("write error"); + exit(-1); + } + if (logging) { + inet_ntop(AF_INET, &client.sin_addr, buffer, sizeof(buffer)); + printf("Request from host %s, port %d\n", buffer, + ntohs(client.sin_port)); + } + close(conn_fd); + exit(0); + } else { /* parent */ + close(conn_fd); + } + } + /* normal exit, never reached */ + exit(0); +} + \end{lstlisting} + \caption{Esempio di codice di un server concorrente elementare per il + servizio daytime.} + \label{fig:TCPel_serv_code} +\end{figure} + +Come si può vedere (alle linee \texttt{\small 21--25}) la funzione +\func{accept} stavolta è chiamata fornendo una struttura di indirizzi in cui +saranno ritornati numero IP e porta da cui il client effettua la connessione, +che stamperemo, se avremo abilitato il logging, sullo standard output +(\texttt{\small 39--43}). + +Quando \func{accept} ritorna il server chiama la funzione \func{fork} +(\texttt{\small 26--30}) per creare il processo figlio che effettuerà tutte le +operazioni relative a quella connessione (\texttt{\small 31--45}), mentre il +padre resterà in attesa di ulteriori connessioni. + +Si noti come il figlio operi solo sul socket connesso, chiudendo +immediatamente il socket \var{list\_fd}; mentre il padre continua ad operare +solo sul socket in ascolto chiudendo \var{sock\_fd} dopo ciascuna +\func{accept}. Per quanto abbiamo detto in \secref{sec:TCPel_func_close} +queste due chiusure non causano l'innesco della sequenza di chiusura perché il +numero di riferimenti non si è annullato. + +Infatti subito dopo la creazione del socket \var{list\_fd} ha una +referenza, e lo stesso vale per \var{sock\_fd} dopo il ritorno di +\func{accept}, ma dopo la fork i descrittori vengono duplicati nel padre e +nel figlio per cui entrambi i socket si trovano con due referenze. Questo fa +si che quando il padre chiude \var{sock\_fd} esso resta con una referenza +da parte del figlio, e sarà definitivamente chiuso solo quando quest'ultimo, +dopo aver completato le sue operazioni, chiamerà la funzione \func{close}. + +In realtà per il figlio non sarebbero necessarie nessuna delle due chiamate a +\func{close} in quanto nella \func{exit} tutti i file ed i socket vengono +chiusi, ma si è preferito effettuare la chiusura esplicitamente per avere una +maggiore chiarezza del codice ed evitare possibili errori. + +Si noti come sia essenziale che il padre chiuda ogni volta il socket connesso +dopo la \func{accept}; se così non fosse nessuno di questi socket sarebbe +effettivamente chiuso dato che alla chiusura da parte del figlio resterebbe +ancora un riferimento. Si avrebbero così due effetti, il padre potrebbe +esaurire i descrittori disponibili (che sono un numero limitato per ogni +processo) e soprattutto nessuna delle connessioni con i client verrebbe +chiusa. + + +\subsection{Le funzioni \func{getsockname} e \func{getpeername}} +\label{sec:TCPel_get_names} + +Queste due funzioni vengono usate per ottenere la socket pair associata ad un +certo socket; la prima restituisce l'indirizzo locale, la seconda quello +remoto. + +\begin{prototype}{sys/socket.h} + {int getsockname(int sockfd, struct sockaddr * name, socklen\_t * namelen)} + Legge l'indirizzo locale del socket \param{sockfd} nella struttura + \param{name}. + +\bodydesc{La funzione restituisce 0 in caso di successo e -1 in caso di + errore. I codici di errore restituiti in \var{errno} sono i seguenti: + \begin{errlist} + \item[\macro{EBADF}] l'argomento \var{sockfd} non è un file descriptor + valido. + \item[\macro{ENOTSOCK}] l'argomento \var{sockfd} non è un socket. + \item[\macro{ENOBUFS}] non ci sono risorse sufficienti nel sistema per + eseguire l'operazione. + \item[\macro{EFAULT}] l'argomento \var{name} punta al di fuori dello + spazio di indirizzi del processo. + \end{errlist}} +\end{prototype} + +La funzione \func{getsockname} si usa tutte le volte che si vuole avere +l'indirizzo locale di un socket; ad esempio può essere usata da un client (che +usualmente non chiama \func{bind}) per ottenere numero IP e porta locale +associati al socket restituito da una \func{connect}, o da un server che ha +chiamato \func{bind} su un socket usando 0 come porta locale per ottenere il +numero di porta effimera assegnato dal kernel. + +Inoltre quando un server esegue una \func{bind} su un indirizzo generico, se +chiamata dopo il completamento di una connessione sul socket restituito da +\func{accept}, restituisce l'indirizzo locale che il kernel ha assegnato a +quella connessione. + +\begin{prototype}{sys/socket.h} + {int getpeername(int sockfd, struct sockaddr * name, socklen\_t * namelen)} + Legge l'indirizzo remoto del socket \param{sockfd} nella struttura + \param{name}. + + \bodydesc{La funzione restituisce 0 in caso di successo e -1 in caso di + errore. I codici di errore restituiti in \var{errno} sono i seguenti: + \begin{errlist} + \item[\macro{EBADF}] l'argomento \var{sockfd} non è un file descriptor + valido. + \item[\macro{ENOTSOCK}] l'argomento \var{sockfd} non è un socket. + \item[\macro{ENOTCONN}] il socket non è connesso. + \item[\macro{ENOBUFS}] non ci sono risorse sufficienti nel sistema per + eseguire l'operazione. + \item[\macro{EFAULT}] l'argomento \var{name} punta al di fuori dello + spazio di indirizzi del processo. + \end{errlist}} +\end{prototype} + + +La funzione \func{getpeername} si usa tutte le volte che si vuole avere +l'indirizzo remoto di un socket. + +Ci si può chiedere a cosa serva questa funzione dato che dal lato client +l'indirizzo remoto è sempre noto quando si esegue la \func{connect} mentre +dal lato server si possono usare, come si è fatto nell'esempio precedente, i +valori di ritorno di \func{accept}. + +In generale però questa ultima possibilità è sempre possibile. In particolare +questo avviene quando il server invece di far gestire la connessione +direttamente a un processo figlio, come nell'esempio precedente, lancia un +opportuno programma per ciascuna connessione usando \func{exec} (questa ad +esempio è la modalità con cui opera il \textsl{super-server} \cmd{inetd} +che gestisce tutta una serie di servizi lanciando per ogni connessione +l'opportuno server). + +In questo caso benché il processo figlio abbia una immagine della memoria che +è copia di quella del processo padre (e contiene quindi anche la struttura +ritornata da \func{accept}), all'esecuzione di \func{exec} viene caricata +in memoria l'immagine del programma eseguito che a questo punto perde ogni +riferimento. Il socket descriptor però resta aperto. Allora se una opportuna +convenzione è seguita per rendere noto al programma eseguito qual'è il socket +connesso (\cmd{inetd} ad esempio fa sempre in modo che i file descriptor 0, +1 e 2 corrispondano al socket connesso) quest'ultimo potrà usare la funzione +\func{getpeername} per determinare l'indirizzo remoto del client. -\subsection{Le porte} +Infine è da chiarire (si legga la pagina di manuale) che, come per +\func{accept}, il terzo parametro, che è specificato dallo standard POSIX.1g +come di tipo \code{socklen\_t *} in realtà deve sempre corrispondere ad un +\ctyp{int *} come prima dello standard perché tutte le implementazioni dei +socket BSD fanno questa assunzione. +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "gapil" +%%% End: