Didattica

Didattica di Tutanum

Reti di calcolatori

Il trasporto

Nella pagina dedicata alla rete internet abbiamo visto i pacchetti trovare la strada da una macchina a un'altra attraverso decine di router, guidati da indirizzi gerarchici e da tabelle compilate con pazienza, e abbiamo anche preso atto del carattere di quel servizio, che gli autori chiamano del miglior sforzo. Un pacchetto affidato al protocollo IP può arrivare, può perdersi in una coda traboccata, può giungere due volte oppure dopo un compagno partito più tardi, e la rete, che tiene i propri router volutamente semplici, lascia agli estremi il compito di accorgersene e di rimediare.

Resta inoltre scoperto un tratto brevissimo e decisivo, perché l'indirizzo IP conduce il pacchetto fino alla macchina, mentre su quella macchina girano contemporaneamente il browser, il programma di posta, un aggiornamento in corso e magari una telefonata, e qualcuno deve stabilire a quale di questi programmi appartenga ogni pacchetto in arrivo. Il livello di trasporto, a cui è dedicata questa pagina, si occupa di entrambe le cose, cioè consegna i dati al programma giusto e, quando serve, trasforma il servizio incerto della rete in una conversazione completa e ordinata.

La nostra guida, il libro Reti di calcolatori di Andrew S. Tanenbaum e David Wetherall, dedica a questo piano il sesto capitolo e ne sottolinea subito una particolarità. Il codice del livello di trasporto gira soltanto sulle macchine agli estremi della conversazione, dentro il loro sistema operativo, mentre i router lungo la strada lo ignorano del tutto e trattano il suo contenuto come un carico qualunque; è il primo livello che appartiene per intero agli utenti della rete anziché ai suoi gestori, ed è per questo che ha potuto evolversi nel tempo senza chiedere il permesso a nessun operatore.

Dietro questa collocazione sta un principio di progetto che ha plasmato l'intera Internet e che gli ingegneri chiamano argomento da estremo a estremo. Esso osserva che certe garanzie, come la certezza che un archivio sia arrivato intero, possono essere verificate fino in fondo soltanto da chi sta ai due capi, dato che un guasto può sempre nascondersi in un punto che i controlli intermedi non coprono, per esempio nella memoria di un router fra una linea e l'altra; tanto vale allora tenere semplice e veloce la rete di mezzo e affidare l'intelligenza alle macchine degli utenti, scelta opposta a quella della vecchia rete telefonica, che aveva centrali sapienti e apparecchi sciocchi, e che spiega perché su Internet sia stato possibile inventare il web, la telefonia e lo scambio di archivi senza toccare un solo router.

Cominciamo dal problema della consegna al programma giusto, che si risolve con un numero di sedici bit chiamato porta. Se l'indirizzo IP è l'indirizzo di un condominio, la porta è il numero dell'interno, e una conversazione è individuata senza ambiguità dalla combinazione di cinque elementi, cioè il protocollo di trasporto, l'indirizzo e la porta di un estremo, l'indirizzo e la porta dell'altro. Con sedici bit si contano sessantacinquemilacinquecentotrentasei porte, numerate da zero in su, e il sistema operativo tiene una tabella che associa ogni porta in uso al programma che l'ha chiesta.

Le porte al di sotto di milleventiquattro sono dette ben note e sono riservate per consuetudine ai servizi più diffusi, cosicché chi vuole raggiungere un servizio sa in anticipo a quale interno bussare. La porta ottanta appartiene al web, la quattrocentoquarantatré al web cifrato, la ventidue all'accesso remoto sicuro, la venticinque alla posta in partenza e la cinquantatré al servizio dei nomi, e su molti sistemi soltanto l'amministratore può mettere un programma in ascolto su queste porte. Il programma che apre la conversazione, detto cliente, riceve invece dal sistema una porta scelta a caso fra quelle alte, chiamata effimera perché viene restituita appena la conversazione termina. Su Linux e sui sistemi affini l'elenco delle corrispondenze fra nomi dei servizi e numeri di porta è conservato in un semplice archivio di testo, /etc/services, che vale la pena sfogliare una volta per farsi un'idea di quanti mestieri diversi la rete abbia accumulato in mezzo secolo.

L'operazione di raccogliere i dati di molti programmi e di spedirli attraverso un unico indirizzo prende il nome di multiplazione, e quella inversa, che in arrivo smista ogni segmento verso il programma destinatario leggendo le porte, si chiama demultiplazione. Per i programmatori tutto questo si presenta sotto forma di un oggetto chiamato socket, parola inglese che indica la presa in cui si infila una spina, introdotto dal sistema Unix di Berkeley nei primi anni ottanta e adottato poi da tutti i sistemi operativi con pochissime variazioni.

L'uso delle prese segue un copione che vale la pena conoscere, perché ogni programma di rete, dal più piccolo al più grande, lo recita allo stesso modo. Il servitore crea una presa, la lega a una porta con l'operazione bind, dichiara con listen di essere pronto a ricevere chiamate e si blocca su accept in attesa del primo cliente; il cliente crea a sua volta una presa e chiama connect indicando l'indirizzo e la porta del servitore; stabilito il contatto, entrambi usano send e recv per scambiare dati, esattamente come scriverebbero e leggerebbero un file, e alla fine chiudono con close. Un particolare elegante sta nel fatto che accept restituisce una presa nuova, dedicata a quel cliente, lasciando libera quella originale per accogliere il successivo.

Un servitore vero deve badare a molti clienti insieme, e i modi per riuscirci riprendono ciò che abbiamo imparato sui processi. Nel modo più antico il servitore, appena accettato un cliente, genera con fork un processo figlio che se ne occupa e torna subito ad attendere il successivo; in un modo più leggero affida ogni cliente a un thread; nel modo oggi preferito dai servitori più carichi un unico processo sorveglia migliaia di prese con una chiamata di sistema che lo avverte di quali abbiano dati pronti, e le serve a turno senza mai bloccarsi su una sola. I programmi che restano in esecuzione sullo sfondo in attesa di chiamate, privi di terminale e avviati insieme al sistema, si chiamano demoni, dal nome dei geni benevoli della mitologia greca, e per tradizione portano una lettera d a fine nome, come sshd e httpd.

Internet offre due protocolli di trasporto dal carattere opposto, e il più semplice si chiama UDP. Esso aggiunge ai dati un'intestazione di soli otto byte, che contiene la porta di partenza, quella di arrivo, la lunghezza e una somma di controllo, e li affida alla rete senza alcuna cerimonia, senza numerarli e senza attendere conferme, cosicché ogni messaggio, chiamato datagramma, parte e arriva per conto proprio, oppure si perde nel silenzio. Il servizio è quello di una cartolina imbucata, rapida da scrivere e priva di ricevuta di ritorno. La somma di controllo copre, oltre ai dati, anche gli indirizzi IP dei due estremi, presi in prestito dal piano inferiore, in modo da smascherare un datagramma consegnato per errore alla macchina sbagliata, e la dimensione dei messaggi conviene che resti al di sotto del carico di una trama, perché un datagramma più grande verrebbe frammentato e la perdita di un solo frammento lo renderebbe inutilizzabile.

Questa essenzialità è una virtù per molte applicazioni, a cominciare dal servizio dei nomi, che rivolge una domanda brevissima e riceve una risposta altrettanto breve, e aprire una connessione costerebbe più dello scambio stesso; una telefonata o una partita in rete preferiscono perdere ogni tanto un frammento anziché fermarsi ad aspettarne la ritrasmissione, dato che un suono arrivato in ritardo di un secondo ha perso ogni utilità; i protocolli più recenti, infine, usano UDP come fondamento su cui costruire da sé, dentro l'applicazione, i meccanismi che desiderano. Anche le chiamate di procedura remota, con cui un programma invoca una funzione che gira su un'altra macchina come se fosse locale, e i protocolli che trasportano audio e video in tempo reale marcando ogni pezzo con l'istante di riproduzione, nascono sopra questo protocollo spoglio.

L'altro protocollo, TCP, è il cavallo da tiro di Internet, e il suo compito consiste nell'offrire ai programmi un flusso di byte affidabile, ordinato e bidirezionale sopra una rete che affidabile e ordinata non è. Il programma che scrive in una presa TCP versa byte da un lato, e il programma all'altro capo li legge nello stesso ordine, senza buchi e senza doppioni, ignorando del tutto i pacchetti, le perdite e le ritrasmissioni avvenute nel mezzo; il protocollo non conserva i confini fra una scrittura e l'altra, sicché due scritture da cento byte possono arrivare come una sola lettura da duecento, e spetta all'applicazione stabilire dove finisce un messaggio e dove comincia il successivo.

TCP spezza il flusso in pezzi chiamati segmenti, ciascuno dei quali viaggia dentro un pacchetto IP, e l'intestazione di un segmento, lunga almeno venti byte, racconta l'intero funzionamento del protocollo. Vi troviamo le due porte, un numero di sequenza che indica la posizione nel flusso del primo byte trasportato, un numero di riscontro che indica il prossimo byte atteso nella direzione opposta, alcuni bit di controllo dai nomi brevi, fra cui SYN per aprire, FIN per chiudere, ACK per confermare e RST per interrompere bruscamente, la dimensione della finestra, cioè lo spazio che il ricevente ha ancora libero, e una somma di controllo che copre intestazione e dati.

Prima di scambiare dati, i due estremi devono accordarsi sui numeri di sequenza iniziali, e lo fanno con una cerimonia detta stretta di mano a tre tempi. Il cliente invia un segmento con il bit SYN e il proprio numero iniziale, scelto in modo imprevedibile; il servitore risponde con un segmento che porta a sua volta il bit SYN, il proprio numero iniziale e il riscontro di quello del cliente; il cliente conclude riscontrando il numero del servitore, e da quell'istante la connessione è aperta in entrambe le direzioni. I tre tempi sono il minimo indispensabile perché ciascuno sappia che l'altro ha ricevuto il suo numero, e proteggono dal caso di un vecchio segmento di apertura, rimasto a vagare nella rete, che giungendo in ritardo farebbe nascere una connessione fantasma. Il problema dei segmenti ritardatari è meno accademico di quanto sembri, perché la rete può trattenere un pacchetto in una coda per molti secondi e consegnarlo quando la conversazione a cui apparteneva è già conclusa; per questo ogni pacchetto ha un tempo di vita limitato, i numeri di sequenza vengono scelti in modo da non ripetersi entro quel tempo, e una macchina appena riavviata, che ha dimenticato tutto, evita per prudenza di riusare subito le stesse combinazioni.

La scelta di numeri iniziali imprevedibili ha anche una ragione di sicurezza, dato che un estraneo capace di indovinarli potrebbe infilare dati falsi in una conversazione altrui. La cerimonia presta poi il fianco a un abuso celebre, l'inondazione di SYN, in cui un malintenzionato invia migliaia di primi tempi senza mai completare il terzo, costringendo il servitore a tenere in memoria altrettante connessioni a metà; la difesa più ingegnosa consiste nel non ricordare nulla dopo il primo tempo e nel nascondere le informazioni necessarie dentro il numero iniziale stesso, calcolato con una funzione segreta, cosicché soltanto chi risponde davvero al secondo tempo ottiene una connessione.

Durante lo scambio, ogni byte del flusso ha il proprio numero d'ordine, e i riscontri sono cumulativi, nel senso che un riscontro con il numero cinquemila dichiara ricevuti tutti i byte fino al quattromilanovecentonovantanove e chiede il successivo. Il mittente conserva una copia di ciò che ha spedito finché non ne riceve il riscontro, e avvia un orologio; se l'orologio scade prima che il riscontro arrivi, il segmento viene considerato perduto e parte di nuovo. Il ricevente, dal canto suo, usa i numeri di sequenza per rimettere in ordine i segmenti arrivati scambiati e per scartare quelli giunti in doppia copia.

La durata dell'orologio è una delle questioni più delicate dell'intero protocollo, perché un valore troppo breve provoca ritrasmissioni inutili e un valore troppo lungo lascia la conversazione ferma dopo ogni perdita. TCP risolve il problema misurando di continuo il tempo che intercorre fra l'invio di un segmento e l'arrivo del suo riscontro, tenendo una media che dà più peso alle misure recenti e una stima di quanto le misure oscillino attorno a quella media, e fissando l'orologio alla media aumentata di un margine proporzionale all'oscillazione; dopo ogni scadenza, inoltre, la durata viene raddoppiata, per prudenza verso una rete che potrebbe essere in difficoltà.

Il campo della finestra realizza il controllo di flusso, cioè impedisce a un mittente rapido di sommergere un ricevente lento. In ogni segmento il ricevente dichiara quanti byte è ancora disposto ad accogliere nella propria memoria di ricezione, e il mittente si impegna a non averne in viaggio più di quanti gliene siano stati concessi; quando il programma destinatario tarda a leggere, la memoria si riempie, la finestra dichiarata si restringe fino a zero e il mittente si ferma, limitandosi a sondare di tanto in tanto con un segmento minuscolo per scoprire se lo spazio è tornato. È la stessa finestra scorrevole incontrata nel collegamento dati, applicata qui all'intero percorso anziché al singolo salto.

Attorno alla finestra sono cresciute alcune astuzie che spiegano comportamenti altrimenti misteriosi. Per non sprecare un pacchetto intero per ogni tasto premuto in una sessione remota, il mittente raccoglie i byte piccoli e li spedisce insieme quando arriva il riscontro precedente, accorgimento che porta il nome di John Nagle; per non sprecare un pacchetto per ogni riscontro, il ricevente attende qualche decina di millisecondi nella speranza di poterlo unire a dei dati in partenza; e per evitare che un ricevente lento annunci finestre di pochi byte, inducendo il mittente a spedire segmenti ridicoli, si attende di avere uno spazio decente prima di annunciarlo.

Il controllo di flusso protegge il ricevente, e resta da proteggere la rete, ed è qui che TCP mostra il suo lato più ingegnoso. Nel 1986 Internet attraversò una serie di collassi durante i quali la capacità utile di alcune linee precipitò a un millesimo del normale, perché i calcolatori, vedendo perdersi i pacchetti, li ritrasmettevano con ostinazione e aggravavano l'ingorgo che aveva causato le perdite. Van Jacobson studiò il fenomeno e propose un rimedio che non richiedeva alcuna modifica ai router, basato su un'idea semplice, cioè interpretare ogni perdita come un segnale di congestione e reagire rallentando.

A questo scopo il mittente tiene una seconda finestra, detta di congestione, che rappresenta la sua stima di quanto la rete riesca a sopportare, e spedisce in ogni momento secondo la più piccola fra questa e la finestra dichiarata dal ricevente. All'apertura della connessione nulla si sa della strada, e il protocollo procede con una fase chiamata partenza lenta, nome ingannevole per una crescita in realtà rapidissima, in cui la finestra parte da pochi segmenti e aumenta di uno a ogni riscontro ricevuto, raddoppiando così a ogni giro completo di andata e ritorno, come chi apre un rubinetto sconosciuto girandolo ogni volta del doppio finché l'acqua comincia a schizzare.

Raggiunta una soglia, oppure avvertita la prima perdita, la crescita diventa cauta, di un solo segmento per ogni giro, e a ogni nuova perdita la finestra viene dimezzata, per poi riprendere a salire piano. Questo andamento, che disegna nel tempo un profilo a dente di sega, ha una proprietà preziosa, dato che più connessioni che condividono la stessa strettoia finiscono per spartirsela in parti all'incirca uguali, senza che nessun arbitro lo imponga, perché chi possiede una finestra più grande perde di più a ogni dimezzamento. È un raro esempio di cortesia collettiva ottenuta da regole puramente individuali. L'ipotesi che ogni perdita segnali un ingorgo regge bene sui cavi e sulle fibre, dove gli errori di trasmissione sono rarissimi, e vacilla sui collegamenti radio, dove un pacchetto può andare perduto per un semplice disturbo; se il protocollo reagisse dimezzando la finestra a ogni disturbo, le prestazioni crollerebbero senza motivo, ed è una delle ragioni per cui il Wi-Fi, come abbiamo visto, ritrasmette per conto proprio le trame guaste, nascondendo quasi tutte le perdite al piano di trasporto.

Per accorgersi delle perdite senza attendere la scadenza dell'orologio, il protocollo sfrutta un indizio. Quando un segmento si perde e i successivi arrivano, il ricevente continua a riscontrare lo stesso numero, quello del byte mancante, e il mittente che vede arrivare tre riscontri identici di seguito conclude che quel segmento è andato perduto e lo ritrasmette subito, con una manovra chiamata ritrasmissione rapida; un'estensione ormai universale permette inoltre al ricevente di indicare con precisione quali blocchi successivi sono già arrivati, cosicché il mittente rispedisce soltanto i buchi.

Le varianti moderne hanno affinato la ricetta, e quella usata per impostazione predefinita da Linux, chiamata CUBIC, fa crescere la finestra secondo una curva che risale in fretta verso il valore a cui si era verificata l'ultima perdita e poi lo esplora con prudenza, adattandosi meglio alle linee veloci e lunghe; un'altra, chiamata BBR, rinuncia a usare la perdita come segnale e stima direttamente la capacità della strettoia e il ritardo minimo del percorso, regolando su queste due misure il ritmo di invio. I router possono infine collaborare marcando i pacchetti quando la coda cresce, in modo che gli estremi rallentino prima che avvenga una perdita.

La chiusura di una connessione avviene una direzione alla volta, poiché il flusso è bidirezionale e un estremo può aver finito di parlare mentre l'altro ha ancora qualcosa da dire. Chi ha terminato invia un segmento con il bit FIN e l'altro lo riscontra, dopodiché la conversazione prosegue a senso unico finché anche il secondo invia il proprio FIN e ne riceve il riscontro; l'estremo che ha chiuso per primo resta poi per qualche decina di secondi in uno stato di attesa, per assorbire eventuali segmenti ritardatari che altrimenti potrebbero essere scambiati per parte di una connessione nuova aperta fra le stesse porte. La chiusura ordinata nasconde un piccolo paradosso logico, che il libro illustra con la storia di due eserciti alleati, accampati su colline opposte, i quali possono vincere soltanto attaccando insieme e comunicano tramite messaggeri che rischiano la cattura. Per quante conferme e conferme delle conferme si scambino, l'ultimo messaggero partito lascia sempre chi l'ha mandato nel dubbio, e nessun protocollo può garantire che entrambi sappiano con certezza di essere d'accordo; TCP accetta questo limite con buon senso, ritrasmettendo il segmento di chiusura un certo numero di volte e poi considerando chiusa la faccenda comunque.

L'intera vita di una connessione è descritta da una macchina a stati, parente stretta di quelle incontrate nella pagina sulla logica sequenziale, con una decina di stati dai nomi parlanti che compaiono negli strumenti di diagnosi. Una presa in ascolto si trova nello stato LISTEN, una connessione aperta e funzionante nello stato ESTABLISHED, quella che attende dopo la chiusura nello stato TIME WAIT, e saperli riconoscere permette di capire a colpo d'occhio che cosa stia facendo un servitore. Capita che uno dei due estremi scompaia senza salutare, per un guasto o per un cavo staccato, lasciando l'altro con una connessione aperta a metà che nessuno userà più, e per scoprirlo il protocollo può inviare a lunghi intervalli un segmento vuoto di sondaggio, abbattendo la connessione se le risposte mancano; quando invece arriva un segmento per una connessione che la macchina non conosce, per esempio dopo un riavvio, essa risponde con il bit RST, che equivale a riagganciare il telefono senza cerimonie.

Dopo quarant'anni di onorato servizio, TCP mostra alcuni limiti che hanno spinto a cercare un successore. La stretta di mano costa un giro di andata e ritorno prima che si possa spedire il primo dato, a cui se ne aggiungono altri per accordarsi sulla cifratura; un solo segmento perduto blocca la consegna di tutti i dati successivi, anche quando appartengono a oggetti indipendenti, come le immagini di una stessa pagina; e una connessione è legata agli indirizzi dei due estremi, sicché un telefono che passa dal Wi-Fi alla rete cellulare deve ricominciare da capo.

La risposta si chiama QUIC, è nata nei laboratori di Google ed è oggi uno standard su cui viaggia la versione più recente del protocollo del web. Essa è costruita sopra UDP, cosa che le permette di attraversare la rete esistente e di essere aggiornata insieme ai programmi anziché insieme ai sistemi operativi, e realizza in proprio l'affidabilità, il controllo di congestione e la cifratura, fusa nella stretta di mano in modo da cominciare a spedire dati dopo un solo giro, oppure subito quando le due parti si conoscono già. I dati viaggiano in più flussi indipendenti dentro la stessa connessione, cosicché una perdita rallenta soltanto il flusso colpito, e la connessione è individuata da un identificatore proprio anziché dagli indirizzi, sopravvivendo al cambio di rete.

Un cenno meritano le prestazioni, che su questo piano dipendono da dettagli poco appariscenti. La velocità di un trasferimento è limitata dalla più piccola fra la capacità della strettoia, la finestra del ricevente e la finestra di congestione, e su percorsi lunghi quest'ultima impiega parecchi giri per crescere, ragione per cui un file piccolo scaricato da un altro continente arriva più lentamente di quanto la banda nominale lascerebbe credere. La finestra originaria di sedici bit, sufficiente per le linee degli anni ottanta, viene oggi moltiplicata per un fattore concordato all'apertura, e la dimensione massima del segmento viene anch'essa negoziata in modo da riempire esattamente il carico di una trama senza provocare frammentazione. Vale la pena nominare anche un difetto scoperto di recente negli apparecchi domestici, cioè le code troppo capienti. Un router che accumula in memoria secondi interi di traffico anziché scartare per tempo qualche pacchetto priva TCP del suo segnale di congestione e fa crescere il ritardo di tutti, ed è il motivo per cui una videochiamata si inceppa quando qualcuno in casa carica un archivio pesante; i sistemi moderni vi rimediano con code gestite attivamente, che misurano quanto a lungo i pacchetti sostano e cominciano a scartare appena l'attesa supera pochi millisecondi.

Il senso d'insieme di questo piano si lascia riassumere così. Le porte portano i dati fino al programma, UDP offre un servizio essenziale a chi preferisce la prontezza, TCP costruisce sopra una rete incerta un tubo affidabile regolando da sé il proprio ritmo per rispetto del ricevente e della rete, e l'interfaccia delle prese mette tutto questo a disposizione di chiunque sappia scrivere poche righe di codice. Con la pagina sul linguaggio C alle spalle, hai già gli strumenti per scrivere un piccolo servitore, e gli esperimenti che seguono ti preparano a farlo.

Passiamo dunque alla pratica, e per il primo esperimento apri un terminale e scrivi ss -tlnp, che elenca le prese TCP in ascolto sulla tua macchina; ogni riga mostra l'indirizzo e la porta su cui un programma attende chiamate e, se esegui il comando come amministratore, anche il nome del programma. Osserva quali ascoltano soltanto sull'indirizzo 127.0.0.1, raggiungibile unicamente dalla macchina stessa, e quali su tutti gli indirizzi, e ripeti con ss -ulnp per vedere le prese UDP, chiedendoti per ciascuna voce se quel servizio ti serve davvero. Questa domanda è il primo gesto di chiunque voglia mettere in sicurezza una macchina, perché ogni porta in ascolto è una porta a cui qualcuno può bussare, e un servizio che non serve è meglio spento che sorvegliato.

Per il secondo esperimento guarda le connessioni vive, cioè apri il browser su un paio di siti e scrivi ss -tn, che mostra le connessioni stabilite con le coppie di indirizzi e porte; riconoscerai le tue porte effimere, numeri alti scelti dal sistema, affiancate alla porta quattrocentoquarantatré dei servitori, e chiudendo il browser e ripetendo il comando vedrai quelle righe scomparire dopo qualche istante.

Per il terzo esperimento costruisci una conversazione con le tue mani usando il programma nc, il cui nome completo è netcat. In un primo terminale scrivi nc -l 5000, che mette un servitore in ascolto sulla porta cinquemila, e in un secondo terminale scrivi nc 127.0.0.1 5000, che vi si collega come cliente; tutto ciò che digiti in una finestra, seguito dal tasto di invio, compare nell'altra, e mentre la conversazione è aperta puoi lanciare ss -tn da un terzo terminale per vederla elencata con le sue due porte.

Per il quarto esperimento ripeti la prova con UDP, aggiungendo l'opzione -u a entrambi i comandi, e nota la differenza di comportamento. Se chiudi il servitore e continui a scrivere dal cliente, con TCP ricevi subito un errore, perché la connessione è stata abbattuta, mentre con UDP i tuoi messaggi partono comunque verso il nulla senza che nessuno ti avverta, dimostrazione concreta di che cosa significhi un servizio senza connessione e senza conferme.

Per il quinto esperimento osserva la stretta di mano con i tuoi occhi, usando il programma tcpdump, che richiede i privilegi di amministratore e mostra i pacchetti in transito. Scrivi sudo tcpdump -n -i lo port 5000 in un terminale e ripeti l'esperimento con nc negli altri due; vedrai comparire tre righe all'apertura, con le lettere S e il punto che indicano i bit SYN e ACK, poi una riga per ogni messaggio con i numeri di sequenza che avanzano della lunghezza del testo, e infine le righe con la lettera F alla chiusura.

Per il sesto esperimento interroga lo stato interno di una connessione. Avvia lo scaricamento di un file grande e, mentre procede, scrivi ss -ti, che per ogni connessione riporta l'algoritmo di congestione in uso, il tempo di andata e ritorno misurato, indicato come rtt, e la finestra di congestione, indicata come cwnd; ripeti il comando ogni pochi secondi e vedrai la finestra salire e, di tanto in tanto, ricadere, cioè il dente di sega di cui abbiamo parlato, mentre il comando sysctl net.ipv4.tcp_congestion_control ti dice quale variante il tuo sistema adotta.

Per il settimo e ultimo esperimento, se nella pagina sul linguaggio C hai preso confidenza con il compilatore, scrivi un piccolo servitore di eco che crei una presa, la leghi alla porta cinquemila, accetti un cliente e gli rimandi indietro tutto ciò che riceve, seguendo il copione di socket, bind, listen, accept, recv e send descritto sopra e consultando le pagine del manuale di ciascuna funzione; collaudalo con nc come cliente, e avrai scritto il tuo primo programma di rete.

Il modulo attinge al libro Reti di calcolatori di Andrew S. Tanenbaum e David Wetherall, e in particolare al capitolo sesto sul livello di trasporto, da cui provengono il servizio di trasporto e l'interfaccia delle prese di Berkeley, l'indirizzamento con le porte, l'apertura e la chiusura delle connessioni, il controllo di flusso a finestra scorrevole, il controllo della congestione, i protocolli UDP e TCP con la gestione degli orologi e le considerazioni sulle prestazioni, integrato per QUIC e per le varianti recenti del controllo di congestione dai documenti pubblici che le definiscono.

Ora che i dati arrivano completi e ordinati fino al programma giusto, possiamo finalmente occuparci dei programmi stessi, cioè di ciò che le persone fanno davvero con la rete quando cercano un nome, spediscono una lettera elettronica oppure aprono una pagina. È il piano più alto della pila e il più vicino all'esperienza quotidiana, e ti invitiamo a proseguire verso la pagina dedicata alle applicazioni e al web, dove i nomi diventano indirizzi e le richieste diventano pagine.