From: Simone Piccardi Date: Sun, 17 Aug 2003 17:17:50 +0000 (+0000) Subject: Aggiunta trattazione del crash del server, eseguite alcune correzioni alla X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=commitdiff_plain;h=24b55de696cfd8c0d7238a6b536802aba233912d;hp=571305d265d04aec0e35182ff17197043757e14a Aggiunta trattazione del crash del server, eseguite alcune correzioni alla trattazione di SIPIPE --- 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/elemtcp.tex b/elemtcp.tex deleted file mode 100644 index a49c48c..0000000 --- a/elemtcp.tex +++ /dev/null @@ -1,2262 +0,0 @@ -%% elemtcp.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 -%% Documentation License, Version 1.1 or any later version published by the -%% Free Software Foundation; with the Invariant Sections being "Prefazione", -%% with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the -%% license is included in the section entitled "GNU Free Documentation -%% License". -%% -\chapter{Socket TCP} -\label{cha:TCP_socket} - -In questo capitolo iniziamo ad approfondire la conoscenza dei socket TCP, -iniziando con una descrizione delle principali caratteristiche del -funzionamento di una connessione TCP. Tratteremo poi le varie funzioni che -servono alla creazione di una connessione fra un server elementare ed il suo -client, fornendo poi alcuni esempi di applicazione elementare. - - - -\section{Il funzionamento di una connessione TCP} -\label{sec:TCP_connession} - -Prima di entrare nei dettagli delle singole funzioni usate nelle applicazioni -che utilizzano i socket TCP, è fondamentale spiegare alcune delle basi del -funzionamento del protocollo, poiché questa conoscenza è essenziale per -comprendere il comportamento di dette funzioni per questo tipo di socket, ed -il relativo modello di programmazione. - -Si ricordi che il protocollo TCP serve a creare degli \textit{stream socket}, -cioè una forma di canale di comunicazione che stabilisce una connessione -stabile fra due stazioni, in modo che queste possano scambiarsi dei dati. In -questa sezione ci concentreremo sulle modalità con le quali il protocollo dà -inizio e conclude una connessione e faremo inoltre un breve accenno al -significato di alcuni dei vari \textsl{stati} ad essa associati. - -\subsection{La creazione della connessione: il \textit{three way handshake}} -\label{sec:TCP_conn_cre} - -Il processo che porta a creare una connessione TCP è chiamato \textit{three - way handshake}; la successione tipica degli eventi (e dei -\textsl{segmenti}\footnote{Si ricordi che il segmento è l'unità elementare di - dati trasmessa dal protocollo TCP al livello successivo; 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 \secref{sec:tcp_protocol}).} di dati che vengono -scambiati) 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, 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 - e 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 \figref{fig:TCP_TWH} -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:TCP_TWH} -\end{figure} - -Si è accennato in precedenza ai \textsl{numeri di sequenza} (che sono anche -riportati in \figref{fig:TCP_TWH}): 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. - -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 \figref{fig:TCP_close}) per l'acknowledgement di un FIN. - -\subsection{Le opzioni TCP.} -\label{sec:TCP_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 \const{TCP\_MAXSEG}. - -\item \textit{window scale option}, %come spiegato in \secref{sec: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;\footnote{ Linux usa come massimo 32767 - per evitare problemi con alcune implementazioni che usano l'aritmetica con - segno per implementare lo stack TCP.} ma alcuni tipi di connessione come - quelle ad alta velocità (sopra i 45Mbit/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 spostare 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'\href{http://www.ietf.org/rfc/rfc1323.txt}{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:TCP_conn_term} - -Mentre per la creazione di una connessione occorre un interscambio di tre -segmenti, la procedura di chiusura ne richiede normalmente quattro. 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 - serve ad indicare che si è finito con l'invio dei dati sulla connessione. - -\item L'altro capo della connessione riceve il FIN e dovrà eseguire la - \textsl{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 Una volta rilevata l'end-of-file 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 -\figref{fig:TCP_close} si è rappresentato graficamente lo sequenza di -scambio dei segmenti che conclude la connessione. - -\begin{figure}[htb] - \centering - \includegraphics[width=10cm]{img/tcp_close} - \caption{La chiusura di una connessione TCP.} - \label{fig:TCP_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 in \secref{xxx_shutdown}, quando parleremo della funzione -\func{shutdown}. - -La emissione del FIN avviene quando il socket viene chiuso, questo però non -avviene solo per la chiamata esplicita della funzione \func{close}, ma anche -alla terminazione di un processo, quando tutti i file vengono chiusi. Questo -comporta ad esempio che se un processo viene terminato da un segnale tutte le -connessioni aperte verranno chiuse. - -Infine occorre sottolineare che, benché nella figura (e nell'esempio che -vedremo più avanti in \secref{sec:TCP_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 nell'esempio di -\figref{fig:TCP_daytime_iter_server_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:TCP_conn_dia} - -Come abbiamo visto le operazioni del TCP nella creazione e conclusione di una -connessione sono piuttosto complesse, ed abbiamo esaminato soltanto quelle -relative ad un andamento normale. In \secref{sec:TCP_states} vedremo con -maggiori dettagli che una connessione può assumere vari stati, che ne -caratterizzano il funzionamento, e che sono quelli che vengono riportati dal -comando \cmd{netstat}, per ciascun socket TCP aperto, nel campo -\textit{State}. - -Non possiamo affrontare qui una descrizione completa del funzionamento del -protocollo; un approfondimento sugli aspetti principali si trova in -\secref{sec:tcp_protocol}, ma per una trattazione completa il miglior -riferimento resta \cite{TCPIll1}. 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 funzione \texttt{close} prima di aver ricevuto un -\textit{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 \figref{fig:TCP_conn_example} è 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:TCP_conn_example} -\end{figure} - -La connessione viene iniziata dal client che annuncia un MSS di 1460, un -valore tipico con Linux per IPv4 su Ethernet, 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:TCP_conn_term}; si noti che il capo della -connessione che esegue la chiusura attiva entra nello stato -\texttt{TIME\_WAIT}, sul cui significato 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:TCP_time_wait} - -Come riportato da Stevens in \cite{UNP1} lo stato \texttt{TIME\_WAIT} è -probabilmente uno degli aspetti meno compresi del protocollo TCP, è infatti -comune trovare 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 \figref{fig:TCP_conn_example}) -\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 ``\textsl{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'\href{http://www.ietf.org/rfc/rfc1122.txt}{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 -\figref{fig:TCP_conn_example}: 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 un socket deve rimanere attivo nello stato \texttt{TIME\_WAIT} anche -dopo l'invio dell'ultimo ACK, per potere essere in grado di gestirne -l'eventuale ritrasmissione, in caso esso venga perduto. - -Il secondo motivo è più complesso da capire, e necessita di una spiegazione -degli scenari in cui può accadere che i pacchetti TCP si possano perdere nella -rete o restare intrappolati, per poi riemergere in un secondo tempo. - -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 TCP, chi l'ha inviato, -non ricevendo un ACK in 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 \texttt{195.110.112.236} porta 1550 e l'IP -\texttt{192.84.145.100} porta 22 (affronteremo il significato delle porte -nella prossima sezione), 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 per le risposte nella direzione opposta. - -In questo modo, prima che venga creata una nuova connessione, il protocollo -TCP si assicura che tutti gli eventuali segmenti residui di una precedente -connessione, che potrebbero causare disturbi, siano stati eliminati dalla -rete. - - -\subsection{I numeri di porta} -\label{sec:TCP_port_num} - -In un ambiente multitasking in un dato momento più processi devono poter 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 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'\href{http://www.ietf.org/rfc/rfc1700.txt}{RFC~1700} che contiene -l'elenco delle porte assegnate dalla IANA (la \textit{Internet Assigned Number - Authority}) ma l'elenco viene costantemente aggiornato e pubblicato su -internet (una versione aggiornata si può trovare all'indirizzo -\texttt{ftp://ftp.isi.edu/in-notes/iana/assignements/port-numbers}); inoltre -in un sistema unix-like un analogo elenco viene mantenuto nel file -\file{/etc/services}, con la corrispondenza fra i vari 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'\href{http://www.ietf.org/rfc/rfc1700.txt}{RFC~1700} i vari sistemi hanno -fatto scelte diverse per le porte effimere, in particolare in -\figref{fig:TCP_port_alloc} sono riportate quelle di BSD 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=15cm]{img/port_alloc} - \caption{Allocazione dei numeri di porta.} - \label{fig:TCP_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 amministratore, 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, contando appunto sul fatto che -solo l'amministratore può usare queste porte. Data l'assoluta inconsistenza in -termini di sicurezza di un tale metodo, al giorno d'oggi esso è in completo -disuso. - -Data una connessione TCP si suole chiamare \textit{socket pair}\footnote{da - non confondere con la coppia di socket della omonima funzione - \func{socketpair} che fanno riferimento ad una coppia di socket sulla stessa - macchina, non ai capi di una connessione TCP.} 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 -(\texttt{195.110.112.152:22}, \texttt{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:TCP_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 quelle che descriveremo in -\secref{sec:TCP_daytime_application} e \secref{sec:TCP_echo_application}) -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 (il cui indirizzo sia -\texttt{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 \texttt{0.0.0.0} usata da -\cmd{netstat} è equivalente all'asterisco utilizzato per il numero di porta, -indica il valore generico, e corrisponde al valore \const{INADDR\_ANY} -definito in \file{arpa/inet.h} (vedi \ref{tab:TCP_ipv4_addr}). - -Inoltre si noti come la porta e l'indirizzo di ogni eventuale connessione -esterna non sono specificati; in questo caso la \textit{socket pair} associata -al socket potrebbe essere indicata come (\texttt{*:22}, \texttt{*:*}), usando -anche per gli indirizzi l'asterisco come carattere che indica il valore -generico. - -Dato che in genere una macchina è associata ad un solo indirizzo IP, ci si può -chiedere che senso abbia l'utilizzo dell'indirizzo generico per specificare -l'indirizzo locale; ma a parte il caso di macchine che hanno più di un -indirizzo IP (il cosiddetto \textit{multihoming}) esiste sempre anche -l'indirizzo di loopback, per cui con l'uso dell'indirizzo generico si possono -accettare connessioni indirizzate verso uno qualunque degli indirizzi IP -presenti. Ma, come si può vedere nell'esempio con il DNS che è in ascolto -sulla porta 53, è possibile anche restringere l'accesso ad 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, per esempio -quella con l'indirizzo \texttt{192.84.146.100}, si dovrà lanciare su -quest'ultima un client \cmd{ssh} per creare una connessione, e il kernel gli -assocerà una porta effimera (ad esempio la 21100), per cui la connessione sarà -espressa dalla socket pair (\texttt{192.84.146.100:21100}, -\texttt{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 \cmd{netstat} otteniamo 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 utilizza anch'esso -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 \cmd{ssh} per una seconda -connessione quello che otterremo usando \cmd{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 per gestirla. - -Tutto ciò mostra come il 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\footnote{ad esempio con il comando \cmd{fuser}, o con \cmd{lsof}.} 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{Le funzioni di base per la gestione dei socket} -\label{sec:TCP_functions} - -In questa sezione descriveremo in maggior dettaglio le varie funzioni che -vengono usate per la gestione di base dei socket TCP, non torneremo però sulla -funzione \func{socket}, che è già stata esaminata accuratamente nel capitolo -precedente in \secref{sec:sock_socket}. - - -\subsection{La funzione \func{bind}} -\label{sec:TCP_func_bind} - -La funzione \funcd{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)} - - Assegna un indirizzo ad un socket. - - \bodydesc{La funzione restituisce 0 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[\errcode{EBADF}] il file descriptor non è valido. - \item[\errcode{EINVAL}] il socket ha già un indirizzo assegnato. - \item[\errcode{ENOTSOCK}] il file descriptor non è associato ad un socket. - \item[\errcode{EACCES}] si è cercato di usare una porta riservata senza - sufficienti privilegi. - \item[\errcode{EADDRNOTAVAIL}] Il tipo di indirizzo specificato non è - disponibile. - \item[\errcode{EADDRINUSE}] qualche altro socket sta già usando l'indirizzo. - \end{errlist} - ed anche \errval{EFAULT} e per i socket di tipo \const{AF\_UNIX}, - \errval{ENOTDIR}, \errval{ENOENT}, \errval{ENOMEM}, \errval{ELOOP}, - \errval{ENOSR} e \errval{EROFS}.} -\end{prototype} - -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}. - -Con i socket 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 (l'elenco di queste porte, e dei relativi servizi, è in -\file{/etc/services}). - -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 l'indirizzo di un socket, ed il kernel -sceglie l'indirizzo di origine quando viene effettuata la connessione, sulla -base dell'interfaccia usata per trasmettere i pacchetti, (che dipenderà 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 -\const{INADDR\_ANY}, il cui valore, come accennato in -\secref{sec:sock_sa_ipv4}, è pari a zero; nell'esempio -\figref{fig:TCP_daytime_iter_server_code} si è usata un'assegnazione immediata -del tipo: \includecodesnip{listati/serv_addr_sin_addr.c} - -Si noti che si è usato \func{htonl} per assegnare il valore -\const{INADDR\_ANY}, anche se, essendo questo nullo, il riordinamento è -inutile. Si tenga presente comunque che tutte le costanti \val{INADDR\_} -(riportate in \tabref{tab:TCP_ipv4_addr}) sono definite secondo -l'\textit{endianess} della macchina, ed anche se esse possono essere -invarianti rispetto all'ordinamento dei bit, è comunque buona norma usare -sempre la funzione \func{htonl}. - -\begin{table}[htb] - \centering - \footnotesize - \begin{tabular}[c]{|l|l|} - \hline - \textbf{Costante} & \textbf{Significato} \\ - \hline - \hline - \const{INADDR\_ANY} & Indirizzo generico (\texttt{0.0.0.0})\\ - \const{INADDR\_BROADCAST}& Indirizzo di \textit{broadcast}.\\ - \const{INADDR\_LOOPBACK} & Indirizzo di \textit{loopback} - (\texttt{127.0.0.1}).\\ - \const{INADDR\_NONE} & Indirizzo errato.\\ - \hline - \end{tabular} - \caption{Costanti di definizione di alcuni indirizzi generici per IPv4.} - \label{tab:TCP_ipv4_addr} -\end{table} - -L'esempio precedente funziona correttamente con IPv4 poiché che l'indirizzo è -rappresentabile anche con un intero a 32 bit; non si può usare lo stesso -metodo con IPv6, in cui l'indirizzo deve necessariamente essere specificato -con una struttura, perché il linguaggio C non consente l'uso di una struttura -costante come operando a destra in una assegnazione. - -Per questo motivo nell'header \file{netinet/in.h} è definita una variabile -\const{in6addr\_any} (dichiarata come \direct{extern}, ed inizializzata dal -sistema al valore \const{IN6ADRR\_ANY\_INIT}) che permette di effettuare una -assegnazione del tipo: \includecodesnip{listati/serv_addr_sin6_addr.c} in -maniera analoga si può utilizzare la variabile \const{in6addr\_loopback} per -indicare l'indirizzo di \textit{loopback}, che a sua volta viene inizializzata -staticamente a \const{IN6ADRR\_LOOPBACK\_INIT}. - - - -\subsection{La funzione \func{connect}} -\label{sec:TCP_func_connect} - -La funzione \funcd{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)} - - Stabilisce una connessione fra due socket. - - \bodydesc{La funzione restituisce zero in caso di successo e -1 per un - errore, nel qual caso \var{errno} assumerà i valori: - \begin{errlist} - \item[\errcode{ECONNREFUSED}] non c'è nessuno in ascolto sull'indirizzo - remoto. - \item[\errcode{ETIMEDOUT}] si è avuto timeout durante il tentativo di - connessione. - \item[\errcode{ENETUNREACH}] la rete non è raggiungibile. - \item[\errcode{EINPROGRESS}] il socket è non bloccante (vedi - \secref{sec:file_noblocking}) e la connessione non può essere conclusa - immediatamente. - \item[\errcode{EALREADY}] il socket è non bloccante (vedi - \secref{sec:file_noblocking}) e un tentativo precedente di connessione non - si è ancora concluso. - \item[\errcode{EAGAIN}] non ci sono più porte locali libere. - \item[\errcode{EAFNOSUPPORT}] l'indirizzo non ha una famiglia di indirizzi - corretta nel relativo campo. - \item[\errcode{EACCES}, \errcode{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: \errval{EFAULT}, \errval{EBADF}, - \errval{ENOTSOCK}, \errval{EISCONN} e \errval{EADDRINUSE}.} -\end{prototype} - -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}. - -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:TCP_daytime_client}, 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 è - \errcode{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 - \errcode{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 - \errcode{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:TCP_func_listen} - -La funzione \funcd{listen} serve ad 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)} - Pone un socket in attesa di una connessione. - - \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[\errcode{EBADF}] l'argomento \param{sockfd} non è un file descriptor - valido. - \item[\errcode{ENOTSOCK}] l'argomento \param{sockfd} non è un socket. - \item[\errcode{EOPNOTSUPP}] il socket è di un tipo che non supporta questa - operazione. - \end{errlist}} -\end{prototype} - -La funzione pone il socket specificato da \param{sockfd} in modalità passiva e -predispone una coda per le connessioni in arrivo di lunghezza pari a -\param{backlog}. La funzione si può applicare solo a socket di tipo -\const{SOCK\_STREAM} o \const{SOCK\_SEQPACKET}. - -L'argomento \param{backlog} indica il numero massimo di connessioni pendenti -accettate; se esso viene ecceduto il client al momento della richiesta della -connessione riceverà un errore di tipo \errcode{ECONNREFUSED}, o se il -protocollo, come accade nel caso del TCP, supporta la ritrasmissione, la -richiesta sarà ignorata in modo che la connessione possa venire 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 La coda delle connessioni incomplete (\textit{incomplete connection - queue} che contiene un riferimento per ciascun socket per il quale è - arrivato un SYN ma il \textit{three way handshake} non si è ancora concluso. - Questi socket sono tutti nello stato \texttt{SYN\_RECV}. -\item La 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 \figref{fig:TCP_listen_backlog}: -quando arriva un SYN da un client il server crea una nuova voce nella coda -delle connessioni incomplete, e poi risponde con il SYN$+$ACK. La voce 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 -la voce viene spostata nella coda delle connessioni complete. Quando il -processo chiama la funzione \func{accept} (vedi \secref{sec:TCP_func_accept}) -la prima voce 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. - -\begin{figure}[htb] - \centering - \includegraphics[width=11cm]{img/tcp_listen_backlog} - \caption{Schema di funzionamento delle code delle connessioni complete ed - incomplete.} - \label{fig:TCP_listen_backlog} -\end{figure} - -Storicamente il valore del parametro \param{backlog} era corrispondente al -massimo valore della somma del numero di voci possibili per ciascuna delle due -code. Stevens in \cite{UNP1} riporta che BSD ha sempre applicato un fattore di -1.5 a detto valore, e fornisce 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 \param{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 funzione \func{sysctl} con il parametro \const{NET\_TCP\_MAX\_SYN\_BACKLOG} -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 \param{backlog} viene troncato ad un massimo di \const{SOMAXCONN} se è -superiore a detta costante (che di default vale 128). - -La scelta storica per il valore di questo parametro era 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}). - -Stevens tratta accuratamente questo argomento in \cite{UNP1}, 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:TCP_func_accept} - -La funzione \funcd{accept} è chiamata da un server per gestire la connessione -una volta che sia stato completato il \textit{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)} - - Accetta una connessione sul socket specificato. - - \bodydesc{La funzione restituisce un numero di socket descriptor positivo in - caso di successo e -1 in caso di errore, nel qual caso \var{errno} viene - impostata ai seguenti valori: - - \begin{errlist} - \item[\errcode{EBADF}] l'argomento \param{sockfd} non è un file descriptor - valido. - \item[\errcode{ENOTSOCK}] l'argomento \param{sockfd} non è un socket. - \item[\errcode{EOPNOTSUPP}] il socket è di un tipo che non supporta questa - operazione. - \item[\errcode{EAGAIN} o \errcode{EWOULDBLOCK}] il socket è stato impostato - come non bloccante (vedi \secref{sec:file_noblocking}), e non ci sono - connessioni in attesa di essere accettate. - \item[\errcode{EPERM}] Le regole del firewall non consentono la connessione. - \item[\errcode{ENOBUFS}, \errcode{ENOMEM}] questo spesso significa che - l'allocazione della memoria è limitata dai limiti sui buffer dei socket, - non dalla memoria di sistema. - \item[\errcode{EINTR}] La funzione è stata interrotta da un segnale. - \end{errlist} - Inoltre possono essere restituiti gli errori di rete relativi al nuovo - socket, diversi a secondo del protocollo, come: \errval{EMFILE}, - \errval{EINVAL}, \errval{ENOSR}, \errval{ENOBUFS}, \errval{EFAULT}, - \errval{EPERM}, \errval{ECONNABORTED}, \errval{ESOCKTNOSUPPORT}, - \errval{EPROTONOSUPPORT}, \errval{ETIMEDOUT}, \errval{ERESTARTSYS}.} -\end{prototype} - -La funzione estrae la prima connessione relativa al socket \param{sockfd} in -attesa sulla coda delle connessioni complete, che associa ad nuovo socket con -le stesse caratteristiche di \param{sockfd}. Il socket originale non viene -toccato e resta nello stato di \texttt{LISTEN}, mentre il nuovo socket viene -posto nello stato \texttt{ESTABLISHED}. Nella struttura \param{addr} e nella -variabile \param{addrlen} vengono restituiti indirizzo e relativa lunghezza -del client che si è connesso. - -I due argomenti \param{addr} e \param{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 -\param{addrlen} deve essere inizializzato alle dimensioni della struttura il -cui indirizzo è passato come argomento in \param{addr}; al ritorno della -funzione \param{addrlen} conterrà il numero di byte scritti dentro -\param{addr}. Se questa informazione non interessa basterà inizializzare a -\val{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:TCP_func_listen}) che il client ha effettuato verso il socket -\param{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 impostato il socket per - essere non bloccante (vedi \secref{sec:file_noblocking}), nel qual caso - ritorna con l'errore \errcode{EAGAIN}. Torneremo su questa modalità di - operazione in \secref{sec:xxx_sock_noblock}.} fintanto che non ne arriva -una. - -La funzione può essere usata solo con socket che supportino la connessione -(cioè di tipo \const{SOCK\_STREAM}, \const{SOCK\_SEQPACKET} o -\const{SOCK\_RDM}). Per alcuni protocolli che richiedono una conferma -esplicita della connessione,\footnote{attualmente in Linux solo DECnet ha - questo comportamento.} la funzione opera solo l'estrazione dalla coda delle -connessioni, la conferma della connessione viene eseguita implicitamente dalla -prima chiamata ad una \func{read} o una \func{write}, mentre il rifiuto della -connessione viene eseguito 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}, per cui l'applicazione deve tenerne conto ed -eventualmente ripetere la chiamata alla funzione come per l'errore di -\errcode{EAGAIN} (torneremo su questo in \secref{sec:TCP_echo_critical}). -Un'altra differenza con BSD è che la funzione non fa ereditare al nuovo socket -i flag del socket originale, come \const{O\_NONBLOCK},\footnote{ed in generale - tutti quelli che si possono impostare con \func{fcntl}, vedi - \secref{sec:file_fcntl}.} che devono essere rispecificati ogni volta. Tutto -questo deve essere tenuto in conto se si devono scrivere programmi portabili. - -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, detto per questo \textit{listening socket}, che resta per tutto il -tempo nello stato \texttt{LISTEN}, mentre le connessioni vengono gestite dai -nuovi socket, detti \textit{connected socket}, ritornati da \func{accept}, che -si trovano automaticamente nello stato \texttt{ESTABLISHED}, e vengono -utilizzati per lo scambio dei dati, che avviene su di essi, fino alla chiusura -della connessione. Si può riconoscere questo schema anche nell'esempio -elementare di \figref{fig:TCP_daytime_iter_server_code}, dove per ogni -connessione il socket creato da \func{accept} viene chiuso dopo l'invio dei -dati. - - -\subsection{Le funzioni \func{getsockname} e \func{getpeername}} -\label{sec:TCP_get_names} - -Oltre a tutte quelle viste finora, dedicate all'utilizzo dei socket, esistono -alcune funzioni ausiliarie che possono essere usate per recuperare alcune -informazioni relative ai socket ed alle connessioni ad essi associate. Le due -funzioni più elementari sono queste, che vengono usate per ottenere i dati -relativi alla socket pair associata ad un certo socket. - -La prima funzione è \funcd{getsockname} e serve ad ottenere l'indirizzo locale -associato ad un socket; il suo prototipo è: -\begin{prototype}{sys/socket.h} - {int getsockname(int sockfd, struct sockaddr * name, socklen\_t * namelen)} - Legge l'indirizzo locale di un socket. - -\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[\errcode{EBADF}] l'argomento \param{sockfd} non è un file descriptor - valido. - \item[\errcode{ENOTSOCK}] l'argomento \param{sockfd} non è un socket. - \item[\errcode{ENOBUFS}] non ci sono risorse sufficienti nel sistema per - eseguire l'operazione. - \item[\errcode{EFAULT}] l'indirizzo \param{name} non è valido. - \end{errlist}} -\end{prototype} - -La funzione restituisce la struttura degli indirizzi del socket \param{sockfd} -nella struttura indicata dal puntatore \param{name} la cui lunghezza è -specificata tramite l'argomento \param{namlen}. Quest'ultimo viene passato -come indirizzo per avere indietro anche il numero di byte effettivamente -scritti nella struttura puntata da \param{name}. Si tenga presente che se si è -utilizzato un buffer troppo piccolo per \param{name} l'indirizzo risulterà -troncato. - -La funzione 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. - -Tutte le volte che si vuole avere l'indirizzo remoto di un socket si usa la -funzione \funcd{getpeername}, il cui prototipo è: -\begin{prototype}{sys/socket.h} - {int getpeername(int sockfd, struct sockaddr * name, socklen\_t * namelen)} - Legge l'indirizzo remoto di un socket. - - \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[\errcode{EBADF}] l'argomento \param{sockfd} non è un file descriptor - valido. - \item[\errcode{ENOTSOCK}] l'argomento \param{sockfd} non è un socket. - \item[\errcode{ENOTCONN}] il socket non è connesso. - \item[\errcode{ENOBUFS}] non ci sono risorse sufficienti nel sistema per - eseguire l'operazione. - \item[\errcode{EFAULT}] l'argomento \param{name} punta al di fuori dello - spazio di indirizzi del processo. - \end{errlist}} -\end{prototype} - -La funzione è identica a \func{getsockname}, ed usa la stessa sintassi, ma -restituisce l'indirizzo remoto del socket, cioè quello associato all'altro -capo della connessione. 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 vedremo in -\figref{fig:TCP_daytime_cunc_server_code}, i valori di ritorno di -\func{accept}. - -Il fatto è che in generale quest'ultimo caso non è sempre possibile. In -particolare questo avviene quando il server, invece di gestire la connessione -direttamente in un processo figlio, come vedremo nell'esempio di server -concorrente di \secref{sec:TCP_daytime_cunc_server}, lancia per ciascuna -connessione un altro programma, usando \func{exec}.\footnote{questa ad esempio - è la modalità con cui opera il \textsl{super-server} \cmd{inetd}, che può - gestire tutta una serie di servizi diversi, eseguendo su ogni connessione - ricevuta sulle porte tenute sotto controllo, il relativo 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} verrà caricata in -memoria l'immagine del programma eseguito, che a questo punto perde ogni -riferimento ai valori tornati da \func{accept}. Il socket descriptor però -resta aperto, e se si è seguita una opportuna convenzione per rendere noto al -programma eseguito qual'è il socket connesso, \footnote{ad esempio il solito - \cmd{inetd} 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. - -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. - - -\subsection{La funzione \func{close}} -\label{sec:TCP_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 file descriptor -associati ad un socket. - -L'azione 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:TCP_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 tutti i file descriptor anche per i socket 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 si applica, come -visto in \secref{sec:file_sharing}, sia ai file descriptor duplicati che a -quelli ereditati dagli eventuali processi figli, ed è il comportamento che ci -si aspetta in una qualunque applicazione client/server. - -Per attivare immediatamente l'emissione del FIN e la sequenza di chiusura -descritta in \secref{sec:TCP_conn_term}, si può invece usare la funzione -\func{shutdown} su cui torneremo in seguito (vedi \secref{sec:xxx_shutdown}). - - - -\section{Un esempio elementare: il servizio \textit{daytime}} -\label{sec:TCP_daytime_application} - -Avendo introdotto le funzioni di base per la gestione dei socket, potremo -vedere in questa sezione un primo esempio di applicazione elementare che -implementa il servizio \textit{daytime} su TCP, secondo quanto specificato -dall'\href{http://www.ietf.org/rfc/rfc0867.txt}{RFC~867}. Prima di passare -agli esempi del client e del server, inizieremo riesaminando con maggiori -dettagli una peculiarità delle funzioni di I/O, già accennata in -\secref{sec:file_read} e \secref{sec:file_write}, che nel caso dei socket è -particolarmente rilevante. Passeremo poi ad illustrare gli esempi -dell'implementazione, sia dal lato client, che dal lato server, che si è -realizzato sia in forma iterativa che concorrente. - - -\subsection{Il comportamento delle funzioni di I/O} -\label{sec:sock_io_behav} - -Una cosa che si tende a dimenticare quando si ha a che fare con i socket è che -le funzioni di input/output non sempre hanno lo stesso comportamento che -avrebbero con i normali file di dati (in particolare questo accade per i -socket di tipo stream). - -Infatti con i socket è comune che funzioni come \func{read} o \func{write} -possano restituire in input o scrivere in output un numero di byte minore di -quello richiesto. Come già accennato in \secref{sec:file_read} questo è un -comportamento normale per le funzioni di I/O, ma con i normali file di dati il -problema si avverte solo in lettura, quando si incontra la fine del file. In -generale non è così, e con i socket questo è particolarmente evidente. - - -\begin{figure}[htb] - \footnotesize \centering - \begin{minipage}[c]{15cm} - \includecodesample{listati/FullRead.c} - \end{minipage} - \normalsize - \caption{La funzione \func{FullRead}, che legge esattamente \var{count} byte - da un file descriptor, iterando opportunamente le letture.} - \label{fig:sock_FullRead_code} -\end{figure} - -Quando ci si trova ad affrontare questo comportamento tutto quello che si deve -fare è semplicemente ripetere la lettura (o la scrittura) per la quantità di -byte restanti, tenendo conto che le funzioni si possono bloccare se i dati non -sono disponibili: è lo stesso comportamento che si può avere scrivendo più di -\const{PIPE\_BUF} byte in una pipe (si riveda quanto detto in -\secref{sec:ipc_pipes}). - -Per questo motivo, seguendo l'esempio di R. W. Stevens in \cite{UNP1}, si sono -definite due funzioni, \func{FullRead} e \func{FullWrite}, che eseguono -lettura e scrittura tenendo conto di questa caratteristica, ed in grado di -ritornare solo dopo avere letto o scritto esattamente il numero di byte -specificato; il sorgente è riportato rispettivamente in -\figref{fig:sock_FullRead_code} e \figref{fig:sock_FullWrite_code} ed è -disponibile fra i sorgenti allegati alla guida nei file \file{FullRead.c} e -\file{FullWrite.c}. - -\begin{figure}[htb] - \centering - \footnotesize \centering - \begin{minipage}[c]{15cm} - \includecodesample{listati/FullWrite.c} - \end{minipage} - \normalsize - \caption{La funzione \func{FullWrite}, che scrive esattamente \var{count} - byte su un file descriptor, iterando opportunamente le scritture.} - \label{fig:sock_FullWrite_code} -\end{figure} - -Come si può notare le due funzioni ripetono la lettura/scrittura in un ciclo -fino all'esaurimento del numero di byte richiesti, in caso di errore viene -controllato se questo è \errcode{EINTR} (cioè un'interruzione della system -call dovuta ad un segnale), nel qual caso l'accesso viene ripetuto, altrimenti -l'errore viene ritornato al programma chiamante, interrompendo il ciclo. - -Nel caso della lettura, se il numero di byte letti è zero, significa che si è -arrivati alla fine del file (per i socket questo significa in genere che -l'altro capo è stato chiuso, e quindi non sarà più possibile leggere niente) e -pertanto si ritorna senza aver concluso la lettura di tutti i byte -richiesti. Entrambe le funzioni restituiscono 0 in caso di successo, ed un -valore negativo in caso di errore, \texttt{FullRead} restituisce il numero di -byte non letti in caso di end-of-file prematuro. - - -\subsection{Il client \textit{daytime}} -\label{sec:TCP_daytime_client} - -Il primo esempio di applicazione delle funzioni di base illustrate in -\secref{sec:TCP_functions} è relativo alla creazione di un client elementare -per il servizio \textit{daytime}, un servizio elementare, definito -nell'\href{http://www.ietf.org/rfc/rfc0867.txt}{RFC~867}, che restituisce -l'ora locale della macchina a cui si effettua la richiesta, e che è assegnato -alla porta 13. - -In \figref{fig:TCP_daytime_client_code} è riportata la sezione principale del -codice del nostro client. Il sorgente completo del programma -(\file{TCP\_daytime.c}, che comprende il trattamento delle opzioni ed una -funzione per stampare un messaggio di aiuto) è allegato alla guida nella -sezione dei codici sorgente e può essere compilato su una qualunque macchina -GNU/Linux. - -\begin{figure}[!htb] - \footnotesize \centering - \begin{minipage}[c]{15cm} - \includecodesample{listati/TCP_daytime.c} - \end{minipage} - \normalsize - \caption{Esempio di codice di un client elementare per il servizio - \textit{daytime}.} - \label{fig:TCP_daytime_client_code} -\end{figure} - -Il programma anzitutto (\texttt{\small 1--5}) include gli header necessari; -dopo la dichiarazione delle variabili (\texttt{\small 9--12}) si è omessa -tutta la parte relativa al trattamento degli argomenti passati dalla linea di -comando (effettuata con le apposite funzioni illustrate in -\secref{sec:proc_opt_handling}). - -Il primo passo (\texttt{\small 14--18}) è creare un socket TCP (quindi di tipo -\const{SOCK\_STREAM} e di famiglia \const{AF\_INET}). La funzione -\func{socket} ritorna il descrittore che viene usato per identificare il -socket in tutte le chiamate successive. Nel caso la chiamata fallisca si -stampa un errore (\texttt{\small 16}) con la funzione \func{perror} e si esce -(\texttt{\small 17}) con un codice di errore. - -Il passo seguente (\texttt{\small 19--27}) è quello di costruire un'apposita -struttura \struct{sockaddr\_in} in cui sarà inserito l'indirizzo del server ed -il numero della porta del servizio. Il primo passo (\texttt{\small 20}) è -inizializzare tutto a zero, per poi inserire il tipo di indirizzo -(\texttt{\small 21}) e la porta (\texttt{\small 22}), usando per quest'ultima -la funzione \func{htons} per convertire il formato dell'intero usato dal -computer a quello usato nella rete, infine \texttt{\small 23--27} si può -utilizzare la funzione \func{inet\_pton} per convertire l'indirizzo numerico -passato dalla linea di comando. - -A questo punto (\texttt{\small 28--32}) usando la funzione \func{connect} sul -socket creato in precedenza (\texttt{\small 29}) si può stabilire la -connessione con il server. Per questo si deve utilizzare come secondo -argomento la struttura preparata in precedenza con il relativo indirizzo; si -noti come, esistendo diversi tipi di socket, si sia dovuto effettuare un cast. -Un valore di ritorno della funzione negativo implica il fallimento della -connessione, nel qual caso si stampa un errore (\texttt{\small 30}) e si -ritorna (\texttt{\small 31}). - -Completata con successo la connessione il passo successivo (\texttt{\small - 34--40}) è leggere la data dal socket; il protocollo prevede che il server -invii sempre una stringa alfanumerica, il formato della stringa non è -specificato dallo standard, per cui noi useremo il formato usato dalla -funzione \func{ctime}, seguito dai caratteri di terminazione \verb|\r\n|, cioè -qualcosa del tipo: -\begin{verbatim} -Wed Apr 4 00:53:00 2001\r\n -\end{verbatim} -questa viene letta dal socket (\texttt{\small 34}) con la funzione \func{read} -in un buffer temporaneo; la stringa poi deve essere terminata (\texttt{\small - 35}) con il solito carattere nullo per poter essere stampata (\texttt{\small - 36}) sullo standard output con l'uso di \func{fputs}. - -Come si è già spiegato in \secref{sec:sock_io_behav} la risposta dal socket -potrà arrivare in un unico pacchetto di 26 byte (come avverrà senz'altro nel -caso in questione) ma potrebbe anche arrivare in 26 pacchetti di un byte. Per -questo nel caso generale non si può mai assumere che tutti i dati arrivino con -una singola lettura, pertanto quest'ultima deve essere effettuata in un ciclo -in cui si continui a leggere fintanto che la funzione \func{read} non ritorni -uno zero (che significa che l'altro capo ha chiuso la connessione) o un numero -minore di zero (che significa un errore nella connessione). - -Si noti come in questo caso la fine dei dati sia specificata dal server che -chiude la connessione (anche questo è quanto richiesto dal protocollo); questa -è una delle tecniche possibili (è quella usata pure dal protocollo HTTP), ma -ce ne possono essere altre, ad esempio FTP marca la conclusione di un blocco -di dati con la sequenza ASCII \verb|\r\n| (carriage return e line feed), -mentre il DNS mette la lunghezza in testa ad ogni blocco che trasmette. Il -punto essenziale è che TCP non provvede nessuna indicazione che permetta di -marcare dei blocchi di dati, per cui se questo è necessario deve provvedere il -programma stesso. - -Se abilitiamo il servizio \textit{daytime}\footnote{in genere questo viene - fornito direttamente dal \textsl{superdemone} \texttt{inetd}, pertanto basta - assicurarsi che esso sia abilitato nel relativo file di configurazione.} -possiamo verificare il funzionamento del nostro client, avremo allora: -\begin{verbatim} -[piccardi@gont sources]$ ./daytime 127.0.0.1 -Mon Apr 21 20:46:11 2003 -\end{verbatim}%$ -e come si vede tutto funziona regolarmente. - - -\subsection{Un server \textit{daytime} iterativo} -\label{sec:TCP_daytime_iter_server} - -Dopo aver illustrato il client daremo anche un esempio di un server -elementare, che sia anche in grado di rispondere al precedente client. Come -primo esempio realizzeremo un server iterativo, in grado di fornire una sola -risposta alla volta. Il codice del programma è nuovamente mostrato in -\figref{fig:TCP_daytime_iter_server_code}, il sorgente completo -(\file{TCP\_iter\_daytimed.c}) è allegato insieme agli altri file degli esempi. - -\begin{figure}[!htbp] - \footnotesize \centering - \begin{minipage}[c]{15cm} - \includecodesample{listati/TCP_iter_daytimed.c} - \end{minipage} - \normalsize - \caption{Esempio di codice di un semplice server per il servizio daytime.} - \label{fig:TCP_daytime_iter_server_code} -\end{figure} - -Come per il client si includono (\texttt{\small 1--9}) gli header necessari a -cui è aggiunto quello per trattare i tempi, e si definiscono (\texttt{\small - 14--18}) alcune costanti e le variabili necessarie in seguito. Come nel caso -precedente si sono omesse le parti relative al trattamento delle opzioni da -riga di comando. - -La creazione del socket (\texttt{\small 20--24}) è analoga al caso precedente, -come pure l'inizializzazione (\texttt{\small 25--29}) della struttura -\struct{sockaddr\_in}. Anche in questo caso (\texttt{\small 28}) si usa la -porta standard del servizio daytime, ma come indirizzo IP si usa -(\texttt{\small 27}) il valore predefinito \const{INET\_ANY}, che corrisponde -all'indirizzo generico. - -Si effettua poi (\texttt{\small 30--34}) la chiamata alla funzione \func{bind} -che permette di associare la precedente struttura al socket, in modo che -quest'ultimo possa essere usato per accettare connessioni su una qualunque -delle interfacce di rete locali. In caso di errore si stampa (\texttt{\small - 31}) un messaggio, e si termina (\texttt{\small 32}) immediatamente il -programma. - -Il passo successivo (\texttt{\small 35--39}) è quello di mettere ``in -ascolto'' il socket; questo viene fatto (\texttt{\small 36}) con la funzione -\func{listen} che dice al kernel di accettare connessioni per il socket che -abbiamo creato; la funzione indica inoltre, con il secondo parametro, il -numero massimo di connessioni che il kernel accetterà di mettere in coda per -il suddetto socket. Di nuovo in caso di errore si stampa (\texttt{\small 37}) -un messaggio, e si esce (\texttt{\small 38}) immediatamente. - -La chiamata a \func{listen} completa la preparazione del socket per l'ascolto -(che viene chiamato anche \textit{listening descriptor}) a questo punto si può -procedere con il ciclo principale (\texttt{\small 40--53}) che viene eseguito -indefinitamente. Il primo passo (\texttt{\small 42}) è porsi in attesa di -connessioni con la chiamata alla funzione \func{accept}, come in precedenza in -caso di errore si stampa (\texttt{\small 43}) un messaggio, e si esce -(\texttt{\small 44}). - -Il processo resterà in stato di \textit{sleep} fin quando non arriva e viene -accettata una connessione da un client; quando questo avviene \func{accept} -ritorna, restituendo un secondo descrittore, che viene chiamato -\textit{connected descriptor}, e che è quello che verrà usato dalla successiva -chiamata alla \func{write} per scrivere la risposta al client. - -Il ciclo quindi proseguirà determinando (\texttt{\small 46}) il tempo corrente -con una chiamata a \texttt{time}, con il quale si potrà opportunamente -costruire (\texttt{\small 47}) la stringa con la data da trasmettere -(\texttt{\small 48}) con la chiamata a \func{write}. Completata la -trasmissione il nuovo socket viene chiuso (\texttt{\small 52}). A questo -punto il ciclo si chiude ricominciando da capo in modo da poter ripetere -l'invio della data in risposta ad una successiva connessione. - -È importante notare che questo server è estremamente elementare, infatti, a -parte il fatto di poter essere usato solo con indirizzi IPv4, esso è in grado -di rispondere ad un solo un client alla volta: è cioè, come dicevamo, un -\textsl{server iterativo}. Inoltre è scritto per essere lanciato da linea di -comando, se lo si volesse utilizzare come demone occorrerebbero le opportune -modifiche\footnote{come una chiamata a \func{daemon} prima dell'inizio del - ciclo principale.} per tener conto di quanto illustrato in -\secref{sec:sess_daemon}. Si noti anche che non si è inserita nessuna forma di -gestione della terminazione del processo, dato che tutti i file descriptor -vengono chiusi automaticamente alla sua uscita, e che, non generando figli, -non è necessario preoccuparsi di gestire la loro terminazione. - - -\subsection{Un server \textit{daytime} concorrente} -\label{sec:TCP_daytime_cunc_server} - -Il server \texttt{daytime} dell'esempio in -\secref{sec:TCP_daytime_iter_server} è 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. - -Come accennato anche in \secref{sec:proc_gen} una delle modalità più comuni di -funzionamento da parte dei server è quella di usare la funzione \func{fork} -per creare, ad ogni richiesta da parte di un client, un processo figlio che si -incarichi della gestione della comunicazione. Si è allora riscritto il server -\textit{daytime} dell'esempio precedente in forma concorrente, inserendo anche -una opzione per la stampa degli indirizzi delle connessioni ricevute. - -In \figref{fig:TCP_daytime_cunc_server_code} è mostrato un estratto del -codice, in cui si sono tralasciati il trattamento delle opzioni e le parti -rimaste invariate rispetto al precedente esempio (cioè tutta la parte -riguardante l'apertura passiva del socket). Al solito il sorgente completo del -server, nel file \file{TCP\_cunc\_daytimed.c}, è allegato insieme ai sorgenti -degli altri esempi. - -\begin{figure}[!htb] - \footnotesize \centering - \begin{minipage}[c]{15cm} - \includecodesample{listati/TCP_cunc_daytimed.c} - \end{minipage} - \normalsize - \caption{Esempio di codice di un server concorrente elementare per il - servizio daytime.} - \label{fig:TCP_daytime_cunc_server_code} -\end{figure} - -Stavolta (\texttt{\small 21--25}) la funzione \func{accept} è chiamata -fornendo una struttura di indirizzi in cui saranno ritornati l'indirizzo IP e -la porta da cui il client effettua la connessione, che in un secondo tempo, -(\texttt{\small 39--43}), se il logging è abilitato, stamperemo sullo standard -output. - -Quando \func{accept} ritorna il server chiama la funzione \func{fork} -(\texttt{\small 26--30}) per creare il processo figlio che effettuerà -(\texttt{\small 31--45}) tutte le operazioni relative a quella connessione, -mentre il padre proseguirà l'esecuzione del ciclo principale in attesa di -ulteriori connessioni. - -Si noti come il figlio operi solo sul socket connesso, chiudendo -immediatamente (\texttt{\small 32}) il socket \var{list\_fd}; mentre il padre -continua ad operare (\texttt{\small 47}) solo sul socket in ascolto chiudendo -\var{sock\_fd} al ritorno dalla \func{fork}. Per quanto abbiamo detto in -\secref{sec:TCP_func_close} nessuna delle due chiamate a \func{close} causa -l'innesco della sequenza di chiusura perché il numero di riferimenti al file -descriptor 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 -\func{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à (\texttt{\small 44}) la funzione \func{close}. - -In realtà per il figlio non sarebbe necessaria nessuna chiamata a -\func{close}, in quanto con la \func{exit} finale (\texttt{\small 45}) tutti i -file descriptor, quindi anche quelli associati ai socket, vengono -automaticamente chiusi. Tuttavia si è preferito effettuare esplicitamente le -chiusure per avere una maggiore chiarezza del codice, e per evitare eventuali -errori, prevenendo ad esempio un uso involontario del \textit{listening - descriptor}. - -Si noti invece come sia essenziale che il padre chiuda ogni volta il socket -connesso dopo la \func{fork}; se così non fosse nessuno di questi socket -sarebbe effettivamente chiuso dato che alla chiusura da parte del figlio -resterebbe ancora un riferimento nel padre. 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. - -Come per ogni server iterativo il lavoro di risposta viene eseguito -interamente dal processo figlio. Questo si incarica (\texttt{\small 33}) di -chiamare \func{time} per leggere il tempo corrente, e di stamparlo -(\texttt{\small 34}) sulla stringa contenuta in \var{buffer} con l'uso di -\func{snprintf} e \func{ctime}. Poi la stringa viene scritta (\texttt{\small - 35--38}) sul socket, controllando che non ci siano errori. Anche in questo -caso si è evitato il ricorso a \func{FullWrite} in quanto la stringa è -estremamente breve e verrà senz'altro scritta in un singolo segmento. - -Inoltre nel caso sia stato abilitato il \textit{logging} delle connessioni, si -provvede anche (\texttt{\small 39--42}) a stampare sullo standard output -l'indirizzo e la porta da cui il client ha effettuato la connessione, usando -i valori contenuti nelle strutture restituite da \func{accept}, eseguendo le -opportune conversioni con \func{inet\_ntop} e \func{atohs}. - -Ancora una volta l'esempio è estremamente semplificato, si noti come di nuovo -non si sia gestita né la terminazione del processo né il suo uso come demone, -che tra l'altro sarebbe stato incompatibile con l'uso della opzione di logging -che stampa gli indirizzi delle connessioni sullo standard output. Un altro -aspetto tralasciato è la gestione della terminazione dei processi figli, -torneremo su questo più avanti quando tratteremo alcuni esempi di server più -complessi. - - - -\section{Un esempio più completo: il servizio \textit{echo}} -\label{sec:TCP_echo_application} - -L'esempio precedente, basato sul servizio \textit{daytime}, è un esempio molto -elementare, in cui il flusso dei dati va solo nella direzione dal server al -client. In questa sezione esamineremo un esempio di applicazione client/server -un po' più complessa, che usi i socket TCP per una comunicazione in entrambe -le direzioni. - -Ci limiteremo a fornire una implementazione elementare, che usi solo le -funzioni di base viste finora, ma prenderemo in esame, oltre al comportamento -in condizioni normali, anche tutti i possibili scenari particolari (errori, -sconnessione della rete, crash del client o del server durante la connessione) -che possono avere luogo durante l'impiego di un'applicazione di rete, partendo -da una versione primitiva che dovrà essere rimaneggiata di volta in volta per -poter tenere conto di tutte le evenienze che si possono manifestare nella vita -reale di un'applicazione di rete, fino ad arrivare ad un'implementazione -completa. - - -\subsection{Il servizio \textit{echo}} -\label{sec:TCP_echo} - - -Nella ricerca di un servizio che potesse fare da esempio per una comunicazione -bidirezionale, si è deciso, seguendo la scelta di Stevens in \cite{UNP1}, di -usare il servizio \textit{echo}, che si limita a restituire in uscita quanto -immesso in ingresso. Infatti, nonostante la sua estrema semplicità, questo -servizio costituisce il prototipo ideale per una generica applicazione di rete -in cui un server risponde alle richieste di un client. Nel caso di una -applicazione più complessa quello che si potrà avere in più è una elaborazione -dell'input del client, che in molti casi viene interpretato come un comando, -da parte di un server che risponde fornendo altri dati in uscita. - -Il servizio \textit{echo} è uno dei servizi standard solitamente provvisti -direttamente dal superserver \cmd{inetd}, ed è definito -dall'\href{http://www.ietf.org/rfc/rfc0862.txt}{RFC~862}. Come dice il nome il -servizio deve riscrivere indietro sul socket i dati che gli vengono inviati in -ingresso. L'RFC descrive le specifiche del servizio sia per TCP che UDP, e per -il primo stabilisce che una volta stabilita la connessione ogni dato in -ingresso deve essere rimandato in uscita fintanto che il chiamante non ha -chiude la connessione. Al servizio è assegnata la porta riservata 7. - -Nel nostro caso l'esempio sarà costituito da un client che legge una linea di -caratteri dallo standard input e la scrive sul server. A sua volta il server -leggerà la linea dalla connessione e la riscriverà immutata all'indietro. Sarà -compito del client leggere la risposta del server e stamparla sullo standard -output. - - -\subsection{Il client: prima versione} -\label{sec:TCP_echo_client} - -Il codice della prima versione del client per il servizio \textit{echo}, -disponibile nel file \file{TCP\_echo\_first.c}, è riportato in -\figref{fig:TCP_echo_client_1}. Esso ricalca la struttura del precedente -client per il servizio \textit{daytime} (vedi -\secref{sec:TCP_daytime_client}), e la prima parte (\texttt{\small 10--27}) è -sostanzialmente identica, a parte l'uso di una porta diversa. - -\begin{figure}[!htb] - \footnotesize \centering - \begin{minipage}[c]{15.6 cm} - \includecodesample{listati/TCP_echo_first.c} - \end{minipage} - \normalsize - \caption{Codice della prima versione del client \textit{echo}.} - \label{fig:TCP_echo_client_1} -\end{figure} - -Al solito si è tralasciata la sezione relativa alla gestione delle opzioni a -riga di comando. Una volta dichiarate le variabili, si prosegue -(\texttt{\small 10--13}) con della creazione del socket con l'usuale controllo -degli errori, alla preparazione (\texttt{\small 14--17}) della struttura -dell'indirizzo, che stavolta usa la porta 7 riservata al servizio -\textit{echo}, infine si converte (\texttt{\small 18--22}) l'indirizzo -specificato a riga di comando. A questo punto (\texttt{\small 23--27}) si può -eseguire la connessione al server secondo la stessa modalità usata in -\secref{sec:TCP_daytime_client}. - -Completata la connessione, per gestire il funzionamento del protocollo si usa -la funzione \code{ClientEcho}, il cui codice si è riportato a parte in -\figref{fig:TCP_client_echo_sub}. Questa si preoccupa di gestire tutta la -comunicazione, leggendo una riga alla volta dallo standard input \file{stdin}, -scrivendola sul socket e ristampando su \file{stdout} quanto ricevuto in -risposta dal server. Al ritorno dalla funzione (\texttt{\small 30--31}) anche -il programma termina. - -La funzione \code{ClientEcho} utilizza due buffer (\texttt{\small 3}) per -gestire i dati inviati e letti sul socket. La comunicazione viene gestita -all'interno di un ciclo (\texttt{\small 5--10}), i dati da inviare sulla -connessione vengono presi dallo \file{stdin} usando la funzione \func{fgets}, -che legge una linea di testo (terminata da un \texttt{CR} e fino al massimo di -\const{MAXLINE} caratteri) e la salva sul buffer di invio. - -Si usa poi (\texttt{\small 6}) la funzione \func{FullWrite}, vista in -\secref{sec:sock_io_behav}, per scrivere i dati sul socket, gestendo -automaticamente l'invio multiplo qualora una singola \func{write} non sia -sufficiente. I dati vengono riletti indietro (\texttt{\small 7}) con una -\func{read}\footnote{si è fatta l'assunzione implicita che i dati siano - contenuti tutti in un solo segmento, così che la chiamata a \texttt{read} li - restituisca sempre tutti; avendo scelto una dimensione ridotta per il buffer - questo sarà sempre vero, vedremo più avanti come superare il problema di - rileggere indietro tutti e soli i dati disponibili, senza bloccarsi.} sul -buffer di ricezione e viene inserita (\texttt{\small 8}) la terminazione della -stringa e per poter usare (\texttt{\small 9}) la funzione \func{fputs} per -scriverli su \file{stdout}. - -\begin{figure}[!htb] - \footnotesize \centering - \begin{minipage}[c]{15.6cm} - \includecodesample{listati/ClientEcho.c} - \end{minipage} - \normalsize - \caption{Codice della prima versione della funzione \texttt{ClientEcho} per - la gestione del servizio \textit{echo}.} - \label{fig:TCP_client_echo_sub} -\end{figure} - -Quando si concluderà l'invio di dati mandando un end-of-file sullo standard -input si avrà il ritorno di \func{fgets} con un puntatore nullo (si riveda -quanto spiegato in \secref{sec:file_line_io}) e la conseguente uscita dal -ciclo; al che la subroutine ritorna ed il nostro programma client termina. - -Si può effettuare una verifica del funzionamento del client abilitando il -servizio \textit{echo} nella configurazione di \cmd{initd} sulla propria -macchina ed usandolo direttamente verso di esso in locale, vedremo in -dettaglio più avanti (in \secref{sec:TCP_echo_startup}) il funzionamento del -programma, usato però con la nostra versione del server \textit{echo}, che -illustriamo immediatamente. - - -\subsection{Il server: prima versione} -\label{sec:TCPsimp_server_main} - -La prima versione del server, contenuta nel file \file{TCP\_echod\_first.c}, è -riportata in \figref{fig:TCP_echo_server_first_code}. Come abbiamo fatto per -il client anche il server è stato diviso in un corpo principale, costituito -dalla funzione \code{main}, che è molto simile a quello visto nel precedente -esempio per il server del servizio \textit{daytime} di -\secref{sec:TCP_daytime_cunc_server}, e da una funzione ausiliaria -\code{ServEcho} che si cura della gestione del servizio. - -\begin{figure}[!htbp] - \footnotesize \centering - \begin{minipage}[c]{15.6cm} - \includecodesample{listati/TCP_echod_first.c} - \end{minipage} - \normalsize - \caption{Codice del corpo principale della prima versione del server - per il servizio \textit{echo}.} - \label{fig:TCP_echo_server_first_code} -\end{figure} - -In questo caso però, rispetto a quanto visto nell'esempio di -\figref{fig:TCP_daytime_cunc_server_code} si è preferito scrivere il server -curando maggiormente alcuni dettagli, per tenere conto anche di alcune -esigenze generali (che non riguardano direttamente la rete), come la -possibilità di lanciare il server anche in modalità interattiva e la cessione -dei privilegi di amministratore non appena questi non sono più necessari. - -La sezione iniziale del programma (\texttt{\small 8--21}) è la stessa del -server di \secref{sec:TCP_daytime_cunc_server}, ed ivi descritta in dettaglio: -crea il socket, inizializza l'indirizzo e esegue \func{bind}; dato che -quest'ultima funzione viene usata su una porta riservata, il server dovrà -essere eseguito da un processo con i privilegi di amministratore, pena il -fallimento della chiamata. - -Una volta eseguita la funzione \func{bind} però i privilegi di amministratore -non sono più necessari, per questo è sempre opportuno rilasciarli, in modo da -evitare problemi in caso di eventuali vulnerabilità del server. Per questo -prima (\texttt{\small 22--26}) si esegue \func{setgid} per assegnare il -processo ad un gruppo senza privilegi,\footnote{si è usato il valore 65534, - ovvero -1 per il formato \ctyp{short}, che di norma in tutte le - distribuzioni viene usato per identificare il gruppo \texttt{nogroup} e - l'utente \texttt{nobody}, usati appunto per eseguire programmi che non - richiedono nessun privilegio particolare.} e poi si ripete (\texttt{\small - 27--30}) l'operazione usando \func{setuid} per cambiare anche -l'utente.\footnote{si tenga presente che l'ordine in cui si eseguono queste - due operazioni è importante, infatti solo avendo i privilegi di - amministratore si può cambiare il gruppo di un processo ad un'altro di cui - non si fa parte, per cui chiamare prima \func{setuid} farebbe fallire una - successiva chiamata a \func{setgid}. Inoltre si ricordi (si riveda quanto - esposto in \secref{sec:proc_perms}) che usando queste due funzioni il - rilascio dei privilegi è irreversibile.} Infine (\texttt{\small 30--36}), -qualora sia impostata la variabile \var{demonize}, prima (\texttt{\small 31}) -si apre il sistema di logging per la stampa degli errori, e poi -(\texttt{\small 32--35}) si invoca \func{daemon} per eseguire in background il -processo come demone. - -A questo punto il programma riprende di nuovo lo schema già visto usato dal -server per il servizio \textit{daytime}, con l'unica differenza della chiamata -alla funzione \code{PrintErr}, riportata in \figref{fig:TCP_PrintErr}, al -posto di \func{perror} per la stampa degli errori. - -Si inizia con il porre (\texttt{\small 37--41}) in ascolto il socket, e poi si -esegue indefinitamente il ciclo principale (\texttt{\small 42--58}). -All'interno di questo si ricevono (\texttt{\small 43--46}) le connessioni, -creando (\texttt{\small 47--50}) un processo figlio per ciascuna di esse. -Quest'ultimo (\texttt{\small 51--55}), chiuso (\texttt{\small 52}) il -\textit{listening socket}, esegue (\texttt{\small 53}) la funzione di gestione -del servizio \code{ServEcho}, ed al ritorno di questa (\texttt{\small 54}) -esce. - -Il padre invece si limita (\texttt{\small 56}) a chiudere il \textit{connected - socket} per ricominciare da capo il ciclo in attesa di nuove connessioni. In -questo modo si ha un server concorrente. La terminazione del padre non è -gestita esplicitamente, e deve essere effettuata inviando un segnale al -processo. - -Avendo trattato direttamente la gestione del programma come demone, si è -dovuto anche provvedere alla necessità di poter stampare eventuali messaggi di -errore attraverso il sistema del \textit{syslog} trattato in -\secref{sec:sess_daemon}. Come accennato questo è stato fatto utilizzando come -\textit{wrapper} la funzione \code{PrintErr}, il cui codice è riportato in -\figref{fig:TCP_PrintErr}. - -In essa ci si limita a controllare (\texttt{\small 2}) se è stato impostato -(valore attivo per default) l'uso come demone, nel qual caso (\texttt{\small - 3}) si usa \func{syslog} (vedi \secref{sec:sess_daemon}) per stampare il -messaggio di errore fornito come argomento sui log di sistema. Se invece si è -in modalità interattiva (attivabile con l'opzione \texttt{-i}) si usa -(\texttt{\small 5}) semplicemente la funzione \func{perror} per stampare sullo -standard error. - -\begin{figure}[!htb] - \footnotesize \centering - \begin{minipage}[c]{15.6cm} - \includecodesample{listati/PrintErr.c} - \end{minipage} - \normalsize - \caption{Codice della funzione \code{PrintErr} per la - generalizzazione della stampa degli errori sullo standard input o - attraverso il \texttt{syslog}.} - \label{fig:TCP_PrintErr} -\end{figure} - -La gestione del servizio \textit{echo} viene effettuata interamente nella -funzione \code{ServEcho}, il cui codice è mostrato in -\figref{fig:TCP_ServEcho_first}, e la comunicazione viene gestita all'interno -di un ciclo (\texttt{\small 6--13}). I dati inviati dal client vengono letti -(\texttt{\small 6}) dal socket con una semplice \func{read}, di cui non si -controlla lo stato di uscita, assumendo che ritorni solo in presenza di dati -in arrivo. La riscrittura (\texttt{\small 7}) viene invece gestita dalla -funzione \func{FullWrite} (descritta in \figref{fig:sock_FullWrite_code}) che -si incarica di tenere conto automaticamente della possibilità che non tutti i -dati di cui è richiesta la scrittura vengano trasmessi con una singola -\func{write}. - -\begin{figure}[!htb] - \footnotesize \centering - \begin{minipage}[c]{15.6cm} - \includecodesample{listati/ServEcho_first.c} - \end{minipage} - \normalsize - \caption{Codice della prima versione della funzione \code{ServEcho} per la - gestione del servizio \textit{echo}.} - \label{fig:TCP_ServEcho_first} -\end{figure} - -In caso di errore di scrittura (si ricordi che \func{FullWrite} restituisce un -valore nullo in caso di successo) si provvede (\texttt{\small 8--10}) a -stampare il relativo messaggio con \func{PrintErr}. Quando il client chiude -la connessione il ricevimento del FIN fa ritornare la \func{read} con un -numero di byte letti pari a zero, il che causa l'uscita dal ciclo e il ritorno -(\texttt{\small 12}) della funzione, che a sua volta causa la terminazione del -processo figlio. - - -\subsection{L'avvio e il funzionamento normale} -\label{sec:TCP_echo_startup} - -Benché il codice dell'esempio precedente sia molto ridotto, esso ci permetterà -di considerare in dettaglio le varie problematiche che si possono incontrare -nello scrivere un'applicazione di rete. Infatti attraverso l'esame delle sue -modalità di funzionamento normali, all'avvio e alla terminazione, e di quello -che avviene nelle varie situazioni limite, da una parte potremo approfondire -la comprensione del protocollo TCP/IP e dall'altra ricavare le indicazioni -necessarie per essere in grado di scrivere applicazioni robuste, in grado di -gestire anche i casi limite. - -Il primo passo è compilare e lanciare il server (da root, per poter usare la -porta 7 che è riservata), alla partenza esso eseguirà l'apertura passiva con -la sequenza delle chiamate a \func{socket}, \func{bind}, \func{listen} e poi -si bloccherà nella \func{accept}. A questo punto si potrà controllarne lo -stato con \cmd{netstat}: -\begin{verbatim} -[piccardi@roke piccardi]$ netstat -at -Active Internet connections (servers and established) -Proto Recv-Q Send-Q Local Address Foreign Address State -... -tcp 0 0 *:echo *:* LISTEN -... -\end{verbatim} %$ -che ci mostra come il socket sia in ascolto sulla porta richiesta, accettando -connessioni da qualunque indirizzo e da qualunque porta e su qualunque -interfaccia locale. - -A questo punto si può lanciare il client, esso chiamerà \func{socket} e -\func{connect}; una volta completato il three way handshake la connessione è -stabilita; la \func{connect} ritornerà nel client\footnote{si noti che è - sempre la \func{connect} del client a ritornare per prima, in quanto - questo avviene alla ricezione del secondo segmento (l'ACK del server) del - three way handshake, la \func{accept} del server ritorna solo dopo - un altro mezzo RTT quando il terzo segmento (l'ACK del client) viene - ricevuto.} e la \func{accept} nel server, ed usando di nuovo -\cmd{netstat} otterremmo che: -\begin{verbatim} -Active Internet connections (servers and established) -Proto Recv-Q Send-Q Local Address Foreign Address State -tcp 0 0 *:echo *:* LISTEN -tcp 0 0 roke:echo gont:32981 ESTABLISHED -\end{verbatim} -mentre per quanto riguarda l'esecuzione dei programmi avremo che: -\begin{itemize} -\item il client chiama la funzione \code{ClientEcho} che si blocca sulla - \func{fgets} dato che non si è ancora scritto nulla sul terminale. -\item il server eseguirà una \func{fork} facendo chiamare al processo figlio - la funzione \code{ServEcho}, quest'ultima si bloccherà sulla \func{read} - dal socket sul quale ancora non sono presenti dati. -\item il processo padre del server chiamerà di nuovo \func{accept} - bloccandosi fino all'arrivo di un'altra connessione. -\end{itemize} -e se usiamo il comando \cmd{ps} per esaminare lo stato dei processi otterremo -un risultato del tipo: -\begin{verbatim} -[piccardi@roke piccardi]$ ps ax - PID TTY STAT TIME COMMAND - ... ... ... ... ... - 2356 pts/0 S 0:00 ./echod - 2358 pts/1 S 0:00 ./echo 127.0.0.1 - 2359 pts/0 S 0:00 ./echod -\end{verbatim} %$ -(dove si sono cancellate le righe inutili) da cui si evidenzia la presenza di -tre processi, tutti in stato di \textit{sleep} (vedi -\tabref{tab:proc_proc_states}). - -Se a questo punto si inizia a scrivere qualcosa sul client non sarà trasmesso -niente fin tanto che non si prema il tasto di a capo (si ricordi quanto detto -in \secref{sec:file_line_io} a proposito dell'I/O su terminale), solo allora -\func{fgets} ritornerà ed il client scriverà quanto immesso sul socket, per -poi passare a rileggere quanto gli viene inviato all'indietro dal server, che -a sua volta sarà inviato sullo standard output, che nel caso ne provoca -l'immediatamente stampa a video. - - -\subsection{La conclusione normale} -\label{sec:TCP_echo_conclusion} - -Tutto quello che scriveremo sul client sarà rimandato indietro dal server e -ristampato a video fintanto che non concluderemo l'immissione dei dati; una -sessione tipica sarà allora del tipo: -\begin{verbatim} -[piccardi@roke sources]$ ./echo 127.0.0.1 -Questa e` una prova -Questa e` una prova -Ho finito -Ho finito -\end{verbatim} %$ -che termineremo inviando un EOF dal terminale (usando la combinazione di tasti -ctrl-D, che non compare a schermo); se eseguiamo un \cmd{netstat} a questo -punto avremo: -\begin{verbatim} -[piccardi@roke piccardi]$ netstat -at -tcp 0 0 *:echo *:* LISTEN -tcp 0 0 localhost:33032 localhost:echo TIME_WAIT -\end{verbatim} %$ -con il client che entra in \texttt{TIME\_WAIT}. - -Esaminiamo allora in dettaglio la sequenza di eventi che porta alla -terminazione normale della connessione, che ci servirà poi da riferimento -quando affronteremo il comportamento in caso di conclusioni anomale: - -\begin{enumerate} -\item inviando un carattere di EOF da terminale la \func{fgets} ritorna - restituendo un puntatore nullo che causa l'uscita dal ciclo di \code{while}, - così la funzione \code{ClientEcho} ritorna. -\item al ritorno di \code{ClientEcho} ritorna anche la funzione \code{main}, e - come parte del processo terminazione tutti i file descriptor vengono chiusi - (si ricordi quanto detto in \secref{sec:proc_term_conclusion}); questo causa - la chiusura del socket di comunicazione; il client allora invierà un FIN al - server a cui questo risponderà con un ACK. A questo punto il client verrà a - trovarsi nello stato \texttt{FIN\_WAIT\_2} ed il server nello stato - \texttt{CLOSE\_WAIT} (si riveda quanto spiegato in - \secref{sec:TCP_conn_term}). -\item quando il server riceve il FIN la \func{read} del processo figlio che - gestisce la connessione ritorna restituendo 0 causando così l'uscita dal - ciclo e il ritorno di \code{ServEcho}, a questo punto il processo figlio - termina chiamando \func{exit}. -\item all'uscita del figlio tutti i file descriptor vengono chiusi, la - chiusura del socket connesso fa sì che venga effettuata la sequenza finale - di chiusura della connessione, viene emesso un FIN dal server che riceverà - un ACK dal client, a questo punto la connessione è conclusa e il client - resta nello stato \texttt{TIME\_WAIT}. -\end{enumerate} - - -\subsection{La gestione dei processi figli} -\label{sec:TCP_child_hand} - -Tutto questo riguarda la connessione, c'è però da tenere conto dell'effetto -del procedimento di chiusura del processo figlio nel server (si veda quanto -esaminato in \secref{sec:proc_termination}). In questo caso avremo l'invio del -segnale \const{SIGCHLD} al padre, ma dato che non si è installato un -gestore e che l'azione predefinita per questo segnale è quella di essere -ignorato, non avendo predisposto la ricezione dello stato di terminazione, -otterremo che il processo figlio entrerà nello stato di zombie\index{zombie} -(si riveda quanto illustrato in \secref{sec:sig_sigchld}), come risulterà -ripetendo il comando \cmd{ps}: -\begin{verbatim} - 2356 pts/0 S 0:00 ./echod - 2359 pts/0 Z 0:00 [echod ] -\end{verbatim} - -Dato che non è il caso di lasciare processi zombie\index{zombie}, occorrerà -ricevere opportunamente lo stato di terminazione del processo (si veda -\secref{sec:proc_wait}), cosa che faremo utilizzando \const{SIGCHLD} secondo -quanto illustrato in \secref{sec:sig_sigchld}. Una prima modifica al nostro -server è pertanto quella di inserire la gestione della terminazione dei -processi figli attraverso l'uso di un gestore. Per questo useremo la funzione -\code{Signal} (che abbiamo illustrato in \figref{fig:sig_Signal_code}), per -installare il gestore che riceve i segnali dei processi figli terminati già -visto in \figref{fig:sig_sigchld_handl}. Basterà allora aggiungere il -seguente codice: \includecodesnip{listati/sigchildhand.c} -\noindent -all'esempio illustrato in \figref{fig:TCP_echo_server_first_code}. - -In questo modo però si introduce un altro problema. Si ricordi infatti che, -come spiegato in \secref{sec:sig_gen_beha}, quando un programma si trova in -stato di \texttt{sleep} durante l'esecuzione di una system call, questa viene -interrotta alla ricezione di un segnale. Per questo motivo, alla fine -dell'esecuzione del gestore del segnale, se questo ritorna, il programma -riprenderà l'esecuzione ritornando dalla system call interrotta con un errore -di \errcode{EINTR}. - -Vediamo allora cosa comporta tutto questo nel nostro caso: quando si chiude il -client, il processo figlio che gestisce la connessione terminerà, ed il padre, -per evitare la creazione di zombie, riceverà il segnale \const{SIGCHLD} -eseguendo il relativo gestore. Al ritorno del gestore però l'esecuzione nel -padre ripartirà subito con il ritorno della funzione \func{accept} (a meno di -un caso fortuito in cui il segnale arriva durante l'esecuzione del programma -in risposta ad una connessione) con un errore di \errcode{EINTR}. Non avendo -previsto questa eventualità il programma considera questo un errore fatale -terminando a sua volta con un messaggio del tipo: -\begin{verbatim} -[root@gont sources]# ./echod -i -accept error: Interrupted system call -\end{verbatim}%# - -Come accennato in \secref{sec:sig_gen_beha} le conseguenze di questo -comportamento delle system call possono essere superate in due modi diversi, -il più semplice è quello di modificare il codice di \func{Signal} per -richiedere il riavvio automatico delle system call interrotte secondo la -semantica di BSD, usando l'opzione \const{SA\_RESTART} di \func{sigaction}; -rispetto a quanto visto in \figref{fig:sig_Signal_code}. Definiremo allora la -nuova funzione \func{SignalRestart}\footnote{anche questa è definita, insieme - alle altre funzioni riguardanti la gestione dei segnali, nel file - \file{SigHand.c}, il cui contento completo può essere trovato negli esempi - allegati.} come mostrato in \figref{fig:sig_SignalRestart_code}, ed -installeremo il gestore usando quest'ultima. - -\begin{figure}[!htb] - \footnotesize \centering - \begin{minipage}[c]{15.6cm} - \includecodesample{listati/SignalRestart.c} - \end{minipage} - \normalsize - \caption{La funzione \funcd{SignalRestart}, che installa un gestore di - segnali in semantica BSD per il riavvio automatico delle system call - interrotte.} - \label{fig:sig_SignalRestart_code} -\end{figure} - -Come si può notare questa funzione è identica alla precedente \func{Signal}, -illustrata in \figref{fig:sig_Signal_code}, solo che in questo caso invece di -inizializzare a zero il campo \var{sa\_flags} di \struct{sigaction}, lo si -inizializza (\texttt{\small 5}) al valore \const{SA\_RESTART}. Usando questa -funzione al posto di \func{Signal} nel server non è necessaria nessuna altra -modifica: le system call interrotte saranno automaticamente riavviate, e -l'errore \errcode{EINTR} non si manifesterà più. - -La seconda soluzione è più invasiva e richiede di controllare tutte le volte -l'errore restituito dalle varie system call, ripetendo la chiamata qualora -questo corrisponda ad \errcode{EINTR}. Questa soluzione ha però il pregio -della portabilità, infatti lo standard POSIX dice che la funzionalità di -riavvio automatico delle system call, fornita da \const{SA\_RESTART}, è -opzionale, per cui non è detto che essa sia disponibile su qualunque sistema. -Inoltre in certi casi,\footnote{Stevens in \cite{UNP1} accenna che la maggior - parte degli Unix derivati da BSD non fanno ripartire \func{select}; altri - non riavviano neanche \func{accept} e \func{recvfrom}, cosa che invece nel - caso di Linux viene sempre fatta.} anche quando questa è presente, non è -detto possa essere usata con \func{accept}. - - -La portabilità nella gestione dei segnali però viene al costo di una -riscrittura parziale del server, la nuova versione di questo, in cui si sono -introdotte una serie di nuove opzioni che ci saranno utili per il debug, è -mostrata in \figref{fig:TCP_echo_server_code_second}, dove si sono riportate -la sezioni di codice modificate nella seconda versione del programma, il -sorgente completo di quest'ultimo si trova nel file -\file{TCP\_echod\_second.c} dei sorgenti allegati alla guida. - -La prima modifica effettuata è stata quella di introdurre una nuova opzione a -riga di comando, \texttt{-c}, che permette di richiedere il comportamento -compatibile nella gestione di \const{SIGCHLD} al posto della semantica BSD -impostando la variabile \var{compat} ad un valore non nullo. Questa è -preimpostata al valore nullo, cosicché se non si usa questa opzione il -comportamento di default del server è di usare la semantica BSD. - -Una seconda opzione aggiunta è quella di inserire un tempo di attesa fisso -specificato in secondi fra il ritorno della funzione \func{listen} e la -chiamata di \func{accept}, specificabile con l'opzione \texttt{-w}, che -permette di impostare la variabile \var{waiting}. Infine si è introdotta una -opzione \texttt{-d} per abilitare il debugging che imposta ad un valore non -nullo la variabile \var{debugging}. Al solito si è omessa da -\figref{fig:TCP_echo_server_code_second} la sezione di codice relativa alla -gestione di tutte queste opzioni, che può essere trovata nel sorgente del -programma. - -\begin{figure}[!htb] - \footnotesize \centering - \begin{minipage}[c]{15.6cm} - \includecodesample{listati/TCP_echod_second.c} - \end{minipage} - \normalsize - \caption{La sezione nel codice della seconda versione del server - per il servizio \textit{echo} modificata per tener conto dell'interruzione - delle system call.} - \label{fig:TCP_echo_server_code_second} -\end{figure} - -Vediamo allora come è cambiato il nostro server; una volta definite le -variabili e trattate le opzioni il primo passo (\texttt{\small 9--13}) è -verificare la semantica scelta per la gestione di \const{SIGCHLD}, a seconda -del valore di \var{compat} (\texttt{\small 9}) si installa il gestore con la -funzione \func{Signal} (\texttt{\small 10}) o con \texttt{SignalRestart} -(\texttt{\small 12}), essendo quest'ultimo il valore di default. - -Tutta la sezione seguente, che crea il socket, cede i privilegi di -amministratore ed eventualmente lancia il programma come demone, è rimasta -invariata e pertanto è stata omessa in -\figref{fig:TCP_echo_server_code_second}; l'unica modifica effettuata prima -dell'entrata nel ciclo principale è stata quella di aver introdotto, subito -dopo la chiamata (\texttt{\small 17--20}) alla funzione \func{listen}, una -eventuale pausa con una condizione (\texttt{\small 21}) sulla variabile -\var{waiting}, che viene inizializzata, con l'opzione \code{-w Nsec}, al -numero di secondi da aspettare (il valore preimpostato è nullo). - -Si è potuto lasciare inalterata tutta la sezione di creazione del socket -perché nel server l'unica chiamata ad una system call critica, che può essere -interrotta dall'arrivo di \const{SIGCHLD}, è quella ad \func{accept}, che è -l'unica funzione che può mettere il processo padre in stato di sleep nel -periodo in cui un figlio può terminare; si noti infatti come le altre -\textit{slow system call}\footnote{si ricordi la distinzione fatta in - \secref{sec:sig_gen_beha}.} o sono chiamate prima di entrare nel ciclo -principale, quando ancora non esistono processi figli, o sono chiamate dai -figli stessi e non risentono di \const{SIGCHLD}. - -Per questo l'unica modifica sostanziale nel ciclo principale (\texttt{\small - 23--42}), rispetto precedente versione di \figref{fig:TCP_ServEcho_first}, è -nella sezione (\texttt{\small 26--30}) in cui si effettua la chiamata di -\func{accept}. Quest'ultima viene effettuata (\texttt{\small 26--27}) -all'interno di un ciclo di \code{while}\footnote{la sintassi del C relativa a - questo ciclo può non essere del tutto chiara. In questo caso infatti si è - usato un ciclo vuoto che non esegue nessuna istruzione, in questo modo - quello che viene ripetuto con il ciclo è soltanto il codice che esprime la - condizione all'interno del \code{while}.} che la ripete indefinitamente -qualora in caso di errore il valore di \var{errno} sia \errcode{EINTR}. Negli -altri casi si esce in caso di errore effettivo (\texttt{\small 27--29}), -altrimenti il programma prosegue. - -Si noti che in questa nuova versione si è aggiunta una ulteriore sezione -(\texttt{\small 31--39}) di aiuto per il debug del programma, che eseguita con -un controllo (\texttt{\small 31}) sul valore della variabile \var{debugging} -impostato dall'opzione \texttt{-d}. Qualora questo sia nullo, come -preimpostato, non accade nulla. altrimenti (\texttt{\small 32}) l'indirizzo -ricevuto da \var{accept} viene convertito in una stringa che poi -(\texttt{\small 33--38}) viene opportunamente stampata o sullo schermo o nei -log. - - - - -\section{I vari scenari critici} -\label{sec:TCP_echo_critical} - -Con le modifiche viste in \secref{sec:TCP_child_hand} il nostro esempio -diventa in grado di affrontare la gestione ordinaria delle connessioni, ma un -server di rete deve tenere conto che, al contrario di quanto avviene per i -server che operano nei confronti di processi presenti sulla stessa macchina, -la rete è di sua natura inaffidabile, per cui è necessario essere in grado di -gestire tutta una serie di situazioni critiche che non esistono per i processi -locali. - - -\subsection{La terminazione precoce della connessione} -\label{sec:TCP_conn_early_abort} - -La prima situazione critica è quella della terminazione precoce, causata da un -qualche errore sulla rete, della connessione effettuata da un client. Come -accennato in \secref{sec:TCP_func_accept} la funzione \func{accept} riporta -tutti gli eventuali errori di rete pendenti su una connessione sul -\textit{connected socket}. Di norma questo non è un problema, in quanto non -appena completata la connessione, \func{accept} ritorna e l'errore sarà -rilevato in seguito, dal processo che gestisce la connessione, alla prima -chiamata di una funzione che opera sul socket. - -È però possibile, dal punto di vista teorico, incorrere anche in uno scenario -del tipo di quello mostrato in \figref{fig:TCP_early_abort}, in cui la -connessione viene abortita sul lato client per un qualche errore di rete con -l'invio di un segmento RST, prima che nel server sia stata chiamata la -funzione \func{accept}. - -\begin{figure}[htb] - \centering - \includegraphics[width=10cm]{img/tcp_client_early_abort} - \caption{Un possibile caso di terminazione precoce della connessione.} - \label{fig:TCP_early_abort} -\end{figure} - -Benché questo non sia un fatto comune, un evento simile può essere osservato -con dei server molto occupati. In tal caso, con una struttura del server -simile a quella del nostro esempio, in cui la gestione delle singole -connessioni è demandata a processi figli, può accadere che il three way -handshake venga completato e la relativa connessione abortita subito dopo, -prima che il padre, per via del carico della macchina, abbia fatto in tempo ad -eseguire la chiamata ad \func{accept}. Di nuovo si ha una situazione analoga -a quella illustrata in \figref{fig:TCP_early_abort}, in cui la connessione -viene stabilita, ma subito dopo si ha una condizione di errore che la chiude -prima che essa sia stata accettata dal programma. - -Questo significa che, oltre alla interruzione da parte di un segnale, che -abbiamo trattato in \secref{sec:TCP_child_hand} nel caso particolare di -\const{SIGCHLD}, si possono ricevere altri errori non fatali all'uscita di -\func{accept}, che come nel caso precedente, necessitano semplicemente la -ripetizione della chiamata senza che si debba uscire dal programma. In questo -caso anche la versione modificata del nostro server non sarebbe adatta, in -quanto uno di questi errori causerebbe la terminazione dello stesso. In Linux -i possibili errori di rete non fatali, riportati sul socket connesso al -ritorno di \func{accept}, sono \errcode{ENETDOWN}, \errcode{EPROTO}, -\errcode{ENOPROTOOPT}, \errcode{EHOSTDOWN}, \errcode{ENONET}, -\errcode{EHOSTUNREACH}, \errcode{EOPNOTSUPP} e \errcode{ENETUNREACH}. - -Si tenga presente che questo tipo di terminazione non è riproducibile -terminando il client prima della chiamata ad \func{accept}, come si potrebbe -fare usando l'opzione \texttt{-w} per introdurre una pausa dopo il lancio del -demone, in modo da poter avere il tempo per lanciare e terminare una -connessione usando il programma client. In tal caso infatti, alla terminazione -del client, il socket associato alla connessione viene semplicemente chiuso, -attraverso la sequenza vista in \secref{sec:TCP_conn_term}, per cui la -\func{accept} ritornerà senza errori, e si avrà semplicemente un end-of-file -al primo accesso al socket. Nel caso di Linux inoltre, anche qualora si -modifichi il client per fargli gestire l'invio di un segmento di RST alla -chiusura dal socket (come suggerito da Stevens in \cite{UNP1}), non si ha -nessun errore al ritorno di \funcd{accept} quanto un errore di -\errcode{ECONNRESET} al primo tentativo di accesso al socket. - - - -\subsection{Il crollo 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. - - - - - -%%% Local Variables: -%%% mode: latex -%%% TeX-master: "gapil" -%%% End: 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/tcpsock.tex b/tcpsock.tex new file mode 100644 index 0000000..20f143e --- /dev/null +++ b/tcpsock.tex @@ -0,0 +1,2403 @@ +%% 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 +%% Documentation License, Version 1.1 or any later version published by the +%% Free Software Foundation; with the Invariant Sections being "Prefazione", +%% with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the +%% license is included in the section entitled "GNU Free Documentation +%% License". +%% +\chapter{Socket TCP} +\label{cha:TCP_socket} + +In questo capitolo iniziamo ad approfondire la conoscenza dei socket TCP, +iniziando con una descrizione delle principali caratteristiche del +funzionamento di una connessione TCP. Tratteremo poi le varie funzioni che +servono alla creazione di una connessione fra un server elementare ed il suo +client, fornendo poi alcuni esempi di applicazione elementare. + + + +\section{Il funzionamento di una connessione TCP} +\label{sec:TCP_connession} + +Prima di entrare nei dettagli delle singole funzioni usate nelle applicazioni +che utilizzano i socket TCP, è fondamentale spiegare alcune delle basi del +funzionamento del protocollo, poiché questa conoscenza è essenziale per +comprendere il comportamento di dette funzioni per questo tipo di socket, ed +il relativo modello di programmazione. + +Si ricordi che il protocollo TCP serve a creare degli \textit{stream socket}, +cioè una forma di canale di comunicazione che stabilisce una connessione +stabile fra due stazioni, in modo che queste possano scambiarsi dei dati. In +questa sezione ci concentreremo sulle modalità con le quali il protocollo dà +inizio e conclude una connessione e faremo inoltre un breve accenno al +significato di alcuni dei vari \textsl{stati} ad essa associati. + +\subsection{La creazione della connessione: il \textit{three way handshake}} +\label{sec:TCP_conn_cre} + +Il processo che porta a creare una connessione TCP è chiamato \textit{three + way handshake}; la successione tipica degli eventi (e dei +\textsl{segmenti}\footnote{Si ricordi che il segmento è l'unità elementare di + dati trasmessa dal protocollo TCP al livello successivo; 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 \secref{sec:tcp_protocol}).} di dati che vengono +scambiati) 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, 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 + e 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 \figref{fig:TCP_TWH} +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:TCP_TWH} +\end{figure} + +Si è accennato in precedenza ai \textsl{numeri di sequenza} (che sono anche +riportati in \figref{fig:TCP_TWH}): 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. + +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 \figref{fig:TCP_close}) per l'acknowledgement di un FIN. + +\subsection{Le opzioni TCP.} +\label{sec:TCP_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 \const{TCP\_MAXSEG}. + +\item \textit{window scale option}, %come spiegato in \secref{sec: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;\footnote{ Linux usa come massimo 32767 + per evitare problemi con alcune implementazioni che usano l'aritmetica con + segno per implementare lo stack TCP.} ma alcuni tipi di connessione come + quelle ad alta velocità (sopra i 45Mbit/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 spostare 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'\href{http://www.ietf.org/rfc/rfc1323.txt}{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:TCP_conn_term} + +Mentre per la creazione di una connessione occorre un interscambio di tre +segmenti, la procedura di chiusura ne richiede normalmente quattro. 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 + serve ad indicare che si è finito con l'invio dei dati sulla connessione. + +\item L'altro capo della connessione riceve il FIN e dovrà eseguire la + \textsl{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 Una volta rilevata l'end-of-file 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 +\figref{fig:TCP_close} si è rappresentato graficamente lo sequenza di +scambio dei segmenti che conclude la connessione. + +\begin{figure}[htb] + \centering + \includegraphics[width=10cm]{img/tcp_close} + \caption{La chiusura di una connessione TCP.} + \label{fig:TCP_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 in \secref{xxx_shutdown}, quando parleremo della funzione +\func{shutdown}. + +La emissione del FIN avviene quando il socket viene chiuso, questo però non +avviene solo per la chiamata esplicita della funzione \func{close}, ma anche +alla terminazione di un processo, quando tutti i file vengono chiusi. Questo +comporta ad esempio che se un processo viene terminato da un segnale tutte le +connessioni aperte verranno chiuse. + +Infine occorre sottolineare che, benché nella figura (e nell'esempio che +vedremo più avanti in \secref{sec:TCP_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 nell'esempio di +\figref{fig:TCP_daytime_iter_server_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:TCP_conn_dia} + +Come abbiamo visto le operazioni del TCP nella creazione e conclusione di una +connessione sono piuttosto complesse, ed abbiamo esaminato soltanto quelle +relative ad un andamento normale. In \secref{sec:TCP_states} vedremo con +maggiori dettagli che una connessione può assumere vari stati, che ne +caratterizzano il funzionamento, e che sono quelli che vengono riportati dal +comando \cmd{netstat}, per ciascun socket TCP aperto, nel campo +\textit{State}. + +Non possiamo affrontare qui una descrizione completa del funzionamento del +protocollo; un approfondimento sugli aspetti principali si trova in +\secref{sec:tcp_protocol}, ma per una trattazione completa il miglior +riferimento resta \cite{TCPIll1}. 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 funzione \texttt{close} prima di aver ricevuto un +\textit{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 \figref{fig:TCP_conn_example} è 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:TCP_conn_example} +\end{figure} + +La connessione viene iniziata dal client che annuncia un MSS di 1460, un +valore tipico con Linux per IPv4 su Ethernet, 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:TCP_conn_term}; si noti che il capo della +connessione che esegue la chiusura attiva entra nello stato +\texttt{TIME\_WAIT}, sul cui significato 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:TCP_time_wait} + +Come riportato da Stevens in \cite{UNP1} lo stato \texttt{TIME\_WAIT} è +probabilmente uno degli aspetti meno compresi del protocollo TCP, è infatti +comune trovare 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 \figref{fig:TCP_conn_example}) +\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 ``\textsl{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'\href{http://www.ietf.org/rfc/rfc1122.txt}{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 +\figref{fig:TCP_conn_example}: 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 un socket deve rimanere attivo nello stato \texttt{TIME\_WAIT} anche +dopo l'invio dell'ultimo ACK, per potere essere in grado di gestirne +l'eventuale ritrasmissione, in caso esso venga perduto. + +Il secondo motivo è più complesso da capire, e necessita di una spiegazione +degli scenari in cui può accadere che i pacchetti TCP si possano perdere nella +rete o restare intrappolati, per poi riemergere in un secondo tempo. + +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 TCP, chi l'ha inviato, +non ricevendo un ACK in 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 \texttt{195.110.112.236} porta 1550 e l'IP +\texttt{192.84.145.100} porta 22 (affronteremo il significato delle porte +nella prossima sezione), 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 per le risposte nella direzione opposta. + +In questo modo, prima che venga creata una nuova connessione, il protocollo +TCP si assicura che tutti gli eventuali segmenti residui di una precedente +connessione, che potrebbero causare disturbi, siano stati eliminati dalla +rete. + + +\subsection{I numeri di porta} +\label{sec:TCP_port_num} + +In un ambiente multitasking in un dato momento più processi devono poter 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 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'\href{http://www.ietf.org/rfc/rfc1700.txt}{RFC~1700} che contiene +l'elenco delle porte assegnate dalla IANA (la \textit{Internet Assigned Number + Authority}) ma l'elenco viene costantemente aggiornato e pubblicato su +internet (una versione aggiornata si può trovare all'indirizzo +\texttt{ftp://ftp.isi.edu/in-notes/iana/assignements/port-numbers}); inoltre +in un sistema unix-like un analogo elenco viene mantenuto nel file +\file{/etc/services}, con la corrispondenza fra i vari 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'\href{http://www.ietf.org/rfc/rfc1700.txt}{RFC~1700} i vari sistemi hanno +fatto scelte diverse per le porte effimere, in particolare in +\figref{fig:TCP_port_alloc} sono riportate quelle di BSD 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=15cm]{img/port_alloc} + \caption{Allocazione dei numeri di porta.} + \label{fig:TCP_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 amministratore, 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, contando appunto sul fatto che +solo l'amministratore può usare queste porte. Data l'assoluta inconsistenza in +termini di sicurezza di un tale metodo, al giorno d'oggi esso è in completo +disuso. + +Data una connessione TCP si suole chiamare \textit{socket pair}\footnote{da + non confondere con la coppia di socket della omonima funzione + \func{socketpair} che fanno riferimento ad una coppia di socket sulla stessa + macchina, non ai capi di una connessione TCP.} 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 +(\texttt{195.110.112.152:22}, \texttt{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:TCP_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 quelle che descriveremo in +\secref{sec:TCP_daytime_application} e \secref{sec:TCP_echo_application}) +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 (il cui indirizzo sia +\texttt{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 \texttt{0.0.0.0} usata da +\cmd{netstat} è equivalente all'asterisco utilizzato per il numero di porta, +indica il valore generico, e corrisponde al valore \const{INADDR\_ANY} +definito in \file{arpa/inet.h} (vedi \ref{tab:TCP_ipv4_addr}). + +Inoltre si noti come la porta e l'indirizzo di ogni eventuale connessione +esterna non sono specificati; in questo caso la \textit{socket pair} associata +al socket potrebbe essere indicata come (\texttt{*:22}, \texttt{*:*}), usando +anche per gli indirizzi l'asterisco come carattere che indica il valore +generico. + +Dato che in genere una macchina è associata ad un solo indirizzo IP, ci si può +chiedere che senso abbia l'utilizzo dell'indirizzo generico per specificare +l'indirizzo locale; ma a parte il caso di macchine che hanno più di un +indirizzo IP (il cosiddetto \textit{multihoming}) esiste sempre anche +l'indirizzo di loopback, per cui con l'uso dell'indirizzo generico si possono +accettare connessioni indirizzate verso uno qualunque degli indirizzi IP +presenti. Ma, come si può vedere nell'esempio con il DNS che è in ascolto +sulla porta 53, è possibile anche restringere l'accesso ad 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, per esempio +quella con l'indirizzo \texttt{192.84.146.100}, si dovrà lanciare su +quest'ultima un client \cmd{ssh} per creare una connessione, e il kernel gli +assocerà una porta effimera (ad esempio la 21100), per cui la connessione sarà +espressa dalla socket pair (\texttt{192.84.146.100:21100}, +\texttt{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 \cmd{netstat} otteniamo 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 utilizza anch'esso +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 \cmd{ssh} per una seconda +connessione quello che otterremo usando \cmd{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 per gestirla. + +Tutto ciò mostra come il 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\footnote{ad esempio con il comando \cmd{fuser}, o con \cmd{lsof}.} 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{Le funzioni di base per la gestione dei socket} +\label{sec:TCP_functions} + +In questa sezione descriveremo in maggior dettaglio le varie funzioni che +vengono usate per la gestione di base dei socket TCP, non torneremo però sulla +funzione \func{socket}, che è già stata esaminata accuratamente nel capitolo +precedente in \secref{sec:sock_socket}. + + +\subsection{La funzione \func{bind}} +\label{sec:TCP_func_bind} + +La funzione \funcd{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)} + + Assegna un indirizzo ad un socket. + + \bodydesc{La funzione restituisce 0 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[\errcode{EBADF}] il file descriptor non è valido. + \item[\errcode{EINVAL}] il socket ha già un indirizzo assegnato. + \item[\errcode{ENOTSOCK}] il file descriptor non è associato ad un socket. + \item[\errcode{EACCES}] si è cercato di usare una porta riservata senza + sufficienti privilegi. + \item[\errcode{EADDRNOTAVAIL}] Il tipo di indirizzo specificato non è + disponibile. + \item[\errcode{EADDRINUSE}] qualche altro socket sta già usando l'indirizzo. + \end{errlist} + ed anche \errval{EFAULT} e per i socket di tipo \const{AF\_UNIX}, + \errval{ENOTDIR}, \errval{ENOENT}, \errval{ENOMEM}, \errval{ELOOP}, + \errval{ENOSR} e \errval{EROFS}.} +\end{prototype} + +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}. + +Con i socket 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 (l'elenco di queste porte, e dei relativi servizi, è in +\file{/etc/services}). + +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 l'indirizzo di un socket, ed il kernel +sceglie l'indirizzo di origine quando viene effettuata la connessione, sulla +base dell'interfaccia usata per trasmettere i pacchetti, (che dipenderà 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 +\const{INADDR\_ANY}, il cui valore, come accennato in +\secref{sec:sock_sa_ipv4}, è pari a zero; nell'esempio +\figref{fig:TCP_daytime_iter_server_code} si è usata un'assegnazione immediata +del tipo: \includecodesnip{listati/serv_addr_sin_addr.c} + +Si noti che si è usato \func{htonl} per assegnare il valore +\const{INADDR\_ANY}, anche se, essendo questo nullo, il riordinamento è +inutile. Si tenga presente comunque che tutte le costanti \val{INADDR\_} +(riportate in \tabref{tab:TCP_ipv4_addr}) sono definite secondo +l'\textit{endianess} della macchina, ed anche se esse possono essere +invarianti rispetto all'ordinamento dei bit, è comunque buona norma usare +sempre la funzione \func{htonl}. + +\begin{table}[htb] + \centering + \footnotesize + \begin{tabular}[c]{|l|l|} + \hline + \textbf{Costante} & \textbf{Significato} \\ + \hline + \hline + \const{INADDR\_ANY} & Indirizzo generico (\texttt{0.0.0.0})\\ + \const{INADDR\_BROADCAST}& Indirizzo di \textit{broadcast}.\\ + \const{INADDR\_LOOPBACK} & Indirizzo di \textit{loopback} + (\texttt{127.0.0.1}).\\ + \const{INADDR\_NONE} & Indirizzo errato.\\ + \hline + \end{tabular} + \caption{Costanti di definizione di alcuni indirizzi generici per IPv4.} + \label{tab:TCP_ipv4_addr} +\end{table} + +L'esempio precedente funziona correttamente con IPv4 poiché che l'indirizzo è +rappresentabile anche con un intero a 32 bit; non si può usare lo stesso +metodo con IPv6, in cui l'indirizzo deve necessariamente essere specificato +con una struttura, perché il linguaggio C non consente l'uso di una struttura +costante come operando a destra in una assegnazione. + +Per questo motivo nell'header \file{netinet/in.h} è definita una variabile +\const{in6addr\_any} (dichiarata come \direct{extern}, ed inizializzata dal +sistema al valore \const{IN6ADRR\_ANY\_INIT}) che permette di effettuare una +assegnazione del tipo: \includecodesnip{listati/serv_addr_sin6_addr.c} in +maniera analoga si può utilizzare la variabile \const{in6addr\_loopback} per +indicare l'indirizzo di \textit{loopback}, che a sua volta viene inizializzata +staticamente a \const{IN6ADRR\_LOOPBACK\_INIT}. + + + +\subsection{La funzione \func{connect}} +\label{sec:TCP_func_connect} + +La funzione \funcd{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)} + + Stabilisce una connessione fra due socket. + + \bodydesc{La funzione restituisce zero in caso di successo e -1 per un + errore, nel qual caso \var{errno} assumerà i valori: + \begin{errlist} + \item[\errcode{ECONNREFUSED}] non c'è nessuno in ascolto sull'indirizzo + remoto. + \item[\errcode{ETIMEDOUT}] si è avuto timeout durante il tentativo di + connessione. + \item[\errcode{ENETUNREACH}] la rete non è raggiungibile. + \item[\errcode{EINPROGRESS}] il socket è non bloccante (vedi + \secref{sec:file_noblocking}) e la connessione non può essere conclusa + immediatamente. + \item[\errcode{EALREADY}] il socket è non bloccante (vedi + \secref{sec:file_noblocking}) e un tentativo precedente di connessione non + si è ancora concluso. + \item[\errcode{EAGAIN}] non ci sono più porte locali libere. + \item[\errcode{EAFNOSUPPORT}] l'indirizzo non ha una famiglia di indirizzi + corretta nel relativo campo. + \item[\errcode{EACCES}, \errcode{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: \errval{EFAULT}, \errval{EBADF}, + \errval{ENOTSOCK}, \errval{EISCONN} e \errval{EADDRINUSE}.} +\end{prototype} + +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}. + +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:TCP_daytime_client}, 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 è + \errcode{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 + \errcode{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 + \errcode{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:TCP_func_listen} + +La funzione \funcd{listen} serve ad 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)} + Pone un socket in attesa di una connessione. + + \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[\errcode{EBADF}] l'argomento \param{sockfd} non è un file descriptor + valido. + \item[\errcode{ENOTSOCK}] l'argomento \param{sockfd} non è un socket. + \item[\errcode{EOPNOTSUPP}] il socket è di un tipo che non supporta questa + operazione. + \end{errlist}} +\end{prototype} + +La funzione pone il socket specificato da \param{sockfd} in modalità passiva e +predispone una coda per le connessioni in arrivo di lunghezza pari a +\param{backlog}. La funzione si può applicare solo a socket di tipo +\const{SOCK\_STREAM} o \const{SOCK\_SEQPACKET}. + +L'argomento \param{backlog} indica il numero massimo di connessioni pendenti +accettate; se esso viene ecceduto il client al momento della richiesta della +connessione riceverà un errore di tipo \errcode{ECONNREFUSED}, o se il +protocollo, come accade nel caso del TCP, supporta la ritrasmissione, la +richiesta sarà ignorata in modo che la connessione possa venire 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 La coda delle connessioni incomplete (\textit{incomplete connection + queue} che contiene un riferimento per ciascun socket per il quale è + arrivato un SYN ma il \textit{three way handshake} non si è ancora concluso. + Questi socket sono tutti nello stato \texttt{SYN\_RECV}. +\item La 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 \figref{fig:TCP_listen_backlog}: +quando arriva un SYN da un client il server crea una nuova voce nella coda +delle connessioni incomplete, e poi risponde con il SYN$+$ACK. La voce 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 +la voce viene spostata nella coda delle connessioni complete. Quando il +processo chiama la funzione \func{accept} (vedi \secref{sec:TCP_func_accept}) +la prima voce 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. + +\begin{figure}[htb] + \centering + \includegraphics[width=11cm]{img/tcp_listen_backlog} + \caption{Schema di funzionamento delle code delle connessioni complete ed + incomplete.} + \label{fig:TCP_listen_backlog} +\end{figure} + +Storicamente il valore del parametro \param{backlog} era corrispondente al +massimo valore della somma del numero di voci possibili per ciascuna delle due +code. Stevens in \cite{UNP1} riporta che BSD ha sempre applicato un fattore di +1.5 a detto valore, e fornisce 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 \param{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 funzione \func{sysctl} con il parametro \const{NET\_TCP\_MAX\_SYN\_BACKLOG} +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 \param{backlog} viene troncato ad un massimo di \const{SOMAXCONN} se è +superiore a detta costante (che di default vale 128). + +La scelta storica per il valore di questo parametro era 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}). + +Stevens tratta accuratamente questo argomento in \cite{UNP1}, 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:TCP_func_accept} + +La funzione \funcd{accept} è chiamata da un server per gestire la connessione +una volta che sia stato completato il \textit{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)} + + Accetta una connessione sul socket specificato. + + \bodydesc{La funzione restituisce un numero di socket descriptor positivo in + caso di successo e -1 in caso di errore, nel qual caso \var{errno} viene + impostata ai seguenti valori: + + \begin{errlist} + \item[\errcode{EBADF}] l'argomento \param{sockfd} non è un file descriptor + valido. + \item[\errcode{ENOTSOCK}] l'argomento \param{sockfd} non è un socket. + \item[\errcode{EOPNOTSUPP}] il socket è di un tipo che non supporta questa + operazione. + \item[\errcode{EAGAIN} o \errcode{EWOULDBLOCK}] il socket è stato impostato + come non bloccante (vedi \secref{sec:file_noblocking}), e non ci sono + connessioni in attesa di essere accettate. + \item[\errcode{EPERM}] Le regole del firewall non consentono la connessione. + \item[\errcode{ENOBUFS}, \errcode{ENOMEM}] questo spesso significa che + l'allocazione della memoria è limitata dai limiti sui buffer dei socket, + non dalla memoria di sistema. + \item[\errcode{EINTR}] La funzione è stata interrotta da un segnale. + \end{errlist} + Inoltre possono essere restituiti gli errori di rete relativi al nuovo + socket, diversi a secondo del protocollo, come: \errval{EMFILE}, + \errval{EINVAL}, \errval{ENOSR}, \errval{ENOBUFS}, \errval{EFAULT}, + \errval{EPERM}, \errval{ECONNABORTED}, \errval{ESOCKTNOSUPPORT}, + \errval{EPROTONOSUPPORT}, \errval{ETIMEDOUT}, \errval{ERESTARTSYS}.} +\end{prototype} + +La funzione estrae la prima connessione relativa al socket \param{sockfd} in +attesa sulla coda delle connessioni complete, che associa ad nuovo socket con +le stesse caratteristiche di \param{sockfd}. Il socket originale non viene +toccato e resta nello stato di \texttt{LISTEN}, mentre il nuovo socket viene +posto nello stato \texttt{ESTABLISHED}. Nella struttura \param{addr} e nella +variabile \param{addrlen} vengono restituiti indirizzo e relativa lunghezza +del client che si è connesso. + +I due argomenti \param{addr} e \param{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 +\param{addrlen} deve essere inizializzato alle dimensioni della struttura il +cui indirizzo è passato come argomento in \param{addr}; al ritorno della +funzione \param{addrlen} conterrà il numero di byte scritti dentro +\param{addr}. Se questa informazione non interessa basterà inizializzare a +\val{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:TCP_func_listen}) che il client ha effettuato verso il socket +\param{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 impostato il socket per + essere non bloccante (vedi \secref{sec:file_noblocking}), nel qual caso + ritorna con l'errore \errcode{EAGAIN}. Torneremo su questa modalità di + operazione in \secref{sec:xxx_sock_noblock}.} fintanto che non ne arriva +una. + +La funzione può essere usata solo con socket che supportino la connessione +(cioè di tipo \const{SOCK\_STREAM}, \const{SOCK\_SEQPACKET} o +\const{SOCK\_RDM}). Per alcuni protocolli che richiedono una conferma +esplicita della connessione,\footnote{attualmente in Linux solo DECnet ha + questo comportamento.} la funzione opera solo l'estrazione dalla coda delle +connessioni, la conferma della connessione viene eseguita implicitamente dalla +prima chiamata ad una \func{read} o una \func{write}, mentre il rifiuto della +connessione viene eseguito 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}, per cui l'applicazione deve tenerne conto ed +eventualmente ripetere la chiamata alla funzione come per l'errore di +\errcode{EAGAIN} (torneremo su questo in \secref{sec:TCP_echo_critical}). +Un'altra differenza con BSD è che la funzione non fa ereditare al nuovo socket +i flag del socket originale, come \const{O\_NONBLOCK},\footnote{ed in generale + tutti quelli che si possono impostare con \func{fcntl}, vedi + \secref{sec:file_fcntl}.} che devono essere rispecificati ogni volta. Tutto +questo deve essere tenuto in conto se si devono scrivere programmi portabili. + +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, detto per questo \textit{listening socket}, che resta per tutto il +tempo nello stato \texttt{LISTEN}, mentre le connessioni vengono gestite dai +nuovi socket, detti \textit{connected socket}, ritornati da \func{accept}, che +si trovano automaticamente nello stato \texttt{ESTABLISHED}, e vengono +utilizzati per lo scambio dei dati, che avviene su di essi, fino alla chiusura +della connessione. Si può riconoscere questo schema anche nell'esempio +elementare di \figref{fig:TCP_daytime_iter_server_code}, dove per ogni +connessione il socket creato da \func{accept} viene chiuso dopo l'invio dei +dati. + + +\subsection{Le funzioni \func{getsockname} e \func{getpeername}} +\label{sec:TCP_get_names} + +Oltre a tutte quelle viste finora, dedicate all'utilizzo dei socket, esistono +alcune funzioni ausiliarie che possono essere usate per recuperare alcune +informazioni relative ai socket ed alle connessioni ad essi associate. Le due +funzioni più elementari sono queste, che vengono usate per ottenere i dati +relativi alla socket pair associata ad un certo socket. + +La prima funzione è \funcd{getsockname} e serve ad ottenere l'indirizzo locale +associato ad un socket; il suo prototipo è: +\begin{prototype}{sys/socket.h} + {int getsockname(int sockfd, struct sockaddr * name, socklen\_t * namelen)} + Legge l'indirizzo locale di un socket. + +\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[\errcode{EBADF}] l'argomento \param{sockfd} non è un file descriptor + valido. + \item[\errcode{ENOTSOCK}] l'argomento \param{sockfd} non è un socket. + \item[\errcode{ENOBUFS}] non ci sono risorse sufficienti nel sistema per + eseguire l'operazione. + \item[\errcode{EFAULT}] l'indirizzo \param{name} non è valido. + \end{errlist}} +\end{prototype} + +La funzione restituisce la struttura degli indirizzi del socket \param{sockfd} +nella struttura indicata dal puntatore \param{name} la cui lunghezza è +specificata tramite l'argomento \param{namlen}. Quest'ultimo viene passato +come indirizzo per avere indietro anche il numero di byte effettivamente +scritti nella struttura puntata da \param{name}. Si tenga presente che se si è +utilizzato un buffer troppo piccolo per \param{name} l'indirizzo risulterà +troncato. + +La funzione 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. + +Tutte le volte che si vuole avere l'indirizzo remoto di un socket si usa la +funzione \funcd{getpeername}, il cui prototipo è: +\begin{prototype}{sys/socket.h} + {int getpeername(int sockfd, struct sockaddr * name, socklen\_t * namelen)} + Legge l'indirizzo remoto di un socket. + + \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[\errcode{EBADF}] l'argomento \param{sockfd} non è un file descriptor + valido. + \item[\errcode{ENOTSOCK}] l'argomento \param{sockfd} non è un socket. + \item[\errcode{ENOTCONN}] il socket non è connesso. + \item[\errcode{ENOBUFS}] non ci sono risorse sufficienti nel sistema per + eseguire l'operazione. + \item[\errcode{EFAULT}] l'argomento \param{name} punta al di fuori dello + spazio di indirizzi del processo. + \end{errlist}} +\end{prototype} + +La funzione è identica a \func{getsockname}, ed usa la stessa sintassi, ma +restituisce l'indirizzo remoto del socket, cioè quello associato all'altro +capo della connessione. 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 vedremo in +\figref{fig:TCP_daytime_cunc_server_code}, i valori di ritorno di +\func{accept}. + +Il fatto è che in generale quest'ultimo caso non è sempre possibile. In +particolare questo avviene quando il server, invece di gestire la connessione +direttamente in un processo figlio, come vedremo nell'esempio di server +concorrente di \secref{sec:TCP_daytime_cunc_server}, lancia per ciascuna +connessione un altro programma, usando \func{exec}.\footnote{questa ad esempio + è la modalità con cui opera il \textsl{super-server} \cmd{inetd}, che può + gestire tutta una serie di servizi diversi, eseguendo su ogni connessione + ricevuta sulle porte tenute sotto controllo, il relativo 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} verrà caricata in +memoria l'immagine del programma eseguito, che a questo punto perde ogni +riferimento ai valori tornati da \func{accept}. Il socket descriptor però +resta aperto, e se si è seguita una opportuna convenzione per rendere noto al +programma eseguito qual'è il socket connesso, \footnote{ad esempio il solito + \cmd{inetd} 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. + +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. + + +\subsection{La funzione \func{close}} +\label{sec:TCP_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 file descriptor +associati ad un socket. + +L'azione 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:TCP_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 tutti i file descriptor anche per i socket 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 si applica, come +visto in \secref{sec:file_sharing}, sia ai file descriptor duplicati che a +quelli ereditati dagli eventuali processi figli, ed è il comportamento che ci +si aspetta in una qualunque applicazione client/server. + +Per attivare immediatamente l'emissione del FIN e la sequenza di chiusura +descritta in \secref{sec:TCP_conn_term}, si può invece usare la funzione +\func{shutdown} su cui torneremo in seguito (vedi \secref{sec:xxx_shutdown}). + + + +\section{Un esempio elementare: il servizio \textit{daytime}} +\label{sec:TCP_daytime_application} + +Avendo introdotto le funzioni di base per la gestione dei socket, potremo +vedere in questa sezione un primo esempio di applicazione elementare che +implementa il servizio \textit{daytime} su TCP, secondo quanto specificato +dall'\href{http://www.ietf.org/rfc/rfc0867.txt}{RFC~867}. Prima di passare +agli esempi del client e del server, inizieremo riesaminando con maggiori +dettagli una peculiarità delle funzioni di I/O, già accennata in +\secref{sec:file_read} e \secref{sec:file_write}, che nel caso dei socket è +particolarmente rilevante. Passeremo poi ad illustrare gli esempi +dell'implementazione, sia dal lato client, che dal lato server, che si è +realizzato sia in forma iterativa che concorrente. + + +\subsection{Il comportamento delle funzioni di I/O} +\label{sec:sock_io_behav} + +Una cosa che si tende a dimenticare quando si ha a che fare con i socket è che +le funzioni di input/output non sempre hanno lo stesso comportamento che +avrebbero con i normali file di dati (in particolare questo accade per i +socket di tipo stream). + +Infatti con i socket è comune che funzioni come \func{read} o \func{write} +possano restituire in input o scrivere in output un numero di byte minore di +quello richiesto. Come già accennato in \secref{sec:file_read} questo è un +comportamento normale per le funzioni di I/O, ma con i normali file di dati il +problema si avverte solo in lettura, quando si incontra la fine del file. In +generale non è così, e con i socket questo è particolarmente evidente. + + +\begin{figure}[htb] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \includecodesample{listati/FullRead.c} + \end{minipage} + \normalsize + \caption{La funzione \func{FullRead}, che legge esattamente \var{count} byte + da un file descriptor, iterando opportunamente le letture.} + \label{fig:sock_FullRead_code} +\end{figure} + +Quando ci si trova ad affrontare questo comportamento tutto quello che si deve +fare è semplicemente ripetere la lettura (o la scrittura) per la quantità di +byte restanti, tenendo conto che le funzioni si possono bloccare se i dati non +sono disponibili: è lo stesso comportamento che si può avere scrivendo più di +\const{PIPE\_BUF} byte in una pipe (si riveda quanto detto in +\secref{sec:ipc_pipes}). + +Per questo motivo, seguendo l'esempio di R. W. Stevens in \cite{UNP1}, si sono +definite due funzioni, \func{FullRead} e \func{FullWrite}, che eseguono +lettura e scrittura tenendo conto di questa caratteristica, ed in grado di +ritornare solo dopo avere letto o scritto esattamente il numero di byte +specificato; il sorgente è riportato rispettivamente in +\figref{fig:sock_FullRead_code} e \figref{fig:sock_FullWrite_code} ed è +disponibile fra i sorgenti allegati alla guida nei file \file{FullRead.c} e +\file{FullWrite.c}. + +\begin{figure}[htb] + \centering + \footnotesize \centering + \begin{minipage}[c]{15cm} + \includecodesample{listati/FullWrite.c} + \end{minipage} + \normalsize + \caption{La funzione \func{FullWrite}, che scrive esattamente \var{count} + byte su un file descriptor, iterando opportunamente le scritture.} + \label{fig:sock_FullWrite_code} +\end{figure} + +Come si può notare le due funzioni ripetono la lettura/scrittura in un ciclo +fino all'esaurimento del numero di byte richiesti, in caso di errore viene +controllato se questo è \errcode{EINTR} (cioè un'interruzione della system +call dovuta ad un segnale), nel qual caso l'accesso viene ripetuto, altrimenti +l'errore viene ritornato al programma chiamante, interrompendo il ciclo. + +Nel caso della lettura, se il numero di byte letti è zero, significa che si è +arrivati alla fine del file (per i socket questo significa in genere che +l'altro capo è stato chiuso, e quindi non sarà più possibile leggere niente) e +pertanto si ritorna senza aver concluso la lettura di tutti i byte +richiesti. Entrambe le funzioni restituiscono 0 in caso di successo, ed un +valore negativo in caso di errore, \texttt{FullRead} restituisce il numero di +byte non letti in caso di end-of-file prematuro. + + +\subsection{Il client \textit{daytime}} +\label{sec:TCP_daytime_client} + +Il primo esempio di applicazione delle funzioni di base illustrate in +\secref{sec:TCP_functions} è relativo alla creazione di un client elementare +per il servizio \textit{daytime}, un servizio elementare, definito +nell'\href{http://www.ietf.org/rfc/rfc0867.txt}{RFC~867}, che restituisce +l'ora locale della macchina a cui si effettua la richiesta, e che è assegnato +alla porta 13. + +In \figref{fig:TCP_daytime_client_code} è riportata la sezione principale del +codice del nostro client. Il sorgente completo del programma +(\file{TCP\_daytime.c}, che comprende il trattamento delle opzioni ed una +funzione per stampare un messaggio di aiuto) è allegato alla guida nella +sezione dei codici sorgente e può essere compilato su una qualunque macchina +GNU/Linux. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \includecodesample{listati/TCP_daytime.c} + \end{minipage} + \normalsize + \caption{Esempio di codice di un client elementare per il servizio + \textit{daytime}.} + \label{fig:TCP_daytime_client_code} +\end{figure} + +Il programma anzitutto (\texttt{\small 1--5}) include gli header necessari; +dopo la dichiarazione delle variabili (\texttt{\small 9--12}) si è omessa +tutta la parte relativa al trattamento degli argomenti passati dalla linea di +comando (effettuata con le apposite funzioni illustrate in +\secref{sec:proc_opt_handling}). + +Il primo passo (\texttt{\small 14--18}) è creare un socket TCP (quindi di tipo +\const{SOCK\_STREAM} e di famiglia \const{AF\_INET}). La funzione +\func{socket} ritorna il descrittore che viene usato per identificare il +socket in tutte le chiamate successive. Nel caso la chiamata fallisca si +stampa un errore (\texttt{\small 16}) con la funzione \func{perror} e si esce +(\texttt{\small 17}) con un codice di errore. + +Il passo seguente (\texttt{\small 19--27}) è quello di costruire un'apposita +struttura \struct{sockaddr\_in} in cui sarà inserito l'indirizzo del server ed +il numero della porta del servizio. Il primo passo (\texttt{\small 20}) è +inizializzare tutto a zero, per poi inserire il tipo di indirizzo +(\texttt{\small 21}) e la porta (\texttt{\small 22}), usando per quest'ultima +la funzione \func{htons} per convertire il formato dell'intero usato dal +computer a quello usato nella rete, infine \texttt{\small 23--27} si può +utilizzare la funzione \func{inet\_pton} per convertire l'indirizzo numerico +passato dalla linea di comando. + +A questo punto (\texttt{\small 28--32}) usando la funzione \func{connect} sul +socket creato in precedenza (\texttt{\small 29}) si può stabilire la +connessione con il server. Per questo si deve utilizzare come secondo +argomento la struttura preparata in precedenza con il relativo indirizzo; si +noti come, esistendo diversi tipi di socket, si sia dovuto effettuare un cast. +Un valore di ritorno della funzione negativo implica il fallimento della +connessione, nel qual caso si stampa un errore (\texttt{\small 30}) e si +ritorna (\texttt{\small 31}). + +Completata con successo la connessione il passo successivo (\texttt{\small + 34--40}) è leggere la data dal socket; il protocollo prevede che il server +invii sempre una stringa alfanumerica, il formato della stringa non è +specificato dallo standard, per cui noi useremo il formato usato dalla +funzione \func{ctime}, seguito dai caratteri di terminazione \verb|\r\n|, cioè +qualcosa del tipo: +\begin{verbatim} +Wed Apr 4 00:53:00 2001\r\n +\end{verbatim} +questa viene letta dal socket (\texttt{\small 34}) con la funzione \func{read} +in un buffer temporaneo; la stringa poi deve essere terminata (\texttt{\small + 35}) con il solito carattere nullo per poter essere stampata (\texttt{\small + 36}) sullo standard output con l'uso di \func{fputs}. + +Come si è già spiegato in \secref{sec:sock_io_behav} la risposta dal socket +potrà arrivare in un unico pacchetto di 26 byte (come avverrà senz'altro nel +caso in questione) ma potrebbe anche arrivare in 26 pacchetti di un byte. Per +questo nel caso generale non si può mai assumere che tutti i dati arrivino con +una singola lettura, pertanto quest'ultima deve essere effettuata in un ciclo +in cui si continui a leggere fintanto che la funzione \func{read} non ritorni +uno zero (che significa che l'altro capo ha chiuso la connessione) o un numero +minore di zero (che significa un errore nella connessione). + +Si noti come in questo caso la fine dei dati sia specificata dal server che +chiude la connessione (anche questo è quanto richiesto dal protocollo); questa +è una delle tecniche possibili (è quella usata pure dal protocollo HTTP), ma +ce ne possono essere altre, ad esempio FTP marca la conclusione di un blocco +di dati con la sequenza ASCII \verb|\r\n| (carriage return e line feed), +mentre il DNS mette la lunghezza in testa ad ogni blocco che trasmette. Il +punto essenziale è che TCP non provvede nessuna indicazione che permetta di +marcare dei blocchi di dati, per cui se questo è necessario deve provvedere il +programma stesso. + +Se abilitiamo il servizio \textit{daytime}\footnote{in genere questo viene + fornito direttamente dal \textsl{superdemone} \texttt{inetd}, pertanto basta + assicurarsi che esso sia abilitato nel relativo file di configurazione.} +possiamo verificare il funzionamento del nostro client, avremo allora: +\begin{verbatim} +[piccardi@gont sources]$ ./daytime 127.0.0.1 +Mon Apr 21 20:46:11 2003 +\end{verbatim}%$ +e come si vede tutto funziona regolarmente. + + +\subsection{Un server \textit{daytime} iterativo} +\label{sec:TCP_daytime_iter_server} + +Dopo aver illustrato il client daremo anche un esempio di un server +elementare, che sia anche in grado di rispondere al precedente client. Come +primo esempio realizzeremo un server iterativo, in grado di fornire una sola +risposta alla volta. Il codice del programma è nuovamente mostrato in +\figref{fig:TCP_daytime_iter_server_code}, il sorgente completo +(\file{TCP\_iter\_daytimed.c}) è allegato insieme agli altri file degli esempi. + +\begin{figure}[!htbp] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \includecodesample{listati/TCP_iter_daytimed.c} + \end{minipage} + \normalsize + \caption{Esempio di codice di un semplice server per il servizio daytime.} + \label{fig:TCP_daytime_iter_server_code} +\end{figure} + +Come per il client si includono (\texttt{\small 1--9}) gli header necessari a +cui è aggiunto quello per trattare i tempi, e si definiscono (\texttt{\small + 14--18}) alcune costanti e le variabili necessarie in seguito. Come nel caso +precedente si sono omesse le parti relative al trattamento delle opzioni da +riga di comando. + +La creazione del socket (\texttt{\small 20--24}) è analoga al caso precedente, +come pure l'inizializzazione (\texttt{\small 25--29}) della struttura +\struct{sockaddr\_in}. Anche in questo caso (\texttt{\small 28}) si usa la +porta standard del servizio daytime, ma come indirizzo IP si usa +(\texttt{\small 27}) il valore predefinito \const{INET\_ANY}, che corrisponde +all'indirizzo generico. + +Si effettua poi (\texttt{\small 30--34}) la chiamata alla funzione \func{bind} +che permette di associare la precedente struttura al socket, in modo che +quest'ultimo possa essere usato per accettare connessioni su una qualunque +delle interfacce di rete locali. In caso di errore si stampa (\texttt{\small + 31}) un messaggio, e si termina (\texttt{\small 32}) immediatamente il +programma. + +Il passo successivo (\texttt{\small 35--39}) è quello di mettere ``in +ascolto'' il socket; questo viene fatto (\texttt{\small 36}) con la funzione +\func{listen} che dice al kernel di accettare connessioni per il socket che +abbiamo creato; la funzione indica inoltre, con il secondo parametro, il +numero massimo di connessioni che il kernel accetterà di mettere in coda per +il suddetto socket. Di nuovo in caso di errore si stampa (\texttt{\small 37}) +un messaggio, e si esce (\texttt{\small 38}) immediatamente. + +La chiamata a \func{listen} completa la preparazione del socket per l'ascolto +(che viene chiamato anche \textit{listening descriptor}) a questo punto si può +procedere con il ciclo principale (\texttt{\small 40--53}) che viene eseguito +indefinitamente. Il primo passo (\texttt{\small 42}) è porsi in attesa di +connessioni con la chiamata alla funzione \func{accept}, come in precedenza in +caso di errore si stampa (\texttt{\small 43}) un messaggio, e si esce +(\texttt{\small 44}). + +Il processo resterà in stato di \textit{sleep} fin quando non arriva e viene +accettata una connessione da un client; quando questo avviene \func{accept} +ritorna, restituendo un secondo descrittore, che viene chiamato +\textit{connected descriptor}, e che è quello che verrà usato dalla successiva +chiamata alla \func{write} per scrivere la risposta al client. + +Il ciclo quindi proseguirà determinando (\texttt{\small 46}) il tempo corrente +con una chiamata a \texttt{time}, con il quale si potrà opportunamente +costruire (\texttt{\small 47}) la stringa con la data da trasmettere +(\texttt{\small 48}) con la chiamata a \func{write}. Completata la +trasmissione il nuovo socket viene chiuso (\texttt{\small 52}). A questo +punto il ciclo si chiude ricominciando da capo in modo da poter ripetere +l'invio della data in risposta ad una successiva connessione. + +È importante notare che questo server è estremamente elementare, infatti, a +parte il fatto di poter essere usato solo con indirizzi IPv4, esso è in grado +di rispondere ad un solo un client alla volta: è cioè, come dicevamo, un +\textsl{server iterativo}. Inoltre è scritto per essere lanciato da linea di +comando, se lo si volesse utilizzare come demone occorrerebbero le opportune +modifiche\footnote{come una chiamata a \func{daemon} prima dell'inizio del + ciclo principale.} per tener conto di quanto illustrato in +\secref{sec:sess_daemon}. Si noti anche che non si è inserita nessuna forma di +gestione della terminazione del processo, dato che tutti i file descriptor +vengono chiusi automaticamente alla sua uscita, e che, non generando figli, +non è necessario preoccuparsi di gestire la loro terminazione. + + +\subsection{Un server \textit{daytime} concorrente} +\label{sec:TCP_daytime_cunc_server} + +Il server \texttt{daytime} dell'esempio in +\secref{sec:TCP_daytime_iter_server} è 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. + +Come accennato anche in \secref{sec:proc_gen} una delle modalità più comuni di +funzionamento da parte dei server è quella di usare la funzione \func{fork} +per creare, ad ogni richiesta da parte di un client, un processo figlio che si +incarichi della gestione della comunicazione. Si è allora riscritto il server +\textit{daytime} dell'esempio precedente in forma concorrente, inserendo anche +una opzione per la stampa degli indirizzi delle connessioni ricevute. + +In \figref{fig:TCP_daytime_cunc_server_code} è mostrato un estratto del +codice, in cui si sono tralasciati il trattamento delle opzioni e le parti +rimaste invariate rispetto al precedente esempio (cioè tutta la parte +riguardante l'apertura passiva del socket). Al solito il sorgente completo del +server, nel file \file{TCP\_cunc\_daytimed.c}, è allegato insieme ai sorgenti +degli altri esempi. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \includecodesample{listati/TCP_cunc_daytimed.c} + \end{minipage} + \normalsize + \caption{Esempio di codice di un server concorrente elementare per il + servizio daytime.} + \label{fig:TCP_daytime_cunc_server_code} +\end{figure} + +Stavolta (\texttt{\small 21--25}) la funzione \func{accept} è chiamata +fornendo una struttura di indirizzi in cui saranno ritornati l'indirizzo IP e +la porta da cui il client effettua la connessione, che in un secondo tempo, +(\texttt{\small 39--43}), se il logging è abilitato, stamperemo sullo standard +output. + +Quando \func{accept} ritorna il server chiama la funzione \func{fork} +(\texttt{\small 26--30}) per creare il processo figlio che effettuerà +(\texttt{\small 31--45}) tutte le operazioni relative a quella connessione, +mentre il padre proseguirà l'esecuzione del ciclo principale in attesa di +ulteriori connessioni. + +Si noti come il figlio operi solo sul socket connesso, chiudendo +immediatamente (\texttt{\small 32}) il socket \var{list\_fd}; mentre il padre +continua ad operare (\texttt{\small 47}) solo sul socket in ascolto chiudendo +\var{sock\_fd} al ritorno dalla \func{fork}. Per quanto abbiamo detto in +\secref{sec:TCP_func_close} nessuna delle due chiamate a \func{close} causa +l'innesco della sequenza di chiusura perché il numero di riferimenti al file +descriptor 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 +\func{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à (\texttt{\small 44}) la funzione \func{close}. + +In realtà per il figlio non sarebbe necessaria nessuna chiamata a +\func{close}, in quanto con la \func{exit} finale (\texttt{\small 45}) tutti i +file descriptor, quindi anche quelli associati ai socket, vengono +automaticamente chiusi. Tuttavia si è preferito effettuare esplicitamente le +chiusure per avere una maggiore chiarezza del codice, e per evitare eventuali +errori, prevenendo ad esempio un uso involontario del \textit{listening + descriptor}. + +Si noti invece come sia essenziale che il padre chiuda ogni volta il socket +connesso dopo la \func{fork}; se così non fosse nessuno di questi socket +sarebbe effettivamente chiuso dato che alla chiusura da parte del figlio +resterebbe ancora un riferimento nel padre. 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. + +Come per ogni server iterativo il lavoro di risposta viene eseguito +interamente dal processo figlio. Questo si incarica (\texttt{\small 33}) di +chiamare \func{time} per leggere il tempo corrente, e di stamparlo +(\texttt{\small 34}) sulla stringa contenuta in \var{buffer} con l'uso di +\func{snprintf} e \func{ctime}. Poi la stringa viene scritta (\texttt{\small + 35--38}) sul socket, controllando che non ci siano errori. Anche in questo +caso si è evitato il ricorso a \func{FullWrite} in quanto la stringa è +estremamente breve e verrà senz'altro scritta in un singolo segmento. + +Inoltre nel caso sia stato abilitato il \textit{logging} delle connessioni, si +provvede anche (\texttt{\small 39--42}) a stampare sullo standard output +l'indirizzo e la porta da cui il client ha effettuato la connessione, usando +i valori contenuti nelle strutture restituite da \func{accept}, eseguendo le +opportune conversioni con \func{inet\_ntop} e \func{atohs}. + +Ancora una volta l'esempio è estremamente semplificato, si noti come di nuovo +non si sia gestita né la terminazione del processo né il suo uso come demone, +che tra l'altro sarebbe stato incompatibile con l'uso della opzione di logging +che stampa gli indirizzi delle connessioni sullo standard output. Un altro +aspetto tralasciato è la gestione della terminazione dei processi figli, +torneremo su questo più avanti quando tratteremo alcuni esempi di server più +complessi. + + + +\section{Un esempio più completo: il servizio \textit{echo}} +\label{sec:TCP_echo_application} + +L'esempio precedente, basato sul servizio \textit{daytime}, è un esempio molto +elementare, in cui il flusso dei dati va solo nella direzione dal server al +client. In questa sezione esamineremo un esempio di applicazione client/server +un po' più complessa, che usi i socket TCP per una comunicazione in entrambe +le direzioni. + +Ci limiteremo a fornire una implementazione elementare, che usi solo le +funzioni di base viste finora, ma prenderemo in esame, oltre al comportamento +in condizioni normali, anche tutti i possibili scenari particolari (errori, +sconnessione della rete, crash del client o del server durante la connessione) +che possono avere luogo durante l'impiego di un'applicazione di rete, partendo +da una versione primitiva che dovrà essere rimaneggiata di volta in volta per +poter tenere conto di tutte le evenienze che si possono manifestare nella vita +reale di un'applicazione di rete, fino ad arrivare ad un'implementazione +completa. + + +\subsection{Il servizio \textit{echo}} +\label{sec:TCP_echo} + + +Nella ricerca di un servizio che potesse fare da esempio per una comunicazione +bidirezionale, si è deciso, seguendo la scelta di Stevens in \cite{UNP1}, di +usare il servizio \textit{echo}, che si limita a restituire in uscita quanto +immesso in ingresso. Infatti, nonostante la sua estrema semplicità, questo +servizio costituisce il prototipo ideale per una generica applicazione di rete +in cui un server risponde alle richieste di un client. Nel caso di una +applicazione più complessa quello che si potrà avere in più è una elaborazione +dell'input del client, che in molti casi viene interpretato come un comando, +da parte di un server che risponde fornendo altri dati in uscita. + +Il servizio \textit{echo} è uno dei servizi standard solitamente provvisti +direttamente dal superserver \cmd{inetd}, ed è definito +dall'\href{http://www.ietf.org/rfc/rfc0862.txt}{RFC~862}. Come dice il nome il +servizio deve riscrivere indietro sul socket i dati che gli vengono inviati in +ingresso. L'RFC descrive le specifiche del servizio sia per TCP che UDP, e per +il primo stabilisce che una volta stabilita la connessione ogni dato in +ingresso deve essere rimandato in uscita fintanto che il chiamante non ha +chiude la connessione. Al servizio è assegnata la porta riservata 7. + +Nel nostro caso l'esempio sarà costituito da un client che legge una linea di +caratteri dallo standard input e la scrive sul server. A sua volta il server +leggerà la linea dalla connessione e la riscriverà immutata all'indietro. Sarà +compito del client leggere la risposta del server e stamparla sullo standard +output. + + +\subsection{Il client: prima versione} +\label{sec:TCP_echo_client} + +Il codice della prima versione del client per il servizio \textit{echo}, +disponibile nel file \file{TCP\_echo\_first.c}, è riportato in +\figref{fig:TCP_echo_client_1}. Esso ricalca la struttura del precedente +client per il servizio \textit{daytime} (vedi +\secref{sec:TCP_daytime_client}), e la prima parte (\texttt{\small 10--27}) è +sostanzialmente identica, a parte l'uso di una porta diversa. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15.6 cm} + \includecodesample{listati/TCP_echo_first.c} + \end{minipage} + \normalsize + \caption{Codice della prima versione del client \textit{echo}.} + \label{fig:TCP_echo_client_1} +\end{figure} + +Al solito si è tralasciata la sezione relativa alla gestione delle opzioni a +riga di comando. Una volta dichiarate le variabili, si prosegue +(\texttt{\small 10--13}) con della creazione del socket con l'usuale controllo +degli errori, alla preparazione (\texttt{\small 14--17}) della struttura +dell'indirizzo, che stavolta usa la porta 7 riservata al servizio +\textit{echo}, infine si converte (\texttt{\small 18--22}) l'indirizzo +specificato a riga di comando. A questo punto (\texttt{\small 23--27}) si può +eseguire la connessione al server secondo la stessa modalità usata in +\secref{sec:TCP_daytime_client}. + +Completata la connessione, per gestire il funzionamento del protocollo si usa +la funzione \code{ClientEcho}, il cui codice si è riportato a parte in +\figref{fig:TCP_client_echo_sub}. Questa si preoccupa di gestire tutta la +comunicazione, leggendo una riga alla volta dallo standard input \file{stdin}, +scrivendola sul socket e ristampando su \file{stdout} quanto ricevuto in +risposta dal server. Al ritorno dalla funzione (\texttt{\small 30--31}) anche +il programma termina. + +La funzione \code{ClientEcho} utilizza due buffer (\texttt{\small 3}) per +gestire i dati inviati e letti sul socket. La comunicazione viene gestita +all'interno di un ciclo (\texttt{\small 5--10}), i dati da inviare sulla +connessione vengono presi dallo \file{stdin} usando la funzione \func{fgets}, +che legge una linea di testo (terminata da un \texttt{CR} e fino al massimo di +\const{MAXLINE} caratteri) e la salva sul buffer di invio. + +Si usa poi (\texttt{\small 6}) la funzione \func{FullWrite}, vista in +\secref{sec:sock_io_behav}, per scrivere i dati sul socket, gestendo +automaticamente l'invio multiplo qualora una singola \func{write} non sia +sufficiente. I dati vengono riletti indietro (\texttt{\small 7}) con una +\func{read}\footnote{si è fatta l'assunzione implicita che i dati siano + contenuti tutti in un solo segmento, così che la chiamata a \texttt{read} li + restituisca sempre tutti; avendo scelto una dimensione ridotta per il buffer + questo sarà sempre vero, vedremo più avanti come superare il problema di + rileggere indietro tutti e soli i dati disponibili, senza bloccarsi.} sul +buffer di ricezione e viene inserita (\texttt{\small 8}) la terminazione della +stringa e per poter usare (\texttt{\small 9}) la funzione \func{fputs} per +scriverli su \file{stdout}. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15.6cm} + \includecodesample{listati/ClientEcho.c} + \end{minipage} + \normalsize + \caption{Codice della prima versione della funzione \texttt{ClientEcho} per + la gestione del servizio \textit{echo}.} + \label{fig:TCP_client_echo_sub} +\end{figure} + +Quando si concluderà l'invio di dati mandando un end-of-file sullo standard +input si avrà il ritorno di \func{fgets} con un puntatore nullo (si riveda +quanto spiegato in \secref{sec:file_line_io}) e la conseguente uscita dal +ciclo; al che la subroutine ritorna ed il nostro programma client termina. + +Si può effettuare una verifica del funzionamento del client abilitando il +servizio \textit{echo} nella configurazione di \cmd{initd} sulla propria +macchina ed usandolo direttamente verso di esso in locale, vedremo in +dettaglio più avanti (in \secref{sec:TCP_echo_startup}) il funzionamento del +programma, usato però con la nostra versione del server \textit{echo}, che +illustriamo immediatamente. + + +\subsection{Il server: prima versione} +\label{sec:TCPsimp_server_main} + +La prima versione del server, contenuta nel file \file{TCP\_echod\_first.c}, è +riportata in \figref{fig:TCP_echo_server_first_code}. Come abbiamo fatto per +il client anche il server è stato diviso in un corpo principale, costituito +dalla funzione \code{main}, che è molto simile a quello visto nel precedente +esempio per il server del servizio \textit{daytime} di +\secref{sec:TCP_daytime_cunc_server}, e da una funzione ausiliaria +\code{ServEcho} che si cura della gestione del servizio. + +\begin{figure}[!htbp] + \footnotesize \centering + \begin{minipage}[c]{15.6cm} + \includecodesample{listati/TCP_echod_first.c} + \end{minipage} + \normalsize + \caption{Codice del corpo principale della prima versione del server + per il servizio \textit{echo}.} + \label{fig:TCP_echo_server_first_code} +\end{figure} + +In questo caso però, rispetto a quanto visto nell'esempio di +\figref{fig:TCP_daytime_cunc_server_code} si è preferito scrivere il server +curando maggiormente alcuni dettagli, per tenere conto anche di alcune +esigenze generali (che non riguardano direttamente la rete), come la +possibilità di lanciare il server anche in modalità interattiva e la cessione +dei privilegi di amministratore non appena questi non sono più necessari. + +La sezione iniziale del programma (\texttt{\small 8--21}) è la stessa del +server di \secref{sec:TCP_daytime_cunc_server}, ed ivi descritta in dettaglio: +crea il socket, inizializza l'indirizzo e esegue \func{bind}; dato che +quest'ultima funzione viene usata su una porta riservata, il server dovrà +essere eseguito da un processo con i privilegi di amministratore, pena il +fallimento della chiamata. + +Una volta eseguita la funzione \func{bind} però i privilegi di amministratore +non sono più necessari, per questo è sempre opportuno rilasciarli, in modo da +evitare problemi in caso di eventuali vulnerabilità del server. Per questo +prima (\texttt{\small 22--26}) si esegue \func{setgid} per assegnare il +processo ad un gruppo senza privilegi,\footnote{si è usato il valore 65534, + ovvero -1 per il formato \ctyp{short}, che di norma in tutte le + distribuzioni viene usato per identificare il gruppo \texttt{nogroup} e + l'utente \texttt{nobody}, usati appunto per eseguire programmi che non + richiedono nessun privilegio particolare.} e poi si ripete (\texttt{\small + 27--30}) l'operazione usando \func{setuid} per cambiare anche +l'utente.\footnote{si tenga presente che l'ordine in cui si eseguono queste + due operazioni è importante, infatti solo avendo i privilegi di + amministratore si può cambiare il gruppo di un processo ad un'altro di cui + non si fa parte, per cui chiamare prima \func{setuid} farebbe fallire una + successiva chiamata a \func{setgid}. Inoltre si ricordi (si riveda quanto + esposto in \secref{sec:proc_perms}) che usando queste due funzioni il + rilascio dei privilegi è irreversibile.} Infine (\texttt{\small 30--36}), +qualora sia impostata la variabile \var{demonize}, prima (\texttt{\small 31}) +si apre il sistema di logging per la stampa degli errori, e poi +(\texttt{\small 32--35}) si invoca \func{daemon} per eseguire in background il +processo come demone. + +A questo punto il programma riprende di nuovo lo schema già visto usato dal +server per il servizio \textit{daytime}, con l'unica differenza della chiamata +alla funzione \code{PrintErr}, riportata in \figref{fig:TCP_PrintErr}, al +posto di \func{perror} per la stampa degli errori. + +Si inizia con il porre (\texttt{\small 37--41}) in ascolto il socket, e poi si +esegue indefinitamente il ciclo principale (\texttt{\small 42--58}). +All'interno di questo si ricevono (\texttt{\small 43--46}) le connessioni, +creando (\texttt{\small 47--50}) un processo figlio per ciascuna di esse. +Quest'ultimo (\texttt{\small 51--55}), chiuso (\texttt{\small 52}) il +\textit{listening socket}, esegue (\texttt{\small 53}) la funzione di gestione +del servizio \code{ServEcho}, ed al ritorno di questa (\texttt{\small 54}) +esce. + +Il padre invece si limita (\texttt{\small 56}) a chiudere il \textit{connected + socket} per ricominciare da capo il ciclo in attesa di nuove connessioni. In +questo modo si ha un server concorrente. La terminazione del padre non è +gestita esplicitamente, e deve essere effettuata inviando un segnale al +processo. + +Avendo trattato direttamente la gestione del programma come demone, si è +dovuto anche provvedere alla necessità di poter stampare eventuali messaggi di +errore attraverso il sistema del \textit{syslog} trattato in +\secref{sec:sess_daemon}. Come accennato questo è stato fatto utilizzando come +\textit{wrapper} la funzione \code{PrintErr}, il cui codice è riportato in +\figref{fig:TCP_PrintErr}. + +In essa ci si limita a controllare (\texttt{\small 2}) se è stato impostato +(valore attivo per default) l'uso come demone, nel qual caso (\texttt{\small + 3}) si usa \func{syslog} (vedi \secref{sec:sess_daemon}) per stampare il +messaggio di errore fornito come argomento sui log di sistema. Se invece si è +in modalità interattiva (attivabile con l'opzione \texttt{-i}) si usa +(\texttt{\small 5}) semplicemente la funzione \func{perror} per stampare sullo +standard error. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15.6cm} + \includecodesample{listati/PrintErr.c} + \end{minipage} + \normalsize + \caption{Codice della funzione \code{PrintErr} per la + generalizzazione della stampa degli errori sullo standard input o + attraverso il \texttt{syslog}.} + \label{fig:TCP_PrintErr} +\end{figure} + +La gestione del servizio \textit{echo} viene effettuata interamente nella +funzione \code{ServEcho}, il cui codice è mostrato in +\figref{fig:TCP_ServEcho_first}, e la comunicazione viene gestita all'interno +di un ciclo (\texttt{\small 6--13}). I dati inviati dal client vengono letti +(\texttt{\small 6}) dal socket con una semplice \func{read}, di cui non si +controlla lo stato di uscita, assumendo che ritorni solo in presenza di dati +in arrivo. La riscrittura (\texttt{\small 7}) viene invece gestita dalla +funzione \func{FullWrite} (descritta in \figref{fig:sock_FullWrite_code}) che +si incarica di tenere conto automaticamente della possibilità che non tutti i +dati di cui è richiesta la scrittura vengano trasmessi con una singola +\func{write}. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15.6cm} + \includecodesample{listati/ServEcho_first.c} + \end{minipage} + \normalsize + \caption{Codice della prima versione della funzione \code{ServEcho} per la + gestione del servizio \textit{echo}.} + \label{fig:TCP_ServEcho_first} +\end{figure} + +In caso di errore di scrittura (si ricordi che \func{FullWrite} restituisce un +valore nullo in caso di successo) si provvede (\texttt{\small 8--10}) a +stampare il relativo messaggio con \func{PrintErr}. Quando il client chiude +la connessione il ricevimento del FIN fa ritornare la \func{read} con un +numero di byte letti pari a zero, il che causa l'uscita dal ciclo e il ritorno +(\texttt{\small 12}) della funzione, che a sua volta causa la terminazione del +processo figlio. + + +\subsection{L'avvio e il funzionamento normale} +\label{sec:TCP_echo_startup} + +Benché il codice dell'esempio precedente sia molto ridotto, esso ci permetterà +di considerare in dettaglio le varie problematiche che si possono incontrare +nello scrivere un'applicazione di rete. Infatti attraverso l'esame delle sue +modalità di funzionamento normali, all'avvio e alla terminazione, e di quello +che avviene nelle varie situazioni limite, da una parte potremo approfondire +la comprensione del protocollo TCP/IP e dall'altra ricavare le indicazioni +necessarie per essere in grado di scrivere applicazioni robuste, in grado di +gestire anche i casi limite. + +Il primo passo è compilare e lanciare il server (da root, per poter usare la +porta 7 che è riservata), alla partenza esso eseguirà l'apertura passiva con +la sequenza delle chiamate a \func{socket}, \func{bind}, \func{listen} e poi +si bloccherà nella \func{accept}. A questo punto si potrà controllarne lo +stato con \cmd{netstat}: +\begin{verbatim} +[piccardi@roke piccardi]$ netstat -at +Active Internet connections (servers and established) +Proto Recv-Q Send-Q Local Address Foreign Address State +... +tcp 0 0 *:echo *:* LISTEN +... +\end{verbatim} %$ +che ci mostra come il socket sia in ascolto sulla porta richiesta, accettando +connessioni da qualunque indirizzo e da qualunque porta e su qualunque +interfaccia locale. + +A questo punto si può lanciare il client, esso chiamerà \func{socket} e +\func{connect}; una volta completato il three way handshake la connessione è +stabilita; la \func{connect} ritornerà nel client\footnote{si noti che è + sempre la \func{connect} del client a ritornare per prima, in quanto + questo avviene alla ricezione del secondo segmento (l'ACK del server) del + three way handshake, la \func{accept} del server ritorna solo dopo + un altro mezzo RTT quando il terzo segmento (l'ACK del client) viene + ricevuto.} e la \func{accept} nel server, ed usando di nuovo +\cmd{netstat} otterremmo che: +\begin{verbatim} +Active Internet connections (servers and established) +Proto Recv-Q Send-Q Local Address Foreign Address State +tcp 0 0 *:echo *:* LISTEN +tcp 0 0 roke:echo gont:32981 ESTABLISHED +\end{verbatim} +mentre per quanto riguarda l'esecuzione dei programmi avremo che: +\begin{itemize} +\item il client chiama la funzione \code{ClientEcho} che si blocca sulla + \func{fgets} dato che non si è ancora scritto nulla sul terminale. +\item il server eseguirà una \func{fork} facendo chiamare al processo figlio + la funzione \code{ServEcho}, quest'ultima si bloccherà sulla \func{read} + dal socket sul quale ancora non sono presenti dati. +\item il processo padre del server chiamerà di nuovo \func{accept} + bloccandosi fino all'arrivo di un'altra connessione. +\end{itemize} +e se usiamo il comando \cmd{ps} per esaminare lo stato dei processi otterremo +un risultato del tipo: +\begin{verbatim} +[piccardi@roke piccardi]$ ps ax + PID TTY STAT TIME COMMAND + ... ... ... ... ... + 2356 pts/0 S 0:00 ./echod + 2358 pts/1 S 0:00 ./echo 127.0.0.1 + 2359 pts/0 S 0:00 ./echod +\end{verbatim} %$ +(dove si sono cancellate le righe inutili) da cui si evidenzia la presenza di +tre processi, tutti in stato di \textit{sleep} (vedi +\tabref{tab:proc_proc_states}). + +Se a questo punto si inizia a scrivere qualcosa sul client non sarà trasmesso +niente fin tanto che non si prema il tasto di a capo (si ricordi quanto detto +in \secref{sec:file_line_io} a proposito dell'I/O su terminale), solo allora +\func{fgets} ritornerà ed il client scriverà quanto immesso sul socket, per +poi passare a rileggere quanto gli viene inviato all'indietro dal server, che +a sua volta sarà inviato sullo standard output, che nel caso ne provoca +l'immediatamente stampa a video. + + +\subsection{La conclusione normale} +\label{sec:TCP_echo_conclusion} + +Tutto quello che scriveremo sul client sarà rimandato indietro dal server e +ristampato a video fintanto che non concluderemo l'immissione dei dati; una +sessione tipica sarà allora del tipo: +\begin{verbatim} +[piccardi@roke sources]$ ./echo 127.0.0.1 +Questa e` una prova +Questa e` una prova +Ho finito +Ho finito +\end{verbatim} %$ +che termineremo inviando un EOF dal terminale (usando la combinazione di tasti +ctrl-D, che non compare a schermo); se eseguiamo un \cmd{netstat} a questo +punto avremo: +\begin{verbatim} +[piccardi@roke piccardi]$ netstat -at +tcp 0 0 *:echo *:* LISTEN +tcp 0 0 localhost:33032 localhost:echo TIME_WAIT +\end{verbatim} %$ +con il client che entra in \texttt{TIME\_WAIT}. + +Esaminiamo allora in dettaglio la sequenza di eventi che porta alla +terminazione normale della connessione, che ci servirà poi da riferimento +quando affronteremo il comportamento in caso di conclusioni anomale: + +\begin{enumerate} +\item inviando un carattere di EOF da terminale la \func{fgets} ritorna + restituendo un puntatore nullo che causa l'uscita dal ciclo di \code{while}, + così la funzione \code{ClientEcho} ritorna. +\item al ritorno di \code{ClientEcho} ritorna anche la funzione \code{main}, e + come parte del processo terminazione tutti i file descriptor vengono chiusi + (si ricordi quanto detto in \secref{sec:proc_term_conclusion}); questo causa + la chiusura del socket di comunicazione; il client allora invierà un FIN al + server a cui questo risponderà con un ACK. A questo punto il client verrà a + trovarsi nello stato \texttt{FIN\_WAIT\_2} ed il server nello stato + \texttt{CLOSE\_WAIT} (si riveda quanto spiegato in + \secref{sec:TCP_conn_term}). +\item quando il server riceve il FIN la \func{read} del processo figlio che + gestisce la connessione ritorna restituendo 0 causando così l'uscita dal + ciclo e il ritorno di \code{ServEcho}, a questo punto il processo figlio + termina chiamando \func{exit}. +\item all'uscita del figlio tutti i file descriptor vengono chiusi, la + chiusura del socket connesso fa sì che venga effettuata la sequenza finale + di chiusura della connessione, viene emesso un FIN dal server che riceverà + un ACK dal client, a questo punto la connessione è conclusa e il client + resta nello stato \texttt{TIME\_WAIT}. +\end{enumerate} + + +\subsection{La gestione dei processi figli} +\label{sec:TCP_child_hand} + +Tutto questo riguarda la connessione, c'è però da tenere conto dell'effetto +del procedimento di chiusura del processo figlio nel server (si veda quanto +esaminato in \secref{sec:proc_termination}). In questo caso avremo l'invio del +segnale \const{SIGCHLD} al padre, ma dato che non si è installato un +gestore e che l'azione predefinita per questo segnale è quella di essere +ignorato, non avendo predisposto la ricezione dello stato di terminazione, +otterremo che il processo figlio entrerà nello stato di zombie\index{zombie} +(si riveda quanto illustrato in \secref{sec:sig_sigchld}), come risulterà +ripetendo il comando \cmd{ps}: +\begin{verbatim} + 2356 pts/0 S 0:00 ./echod + 2359 pts/0 Z 0:00 [echod ] +\end{verbatim} + +Dato che non è il caso di lasciare processi zombie\index{zombie}, occorrerà +ricevere opportunamente lo stato di terminazione del processo (si veda +\secref{sec:proc_wait}), cosa che faremo utilizzando \const{SIGCHLD} secondo +quanto illustrato in \secref{sec:sig_sigchld}. Una prima modifica al nostro +server è pertanto quella di inserire la gestione della terminazione dei +processi figli attraverso l'uso di un gestore. Per questo useremo la funzione +\code{Signal} (che abbiamo illustrato in \figref{fig:sig_Signal_code}), per +installare il gestore che riceve i segnali dei processi figli terminati già +visto in \figref{fig:sig_sigchld_handl}. Basterà allora aggiungere il +seguente codice: \includecodesnip{listati/sigchildhand.c} +\noindent +all'esempio illustrato in \figref{fig:TCP_echo_server_first_code}. + +In questo modo però si introduce un altro problema. Si ricordi infatti che, +come spiegato in \secref{sec:sig_gen_beha}, quando un programma si trova in +stato di \texttt{sleep} durante l'esecuzione di una system call, questa viene +interrotta alla ricezione di un segnale. Per questo motivo, alla fine +dell'esecuzione del gestore del segnale, se questo ritorna, il programma +riprenderà l'esecuzione ritornando dalla system call interrotta con un errore +di \errcode{EINTR}. + +Vediamo allora cosa comporta tutto questo nel nostro caso: quando si chiude il +client, il processo figlio che gestisce la connessione terminerà, ed il padre, +per evitare la creazione di zombie, riceverà il segnale \const{SIGCHLD} +eseguendo il relativo gestore. Al ritorno del gestore però l'esecuzione nel +padre ripartirà subito con il ritorno della funzione \func{accept} (a meno di +un caso fortuito in cui il segnale arriva durante l'esecuzione del programma +in risposta ad una connessione) con un errore di \errcode{EINTR}. Non avendo +previsto questa eventualità il programma considera questo un errore fatale +terminando a sua volta con un messaggio del tipo: +\begin{verbatim} +[root@gont sources]# ./echod -i +accept error: Interrupted system call +\end{verbatim}%# + +Come accennato in \secref{sec:sig_gen_beha} le conseguenze di questo +comportamento delle system call possono essere superate in due modi diversi, +il più semplice è quello di modificare il codice di \func{Signal} per +richiedere il riavvio automatico delle system call interrotte secondo la +semantica di BSD, usando l'opzione \const{SA\_RESTART} di \func{sigaction}; +rispetto a quanto visto in \figref{fig:sig_Signal_code}. Definiremo allora la +nuova funzione \func{SignalRestart}\footnote{anche questa è definita, insieme + alle altre funzioni riguardanti la gestione dei segnali, nel file + \file{SigHand.c}, il cui contento completo può essere trovato negli esempi + allegati.} come mostrato in \figref{fig:sig_SignalRestart_code}, ed +installeremo il gestore usando quest'ultima. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15.6cm} + \includecodesample{listati/SignalRestart.c} + \end{minipage} + \normalsize + \caption{La funzione \funcd{SignalRestart}, che installa un gestore di + segnali in semantica BSD per il riavvio automatico delle system call + interrotte.} + \label{fig:sig_SignalRestart_code} +\end{figure} + +Come si può notare questa funzione è identica alla precedente \func{Signal}, +illustrata in \figref{fig:sig_Signal_code}, solo che in questo caso invece di +inizializzare a zero il campo \var{sa\_flags} di \struct{sigaction}, lo si +inizializza (\texttt{\small 5}) al valore \const{SA\_RESTART}. Usando questa +funzione al posto di \func{Signal} nel server non è necessaria nessuna altra +modifica: le system call interrotte saranno automaticamente riavviate, e +l'errore \errcode{EINTR} non si manifesterà più. + +La seconda soluzione è più invasiva e richiede di controllare tutte le volte +l'errore restituito dalle varie system call, ripetendo la chiamata qualora +questo corrisponda ad \errcode{EINTR}. Questa soluzione ha però il pregio +della portabilità, infatti lo standard POSIX dice che la funzionalità di +riavvio automatico delle system call, fornita da \const{SA\_RESTART}, è +opzionale, per cui non è detto che essa sia disponibile su qualunque sistema. +Inoltre in certi casi,\footnote{Stevens in \cite{UNP1} accenna che la maggior + parte degli Unix derivati da BSD non fanno ripartire \func{select}; altri + non riavviano neanche \func{accept} e \func{recvfrom}, cosa che invece nel + caso di Linux viene sempre fatta.} anche quando questa è presente, non è +detto possa essere usata con \func{accept}. + + +La portabilità nella gestione dei segnali però viene al costo di una +riscrittura parziale del server, la nuova versione di questo, in cui si sono +introdotte una serie di nuove opzioni che ci saranno utili per il debug, è +mostrata in \figref{fig:TCP_echo_server_code_second}, dove si sono riportate +la sezioni di codice modificate nella seconda versione del programma, il +sorgente completo di quest'ultimo si trova nel file +\file{TCP\_echod\_second.c} dei sorgenti allegati alla guida. + +La prima modifica effettuata è stata quella di introdurre una nuova opzione a +riga di comando, \texttt{-c}, che permette di richiedere il comportamento +compatibile nella gestione di \const{SIGCHLD} al posto della semantica BSD +impostando la variabile \var{compat} ad un valore non nullo. Questa è +preimpostata al valore nullo, cosicché se non si usa questa opzione il +comportamento di default del server è di usare la semantica BSD. + +Una seconda opzione aggiunta è quella di inserire un tempo di attesa fisso +specificato in secondi fra il ritorno della funzione \func{listen} e la +chiamata di \func{accept}, specificabile con l'opzione \texttt{-w}, che +permette di impostare la variabile \var{waiting}. Infine si è introdotta una +opzione \texttt{-d} per abilitare il debugging che imposta ad un valore non +nullo la variabile \var{debugging}. Al solito si è omessa da +\figref{fig:TCP_echo_server_code_second} la sezione di codice relativa alla +gestione di tutte queste opzioni, che può essere trovata nel sorgente del +programma. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15.6cm} + \includecodesample{listati/TCP_echod_second.c} + \end{minipage} + \normalsize + \caption{La sezione nel codice della seconda versione del server + per il servizio \textit{echo} modificata per tener conto dell'interruzione + delle system call.} + \label{fig:TCP_echo_server_code_second} +\end{figure} + +Vediamo allora come è cambiato il nostro server; una volta definite le +variabili e trattate le opzioni il primo passo (\texttt{\small 9--13}) è +verificare la semantica scelta per la gestione di \const{SIGCHLD}, a seconda +del valore di \var{compat} (\texttt{\small 9}) si installa il gestore con la +funzione \func{Signal} (\texttt{\small 10}) o con \texttt{SignalRestart} +(\texttt{\small 12}), essendo quest'ultimo il valore di default. + +Tutta la sezione seguente, che crea il socket, cede i privilegi di +amministratore ed eventualmente lancia il programma come demone, è rimasta +invariata e pertanto è stata omessa in +\figref{fig:TCP_echo_server_code_second}; l'unica modifica effettuata prima +dell'entrata nel ciclo principale è stata quella di aver introdotto, subito +dopo la chiamata (\texttt{\small 17--20}) alla funzione \func{listen}, una +eventuale pausa con una condizione (\texttt{\small 21}) sulla variabile +\var{waiting}, che viene inizializzata, con l'opzione \code{-w Nsec}, al +numero di secondi da aspettare (il valore preimpostato è nullo). + +Si è potuto lasciare inalterata tutta la sezione di creazione del socket +perché nel server l'unica chiamata ad una system call critica, che può essere +interrotta dall'arrivo di \const{SIGCHLD}, è quella ad \func{accept}, che è +l'unica funzione che può mettere il processo padre in stato di sleep nel +periodo in cui un figlio può terminare; si noti infatti come le altre +\textit{slow system call}\footnote{si ricordi la distinzione fatta in + \secref{sec:sig_gen_beha}.} o sono chiamate prima di entrare nel ciclo +principale, quando ancora non esistono processi figli, o sono chiamate dai +figli stessi e non risentono di \const{SIGCHLD}. + +Per questo l'unica modifica sostanziale nel ciclo principale (\texttt{\small + 23--42}), rispetto precedente versione di \figref{fig:TCP_ServEcho_first}, è +nella sezione (\texttt{\small 26--30}) in cui si effettua la chiamata di +\func{accept}. Quest'ultima viene effettuata (\texttt{\small 26--27}) +all'interno di un ciclo di \code{while}\footnote{la sintassi del C relativa a + questo ciclo può non essere del tutto chiara. In questo caso infatti si è + usato un ciclo vuoto che non esegue nessuna istruzione, in questo modo + quello che viene ripetuto con il ciclo è soltanto il codice che esprime la + condizione all'interno del \code{while}.} che la ripete indefinitamente +qualora in caso di errore il valore di \var{errno} sia \errcode{EINTR}. Negli +altri casi si esce in caso di errore effettivo (\texttt{\small 27--29}), +altrimenti il programma prosegue. + +Si noti che in questa nuova versione si è aggiunta una ulteriore sezione +(\texttt{\small 31--39}) di aiuto per il debug del programma, che eseguita con +un controllo (\texttt{\small 31}) sul valore della variabile \var{debugging} +impostato dall'opzione \texttt{-d}. Qualora questo sia nullo, come +preimpostato, non accade nulla. altrimenti (\texttt{\small 32}) l'indirizzo +ricevuto da \var{accept} viene convertito in una stringa che poi +(\texttt{\small 33--38}) viene opportunamente stampata o sullo schermo o nei +log. + + + + +\section{I vari scenari critici} +\label{sec:TCP_echo_critical} + +Con le modifiche viste in \secref{sec:TCP_child_hand} il nostro esempio +diventa in grado di affrontare la gestione ordinaria delle connessioni, ma un +server di rete deve tenere conto che, al contrario di quanto avviene per i +server che operano nei confronti di processi presenti sulla stessa macchina, +la rete è di sua natura inaffidabile, per cui è necessario essere in grado di +gestire tutta una serie di situazioni critiche che non esistono per i processi +locali. + + +\subsection{La terminazione precoce della connessione} +\label{sec:TCP_conn_early_abort} + +La prima situazione critica è quella della terminazione precoce, causata da un +qualche errore sulla rete, della connessione effettuata da un client. Come +accennato in \secref{sec:TCP_func_accept} la funzione \func{accept} riporta +tutti gli eventuali errori di rete pendenti su una connessione sul +\textit{connected socket}. Di norma questo non è un problema, in quanto non +appena completata la connessione, \func{accept} ritorna e l'errore sarà +rilevato in seguito, dal processo che gestisce la connessione, alla prima +chiamata di una funzione che opera sul socket. + +È però possibile, dal punto di vista teorico, incorrere anche in uno scenario +del tipo di quello mostrato in \figref{fig:TCP_early_abort}, in cui la +connessione viene abortita sul lato client per un qualche errore di rete con +l'invio di un segmento RST, prima che nel server sia stata chiamata la +funzione \func{accept}. + +\begin{figure}[htb] + \centering + \includegraphics[width=10cm]{img/tcp_client_early_abort} + \caption{Un possibile caso di terminazione precoce della connessione.} + \label{fig:TCP_early_abort} +\end{figure} + +Benché questo non sia un fatto comune, un evento simile può essere osservato +con dei server molto occupati. In tal caso, con una struttura del server +simile a quella del nostro esempio, in cui la gestione delle singole +connessioni è demandata a processi figli, può accadere che il three way +handshake venga completato e la relativa connessione abortita subito dopo, +prima che il padre, per via del carico della macchina, abbia fatto in tempo ad +eseguire la chiamata ad \func{accept}. Di nuovo si ha una situazione analoga +a quella illustrata in \figref{fig:TCP_early_abort}, in cui la connessione +viene stabilita, ma subito dopo si ha una condizione di errore che la chiude +prima che essa sia stata accettata dal programma. + +Questo significa che, oltre alla interruzione da parte di un segnale, che +abbiamo trattato in \secref{sec:TCP_child_hand} nel caso particolare di +\const{SIGCHLD}, si possono ricevere altri errori non fatali all'uscita di +\func{accept}, che come nel caso precedente, necessitano semplicemente la +ripetizione della chiamata senza che si debba uscire dal programma. In questo +caso anche la versione modificata del nostro server non sarebbe adatta, in +quanto uno di questi errori causerebbe la terminazione dello stesso. In Linux +i possibili errori di rete non fatali, riportati sul socket connesso al +ritorno di \func{accept}, sono \errcode{ENETDOWN}, \errcode{EPROTO}, +\errcode{ENOPROTOOPT}, \errcode{EHOSTDOWN}, \errcode{ENONET}, +\errcode{EHOSTUNREACH}, \errcode{EOPNOTSUPP} e \errcode{ENETUNREACH}. + +Si tenga presente che questo tipo di terminazione non è riproducibile +terminando il client prima della chiamata ad \func{accept}, come si potrebbe +fare usando l'opzione \texttt{-w} per introdurre una pausa dopo il lancio del +demone, in modo da poter avere il tempo per lanciare e terminare una +connessione usando il programma client. In tal caso infatti, alla terminazione +del client, il socket associato alla connessione viene semplicemente chiuso, +attraverso la sequenza vista in \secref{sec:TCP_conn_term}, per cui la +\func{accept} ritornerà senza errori, e si avrà semplicemente un end-of-file +al primo accesso al socket. Nel caso di Linux inoltre, anche qualora si +modifichi il client per fargli gestire l'invio di un segmento di RST alla +chiusura dal socket (come suggerito da Stevens in \cite{UNP1}), non si ha +nessun errore al ritorno di \funcd{accept} quanto un errore di +\errcode{ECONNRESET} al primo tentativo di accesso al socket. + + + +\subsection{La terminazione precoce del server} +\label{sec:TCP_server_crash} + +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. + + + + + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "gapil" +%%% End: