Sistemi di Elaborazione e Trasmissione dell'Informazione - PDF
Document Details
Uploaded by Deleted User
Ludovica Beretta
Tags
Summary
These are notes on information processing and transmission systems. Topics covered include RFCs, network structure, IP protocol, IP addresses, forwarding tables, TCP/UDP protocols, and ports. The notes also discuss socket programming.
Full Transcript
Sistemi di Elaborazione e Trasmissione dell'Informazione Appunti di Ludovica Beretta RFC ( Request For Comment ) I protocolli di rete vengono identificati secondo la notazione RFC - numero (request for comments) Sono documenti di tipo testuale che raccolgono tutte le specifiche di un certo protocol...
Sistemi di Elaborazione e Trasmissione dell'Informazione Appunti di Ludovica Beretta RFC ( Request For Comment ) I protocolli di rete vengono identificati secondo la notazione RFC - numero (request for comments) Sono documenti di tipo testuale che raccolgono tutte le specifiche di un certo protocollo e altri dettagli di basso livello Struttura della rete La rete internet è l'unione, molto complessa, di vari strati o livelli. Esistono 2 approcci per rappresentare la struttura della rete: Approccio ISO/OSI stratificato in 7 livelli, modello ad oggi non utilizzato. [A Chiola non piace perché il modello non è aderente alla realtà - non parlarne all'esame] Approccio Pratico (conforme alla realtà): Internet stratificato in 5 livelli Livello Nome Protocollo 5 Applicativo HTTP (uno dei tanti) 4 Trasporto TCP/UDP 3 Rete IP(v4 O v6) 2 Datalink Ethernet 1 Fisico - Invio di un messaggio nella rete L'invio di un messaggio attraverso la rete avviene tramite 2 dispositivi: Host (Sender) Scrive il messaggio nella RAM - trasferisce il messaggio tramite DMA nella NIC - invia il messaggio in rete. Host (Receiver) Riceve il messaggio attraverso la NIC - lo memorizza nella NIC - la DMA sposta il messaggio in RAM - il Sistema Operativo legge da RAM Il passaggio del messaggio tramite internet avviene secondo la tecnica del store & forward, i dispositivi sono così connessi a coppie. Per giungere al destinatario può essere necessario passare per Host intermedi; l'instradamento dei messaggi avviene per salti successivi multi hop. Tutto questo viene realizzato nel livello 3 "rete". Protocollo IP (Livello 3) Questo protocollo definisce la comunicazione tra 2 host attraverso il loro indirizzo IP. in pratica il protocollo IP contribuisce alla costruzione di una rete che mette in comunicazione delle sottoreti locali: una rete di reti. Il protocollo IP, insieme ai protocolli del livello di trasporto (TCP/UDP), è il fondamento su cui si basa il funzionamento della rete internet. Ad ogni PC in rete è assegnato un indirizzo IP che ne permette l'identificazione. Ad oggi vengono utilizzati le versioni IP v4 e IP v6 [A causa della mancanza di indirizzi si sta effettuando una transizione verso la versione 6] IP v4 riserva 32 bit per l'indirizzamento (4 MLD di indirizzi) IP v6 riserva 128 bit per l'indirizzamento (ovvero centinaia di bilioni di indirizzi) Indirizzo IP Internet è una rete di reti, una parte dell'indirizzo deve indicare una rete locale a cui il router fa riferimento; in essa ci sono tanti host ognuno con un IP diverso, quindi la seconda parte deve andare a specificare l'host in quella sottorete. Gli indirizzi IP sono quindi divisi in due parti : - Network: corrisponde agli indirizzi di rete (parte utile da esaminare da parte del router) - Host: corrisponde agli indirizzi dell'host di quella rete (ultimo router tiene conto del numero di host per poter inviare messaggio) Esiste inoltre una Netmask, che e un numero delle stesse dimensioni dell'indirizzo IP e definisce la dimensione della sottorete; E' costituita da un insieme di: - Uni: che identificano la sottorete. - Zeri: che identificano la parte destinata agli host. ex. 1111111100000000000000000000000000000000 8 bit per la subnet, 24 per gli host All'interno della tabella possono essere memorizzate porzioni di indirizzi di quantità arbitraria. Più piccolo è il prefisso (ovvero i bit dedicati al network) peggiore è la qualità di instradamento. All'aumentare del prefisso, la qualità di instradamento migliora, ma le tabelle di forwarding devono essere più grandi. Bisogna trovare il giusto equilibrio; se siamo vicini all'host di destinazione scriviamo tanti bit per l'indirizzo; se siamo molto lontani possiamo utilizzare un indirizzo più corto. Saranno rari i casi in cui i 32 bit saranno significativi per ottenere l'instradamento corretto; ci basiamo quindi sulle reti locali per avere tabelle ridotte in modo da essere gestite in modo efficiente. Tabella di Inoltro (Forwarding Table) Esse servono per associare indirizzo IP e numero di porta; la loro costruzione prevede il coinvolgimento di algoritmi di routing e di un protocollo ausiliario ad IP detto ICMP. Bisogna avere inoltre un buon supporto hardware: una memoria associativa che supporti celle di lunghezza variabile, bus, reti di interconnessione; questi servono per creare la tabella e collegare i buffer dei router. Gli algoritmi di instradamento servono per scegliere i percorsi dei pacchetti; essi però non vengono eseguiti quando arriva il pacchetto, sarebbe una perdita di tempo enorme; questo algoritmo funziona offline: prima che arrivi il pacchetto, l'algoritmo calcola dove viene smistato; ciò permette una maggiore velocità. Questi algoritmi devono essere eseguiti continuamente, in modo da continuare a cercare strade più vantaggiose; da questo deriva il fatto che se noi mandiamo una serie di datagrammi allo stesso destinatario non è detto che seguono tutti la stessa strada. Il router oltre a smistare i pacchetti, comunica con altri router per poter scambiare informazioni utili per il calcolo di instradamenti; la comunicazione tra router avviene grazie al protocollo ICMP. Protocollo ICMP Questo protocollo serve ai router per poter dialogare tra di loro e per creare tabelle di routing; l'IP serve poi agli utenti per mandare datagrammi da mittente a destinatario usando la tabella di forwarding. ICMP è un protocollo ausiliario: segnala errori, debugga la rete e usa, per poter inviare messaggi a lunga distanza, gli IP e l'instradamento IP. IP e ICMP sono stati sviluppati assieme quasi come se fossero un protocollo unico: ICMP non può fare a meno di IP e viceversa. Algoritmo di routing Lo scopo è quello di arrivare a costruire delle associazioni tra: - Prefisso Indirizzo IP (Network) - Numero di Porta La qualità dell'instradamento dipende dal numero di bit che costituiscono il prefisso dell'indirizzo di cui teniamo conto: se abbiamo dei prefissi composti da pochi bit la qualità sarà scarsa, man mano che andiamo ad aumentare i bit del prefisso, otteniamo una precisione sempre maggiore di instradamento. Se ho 2 indirizzi, uno con prefisso da 4 bit e l'altro da 24 bit, verrà data la priorità al prefisso da 24 bit perché ci da una maggiore precisione. Un router, contattando altri router, può capire quali sono gli instradamenti migliori attraverso due approcci diversi: 1) Link state: Approccio di tipo Globale: ogni router comunica con tutti gli altri router della rete e dice quali sono le sue connessioni; viene quindi creato un grafo dove i nodi sono tutti i dispositivi connessi alla rete; questo permette a tutti i dispositivi di avere informazioni precise su tutti gli altri; A questo punto si può sfruttare l'algoritmo di Dijkstra per cercare il percorso minimo tra due nodi di un grafo. 2) Distance Vector: Approccio di tipo Locale: ogni router comunica coi suoi vicini: se un router è connesso con 3 vicini dice a questi 3 tutte le sue informazioni. La stessa cosa la fanno gli altri 3 router distribuendo le loro informazioni con chi sono connessi: il messaggio si propaga e dopo un periodo di tempo abbastanza breve tutti riescono ad avere delle stime abbastanza buone sulla rete. Ogni router misura la distanza che lo separa dai router adiacenti; a partire da quella crea una tabella dove associa ogni destinazione conosciuta con la stima della distanza e il primo passo da eseguire verso essa. In questo modo riusciamo rapidamente a trovare una serie di indirizzi di macchine raggiungibili in pochi hop; ho quindi delle informazioni approssimative su quale porta di uscita è meglio mandare i messaggi. Sulle reti di piccole dimensioni, un approccio globale (Link State) risulta essere più vantaggioso; esso risulta essere molto preciso ma pesante in termini di tempo. Sulle reti di grande dimensioni ha più senso usare un approccio locale (Distance Vector); esso è approssimativo ma molto veloce. Protocolli TCP/UDP e Porte (Livello 4) La comunicazione a livello di Trasporto avviene tramite protocollo TCP (stream) o UDP (datagram). II livello di trasporto stabilisce il modo per indirizzare la singola applicazione all'interno di una macchina; su un host multiprogrammato è possibile far girare contemporaneamente più applicazioni: ognuna di queste può usare il suo protocollo di comunicazione a livello di trasporto, per inviare e ricevere messaggi. L'indirizzo IP quindi non basta, perché su una singola macchina host possono girare contemporaneamente diverse applicazioni; per identificarle viene utilizzato un ulteriore indirizzo chiamato porta di comunicazione, di 16 bit, a cui si collegano le applicazioni. Vengono quindi utilizzati meccanismi di multiplexing e demultiplexing che si occupano di raccogliere tutti i messaggi e mandarli in rete nel primo caso, oppure indirizzarli verso l'operazione corretta nel secondo. In questo modo tutte le applicazioni che girano su un calcolatore possono accedere individualmente alla rete. Ricapitolando: abbiamo due livelli di indirizzamento: - a livello di rete si utilizza l'indirizzo IP dell'host per recapitare il messaggio - una volta arrivato a destinazione, il messaggio deve essere indirizzato alla giusta applicazione tramite le porte di comunicazione; ogni applicazione ha la sua porta. Socket (Livello 5) L'interfaccia di programmazione per scrivere delle applicazioni è detta socket: attraverso esso è possibile accedere a tutte le primitive di comunicazione disponibili sulla macchina. Si accede così ad un'unica versione virtuale della rete, che implementa tutte le sue varie funzionalità. I socket rappresentano quindi delle interfacce necessarie per gestire la rete a livello applicativo. A questo livello vengono utilizzati svariati protocolli che offrono svariate funzionalità, un esempio è il protocollo HTTP. Approfondimento sulla Struttura della Rete Internet Per gestire la complessità della rete la si stratifica in 5 livelli: Livello Nome Protocollo In breve 5 Applicativo HTTP (uno dei tanti) Applicazione 4 Trasporto TCP/UDP Connessione tra applicazioni 3 Rete IP (v4 o v6) Instradamento Multi-Hop 2 Datalink Ethernet Interazione mittente-destinatario 1 Fisico - Tipo di Canale Livello 1: Fisico Viene specificato il funzionamento della codifica sul canale, ad esempio: canale radio, fibra ottica, ecc... Livello 2: Datalink A livello datalink viene implementata la comunicazione a livello logico e fisico tra il mittente ed il destinatario. Il protocollo principale del data link è il protocollo Ethernet IEEE 802.3. Questo livello è necessario alla realizzazione dei livelli successivi. Livello 3: Rete La funzionalità principale del livello di rete è di fornire instradamento. Sulla base dell'indirizzo del destinatario viene calcolato un percorso che dovrà essere seguito dai pacchetti per arrivare al device destinatario. A livello di rete abbiamo il protocollo IPv4 e IPv6 i quali differiscono solo per il modo in cui viene codificato l'indirizzo: IPv4 su 32 bit IPv6 su 128 bit Livello 4: Trasporto La funzionalità principale del livello di trasporto è quella di stabilire una connessione tra applicazioni che girano in una certa macchina. Oltre all'indirizzo che viene specificato al livello di rete, al livello di trasporto, viene specificato il numero di porta. Questo numero di porta è associato ad un processo in fase di esecuzione sulla macchina e in questo modo tra tutte le applicazioni attive si può scegliere l'applicazione giusta. A livello di Trasporto abbiamo due protocolli principali, il protocollo TCP e il protocollo UDP. Livello 5: Applicativo Al livello applicativo troviamo tutto il resto, sono in esso implementate le funzionalità del sistema e della rete; si può scegliere se mandare messaggi piuttosto che scaricare risorse da remoto o upload, streaming, audio, video. vengono in questo livello implementati svariati protocolli dalle mansioni, quali per esempio HTTP (web) o SMTP (mail); tutto questo si basa sulle funzionalità offerte dagli altri livelli. L'interfaccia di programmazione è l'interfaccia del socket grazie alla quale è possibile accedere a tutte le primitive di comunicazione disponibili su quella macchina. In questo modo si accede a una versione virtuale della rete che implementa tutte le capacità dei nostri protocolli di comunicazione. Livello 4: Modalità Datagram (Protocol UDP): La modalità Datagram si basa su un host mittente, un host destinatario e la rete internet, che attraverso dispositivi chiamati router definisce il percorso che devono fare i messaggi (o più propriamente datagrammi) per arrivare a destinazione. UDP funziona quindi attraverso router e datagrammi: - I router implementano l'instradamento di rete; attraverso essi si definisce un percorso calcolato dal livello IP che andranno a percorrere i pacchetti. La modalità di comunicazione prevede l'invio di messaggi di una certa lunghezza specificata dal mittente. - Il protocollo UDP sta per User Datagram Protocol; i datagrammi sono quindi parte fondamentale di esso e li possiamo immaginare come delle lettere da scrivere nel quale abbiamo una intestazione (header) che contiene l'indirizzo della porta del destinatario e altre informazioni utili al trasferimento, seguita dal contenuto del messaggio (payload). Esempio di Comunicazione Abbiamo un host mittente (A) e un host destinatario (B); A e B si devono mettere d'accordo in modo da eseguire correttamente le primitive send() e receive(); a questo punto la comunicazione si divide in 3 passaggi principali: 1) Host A esegue una send(), la quale termina quando è stato scritto il messaggio nel buffer di trasmissione. Durante la send A deve specificare l'indirizzo dell'area di memoria che contiene il datagramma che deve essere inviato. Ci saranno due parametri principali, il puntatore al primo byte più un'indicazione alla lunghezza espressa in byte del datagramma stesso. I datagrammi possono essere di lunghezza variabile. Per indicare la lunghezza dei datagrammi si usa un'indicazione numerica su 16 bit quindi possiamo avere datagrammi di lunghezza compresa tra 0 e 65535 byte. 2) I datagrammi del protocollo UDP, sono quelli implementati a livello di rete dal protocollo IP. UDP non deve fare praticamente nulla, poiché la spedizione dei datagrammi è implementata dal livello 3. Il datagramma passa quindi dal buffer TX della NIC del mittente fino al buffer RX del primo router; a questo punto il datagramma salta quindi di router in router attraverso vari hop: un passaggio delicato dove si rischia di perdere il messaggio. Una caratteristica importante di questa modalità di comunicazione di tipo datagram è che non viene garantito il recapito di tutti i messaggi, quindi alcuni potrebbero andare persi. Per questo il protocollo UDP è considerato poco affidabile. Se va tutto bene, il messaggio arriva intatto all'host di destinazione. 3) Host B esegue quindi una receive(), che termina quando riceve il messaggio che si aspettava. B nella receive() deve specificare un'area di memoria, indicare un puntatore e una dimensione, passare il puntatore e la dimensione come parametro alla primitiva e sapere quanto è lungo il messaggio inviato da A, affinché sia possibile predisporre una quantità di memoria adeguata. Se la possibile perdita di datagrammi non può essere tollerata, o è necessario 16 inviare messaggi di dimensione superiore a 2 , allora conviene passare alla modalità stream. Livello 4: Modalità stream (protocollo TCP): Risolve il problema dell'affidabilità e della dimensione del messaggio creando un canale virtuale e bidirezionale di comunicazione. Invece che mandare singoli messaggi è come se venisse stabilito un tubo bidirezionale nel quale possono passare informazioni da entrambe le direzioni. Una volta stabilita questa connessione A può mandare a B una quantità arbitraria di byte e viceversa. I byte mandati da A sono ricevuti da B nell'ordine in cui sono stati mandati; se ad un certo punto A vuole mandare più byte di quelli ricevibili da B, la parte di essi non ancora memorizzata resta sul canale di comunicazione fino a che B non svuota il suo buffer di ricezione. Se invece B ad un certo punto cerca di ricevere un numero di byte maggiore rispetto a quelli inviati da A, entra in gioco il meccanismo di blocco della receive(), che fa in modo che B aspetti fino alla completa ricezione di essi. Apertura di connessione TCP attraverso 3-way handshake: Per poter stabilire questa connessione si deve avere una fase preliminare di apertura di questa connessione chiamata 3-way handshake. E' necessario che A e B si comportino in modo diverso, e che instaurino una relazione di tipo Client-Server, infatti: - Uno dei due si deve comportare come server, entità passiva; - l'altro si deve comportare come client, entità attiva. Non importa quale dei due assuma S e C; il client per convenzione è quello che invia il primo messaggio; se tutto va nel verso giusto, il 3-Way-Handshake funziona a grandi linee attraverso 3 passaggi: 1) Il client prende quindi l'iniziativa e manda un primo messaggio, in cui esplica la sua volontà di aprire la connessione, 2) Il server quando arriva la richiesta di connessione risponde comunicando anch'egli la sua volontà di aprirla. 3) Quando il client ha ricevuto una risposta da parte del server, manda un ulteriore messaggio dicendo che la connessione è stata aperta ed è pronta ad essere utilizzata. Header TCP: Abbiamo una parte di intestazione formata da: - Numero di porta sorgente (16 bit) Numero di destinazione (16 bit) - Numero di sequenza (32 bit) - Numero di acknowledgment (32 bit) - Gli ultimi due numeri permettono la realizzazione del livello di astrazione delle stream di byte e quindi permettono di passare da datagram a stream, una volta fatto il 3-way handshake. Gli ultimi 16 bit sono divisi in: - una parte che indica la lunghezza dell'header - dei bit inutilizzati - 6 bit dedicati ai flag, che servono per gestire tutte le varie situazioni della comunicazione. I flag sono: 1) SYN: Synchronization 2) ACK: Acknowledgement 3) URG: Urgent 4) PSH: Push 5) RST: Reset 6) FIN: Finish I numeri di sequenza e acknowledge sono inoltre e soprattutto dei controlli di sicurezza del 3-way-handshake; sono codici numerici generati casualmente (da 0 a 32 2 − 1) che vengono scambiati da i due host in questione, che assicurano che la comunicazione sia riservata, e forniscono sicurezza e affidabilità. In poche parole ci assicurano che nessuno si intrometta nella comunicazione. La modalità stream permette infatti di mandare sequenze arbitrarie di byte con la certezza che queste vengano ricevute, quindi è considerata più affidabile. 3-Way Handshake nel Dettaglio II 3-Way-Handshake tra due Host H1 e H2, rispettivamente Client e Server, avviene in 3 passaggi: 1) C-> S, ovvero H1->H2 H1 manda un messaggio attivando il flag syn per richiedere l'apertura della connessione. Nel numero di sequenza viene inserito un numero generato casualmente, che chiameremo x. Vengono riempiti i campi riguardanti i numeri di porta, mentre il numero di acknowledge è per ora vuoto. 2) S-> C, ovvero H2->H1 Nel caso H2 non voglia aprire la connessione, attiva il flag RST e la comunicazione si ferma, e il 3-WH non va a buon fine. Se invece H2 volesse anch'egli aprire la connessione, risponde attivando: - il flag syn, che comunica la sua volontà di aprire la connessione - il flag ack, che comunica che il messaggio di H1 è stato ricevuto correttamente H2 inserisce inoltre: - nel numero di sequenza un numero generato casualmente che chiameremo y. - nel numero di acknowledge andrà ad inserire x+1, ovvero: numero di acknowledge = numero di sequenza della controparte + numero di byte ricevuti Numero di sequenza e numero di acknowledge assumono quindi anche il significato di contatori, che tengono traccia rispettivamente del numero di byte inviati e ricevuti. 3) C-> S, ovvero H1->H2 H1 riceve il messaggio e manda ad H2 un feedback positivo che indica la conferma che la connessione è stata stabilita con successo. Lo fa attivando il flag ack, mentre il flag syn non serve più e viene quindi spento. H1 inserisce inoltre: - nel numero di sequenza il suo x+1. - nel numero di acknowledge andrà ad inserire y+1. Grazie a questo processo la comunicazione viene mantenuta privata e sicura grazie ad i numeri di sequenza, riservati ad i due host. Dal terzo datagramma in poi i due host possono cominciare a scambiarsi messaggi: ogni byte inviato incrementa di uno acknowledge del ricevente; un esempio di ulteriore comunicazione potrebbe essere: Sempre nel messaggio 3, ovvero C -> S, ovvero H1->H2 Oltre al flag ack ed i numeri di sequenza e acknowledge, H1 inserisce inoltre nel payload il messaggio "ciao". Vengono quindi mandati 5 byte ad H2. 4) S-> C, ovvero H2->H1 H2 riceve il messaggio e manda: - il flag ack che comunica che ha ricevuto il messaggio; - nel numero di sequenza y+1. - nel numero di acknowledge x+6, ovvero x+1+5. Timeout: Buffer e Round-Trip Time Abbiamo due buffer: TX buffer e RX buffer. La capacità di ogni buffer è di 2048 byte o 1024 parole a 16 bit. Nel TX buffer viene memorizzato il contenuto del messaggio che deve essere mandato, nel RX buffer invece viene memorizzato il contenuto del messaggio che viene ricevuto. Entrambi i buffer vengono mappati nello stesso spazio degli indirizzi. Si accede al buffer TX con transazioni di scrittura, mentre si accede al buffer RX con transazioni di lettura. Non è possibile leggere dal buffer TX o scrivere nel buffer RX. Quando uno dei due device, dopo aver mandato un messaggio, non riceve una risposta, deve rimandare il messaggio. Vi è un buffer che misura il tempo trascorso dall'invio del messaggio e si chiama Timeout TX buffer (timeout del buffer di trasmissione). Il device che manda il messaggio lo salva quindi sul Time out TX buffer ed al raggiungimento di una certa soglia di timeout, se non è stata ricevuta risposta, lo rimanda. La soglia di timeout dipende dalla distanza dei due host e dalle caratteristiche fisiche di essi; queste variabili vengono considerate attraverso il calcolo di una metrica detta Round-Trip-Time: Infatti, se l'invio di un messaggio va a buon fine viene calcolato il tempo trascorso tra l'invio del messaggio e la risposta da parte dell'altro device, che viene detto round-trip time. Per stimare la soglia di timeout quindi: 1) All'inizio parto da valori molto alti 2) Poi calcolo e faccio una media dei RTT e pongo la soglia di timeout leggermente maggiore a questa media. Quindi: 1) Se si perde il messaggio lo reinvio dopo il timeout. 2) Inoltre, grazie ai numeri di sequenza e di acknowledge posso gestire anche se il messaggio di acknowledge viene perso; In questo caso il mittente che non riceve la conferma della ricezione del messaggio pensa che quest'ultimo sia andato perso, e rinvia quindi il messaggio, come nel caso uno. Il ricevente riceve quindi un duplicato, che però riconosce grazie al numero di sequenza: non considera quindi il payload ma rimanda semplicemente l'acknowledge. Piggybacking e Algoritmo di Nagle Il piggybacking è una tecnica ottimizzazione effettuata dal S.O. dell'host mittente. Il device che riceve il primo messaggio può decidere di aspettare un breve lasso di tempo prima di mandare la risposta in modo da mandare un ACK# cumulativo nel caso ricevesse un altro messaggio subito dopo la ricezione del primo. Questo processo viene chiamato piggybacking, e permette di utilizzare un unico datagramma per rispondere a diversi messaggi. Esso funziona tramite l'algoritmo di Nagle (nagle's algorithm), che consiste nel posticipare l'invio di messaggi brevi in modo da mandarli insieme e ottimizzare l'utilizzo degli ACK. E' importante notare che il piggybacking potrebbe essere un rallentamento se si inviano solo messaggi brevi, è quindi possibile disattivarlo se necessario, attraverso una chiamata di sistema. Banda e Latenza Sono due concetti fondamentali per apprendere le reti: Vantaggi e svantaggi della modalità di tipo stream (Protocollo TCP) Il protocollo TCP consiste nel creare un canale di comunicazione tra 2 device costituito come da due tubi: uno per mandare i dati e l'altro per riceverli. In questo modo dopo il 3-WH non devo più inserire la porta di destinazione. Un altro lato positivo è che è una modalità sicura ovvero non c'è il rischio di perdere i dati che si inviano. Il lato negativo è che prima di potersi scambiare i dati bisogna aprire la connessione utilizzando il protocollo 3-way handshake e questa operazione richiede molto tempo, tempo che non dipende dalla qualità della connessione dei due device ma dalla loro distanza. Un altro svantaggio è che bisogna assegnare ai due device il ruolo di server e di client. Inoltre bisogna anche chiudere la connessione. Chiusura di una connessione di tipo stream Vi sono 3 possibili modi per chiudere un canale: 1) Il flag FIN permette di stabilire in modo indipendente se i due dispositivi hanno ancora bisogno della connessione. Quando uno dei due dispositivi vuole chiudere la connessione manda un segnale FIN e l'altro dovrà rispondere con un FIN-ACK. Se in un momento uno dei due dispositivi chiude la connessione questa diventa unidirezionale e quel dispositivo può solo ricevere messaggi. 2) Un altro modo per chiudere la connessione è quello di mandare un messaggio con attiva la flag RST o reset, come quello che si manda quando si rifiuta di aprire la connessione; questo messaggio chiude il canale direttamente e in ambo le direzioni. 3) Il canale si può anche chiudere automaticamente quando i due dispositivi smettono di scambiarsi dati. Vantaggi e svantaggi della modalità di tipo datagram (Protocollo UDP) La qualità del servizio dipende dal protocollo IP; in generale UDP risulta più semplice e veloce di TCP. Devo, al contrario di TCP, sempre specificare numero di porta del destinatario. Rispetto alla modalità stream la modalità datagram è meno affidabile, infatti potrebbe capitare che il messaggio inviato non venga recapitato oppure mandando più messaggi questi potrebbero arrivare in ordine sparso. Questa inaffidabilità deriva dal fatto che i messaggi vengono instradati indipendentemente, senza un canale e senza il meccanismo degli ACK. Il protocollo UDP utilizza un multiplexer per direzionare i pacchetti in uscita dalle porte, ovvero numeri che identificano i processi in esecuzione, alla NIC; Utilizza anche un demultiplexer per dirigere i pacchetti in entrata dalla NIC alle varie porte. UDP VS TCP: Recap UDP TCP Semplice e Veloce Lento: Devono aprire (3-WH) e chiudere la connessione Inaffidabile Affidabile e Sicuro Nessuna Relazione tra host Host in relazione C-S Devono sempre inserire IP e porta dei due Dopo il 3-WH posso evitare di inserire porta host e l’instradamento può variare e IP e il percorso dei messaggi è sempre sul canale di comunicazione Socket (Livello 5) La virtualizzazione della rete avviene attraverso i socket ovvero interfacce che ci permettono di accedere alle primitive di comunicazione, creando una connessione tra le applicazioni. Un socket è una sorta di porta di comunicazione che permette il passaggio di informazioni da un'applicazione alla rete e viceversa. Esso deve essere messo in comunicazione con i dispositivi fisici della macchina, in particolare la NIC; in questo modo l'informazione che arriva dalla rete passa attraverso il socket dell'applicazione di destinazione e giunge alla NIC, permettendo quindi di far passare informazioni dal livello applicativo a quello del sistema operativo (e viceversa). Vi sono due tipi di socket: Stream socket: orientati alla connessione (connection-oriented), basati su protocolli affidabili come TCP o SCTP. I socket possono essere visti come una coppia di file per supportare la comunicazione bidirezionale: un file in lettura ed uno in scrittura. Dopo il 3-WH, l'idea è quella di mappare lo stream sulla rete. L'host A può effettuare una serie di operazioni di scrittura sul socket; le informazioni viaggeranno sullo stream attraverso i protocolli TCP/IP e giungeranno all'host B, che potrà leggerle attraverso operazioni di lettura sul socket. Le operazioni di lettura e scrittura sono supportate dai buffer di trasmissione e ricezione. I socket di tipo stream essendo basati su protocolli a livello di trasporto come TCP, garantiscono una comunicazione affidabile, full-duplex, orientata alla connessione, e con un flusso di byte di lunghezza variabile. Datagram socket: non orientati alla connessione (connectionless), basati sul protocollo veloce ma inaffidabile UDP. Organizzazione dell'Interfaccia di Programmazione L'interfaccia di programmazione viene organizzata attraverso varie chiamate di sistema che svolgono una parte delle operazioni legate alla ricezione, alla trasmissione e, in caso di un socket TCP, anche all'apertura della connessione. La system-call socket() sul client lo inizializza, aprendo il file di comunicazione nell'applicazione; la chiamata restituisce un intero che corrisponde al file descriptor associato ad esso. Socket di tipo Stream Il passo successivo consiste nello stabilire una connessione, ponendo in una relazione Client-Server i due Host. Lato Client: Il client esegue la funzione connect(), che permette di mandare un messaggio SYN verso il Server, ovvero di richiedere l'apertura della connessione. Lato Server: Il server procede anch'esso alla creazione di un socket di tipo stream, attraverso la funzione socket(); il socket agirà in modalità server, aspettando una richiesta di apertura della connessione da parte del client; per fare ciò sono necessari una serie di passi intermedi: Dopo aver creato il socket il server chiama la funzione bind() che definisce il numero di porta sulla quale avverrà la comunicazione e quindi sulla quale il client dovrà fare la sua connect(). Attraverso la bind() il server può comunicare al S.O. il suo numero di porta, permettendo così il demultiplexing delle informazioni che giungono dalla rete. A questo punto il server esegue la funzione listen() per informare il S.O. che è disposto ad accettare connessioni sulla porta indicata dalla bind(). Dopo la ricezione del messaggio SYN da parte del client, comunicata al socket dal S.O., il server, se vuole aprire la connessione, deve utilizzare la funzione accept() per inviare un messaggio SYN/ACK e aprire la connessione. La accept() è una funzione bloccante, ferma l'applicazione, che può ripartire solo dopo aver aperto la connessione. Essa restituisce un intero corrispondente ad un file descriptor che identifica un altro socket. Il server utilizza quindi n+1 socket: - uno (il primo) su cui sono state effettuate bind() e listen() e che si occupa di ricevere e gestire le richieste di connessione e di creare gli altri socket, - gli altri n socket sono per le n connessioni con gli n client collegati al server; essi rispondono alle eventuali send, receive, write e read. Questi n socket mantengono comunque il numero di porta del socket principale. Socket di tipo Datagram Rispetto alla versione di tipo stream i socket rimangono invariati, una non c'è più l'idea di poter andare a leggere e scrivere sopra come se fossero file. All'interno dei socket di tipo datagram è possibile scrivere una lettera con degli header in cui i viene specificato l'indirizzo del destinatario. Per fare questo vengono utilizzate delle primitive di comunicazione diverse rispetto a quelle della modalità stream, dette send_to() e recvfrom(). La send_to(), rispetto alle primitive send() e write() della modalità stream, prevede che venga specificato l'indirizzo del destinatario. Quando ricevo un messaggio attraverso la recvfrom() viene specificato l'indirizzo del mittente. Per poter avviare la comunicazione non è necessario stabilisce dei ruoli, ma è comunque necessario che all'inizio uno dei due host conosca l'indirizzo dell'altro. Dopo il primo messaggio, attraverso la recvfrom() anche il secondo host conoscerà l'indirizzo del primo, a cui potrà inviare messaggi. Per realizzare una comunicazione di tipo datagram davvero innanzitutto utilizzare la syscall socket() per la creazione dei socket. Questa system call restituisce un intero che corrisponde ad un file descriptor (i socket sono file descriptor). Una volta creati i socket almeno uno dei due deve richiamare la primitiva bind() per poter informare il sistema operativo della connessione che si vuole stabilire. Attraverso questa syscall viene assegnato il numero di porta. Tra i due host deve esistere una convenzione: uno dei due deve aver comunicato all'altro in precedenza il numero di porta che si vuole utilizzare. L'altro host può così procedere all'invio del messaggio, senza necessariamente effettuare una bind(): inviando un messaggio attraverso la sendto() il sistema operativo assegna automaticamente un numero di porta all'applicazione. DNS: II DNS (domain name system) è un sistema dei nomi di dominio, ovvero complesso di server molto grande che si occupa di tradurre gli indirizzi web in IPv4. I client si connettono a dei server locali in modalità datagram UDP sulla porta 53, mandano un datagramma contenente la stringa da tradurre e il server risponde con la sua traduzione. Distribuzione: I server si distribuiscono ad albero: La radice, il Root Nameserver: risponde alle richieste di risoluzione dei nomi riguardanti il namespace del dominio principale (detto root, radice). Il suo compito è quello di reindirizzare le richieste relative a ciascun dominio di primo livello (top-level domain) ai nameserver propri di quel TLD. Top-Level Domain: è l'ultima parte del nome di dominio internet. Corrisponde alla sigla alfanumerica che segue il 'punto' più a destra dell'URL, per esempio: l'indirizzo Internet di Youtube è Youtube.com quindi la parte dell'indirizzo web che ricade entro il dominio di primo livello è com. Ogni TLD gestisce un dominio. DNS autoritativi: sono i DNS che contengono i dati specifici del nome del dominio, rispondono alle richieste per quel dominio e ne forniscono i dati relativi (web, mail, ftp, ecc.). Proprio perché i DNS autoritativi contengono i dati specifici del nostro dominio, sono gestiti dalla società che ci ha fornito il nome di dominio. Il client si connette al server locale, il quale si connette con un altro server il quale a sua volta si connette con un altro e così fino ad arrivare alla radice. Algoritmi di ricerca: Vi sono due algoritmi di ricerca: ricorsivo e iterativo. In quello ricorsivo il server locale contatta il Root nameserver, il quale avendo l'elenco di tutti i Top-level domain, va a contattare quello che gestisce il dominio che il client sta cercando. Questo poi andrà a contattare il server autoritativo. L'algoritmo ricorsivo non è molto efficiente. Nell'algoritmo iterativo invece chi sa di più cerca di dare subito una risposta: il cliente contatta il server locale il quale risponde con l'IP del root. Il client contatta il Root il quale risponderà con l'IP del TLD e così via fino ad arrivare alla pagina che si sta cercando. In pratica non si usa né l'algoritmo ricorsivo né quello iterativo ma vengono combinati: la ricerca dei nomi è iterativa mentre il server locale è ricorsivo. Il client contatta il server locale, questo contatta il root, riceve la risposta e contatta il TLD e così via; Praticamente il server locale si comporta come un intermediario, in questo modo il client non deve inoltrare troppe richieste perché lo fa il server locale. I vantaggi di questa combinazione sono che si crea meno carico sulla gerarchia dei server dal momento che essi inoltrano subito la risposta, non c’è ritardo e non devono memorizzare nulla. Dal momento che si utilizzano datagrammi per la comunicazione può capitare che vengano persi alcuni messaggi, per ovviare a ciò i server locali devono mantenere a mente le richieste mandate e ancora senza risposta. Dopo una certa quantità di tempo se non hanno ancora ricevuto una risposta rimandano la richiesta (timeout). Il server locale alla fine di tutto questo procedimento conoscerà la risposta alla richiesta fatta dal client e può tenerla memorizzata nella sua cache in modo che se un altro client manda la stessa richiesta potrà ricevere immediatamente una risposta. Gli elementi in cache possiedono un TTL (time to leave), un tempo dopo il quale vengono cancellati dalla cache. Header DNS I messaggi che si scambiano i server sono decodificati in una struttura: Identificatore: identifica univocamente una richiesta; andremo a vedere la sua utilità contro il DNS Cache Poisoning. Richieste: una stringa alfanumerica con cui vengono identificati gli host sulla rete, ovvero: una serie di caratteri separati da punti che vanno ad identificare quindi TLD e server autoritativi. ex. www.servizionline.unige.it Risposte (non Autoritative): Risposte contenenti l'indirizzo IP cercato acquisito dalla cache ed il suo TTL. Risposte Autoritative: Risposte contenenti l'indirizzo IP acquisito attraverso l'algoritmo di ricerca ricorsivo/iterativo, senza l'utilizzo della cache, con il suo TTL. Risposte Addizionali: Indirizzo IP del server autoritativo: necessario per verificare la validità di una risposta autoritativa; non sempre viene eseguita questa verifica. Esempio di Funzionamento 1) Client cerca prima nella sua cache locale, in un file di associazioni statiche tra indirizzi web e IP, chiamato /etc/hosts. Se non trova niente, allora richiede un indirizzo IP corrispondente ad un indirizzo web al server locale. 2) Il server lascia intatto l'indirizzo web richiesto all'interno del suo datagramma; dato che è stateless questo passaggio è fondamentale per ricordare e inoltrare la richiesta iniziale. 3) A questo punto il server cerca la risposta nella sua cache, e può: - trovare in cache la risposta pronta e fornire una risposta non autoritativa; - o, se non ha la risposta in cache, attivare il meccanismo iterativo e interrogare la gerarchia dei server. Dettagli sul DNS Ci sono 13 copie del Root nameserver, ciascuna con un indirizzo IP diverso, in modo da distribuire uniformemente il carico di richieste e non sovraccaricare uno solo. Oltre a queste 13 vi sono delle altre copie: infatti, sono i provider che si occupano di distribuire le richieste ai Root, ed ogni provider ha il suo nameserver (nameserver e root nameserver sono due cose diverse). In questo modo ogni provider diminuisce il più possibile la distanza del nameserver rispetto agli host, ed in questo modo diminuisce i RTT e rende la connessione più efficiente. Anche i client possono operare in modalità stateless ovvero non memorizzano la richiesta che hanno fatto la quale verrà riportata dal server locale assieme alla risposta ad essa. DNS Cache Poisoning Un utente malevolo potrebbe intercettare la richiesta mandata da un cliente in modo da modificare la risposta da parte del server locale. Per fare ciò però l'utente malintenzionato deve conoscere sia l'ID della richiesta del client il che suo numero di porta, entrambi generati casualmente ad ogni richiesta, i quali sono codificati in 16 bit ciascuno e questo vuole dire che esiste una 32 possibilità su 2 di imbroccare la combinazione giusta. Se il teppista riuscisse a fare ciò nella cache del server locale verrebbe memorizzata la risposta falsa e tutti gli utenti connessi a quel server locale che andranno a mandare la stessa richiesta del client che è stato intercettato riceveranno quella finta risposta. I browser possiedono una memoria nella quale sono salvati gli indirizzi IP più famosi in modo da non mandare una richiesta al server locale nel caso in cui l'utente cerchi di accedervi. Ancora sul DNS Il protocollo DNS utilizza principalmente come livello di trasporto il protocollo UDP. Solo in rari casi e con una modalità di funzionamento diversa utilizza connessioni di tipo stream. In entrambi i casi, a livello applicativo dobbiamo programmare queste applicazioni attraverso la primitiva dei socket; Per esempio: poniamo di avere un'applicazione che gira su un certo host H1 e un'altra applicazione che gira sull'host H2; in mezzo ad H1 e H2 c'è la rete, in qualche modo H1 e H2 sono connessi alla rete e quindi questo schema è valido indipendentemente da che modalità usiamo, dobbiamo comunque aprire un socket sull'applicazione dentro H1 e H2 e stabilire in qualche modo la connessione virtuale tra il socket e la scheda di interconnessione di rete. Comunicazione di tipo Datagram Chiunque può mandare datagrammi a chiunque è connesso alla rete, ma necessita di conoscere l'indirizzo IP del destinatario e il numero di porta dell'applicazione. Ogni datagramma è una lettera che viene inviata sulla rete; il rischio principale è il fatto che essi possano perdersi nella rete e non arrivare a destinazione. Nella comunicazione di tipo datagram: 1) Vengono utilizzate due primitive diverse: - send_to(): per l'invio di messaggi; deve specificare l'indirizzo del destinatario, - receive_from(): per la ricezione di messaggi; ci dice l'indirizzo del mittente (IP e numero di porta) 2) Per avviare la connessione serve che uno conosca l'indirizzo dell'altro e vengono usate le system call: - socket(): creazione di punti di ricezione datagrammi, i socket restituisce un file descriptor, - bind(): assegna il numero di porta. Il numero di porta del server è stabilito a priori; ad esempio, nel protocollo DNS, il numero di porta dei server, definito da IANA, è 53. Esempio di comunicazione: 1) A conosce a priori IP e porta del server; crea il suo socket con la primitiva socket() e il suo numero di porta con la primitiva bind(). 2) A fa una send_to() a B, inviandogli un messaggio, contenente anche il suo indirizzo IP e il suo numero di porta. 3) B tramite la funzione receive_from() riceve il messaggio e identifica il mittente tramite IP e numero di porta. I datagrammi IP sono caratterizzati da una dimensione massima di 2^16 Byte, questo pone un limite sulla dimensione massima che possono avere i messaggi. Comunicazione di tipo Stream Il protocollo DNS prevede la possibilità di usare anche il protocollo di trasporto TCP per superare questo limite. Questo processo è molto costoso perché bisogna per forza fare il 3 way handshake all'apertura delle connessioni. Apertura della Connessione Poniamo di avere un server ed uno o più client: 1) Ognuno dei client deve richiamare la primitiva socket() in modo da consentire la comunicazione con il sistema operativo 2) Anche server deve aprire un socket per consentire la comunicazione con il SO. Quando si crea un socket bisogna decidere a priori se è un socket di tipo stream o di tipo datagram, in questo caso la comunicazione è TCP quindi sarà di tipo stream. 3) Dal lato client bisogna conoscere indirizzo IP e numero di porta, in modo da poter chiamare la funzione connect() che da avvio al messaggio syn per stabilire la connessione con il 3-way handshake. 4) Dal lato server bisogna fare la bind(), creando la porta in relazione al socket. 5) Il server esegue poi una listen() che gli permette di ricevere il messaggio syn; 6) la accept() infine, permette al server di mandare un messaggio syn/ack al client, che permette di avviare una connessione. Il primo client manda un messaggio di tipo syn sulla porta concordata; attraverso le primitive socket(), listen() e accept() il server si crea un nuovo socket e lo connette con il client C1. A questo punto il client C2 può chiedere una connessione mandando un messaggio syn, e ripetendo il procedimento il server può crearsi un altro socket connesso con il client C2 (e così via). Avremo quindi un socket che resta in ascolto e si occupa di stabilire le connessioni con i nuovi host; si aggiungono ad esso un numero di socket che hanno una connessione in corso con i rispettivi host. Per esempio, per gestire la comunicazione stream con 3 client il server avrà aperto 4 socket, in modo da poter gestire le connessioni usando socket diversi. Il server può quindi comunicare con tanti client, ciascuno di essi individuato da un socket. Prestazioni di una Rete Le prestazioni di una rete non sono facili da calcolare: nell'invio di un messaggio da A a B sono molte le strade possibili nella rete che gli permettono di arrivare a destinazione attraverso il processo multi-hop. I due host utilizzano le due seguenti funzioni: 1) Host A chiama la funzione Write(buf, n) con: - buf il buffer di partenza del messaggio; - n il numero di byte da inviare. 2) Host B chiama la funzione Read(buf, n) con: - buf il buffer di arrivo del messaggio; - n il numero di byte da ricevere. Se queste due funzioni sono poste in modalità bloccante, vuol dire che la loro esecuzione blocca il resto dell'applicazione. Calcolo del Tempo tra Partenza e Arrivo del Messaggio Per calcolare il tempo tra partenza e arrivo di messaggi da un Host A ad un Host B utilizziamo un'applicazione chiamata Ping Pong; essa ci permette di inviare più volte lo stesso messaggio per stimare al meglio il risultato. Gli host possono quindi calcolare al meglio il tempo trascorso tra: - la write() di A, - la read() di B. Possiamo quindi calcolare: E mettere assieme questi due dati nella misurazione del Round-Trip-Time: Possiamo inoltre mettere in relazione tempo e dimensione del messaggio grazie al modello banda-latenza. Modello Banda-Latenza Il modello B-L descrive la seguente legge, che calcola il delay nella comunicazione tra A e B: Per calcolare Latenza e Banda devo usare usare l'applicazione Ping-Pong per studiare questi 2 casi: - caso di 𝑁𝑏𝑦𝑡𝑒 minimi, in ogni caso più piccoli possibile ad esempio mando solo l'header di un datagramma. - caso di 𝑁𝑏𝑦𝑡𝑒 massimi (più grandi possibile); - E creare quindi un sistema a due equazioni e due incognite: Posso dunque calcolare la Banda e la Latenza. Protocollo NTP Il protocollo NTP (Network Time Protocol) è un protocollo fondamentale per la sincronizzazione degli orologi di una rete. Sincronizzare gli orologi è fondamentale per meccanismi che comparano i tempi tra più macchine come il caching. L'NTP è un protocollo client-server appartenente al livello applicativo ed è in ascolto sulla porta UDP 123. In pratica gli orologi sono contatori che si incrementano alla frequenza di 1Hz (un incremento al secondo). Il contatore era originariamente diviso in due sezioni: - Una contava i secondi: - In unix a 32 bit con segno (negativo prima dell'epoca) - In UTC a 32 bit senza segno - Una contava i nanosecondi: a 32 bit senza segno sia in unix che in UTC. Il valore 0 del contatore, ovvero la data e l'orario di partenza di esso è detto epoca: - Per Unix: 01/01/1970 - Per UTC: 01/01/1900 Il problema che sorge da queste prime affermazioni è che il numero di bit ristretto fa si che entrambe queste versioni vadano in overflow: - Unix dopo il 2038 - UTC dopo il 2036 Ovvero: non ci sono abbastanza bit per contare i secondi dopo queste due date. Si è passati quindi a 64 bit - i secondi possono quindi essere contati per miliardi di anni; - l'altro contatore non conta più i nanosecondi ma i picosecondi; il maggior numero di bit riesce quindi a dare più precisione al contatore. Funzionamento del Protocollo Bisogna effettuare una sincronizzazione dell'orologio di un client rispetto ad un server. Siccome NTP sfrutta UDP, la poca affidabilità dei messaggi viene risolta interrogando più server; si va quindi ad aumentare la ridondanza e a verificare quale sia il server più affidabile. I server sono classificabili in base alla loro precisione grazie al loro strato, ovvero alla loro vicinanza rispetto ad un orologio atomico: - Un server con al suo interno un orologio atomico è detto di strato 0, - Un server collegato ad uno di strato 0 è detto di strato 1, - Un server collegato ad uno di strato n diventa di strato n+1, - Siccome vi sono 4 bit allocati per lo strato, il masssimo n possibile è 15, e per convenzione identifica un server non sincronizzato. Una volta sincronizzato il client al server di strato n, esso diventa quindi di strato n+1. La sincronizzazione può portare dei problemi alle applicazioni che stavano usando l'orologio nel momento di cambio dell'ora. Per risolvere questo aspetto, al cambio dell'ora, agisco non sul tempo ma sulla frequenza, in pratica: - aumento la frequenza se l'orologio è indietro, - a diminuisco se l'orologio è avanti. Scelta del Server Abbiamo detto che, siccome NTP sfrutta UDP, la poca affidabilità dei messaggi viene risolta interrogando più server; per trovare il server più affidabile si procede in questo modo: 1) Vengono interrogati quanti più server possibili per quanti piu round possibili. 2) Dopo n round cerco di stabilire quale sia il server piu affidabile, in base al numero di risposte che ho ricevuto e che non sono andate perse. 3) Una volta scelto il server e dopo la sincronizzazione con esso, continuò a fare interrogazioni (con minor frequenza) per verificare che fosse ancora il server più affidabile. Nella scelta del server tengo anche in considerazione: - Lo strato (la precisione) di esso, - Il round-trip-time: se ho un RTT alto vuol dire che il server è più lontano e/o lento nella trasmissione del messaggio, - La variazione dei round-trip-time: se un server ha RTT che oscilla di molto vuol dire che cambia spesso instradamento ed è quindi poco affidabile. Via via diminuiscono l'interrogazione ai server, ovvero la frequenza dei round, poiché ho già un orologio preciso, mi basta verificare ogni tanto che resti il migliore tra quelli possibili. Arrivò quindi ad avere una precisione nell'ordine dei millisecondi. Sicurezza nel NTP La mancata sincronizzazione dell'orologio di un dispositivo può portare a varie problematiche di sicurezza: un esempio è legato ai certificati digitali. In pratica un certificato digitale è un'attestazione di proprietà di una chiave pubblica crittografata, su cui si basano molti meccanismi della rete. In pratica, questi certificati hanno una data ed un'ora di scadenza; se portiamo indietro il nostro orologio allora potremmo rischiare di accettare certificati ormai scaduti. Inoltre il protocollo NTP, derivando dall'UDP, soffre di spoofing, ovvero la falsificazione della propria identità. Il protocollo NTP è quindi ad esempio sfruttato per attacchi DDos: in pratica, un host malevolo potrebbe usare l'IP dell'host vittima ed inoltrare con quell'indirizzo moltissime richieste NTP a svariati server; in questo modo, l'host vittima riceverà tantissime risposte da i vari server che impediranno il corretto funzionamento della macchina. Per questo esiste anche un protocollo chiamato NTPsec, dove sec sta per security, è quindi una versione di NTP più sicura. Esempio di Uso di NTP Un esempio di uso del NTP è il sistema GPS. Esso è basato sulla misurazione dei tempi di orologi atomici contenuti all'interno di satelliti; in pratica tramite l'interrogazione di più satelliti, si può capire una determinata posizione. SNTP Esiste una versione semplificata di NTP, ovvero il protocollo SNTP (Simplified Network Time Protocol); questa versione porta ad un minore dispendio di memoria e batteria, ma anche ad una minore precisione degli orologi: viene ad esempio utilizzata nei dispositivi mobile. SMTP (E-Mail) SMTP consente l'invio dei messaggi di posta elettronica e si basa sullo stabilire una connessione in modalità di trasferimento asincrono, infatti possiamo mandare messaggi anche quando il destinatario non è disponibile. La comunicazione tra due dispositivi è mediata dalla presenza di server nei quali vi è una mailbox dove vengono inseriti i messaggi in modo che il destinatario vi possa accedere e controllare la propria posta. Se sono arrivati dei messaggi il protocollo SMTP si occupa del trasferimento di essi dal mittente alla mailbox del destinatario. Ci possono essere degli altri server intermedi che memorizzano il messaggio prima di recapitarlo nella mailbox e servono per tollerare la non presenza del server con la mailbox del destinatario. Quando quest'ultimo tornerà online andrà a pescare il messaggio memorizzato nel server mediano. Struttura dei Messaggi I dati che passano sulla rete sono codificati in ASCII a 7 bit. I messaggi sono composti da un header e un body entrambi codificati tramite ASCII. L'header è codificato dando una serie di nomi predefiniti, per esempio: subject, from, to, date. Il campo to è l'unico obbligatorio, gli altri sono facoltativi. Tutto ciò viene codificato in righe di testo di massimo 72 caratteri seguiti da un a capo e dato che i diversi sistemi hanno diversi terminatore di riga quello dell'e-mail è: Per terminare l'header si lascia una riga vuota Per terminare il body si usa un punto e una riga vuota. Le lettere accentate sono codificate attraverso la codifica MIME che non usa caratteri ascii a 7 bit e si usa anche per mandare come allegati foto e video. MIME ha come base il codice radix64 ovvero un alfabeto di 64 simboli che mi permette di scrivere i numeri in base 64. I caratteri sono un sottoinsieme degli ascii per esempio: 0, 1, 2...; a, b, c....; A, B, C....; +, /.... SMTP (protocollo usato per l'invio della posta elettronica) II Simple Mail Transfer Protocol è un protocollo standard per la trasmissione di email, inizialmente proposto nella RFC 778 nel 1981. È un protocollo a livello applicativo, basato sul protocollo di trasporto TCP e su quello di rete IP; il servizio viene fornito sulla porta 25. L'idea del protocollo è di mandare messaggi in maniera asincrona, posso quindi mandare messaggi anche quando il ricevente non c'è; essa si basa su un server centrale, dove al suo interno risiede una mailbox, dove permangono i messaggi che un host ha ricevuto. Esempio di Comunicazione La comunicazione avviene quindi in 3 passi: 1) Abbiamo un'applicazione chiamata MUA, che dialoga con un server MTA. L'idea è quella che il server si prenda in carico l'invio del messaggio da parte dell'applicazione; 2) Se il server con la mailbox è al momento non disponibile, il messaggio può essere trasportato e memorizzato temporaneamente da un server intermedio; questo passaggio permette di tollerare l'assenza del server con la mailbox; 3) Una volta giunto a destinazione, il messaggio viene memorizzato sotto forma di file nella mailbox server. Caratteristiche del SMTP SMTP è uno dei primi protocolli sviluppati, ha quindi un trattamento particolare da parte del DNS. II DNS contiene record chiamati record MX nel quale viene specificato per ciascun dominio qual è il server di posta elettronica da contattare; attraverso un'interrogazione DNS il server SMTP conosce il destinatario. Il destinatario viene determinato prendendo l'indirizzo del destinatario (nome@indirizzohost), in particolare la parte dopo la chiocciola; viene quindi fatta un'interrogazione DNS su quel dominio e ricavato qual'è il server di posta da contattare per poter raggiungere quel destinatario. Questo protocollo viene definito come una qualità di servizio best effort; è una via di mezzo in termini di affidabilità: si prende cura della possibilità di errori, cerca di rimediare senza però dare la completa affidabilità. La cosa peggiore che può succedere è che cada la connessione; utilizza una quantità limitata di risorse per avere una quantità limitata di errori. L'idea di best effort è di mantenere in memoria il messaggio da inviare finché non abbiamo la conferma di ricezione da parte del destinatario; se un server deve mandare un messaggio ad un'altro server, se la trasmissione va a buon fine ovvero il server destinatario risponde "250 ok”, allora esso si è preso carico del messaggio e il server mittente può cancellarlo. Codifica dei Dati Il protocollo supporta direttamente caratteri ASCII a 7 bit. Il body può contenere righe al massimo di 72 caratteri prima di andare a capo (per evitare problemi di visualizzazione su stampanti e monitor). Codifica dei dati in UTF-8 significa che si può usare l'8 bit, attraverso questa estensione si permette l'uso del protocollo di posta elettronica a paesi che non usano il modo di scrittura americana. Per quanto riguarda la possibilità di aggiungere allegati esiste MIME estensione del protocollo che permette di codificare formati di file arbitrari in modo da trasformarli in sequenze di caratteri che sono un sottoinsieme dei caratteri ASCII. Il sottoinsieme dei caratteri usati è il radix-64 e la codifica consiste nel trasformare terne di byte successive in quaterne rappresentate su 6 bit. L'idea è prendere 24 bit e trasformarli in 4 caratteri radix-64. Questo perchè radix lavora 6 bit alla volta; per rendere questa codifica compatibile all'invio del messaggio, bisogna fare in modo che sia un multiplo di 3 byte; se non lo è bisogna aggiungere un padding (aggiungere zeri alla fine) per fare in modo che lo sia. In SMTP è previsto anche l'uso della codifica UUencode: un sistema che permette di convertire file in formato binario (quali archivi compressi, disegni o programmi eseguibili) in un formato ASCII contenente solo caratteri compresi tra 32 e 95. Questa codifica esclude i caratteri di controllo e i caratteri a 8 bit, permettendo di trasmettere con sicurezza il file come allegato ad un messaggio e-mail. Tradizionalmente la codifica è eseguita da un programma chiamato uuencode, e la decodifica (che riporta il file nel formato originario) da uudecode. Sicurezza legata ai messaggi di posta elettronica Facciamo un esempio esplicativo: - A vuole mandare un messaggio a B: - A si collega a un server di posta elettronica, specifica il messaggio usando il protocollo SMTP e poco alla volta il messaggio viene recapitato a B. - a questo punto B per poter leggere il messaggio deve aprire una sessione di lavoro sulla sua macchina, in modo da poter accedere alla mailbox, che è un file che contiene tutti i messaggi ricevuti; - per poter accedere ai messaggi B si deve autenticare specificando username e password garantendo in questo modo anche la privacy di A, in quanto i messaggi possono essere letti solo da lui. Esempio di Comunicazione, nel Dettaglio Andando più nel dettaglio di un esempio di comunicazione: - "A" tramite il suo MUA si connette a un server SMTP, aprendo una connessione nella porta 25. - Una volta stabilita la connessione la prima cosa che fa il server è quella di mandare un messaggio di tipo 220 OK con il quale comunica qual è il proprio indirizzo e che tipo di server è. - Il client dopo aver ricevuto la risposta positiva dal server è previsto che si presenti anche lui, mandando il comando HELO e passando il suo indirizzo - Il server risponde con 250 e reinviando l'indirizzo che ha dato il client. A questo punto il client può mandare una serie di comandi che sono quelli per spedire un messaggio ovvero: - indirizzo destinatario: [email protected]. RCPT TO che specifica al client deve mettere anche il proprio indirizzo di posta elettronica con il comando MAIL FROM: [email protected] - Server riceve e risponde con 250 OK L'indirizzo del mittente serve al protocollo SMTP per poter inviare eventualmente notifiche in caso di problemi; una volta acquisito, il protocollo ha gli indirizzi che gli servono e può procedere allo scambio di messaggi. - Con il comando DATA il client richiede al server il permesso di poter mandare il messaggio. - Il server dopo aver ricevuto il RCPT TO, il MAIL FROM e il DATA manda una risposta 354; dopo di essa il server si aspetta di ricevere i byte fino alla fine del messaggio, e lo comunica con la stringa: end data with - Dopo aver ricevuto 354 end data with, inizia il vero e proprio invio del messaggio; il client comincia a mandare sequenza di byte codice ASCII 7 bit, iniziando con I'Header, costituito da due campi: - from: [email protected] (scrivere qui qualcosa di diverso significa fare spoofing dell'utente) - to: [email protected] (scrivere qui qualcosa di diverso aggiunge un hop alla comunicazione perché viene Chi riceve il messaggio non ha modo di sapere se il from è corretto: al di là della problematica di mancanza di garanzia di autenticità del mittente, questa debolezza del protocollo SMTP ha avuto un effetto devastante per quanto riguarda le problematiche riguardo lo spoofing e lo spam, ovvero lo sfruttare le caratteristiche di posta elettronica per ottenere vantaggi a scapito degli utenti. Spam Per un messaggio singolo pagano sia il mittente che i destinatario. Se mando un messaggio a 10mila destinatari diversi pago come se avessi mandato un messaggio solo, la moltiplicazione per 10mila la pagano i 10mila destinatari. Il servizio di posta elettronica, pur essendo gratuito, è quindi pagato anche da chi riceve; normalmente ricevere un messaggio di posta elettronica non ha un grosso costo ma se si diffonde l'abitudine di mandare 10mila di messaggi per un solo mittente questo si traduce in uno spreco enorme di risorse. Per evitare lo spam l'idea è stata di aggiungere dei filtri; prima di andare a inserire il messaggio nella mailbox del destinatario ci si chiede quindi se quello può essere spam oppure no, tramite lo sviluppo di vari algoritmi. Open Relay Un server senza filtri, ovvero che accetta di mandare messaggi senza accertare la loro provenienza viene chiamato open relay; Un server SMTP open relay permette quindi a chiunque sulla rete di inviare e-mail attraverso di esso; questo tipo di server è da evitare, quindi si aggiunge un meccanismo di autenticazione da parte del mittente e di tracciamento degli indirizzi IP, tramite estensioni del protocollo; in questo modo una qualsiasi azione malevola e di spam può essere tracciata. Viene implementato un comando EHLO che mette in gioco un meccanismo di autenticazione che chiede all'utente di identificarsi usando username e password. Nel caso posso anche inserire una copia dati della procedura preliminare insieme al messaggio: in essa non uso gli indirizzi mail ma gli indirizzi IP, in modo che non possano essere contraffatti. L'utente può finire onload ed essere registrato e mantenuto nel tempo; in questo modo se egli manda messaggi attraverso spam si può agire su di esso disabilitando. Al giorno d'oggi, in ogni caso, non ci potremmo connettere ad un server a caso ma dovremmo comunque connetterci ad un server SMTP. Protocolli per la ricezione dei messaggi L'idea è quella di poter consentire a "B" di poter accedere alla sua mailbox senza dover effettuare login sul server di posta nella quale è stata depositata. Se B usa una macchina diversa può connettersi via rete e ha bisogno di un protocollo che gli permetta di scambiare informazioni senza ricorrere a una shell sulla macchina. I due protocolli introdotti sono il protocollo POP e IMAP; le versioni in uso sono la versione POP3 e IMAP4. POP Pop è un protocollo semplice e offline. Prendiamo l'esempio precedente: POP effettua una copia della mailbox sulla macchina di "B": - POP3 si connette alla macchina, va a vedere i messaggi nella mailbox e li copia sulla macchina locale di "B", qui ci possono essere due modi di funzionamento: 1) Copia dalla mailbox alla macchina: sistema più affidabile, ma spreco di spazio perchè ho bisogno di memoria sul server e sul local. 2) Sposta dalla mailbox alla macchina (effettua una copia e la cancella): meno spazio occupato, ma meno efficienza e rapidità. In attacchi di tipo spam se non si provvede a una cancellazione periodica si esaurisce lo spazio e non si possono più ricevere mail. Un altro svantaggio di POP3 sono i problemi legati al poter accedere con più dispositivi sulla stessa mailbox. Se io volessi accedere alla mia posta elettronica usando due dispositivi diversi, non posso fare l'operazione di cancellazione, sono costretto a mantenere l'originale sul server, altrimenti non posso accedere alle stesse informazioni con l'altro dispositivo; devo quindi sempre tenere traccia di quali messaggi sono sui vari dispositivi. Devo quindi essere sempre sincronizzato tra server e macchina e non posso implementare un meccanismo di caching (come nell'IMAP). Questo protocollo è usato quando non posso connettermi ad un server; il suo impiego principale è nei dispositivi mobile. IMAP IMAP è un protocollo sicuro e online. Esso dà la possibilità a più dispositivi HW di accedere alla stessa mailbox; IMAP infatti mantiene la mailbox sul server ed effettua copie parziali usando una tecnica di caching sulle singole macchine. Il protocollo permette di interrogare il server chiedendo quali sono i messaggi; dopo di chè se voglio vedere un messaggio, esso viene copiato e mantenuto in locale nella macchina ma l'originale viene mantenuto sul server. Quando si esaurisce lo spazio nella macchina, usando una tecnica di caching, viene eliminato il messaggio letto con minor frequenza per lasciare spazio a un nuovo messaggio; questo risolve in modo efficiente il problema di gestione dello spazio e della comunicazione su client e server. IMAP risulta quindi molto più flessibile di POP. Nel caso in cui non ho la possibilità di connettermi al server pero POP risulta vantaggioso rispetto a IMAP. Webmail La versione di accesso alla posta elettronica attualmente in uso è chiamata webmail; essa è simile a IMAP ma usa I'HTTP. Il protocollo HTTP viene sfruttato per accedere via rete a un server (HTTP) che a sua volta si connette al server di posta elettronica per far vedere tramite richiesta (HTTP) il contenuto della mailbox presente su di esso. La posta rimane quindi all'interno del server della mailbox. Protocollo HTTP Abbiamo visto tre versioni del protocollo HTTP: - 1.0 (del 1996), descritta nel RFC 1945 - 1.1 (del 1999), descritta negli RFC 2068 e 2616 - 2 (del 2015), descritta nel RFC 7540 (quest'ultima è quella attualmente in uso) - 3 (del 2022), descritta nel RFC 9114 Il protocollo HTTP è un protocollo di tipo client-server: Il client, solitamente il browser, apre la connessione TCP sulla porta 80 per mettersi in comunicazione con il server; a questo punto si effettuano una serie di richieste e risposte codificate in ASCII, così strutturate: Richiesta ( Client ): Metodo Risorsa Protocollo Host User Agent Cookie If - Modified - Since Connection_close …altre opzioni (arbitrarie) La prima riga contiene l'indicazione di: - un metodo, - una risorsa, - un protocollo da utilizzare. Un metodo è il tipo di operazione che si vuole effettuare, esempio: get, head, put, delete, patch, option, trace, connect. La risorsa di riferisce al path su cui vogliamo agire; ad esempio GET index.html 1.0 richiede la risorsa index.html; I protocolli sono ad esempio quelli descritti in precedenza; uno di essi va specificato come ultimo campo della riga; essi permettono l'upload e il download dai server, per esempio il metodo get corrisponde al download di una risorsa identificata da un pathname, il metodo post invece corrisponde all'upload di informazioni dal client al server; il post è quasi sempre disattivato nei server. La prima riga termina con il terminatore di riga. Alla prima riga possono seguire delle righe opzionali che contengono delle opzioni, per esempio: 1) Host: permette di impostare il nome dell'host a cui ci si vuole riferire; questa opzione può essere utile quando viene utilizzata la funzionalità virtual host ovvero la possibilità di mettere sullo stesso server diversi domini e quindi potendo decidere attraverso il nome a quale dominio riferirmi. 2) User-Agent: specifica il tipo di client che sta effettuando la richiesta (nome del browser, versione eccetera) per permettere al server di utilizzare il metodo più consono per rispondere. 3) Cookie 4) If-Modified-Since 5) connection_close nella 1.0 Il numero delle opzioni è arbitrario e ogni opzione deve essere terminata dai caratteri \n\r ovvero (terminatori di stringa). Per dire che non ci sono altre opzioni si inserisce una riga vuota, che identifica anche la fine del messaggio di richiesta. Risposta (server): E' composta da una prima riga che contiene lo stato, codificato in forma numerica, a cui è associata una stringa leggibile ad occhio umano; i codici principali sono: - 200 che significa "ok", - 301 che significa "moved permanently", ovvero che le risorse non sono più su quel sito, - 304 che significa "not modified" nel caso in cui il client possedesse già la versione più recente della risorsa richiesta, - 400 che significa "bad request" ovvero richiesta mal formata, - 404 "not found" ovvero pagina non trovata, - 500 che significa "internal server error" ovvero errori interni del server, - 505 che significa "HTTP method not implemented" ovvero che quel server non è in grado di rispondere a tale richiesta. Dopo di che si indicano le varie opzioni, le più comuni sono: 1) Server: mi comunica il tipo di server che sta rispondendo. 2) Content-Type che indica il tipo di file allegato con la risposta. A questo punto abbiamo una riga vuota e poi il body che contiene il contenuto del file richiesto. La codifica ASCII si limita alla parte header, non al body. Meccanismo dei cookie Essendo HTTP un protocollo senza stato (il server si dimentica, subito dopo aver risposto, la domanda che gli era stata posta e chi gliel'aveva posta) vi sono delle problematiche in ambito di autenticazione, per esempio se facessimo l'accesso su un sito con username e password ogni volta che ricarichiamo la pagina dovremmo nuovamente fare il login. Per questo motivo sono stati introdotti i cookie: nell'header della risposta da parte del server vi può essere una stringa set-cookie contenente il nome, il valore (uid) e la data di scadenza di esso. Questa stringa assegna al client un cookie che esso memorizzerà e rimanderà al server ad ogni futura richiesta in modo da essere riconosciuto da quest'ultimo attraverso l'uid salvato nel cookie. I cookie possono essere quindi utilizzati per: - accedere ad aree riservate: una volta effettuata l'autenticazione, viene mandato al client un cookie, che egli invierà al server nei prossimi accessi, per evitare di doversi autenticare nuovamente - meccanismi di tracciamento: il server imposta il cookie con un identificatore univoco, in modo da poter ricostruire la sequenza delle richieste del client Versione 1.0 E' la versione più semplice del protocollo, che si complica poi nelle versione successive; questa versione ha delle connessioni non permanenti (dopo aver effettuato la richiesta e ricevuta la risposta viene chiusa la connessione). Tra le righe opzionali infatti è presente connection_close che chiude la connessione al termine di una comunicazione. Questa modalità semplifica l'implementazione del protocollo e: - evita di intasare il server con tante connessioni aperte - ma è poco efficiente, perché per ricevere un file devo aspettare 2 round trip time: uno per la 3 way-handshake e uno per la risposta. Dopodichè bisognerà rifare il 3 way-handshake perchè la connessione è stata chiusa. Versione 1.1 Dal momento che la versione 1.0 non era efficiente si è passati alla 1.1 rendendo la connessione permanente; la connessione rimane quindi aperte fino allo scadere di un timeout, che parte quando i due host non comunicano più niente. Nella versione 1.1 vi è anche la richiesta in pipeline ovvero: dopo aver aperto la connessione il client può mandare la prima richiesta, e ricevuta una risposta può mandare in successione altre richieste senza aspettare ogni volta la risposta del server. In questo modo si utilizza: - 1 RTT per aprire la connessione - 1 RTT per ogni file nella pagina Il server dovrà rispondere alle richieste del client nello stesso ordine in cui sono state poste. Quando il client pone la prima richiesta deve aspettare una risposta da parte del server prima di poter effettuare le altre richieste in successione. Questo è l'unico momento in cui non si può fare pipeline perché la prima richiesta indica la quantità di file da scaricare sul client. Un limite della pipeline è il vincolo di dover mantenere lo stesso ordine per le richieste e le risposte e quindi se la prima richiesta richiede il download di un file di grandi dimensioni (come una foto del mio pene) il client dovrà aspettare di aver scaricato tutto il primo file prima di poter scaricare i seguenti. Per ovviare a questo problema si è passati alla versione 2.0. Versione HTTP 1.1 con Pipeline - Per migliorare ulteriormente le prestazioni si può usare la tecnica del pipelining - Il pipelining consiste nell’invio di molteplici richieste da parte del client prima di terminare la ricezione delle risposte - Le risposte debbono però essere date nello stesso ordine delle richieste, poiché non è specificato un metodo esplicito di associazione tra richiesta e risposta (si pensi al funzionamento di TCP al sottostante livello di trasporto) Differenze tra HTTP 1.0 e HTTP 1.1 - La principale differenza tra HTTP 1.0 e HTTP 1.1 è che HTTP 1.1 consente l'uso di connessioni persistenti, permettendo di inviare più richieste e ricevere risposte sulla stessa connessione. Invece, HTTP 1.0 utilizza connessioni non persistenti, chiudendo la connessione dopo ogni richiesta e risposta. Inoltre, HTTP 1.1 supporta il pipelining delle richieste per migliorare le prestazioni. Versione 2.0 La versione 2.0 prevede l'utilizzo di un sistema di multiplexing attraverso l'uso di identificatori in modo tale da associare ad ogni richiesta una risposta avente lo stesso identificatore, togliendo in questo modo il vincolo di rispettare l'ordine richiesta-risposta In questo modo richieste e risposte possono essere inoltrate nel modo più efficiente possibile. In questa versione abbiamo anche altre ottimizzazioni come la compressione degli header utilizzando un algoritmo simile a quello degli ZIP. Versione 3.0 A differenza delle versioni precedenti che si basano sul protocollo TCP, HTTP 3.0 utilizza QUIC, un protocollo di trasporto multiplexato costruito su UDP. HTTP 3.0 utilizza una semantica simile rispetto alle precedenti revisioni del protocollo, inclusi gli stessi metodi di richiesta, codici di stato e campi di messaggio, ma li codifica e mantiene lo stato della sessione in modo diverso. Tuttavia, in parte a causa dell'adozione di QUIC da parte del protocollo, HTTP 3.0 ha una latenza inferiore e si carica più rapidamente nell'uso reale rispetto alle versioni precedenti, in alcuni casi oltre quattro volte più veloce rispetto a HTTP 1.1 (che, per molti siti web, è l'unica versione HTTP distribuita). Controlli nel Protocollo TCP TCP ha due funzionalità di controllo per assicurare la sua affidabilità: - il controllo di flusso - il controllo di congestione Controllo di flusso: Il controllo di flusso serve per ridurre le perdite dei messaggi agendo in base all'RX buffer dell'host ricevente ed evitando il suo overflow. Prendendo in considerazione una rete multi-hop abbiamo: il mittente host, i router e il destinatario host. Ogni router riceve il messaggio, va a vedere nell'header l'indirizzo di destinazione, calcola il percorso e inoltra il messaggio al router successivo. Come sappiamo i messaggi possono venire persi per errori di trasmissione sul canale. Quando il messaggio viene ricevuto si verifica la sua integrità e se non è integro viene scartato. Attualmente la probabilità di errore è bassa grazie alle connessioni odierne e la causa più comune di perdita di pacchetti deriva dalla mancanza di spazio sul buffer di ricezione. Con il controllo di flusso si evita che il messaggio sia troppo grande rispetto al buffer di ricezione del destinatario. Bisogna quindi evitare l'overflow del buffer di ricezione rallentando l'invio del messaggio dal mittente, per lasciare più tempo al destinatario per svuotare il buffer. Tutto ciò è possibile grazie alla struttura dell'header TCP. Header TCP e Receive Window: Source port Destination port seq# (4 byte) ack# (4 byte) Lunghezza Bit non utilizzati Flag (2 byte) Finestra di intestazione (2 byte) ricezione (16 byte) Ricapitolando, le fasi principali in una connessione TCP sono: - Prima sia apre la connessione attraverso 3-way handshake: - con il primo messaggio syn la finestra di ricezione ha come valore la dimensione del buffer di ricezione; - quando il server risponde mette nella receive window lo spazio disponibile nel buffer. - Server e client prima di mandare i messaggi allocano spazio nel buffer e quando si invia un datagramma si tiene conto della dimensione dei buffer del ricevente. - Se il destinatario non ha abbastanza spazio libero per memorizzare il datagramma il mandante manda solo la parte di messaggio che può essere salvata e quando riceverà la comunicazione che si è liberato dello spazio manderà il resto del messaggio. Lo spazio va liberato con la syscall recv(). - I messaggi rimangono nel buffer per un lasso di tempo preciso e poi vengono cancellati per fare spazio ai prossimi. Deadlock Però c'è un problema: quando la comunicazione è unidirezionale ( perché uno dei due canali è stato chiuso dopo fin ) e A ha mandato la parte del suo datagramma a B occupando tutto il buffer, in seguito B non potrà rispondere ad A per comunicargli che ha svuotato il buffer e che quindi è pronto per ricevere il resto del datagramma. Questa situazione è detta di deadlock. Per ovviare a ciò vi è un timeout allo scadere del quale A prova a mandare il resto del messaggio; se B è riuscito a memorizzarlo manderà un ack indietro. Quando la receive window è 0 il mittente manda un solo byte per verificare se il destinatario ha liberato dello spazio, in modo da minimizzare il più possibile lo spreco di tempo. Controllo di congestione TCP Il controllo di flusso serve per ridurre le perdite dei messaggi agendo in base all'RX buffer dei router intermedi ed evitando il loro overflow. Esso serve quindi per evitare di perdere messaggi se non vi è abbastanza spazio libero sui loro buffer di ricezione. Ogni router ha un buffer di ricezione e uno di trasmissione e se quello di ricezione si riempie quel router non potrà più inoltrare i datagrammi. Nel protocollo IP in passato non era implementato un controllo di flusso infatti se si perdeva un datagramma veniva mandato indietro un messaggio ICMP, che notificava il mittente della perdita. Per colmare questo problema è stato creato il controllo di congestione a livello di trasporto. Implementazione Viene definita una congestion window nell'header che indica lo spazio libero minimo del buffer di ricezione dei router tra quelli che dovrà attraversare il datagramma per arrivare a destinazione. Per stimare questo valore vi è un algoritmo che inizia con una fase di slow start: si parte inviando messaggi di piccole dimensioni e via a via che vado avanti le aumento. Se A riceve un ack vorrà dire che la dimensione del messaggio che ha mandato è minore di quella dello spazio disponibile sul buffer di ricezione del router con buffer più piccolo. Si procede così aumentando esponenzialmente (1, 2, 4, 8... ) la dimensione del messaggio fino a quando non si è mandato tutto il datagramma oppure non si ha più ricevuto l'ack da parte di B. Una volta che conosciamo il peso massimo sostenibile conosciamo anche la congestion window. Ad esempio: se non ricevessimo l'ack dopo aver mandato un datagramma di dimensione = 8 sapremmo che la congestion window e compresa tra 4 e 8; a questo punto possiamo andare avanti un byte alla volta ripartendo da capo, quindi 1,2,3,4... per andare a trovare precisamente la congestion window. Bisogna tenere conto del fatto di due difetti: - non essendo gli unici ad utilizzare la rete potrebbe essere che un router ha poco spazio sul proprio buffer perchè sta venendo utilizzato da qualcun altro. - Gli stessi router possono essere usati contemporaneamente da più connessioni (anche UDP): i buffer di ricezione possono essere riempiti anche da altre connessioni. Il protocollo TCP ha sia il controllo di flusso che di congestione e per determinare la dimensione massima del messaggio si prende il valore minimo tra la congestion e la receive window. ALGORITMO L'algoritmo di controllo di congestione presenta due fasi: - Partenza Lenta (slow start) - Aumento Additivo Diminuzione Moltiplicativa (Additive Increase Multiplicative Decrease) Per distinguere tra le due fasi viene usata una variabile chiamata ssthresh (slow start threshold). Quando il valore della cwnd è minore del valore di ssthresh ci troviamo nella fase di slow start, altrimenti siamo nella fase AIMD. All'avvio della trasmissione la variabile viene settata ad un valore molto alto, mentre la dimensione della cwnd è pari alla dimensione di un segmento. La versione Reno inoltre ha una terza fase detta ripresa veloce (Fast Recovery). Slow start Durante la fase di partenza lenta (in inglese, slow start) la finestra di congestione cwnd viene impostato di default ad 1 (il che comporta una velocità di invio iniziale di circa MSS/RTT) e il mittente TCP inizia trasmettendo il primo segmento dati, attendendo un riscontro. La cwnd aumenta di una quantità pari al MSS per ciascun segmento che, dopo esser stato trasmesso, viene riscontrato (si riceve un ACK). Quindi ad ogni RTT la cwnd raddoppia di dimensioni (e conseguentemente ne raddoppia anche la velocità trasmissiva). La fase di slow start viene mantenuta fintanto che non si incorre in un evento di congestione, come la perdita di un segmento dati, oppure quando la dimensione della finestra di congestione raggiunge o supera il valore della variabile ssthresh, evento che conduce alla fase di aumento additivo - diminuzione moltiplicativa (AIMD), o infine nel caso in cui vengono identificati tre segmenti riscontrati duplicati. In tal caso il mittente TCP pone cwnd ad 1 ed inizia di nuovo il processo di Slow Start. Inoltre pone il valore di ssthresh a cwnd/2. Successivamente si passa all'algoritmo di Congestion Avoidance (quando cwnd >= ssthresh) oppure Fast Recovery (quando vengono identificati tre segmenti riscontrati duplicati). AIMD Durante la fase AIMD si ha un incremento additivo lineare del valore di cwnd di 1 MSS ogni RTT. Ciò si può ottenere attraverso l'incremento da parte del mittente della propria cwnd di una quantità di byte pari a: ogni volta che giunge un nuovo riscontro. Tale comportamento viene detto Additive Increase o anche Congestion Avoidance. Il termine Multiplicative Decrease si riferisce al comportamento messo in atto dal mittente alla ricezione di tre ACK duplicati consecutivi (con lo stesso numero di riscontro): la variante TCP Reno, in questa circostanza, imposta il valore di SSTRESH a cwnd/2 e assegna a cwnd tale valore incrementato di 3 MSS. Se la congestione diventa eccessiva, uno o più buffer dei router lungo il percorso vanno in overflow, causando l'eliminazione di un datagramma IP che contiene un segmento TCP. Rilevato questo evento allo scadere di un timeout di ritrasmissione, TCP reagisce diminuendo il valore di ssthresh e reimpostando cwnd alla dimensione di un segmento, tornando quindi nella fase di slow start. Header IPv4 L'header IPv4 ha dimensioni variabili, identificate dal campo Lunghezza dell'Header. L'identificatore di servizio è composto da una serie di bit che identificano il tipo di datagramma (non è usato molto spesso). Questi bit servivano all'host mittente per specificare il modo e in particolare la precedenza con cui l'host ricevente doveva trattare il datagramma. Ad esempio un host poteva scegliere una bassa latenza, mentre un altro preferire un'alta affidabilità. Nella pratica questo uso di questo campo non ha preso largamente piede. Dopo molte sperimentazioni e ricerche, recentemente questi 8 bit sono stati ridefiniti ed hanno funzioni necessarie per le nuove tecnologie basate sullo streaming dei dati in tempo reale. Il Next Level Protocol indica se utilizziamo il protocollo TCP o UDP. Il Checksum e un valore che viene creato nel momento in cui il mittente manda il datagramma e cambia ad ogni hop, anche se rimane sempre calcolabile dai router intermedi tramite un algoritmo che si basa sugli altri campi dell'header; se non corrisponde tra mittente e destinazione vuol dire che probabilmente c'è stato qualche errore. Questo numero è costituito da 16 bit, la probabilità quindi di non individuare un possibile errore e: Nel protocollo Ethernet viene quindi inserito un altro controllo chiamato CRC32, che funziona in modo analogo ma usa 32 bit; la probabilità di non rilevare l'errore è quindi di: L'indirizzo sorgente serve per inoltrare al mittente, nel caso ci fossero, i messaggi di errore mentre l'indirizzo di destinazione serve chiaramente ai router per inoltrare il messaggio per farlo arrivare al destinatario. In IPv4 i bit meno significativi dell'indirizzo destinazione indicano l'indirizzo della sottorete mentre quelli più significativi indicano il numero di host sotto quella rete. II TTL serve per evitare che errori nella trasmissione facciano girare messaggi all'infinito; esso contiene un numero che indica i salti massimi per cui il datagramma rimane memorizzato nel router prima di essere cancellato; il campo riservato al TTL e di 8 bit, quindi un datagramma può fare al massimo dagli 0 ai 255 salti a seconda di come viene impostato il TTL. Dopo aver cancellato il datagramma viene mandato al mittente un messaggio di errore. La lunghezza del datagramma è: - al minimo 20 byte (la lunghezza minima dell'header) - al massimo 2^16 byte. Il datagramma potrebbe quindi essere maggiore alla MTU (1500 byte): in questo caso si ricorre alla frammentazione. Per andare ad analizzare gli altri campi dobbiamo parlare proprio della frammentazione; Frammentazione Il processo consiste, ad esempio, nel ricevere un datagramma in ingresso e mandarne due in uscita; chi riceve deve necessariamente riassemblare i frammenti ricevuti. Per ogni frammento vi e un identificatore, che serve per riconoscere i pezzi in cui e stato diviso il datagramma e rimetterli insieme. L'ordine con cui dovrò riassemblare i pezzi e specificato dall'offset di frammentazione: Il primo frammento ha offset 0, il secondo 1 e così via, l'ultimo ha offset 1499 (la MTU-1). Se il flag frammentazione è uguale a 1 vuol dire che quel datagramma è stato frammentato; i frammenti hanno dimensione minima variabile che però deve essere un multiplo di 8 byte. La frammentazione e soprattutto il riassemblaggio hanno costo molto alto perché bisogna mantenere i frammenti di messaggi sul buffer RX finché non arriva il messaggio completo e ovviamente finché un router non ha terminato tale operazione non può inoltrare il messaggio Il router successivo. Header IPv6 La versione e ovviamente la 6, a questo campo sono riservati 4 bit: 0110. La classe di traffico è simile a l'identificatore di servizio dell'header IPv4. Permette di gestire le code by priority assegnando ad ogni pacchetto una classe di priorità rispetto ad altri pacchetti provenienti dalla stessa sorgente. Viene usata nel controllo della congestione. L'etichetta di flusso indica i datagrammi dello stesso flusso. L'header IPv6 ha dimensioni fisse (40 byte), mentre possono variare quelle del payload, identificate dal campo Lunghezza Payload. L'header di alto livello può contenere sotto header incapsulati; questo campo include anche option di IPv4; questo sopperisce anche al fatto che un header abbia lunghezza fissa e non variabile. In IPv6 il TTL e stato sostituito dal hop limit, un valore che viene decrementato ad ogni hop e una volta che arriva a 0 cancella il messaggio. L'indirizzo Sorgente e quello di Destinazione sono di 128 bit; sono talmente grandi che con datagrammi IPv6 non necessitano di NAT e Port Forwarding. Altri Dettagli La frammentazione non è più gestita dai router ma dal mittente, quindi vengono eliminati tutti i campi ad essa riguardanti. Il checksum è eliminato perché il controllo di integrità e gestito dal livello datalink attraverso il CRC-32, che controlla anche il payload del messaggio (oltre che l'header) e con maggiore precisione. Ci possono essere piu header in un datagramma: i router lavorano sul primo header IPv6 scalando HOP limits; una volta arrivati al receiver vengono elaborati anche gli altri header che sono quindi di tipo end-to-end: i router non guardano gli header interni e in questo modo si aumenta la privacy. Se l'header di alto livello contiene: - IPv6, vuol dire che nel suo payload vi e un altro header; - TCP o UDP, vuol dire che nel suo payload vi e il messaggio da trasportare e viene indicato attraverso quale protocollo. In questo momento della storia dell'informatica siamo in un periodo di transizione da IPv4 a IPv6, quindi: - alcune macchine supportano IPv4, - alcune macchine supportano IPv6. Poniamo il caso che i due host che comunicano siano IPv6 ma un router intermedio sia IPv4: se un router supporta solo IPv4 e riceve un datagramma IPv6, lo incapsula in un datagramma IPv4, mettendolo nel suo payload; a questo punto, se il ricevente supporta IPv6 lo "scarta", rimuovendo l'header IPv4; questo processo e detto tunneling. Se invece, uno dei due Host è IPv4, l'altro si deve adattare. Per passare da IPv4 a IPv6 basta aggiungere una serie di zero davanti all'indirizzo fino al completamento di esso, un'abbreviazione di tale operazione e l'inserimento di una coppia di ":" prima dell'indirizzo IPv4. esempio: 192.168.1.22 ----- > :: 192.168.1.22 In questo modo IPv6 risulta retrocompatibile con IPv4, in un processo IPv4-mapped address. Indirizzi IP Ogni header IP abbiamo visto che contiene: indirizzo sorgente: identifica il mittente, indirizzo destinazione: serve per instradare il messaggio. Gli indirizzi IP sono divisi in: Subnet Host Nei vecchi protocolli Subnet e Host erano divise in classi: Classe A 1 byte per la Subnet 3 byte per l’Host Classe B 2 byte per la Subnet 2 byte per l’Host Classe C 3 byte per la Subnet 1 byte per l’Host Nei nuovi protocolli viene utilizzata la netmask, che ha gli stessi bit dell’indirizzo e: la parte composta da uni corrisponde e identifica la subnet, la parte composta da zero corrisponde e identifica l’host. Per esempio: 1111111100000000000000000000000000000000 i primi 8 bit sono per la subnet, gli altri 24 per l’host Indirizzi Privati Il protocollo IP, specialmente nella versione 4, non poteva supportare l’assegnazione di indirizzi IP per tutti gli host delle reti private. Esistono quindi degli indirizzi privati, utilizzabili in tutte le LAN, che poi attraverso meccanismi di NAT e Port Forwarding vengono convertiti per non creare conflitti sulla rete. Gli indirizzi privati sono usati nelle reti locali private. Essi si dividono in 3 classi: Classe A 10.x.x.x (255.0.0.0) Classe B 172.16.x.x (255.240.0.0) Classe C 192.168.x.x (255.255.0.0) Questi indirizzi servono per gestire le reti locali e gli utenti possono accedervi senza richiedere il permesso al provider (IANA). NAT Gli indirizzi privati non sono instradabili dai router, se un router si vede arrivare un indirizzo che inizia con 10 (indirizzo di classe A) o con 192.168 (indirizzo di classe B) non lo instrada all’esterno; questo perché in giro per il mondo ci possono essere device con gli stessi indirizzi privati sotto diverse reti locali. Per mandare messaggi da reti locali a altre reti locali si utilizza il NAT, una tecnica che traduce indirizzi privati (quelli che abbiamo nelle reti locali) in pubblici prima di essere instradati nella rete. Il dispositivo che esegue il NAT ha due interfacce di rete quindi ci permette di comunicare dall’interno della subnet sione ambo i lati. Il dispositivo che effettua la conversione è un router (detto di frontiera) con un indirizzo detto Default Gateway e appunto trasforma un indirizzo IP privato in uno pubblico. Esempio: La rete utilizza un indirizzo privato 10.20.30.40 e uno pubblico 12.12.12.12 Se A, all’interno della rete locale, volesse comunicare con B che si trova all’esterno dovrà mandare un datagramma che avrà come indirizzo sorgente l’indirizzo privato di A (10.10.10.10) e come indirizzo destinazione l’indirizzo pubblico di B (11.11.11.11). A questo punto il NAT andrà a tradurre l’indirizzo sorgente in quello pubblico ovvero quello del NAT (12.12.12.12) mentre manterrà invariato quello di destinazione. Se B volesse rispondere metterà nel datagramma l’indirizzo destinazione del NAT di A e quando il messaggio arriverà a destina