Berkeley Socket La programmazione dei Berkeley Socket per esempi.

Dimensione: px
Iniziare la visualizzazioe della pagina:

Download "Berkeley Socket La programmazione dei Berkeley Socket per esempi."

Transcript

1 Berkeley Socket La programmazione dei Berkeley Socket per esempi Ing Alessio Palma

2 Note legali e licenza d'uso Questo documento non è assolutamente da considerarsi di pubblico dominio, viene concessa la libera diffusione ed il suo uso gratuito solo per fini non commerciali e fin tanto che questa nota di copyright è lasciata inalterata La riproduzione parziale o totale, come il suo uso per fini commerciali o la sua ridistribuzione in qualsiasi forma e qualsiasi supporto possibile senza l'autorizzazione scritta della Know How & Technologies sas, la rimozione della nota di copyright, il riuso parziale dei contenuti dell'opera senza autorizzazione scritta della Know How & Technologies sas è da considerata violazione dei diritti d'autore e come tale è soggetta a persecuzione ai fini della legge dello stato italiano ed internazionale Know How & Technologies sas Non è da ritenersi assolutamente responsabile per danni a persone o cose che il seguente documento può provocare direttamente o indirettamente Tutti i marchi registrati sono proprietà dei rispettivi proprietari L'ultima versione di questo documento può essere scaricata dalla URL http//wwwkhetnet/libreria/networking/linux/berkeley/ E' possibile ricevere automaticamente gli aggiornamenti, che saranno man mano caricati on line, inviato una a info@khetnet indicando come oggetto Berkeley Socket Data ultima revisione 22/07/ , 2002, 2003 Know How & Technologies sas PIVA Viale Unità D'Italia 6D Chieti Scalo Tutti i diritti riservati

3 Berkeley's sockets Un socket, punto terminale della comunicazione, è in realtà l'astrazione di due differenti punti terminali uno per le trasmissioni connection oriented (protocollo TCP); uno per le trasmissioni connection less (protocollo UDP) Ovviamente a differenti metodi di trasmissione corrispondono differenti modi di funzionamento del socket stesso Se le API sono astratte dal protocollo, la specializzazione per il protocollo (leggi scrittura di nuove API basate su quelle disponibili) è a carico del programmatore Ad una maggiore flessibilità, grazie alla astrazione, corrisponde un maggior carico di lavoro per il programmatore Questo non è sempre negativo, dipende da cosa si desidera fare I moderni linguaggi di programmazione ad oggetti, senza dubbio molto semplici da essere usati, hanno unapproccio totalmente differente alla programmazione di rete Nella OOP (leggi Java) i socket, se possiamo ancora chiamarli così, sono sottoposti ad una specializzazione estrema ed è esattamente il contrario di quanto avviene nelle classiche Berkeley's sockets Due approcci differenti a seconda del tipo di linguaggio utilizzato e purtroppo sono così differenti che non si può fare a meno di trattare entrambi Di certo per un programmatore pensare ai socket TCP come ai telefoni gli semplifica non poco la vita, qualcosa si simile si può dire per i socket UDP, dove il modello del sistema postale è quanto di meglio si avvicina alla loro realtà Fare corrispondere ai nomi delle funzioni, che il programmatore userà, gesti della sua vita quotidiana, soprattutto per i principianti è una bella semplificazione mentre per i professionisti significa avere un codice leggibile e che si commenta da solo, il che non è affatto male Cosa è un socket nella realtà fisica è molto semplice da dirsi è un buffer di memoria a cui è associato uno stato Mentre l'interfaccia di rete è accessibile solo dal sistema operativo, la memoria del socket è accessibile anche dal software applicativo e questo significa che viene allocata nella memoria utente La preparazione di un socket è a carico del sistema operativo che registra in sue tabelle sia l'identificativo del socket che del programma che lo ha aperto Possono esserci più programmi con più socket aperti, se sulla macchina sono presenti più interfacce di rete (leggiindirizzi IP) il programma può scegliere anche quale/i interfaccia/e associare il suo socket Il numero di porta, ma si tratta di una cattiva italianizzazione di port number poiché in inglese port significa Porto e non porta (door), alla buona e per ora identifica il programma che ha aperto il socket Quando arriva un pacchetto dati su una interfaccia di rete il sistema operativo lo rileva e controlla a chi è destinato destinato; se c'è un socket con il numero di porta indicato nel pacchetto ed il socket usa lo stesso protocollo del pacchetto, il sistema operativo copia i dati nel buffer dell'utente e ne aggiorna lo stato Concettualmente semplice, trova complicazioni inverosimili se il protocollo è TCP Tra il porre i dati del buffer e chiedere al sistema operativo di spedirli sono presenti sofisiticati meccanismi che sono nascosti all'utente, questo è proprio quello che l'incapsulamento e la stratificazione del software di rete (stack) impone Volendo fare di testa propria è possibile avere accesso direttamente al socket in modalità RAW per preparare pacchetti completamente personalizzati, ovviamente non tutti i software possono agire in questo modo ed occorrono dei privilegi Così si spiegano programmi quali che sono gli sniffer o altre amenità simili che procurano solo problemi, se si riesce ad aggirare lo stack di rete possiamo agire anche sulle intestazioni che accompagnano i dati e che di norma sono a carico del sistema operativo Il vostro primo programma di rete Credo di aver capito che per i neuroni, mattoni fondamentali dei cervelli umani, l'apprendimento per esempi sia la cosa migliore che possa capitargli, ragion per cui tutto il trattato è fatto per esempi L'ambiente di sviluppo per ora è il C, successivamente vedremo anche la programmazione di rete in Java Nel Perl e quindi in PHP le cose sono molti simili al C classico ed il networking in questi linguaggi praticamente è un bonus per chi conosce il C (leggi si impara senza sforzi aggiuntivi) Gli esempi riportati sono stati realizzati e testati sul FreeBSD, sebbene Linux vada per la maggiore BSD è pur sempre BSD e guarda caso i socket sono proprio BSD Esempio 1 Questo semplice programma apre e chiude un socket UDP Include a parte, che sono tutti nuovi per molti di voi, osserviamo tre dei quattro passi importanti che rappresentano lo scheletro di ogni applicazione di rete allocazione del socket; collegamento dello stesso ad un indirizzo IP ed una porta; trasmissione e ricezione dati; rilascio o chiusura del socket La trasmissione e ricezione di dati ha modalità differenti a seconda del protocollo usato, approfondiremo il problema in seguito Ad allocare un socket si fa presto lesto con la funzione socket() che accetta tre parametri dominio, tipo e protocollo

4 Nel dettaglio Il dominio per ora è solo AF_INET perchè siamo soprattutto interessati a comunicare attraverso Internet; il tipo del socket indica il protocollo che sarà usato e per adesso è solo UDP (SOCK_DGRAM); il terzo parametro, con il nome ingannevole di protocollo, per ora vale zero ed anche in futuro non è che sentirete il bisogno di cambiarlo Per togliere la curiosità serve solo nel caso in cui il parametro tipo non è sufficiente da solo a specificare che protocollo usare per il socket Per il collegamento del socket ad un indirizzo ip (ovvero interfaccia di rete) e quindi ad un numero di porta, siamo obbligati ad usare la struttura sockaddr_in La funzione htons host to network (int) short converte i numeri interi dal formato macchina, che può essere big endian (motorola, HP, IBM) o little endian (Intel, DEC, VAX), in quello usato nella rete (SEMPRE big endian) ATTENZIONE! Se vi scordate di usare la funzione htons, il vostro codice rischia erroneamente di funzionare su alcune macchine Avete letto bene rischia di funzionare e voi rischiate di passare ore a debbugare sulla vostra macchina un errore che apparentemente non c'è Ma che sia colpa del computer del cliente? ;-) Anziché specificare un indirizzo IP ben preciso, l'uso della costante INADDR_ANY significa su tutte le interfacce disponibili Nell'esempio l'assunzione è che sia presente una sola scheda di rete per tanto questa costante non è un problema, in seguito complicheremo il discorso La funzione bind collega il socket alla porta ed all' interfaccia desiderata, inutile commentare questa funzione, si usa come indicato nel listato La cosa importante da osservare nell'uso della bind è la possibilità che il numero di porta sia già occupato, controllate sempre il codice che restituisce Il problema di scegliersi un numero di porta non è tanto di chi trasmette, ma di chi deve ricevere Il web server si aspetta che le connessioni arrivino sulla porta 80 ed è lì che passa il tempo ad ascoltare eventuali richieste Se non dovete ascoltare quindi va bene un qualsiasi numero di porta e potete usare il valore 0 (zero), penserà il sistema operativo ad assegnarvi la prima porta disponibile sul sistema (rigorosamente > 1024) La chiusura di un socket è abbastanza diretta, un semplice close per ora risolve il problema Questo esempio, che ci avvicina all'affascinante mondo della programmazione di rete, non fa assolutamente nulla di interessante perchè non sono trasmessi o ricevuti dati Download >>> Esempio1c Esempio2 Iniziamo a vedere più da vicino la trasmissione e ricezione di dati usando il protocollo UDP Il nostro esempio è diviso in due parti un listener che ascolta ad una determinata porta e li stampa sul video; un sender che invia i dati verso il listener Listener Questo semplice programma è simile all'esempio uno, la differenza sta solo nella presenza della funzione recvfrom(), vera novità di questo sorgente Quello che accade quando viene eseguita questa funzione è veramente molto semplice dal socket indicato sono prelevati al più 255 bytes, se ne sono disponibili meno la variabile bytes_read indicherà quanti ne sono stati letti; se non sono presenti dati nel socket la funzione blocca il programma e li attende Si può anche rendere il socket non bloccante, ma questo per ora non ci interessa La funzione recvfrom necessita di una struttura del tipo sockaddr_in in cui inserirà l'indirizzo del mittente, questo sarà composto dal numero di porta e indirizzo IP del socket da cui il datagramma è partito; se dobbiamo rispondere al mittente avremo per forza bisogno di questo indirizzo Il quarto parametro serve per impostare alcuni flag, per ora ci basterà sapere che va posto a zero * Programmazione di rete * * Semplicissimo listener UDP * * lanciare questo programma PRIMA di avviare il sender UDP * * Ing Alessio Palma * * $Id ex2-recvc,v /05/ alessiop Exp $ * #include<stdioh> #include<sys/typesh> #include<netinet/inh>

5 #include<arpa/ineth> #include<netdbh> #include<sys/socketh> main() { int socket_descriptor, i, bytes_read; struct sockaddr_in socket_address, remote_address; socklen_t addr_len; char buffer[ 255 ]; if ( (socket_descriptor = socket(af_inet, SOCK_DGRAM,0)) < -0 ) { printf("non posso aprire il socket\n"); exit(1); socket_addresssin_family = AF_INET; socket_addresssin_addrs_addr = htonl(inaddr_any); socket_addresssin_port = htons( 6601 ); if ( bind( socket_descriptor, (struct sockaddr *) &socket_address, sizeof(socket_address) ) ) { printf("non posso collegare il socket\n"); exit(1); for (i=0; i<20; i++) { * SE NON SONO PRESENTI DATI IL PROGRAMMA LI ATTENDE(SOCKET BLOCCANTI) bytes_read = recvfrom( socket_descriptor, buffer, 255,0, (struct sockaddr *) &remote_address, &addr_len ); printf("%d bytes %s strt ind %d\n", bytes_read,buffer,addr_len); close(socket_descriptor); Download >>> ex2-recvc Sender Il secondo listato che compone il nostro esempio è un sender UDP, che invia dei dati verso la porta su cui il listener dovrebbe essere in ascolto e che ovviamente gli deve essere nota Questo listato differisce e non di poco dal precedente innanzi tutto sono definiti due indirizzi in due strutture sockaddr_in differenti, uno è per il socket che il programma apre per poter comunicare e l'altro ha le coordinate del socket di destinazione; successivamente è utilizzata la funzione sendto() per inviare i dati verso il listener, questa è la funzione complementare alla recvfrom(); La funzione sendto necessita di una struttura sockaddr_in in cui deve essere specificato di volta in volta le coordinate del socket di destinazione, infatti UDP al contrario del protocollo TCP è connection-less Tornado ai sorgenti possiamo capire che sendto invia i dati e basta e che non c'è nessuna garanzia che la destinazione li riceva, ragion per cui questa funzione non è mai bloccante al contrario della recvfrom() remote_addresssin_family = AF_INET; remote_addresssin_addrs_addr = htonl(inaddr_any); remote_addresssin_port = htons( 6601 ); if ( bind( socket_descriptor, (struct sockaddr *) &socket_address, sizeof(socket_address) ) ) { printf("non posso collegare il socket\n"); exit(1);

6 for (i=0; i<20;i++) { strcpy( buffer, "messaggio da inviare posso allungarlo"); sendto( socket_descriptor, buffer, strlen(buffer)+1,0, (struct sockaddr *) &remote_address,sizeof(remote_address) ); Download >>> ex2-sendc Per inserire i listati nel vostro editor basta eseguire un semplice copia-incolla prelevando il codice da questa pagina, attenzione che il solo Linux o *BSD non basta ed è necessario aver caricato sulla macchina anche l'ambiente di sviluppo Socket non bloccanti E' possibile impedire al programma di bloccarsi sulla chiamata recvfrom() con dei semplici accorgimenti si imposta il socket come non bloccante oppure si controlla se ci sono dati in attesa prima di eseguire la recvfrom() Esempio 3 In questo caso il programma controlla la presenza di dati nel socket e, se sono presenti, la successiva recvfrom() li preleverà senza il rischio di bloccare il programma Il controllo, per la verifica della presenza di dati nel socket, è eseguito dalla funzione select() abbastanza criptica da usarsi; infatti essa richiede l'uso delle strutture timeval e fd_set nonché di alcune macro La select() può essere usata con un qualsiasi file descriptor, non solo i socket Questa sua portabilità tra vari ambiti è dovuta alla filosofia che regna in *nix ogni cosa è file, socket compresi; non meravigliatevi se alcuni programmatori usano read() e write() per gestirli Con la funzione select() si può controllare in un colpo solo se sul canale di I/O sono presenti dei dati; se il canale di I/O è pronto per eseguire una operazione di scrittura; se si è in stato particolare (un giorno capirete) Grazie all'uso della struttura timeval la select può anche attendere un certo intervallo di tempo prima di tornare al programma chiamante, nel listato è mostrato come caricare la struttura timeval con il numero di secondi e microsecondi (1sec = usec) Se il tempo è zero la funzione non rimane in attesa e la sua esecuzione è immediata, ovviamente questa potrebbe sembrare la scelta migliore, ma non sempre lo è Se vi chiedete a cosa serve attendere i dati anziché bloccare il programma sulla recvfrom(), provate a farvi un'altra domanda come si fa a gestire i timeout in modo semplice ed efficiente? Per i tre controlli possibili su un file/socket_decriptor (leggi entità contenente lo stato del canale di comunicazione) servono tre strutture fd_set differenti per le tre classi di controllo Poiché nel caso in esame siamo interessati solo a rilevare la presenza di dati viene definita solo la fd_read; questa variabile sarà azzerrata dalla macro FD_ZERO; associata al socket con FD_SET; caricata con lo stato corrente grazie alla select(); controllata con FD_ISSET Non mi rimane che darvi la buona visione del sorgente #include<sys/timeh> fd_set fd_read; * timeval * ci serve per indicare quanto tempo deve durare il controllo * sullo stato del socket

7 * struct timeval timeout; * Inizializziamo le struttura del timeout * nessuna attesa PS usec = microsecondi non milli timeouttv_sec = 0; timeouttv_usec = 0; * Inizializzazione della struttra File Description FD_ZERO( &fd_read ); for (i=0;i<i;) { * Inizializziamo le struttura del file descriptor * * FD_SET associa la struttura al nostro socket FD_SET ( socket_descriptor, &fd_read); * Interroga il socket * Attenzione al +1 if ( select( socket_descriptor+1, &fd_read, 0, 0, &timeout) == -1 ) { printf("select error\n"); exit(1); * Ci sono dati? * * SI allora leggiamoli, recvfrom non si bloccherà if ( FD_ISSET( socket_descriptor, &fd_read) ) { bytes_read = recvfrom( socket_descriptor, buffer, 255,0, (struct sockaddr *) &remote_address, &addr_len ); Download >>> ex3-recvc Esempio 4 Adesso evitiamo il blocco del programma non usando la select(), ma usiamo direttamente dei socket non bloccanti Il listato mostra come le cose sono molto più semplici, tutto viene realizzato grazie alla fnctl() che imposta per il file indicato le modalità di funzionamento volute #include<fcntlh>

8 * Vogliamo un socket che non si blocchi fcntl( socket_descriptor, F_SETFL, O_NONBLOCK); * Ci sono dati? * * SI allora recvfrom restiusce un numero!= -1 * bytes_read = recvfrom( socket_descriptor, buffer, 255,0, (struct sockaddr *) &remote_address, &addr_len ); if ( bytes_read!= -1 ) { printf("count%u bytes%d %s\n", counter, bytes_read, buffer); i++; Download >>> ex4-recvc Indirizzi IP Se c'è una cosa di cui si sente presto l'urgenza di fare nella programmazione di rete, è passare l'indirizzo IP al programma che si sta scrivendo, in questa sede vedremo come utilizzare alcune funzioni per convertire l'indirizzo IP (passato come stringa) in qualcosa che sia direttamente gestibile dalle API di Berkeley Supponiamo di voler realizzare una semplice soluzione di chat per inviare e ricevere messaggi, possiamo facilmente modificare i programmi scritti in precedenza e, per rendere le cose più semplici, per ora ogni partecipante disporrà di due moduli differenti un listener per la ricezione; un sender per l'invio Come fondere i due programmi lo vedremo in seguito, di certo non sarà cosa difficile poiché sappiamo già usare i socket in modalità non bloccante Esempio 5 In questo esempio il sender UDP è stato modificato in modo da poter ricevere sulla riga di comando tre parametri L'indirizzo IP del computer di destinazione; il numero di porta; il messaggio L'indirizzo IP viene passato come primo argomento, attenzione perchè per ora il software non controlla la correttezza dello stesso Il numero di porta deve essere passato come secondo argomento e non può essere inferiore a 1024, le porte da 0 a 1023 sono particolari e non riservate agli utenti comuni Il messaggio deve essere racchiuso tra virgolette se contiene anche degli spazi L'unica difficoltà in questo programma sta nel caricamento delle coordinate di destinazione, purtroppo non è intuitivo da capirsi Il numero di porta può essere semplicemente convertito da stringa a numero intero usando la funzione atoi() (ascii to integer) e quindi nel formato di rete con htons(), mentre l'indirizzo IP deve essere caricato direttamente nella struttura sockaddr_in usando la funzione inet_aton() Come si nota nel listato, è necessario porre a zero 8 bytes della struttura dove l'indirizzo IP è stato caricato Perchè azzerare 8 bytes? Perchè poi proprio 8 bytes? Per compatibilità all'indietro con la vecchia sockaddr (senza _in ) E' facile fare il cast di un puntatore se le strutture hanno la stessa dimensione (ragion per cui sono 8 bytes) e poiché i dati non sono posti a zero dopo la loro allocazione e necessario azzerarli a mano * Converte e copia l'indirizzo IP direttamente nella struttura inet_aton( argv[1], &remote_addresssin_addr ); memset(&remote_addresssin_zero, 0x00, 8);

9 Download >>> ex5-sendc Il programma listener non ha subito nessuna modifica essenziale, adesso il numero di porta su cui deve rimanere in ascolto deve essere indicato come parametro (e deve essere indicato!) La parte interessante di questa nuova versione risiede nel codice che stampa l'indirizzo IP e il numero di porta da cui il messaggio è arrivato come possiamo notare la funzione inet_ntoa() interviene su tutto il campo sinaddr come la sua analoga inet_aton() Fate attenzione perchè per indicare una qualsiasi interfaccia si deve intervenire sul sottocampo sinadds_addr Spesso si commette facilmente l'errore di scrivere socket_addresssin_addr= htonl(inaddr_any) invece di socket_addresssin_addrs_addr = htonl(inaddr_any) Se avete più di una scheda di rete e provate a porre il listener in ascolto su una porta che pensate libera, ma il programma non riesce nell'intento allora potrebbe essere occupata su un'altra interfaccia Quando si usa ADDR_ANY la porta indicata deve essere libera su TUTTE le interfacce del vostro sistema void print_message() { if ( bytes_read!= -1 ) { printf("msg da %s porta %d>>", inet_ntoa(remote_addresssin_addr), ntohs(remote_addresssin_port)); printf("%s\n", str_message); Download >>> ex5-recvc Scrivere un programma che usa la rete per comunicare è un bell'impegno, se non si presta attenzione alla robustezza presto lesto il software finisce per essere un problema per la sicurezza Implicitamente abbiamo fatto un importante legame i programmi robusti sono anche quelli sicuri; un programma è robusto quando rimane stabile in presenza di input o in condizioni di funzionamento non previste nella fase di progetto Esempi inserisco una stringa al posto di un numero in risposta ad un input; il programma viene mandato in esecuzione con meno memoria di quella che necessita Se il programma non si pianta, ma riesce ad evitare ed isolare il problema possiamo dire che è robusto, naturalmente bisogna anche vedere anche quanto è robusto Per illustrare più da vicino la problematica introdotta, modifichiamo il software già presentato altre volte in modo da inviare attraverso la rete strutture dati Se il programma in ascolto riceve un dump corretto delle strutture non ci sono problemi, ma se un utente malizioso invia ben altro il programma ricevente potrebbe iniziare ad accusare disfunzionamenti pericolosi anche per la sicurezza del sistema I due sorgenti (in realtà sono tre perchè è presente un file header comune ai due) non fanno altro che inviarsi una struttura composta di due campi di tipo stringa, il problema per la sicurezza è che qualcuno potrebbe inviare un pacchetto spazzatura con stringhe non terminate con 0x00 Ma si devono sempre trasmettere delle strutture (record)? Di certo ricevere qualcosa che è direttamente gestibile in C senza eseguire nessuna conversione non è male, così si utilizzano più di quanto si creda Il programmatore che vuole rendere il software robusto dovrebbe controllare sempre se i dati ricevuti sono conformi con i i limiti che il programma si attende; se i dati sono fuori range il pacchetto dovrebbe essere scartato e l'anomalia inviata al file di log E le trasmissioni cifrate? Non vanno bene per proteggere i programmi? Se nessuno riesce a capire cosa viene inviato è difficile progettare un input fatto su misura per cercare di far cadere il software L'assunzione è vera e non esonera i programmatori dal controllare che i dati ricevuti siano negli intervalli consentiti; inviare pacchetti in cifra aiuta a rendere i programmi robusti, ma non li fa diventare tali I sorgenti mettono in luce anche un altro piccolo problema il programma ricevente termina se il programma mittente viene terminato, per implementare questa funzionalità si invia un messaggio che contiene la stringa quit Cosa accade se qualcuno tenta di fare eseguire un'operazione ad un programma con dei pacchetti pirata? Le operazioni critiche che portano il software a fare delle scelte dovrebbero seguire un protocollo ben preciso del tipo chiedere conferma oppure chiedere una pass opportuna Per rendere un programma sicuro bisogna renderlo robusto, nella programmazione di software di rete questo vale a maggior ragiore poiché tutti possono provare a inviarci il proprio input Riassumendo, i problemi ed i rischi dei programmi di rete possiamo porli in due classi cattiva interpretazione di dati (che a volte generano anche i buffer overflow); esecuzione di meccanismi mal protetti Naturalmente l'assunzione tacitamente fatta è che il supporto di rete del sistema operativo sia robusto, serve a poco realizzare software robusto se poi il SO non lo è

10 Programmi di rete e sicurezza Scrivere un programma che usa la rete per comunicare è un bell'impegno, se non si presta attenzione alla robustezza presto lesto il software finisce per essere un problema per la sicurezza Implicitamente abbiamo fatto un importante legame i programmi robusti sono anche quelli sicuri; un programma è robusto quando rimane stabile in presenza di input o in condizioni di funzionamento non previste nella fase di progetto Esempi inserisco una stringa al posto di un numero in risposta ad un input; il programma viene mandato in esecuzione con meno memoria di quella che necessita Se il programma non si pianta, ma riesce ad evitare ed isolare il problema possiamo dire che è robusto Per vedere più da vicino la problematica introdotta modifichiamo il software già presentato altre volte, in questo caso inviamo attraverso la rete strutture dati Se il programma in ascolto riceve un dump corretto delle strutture non ci sono problemi, ma se un utente malizioso invia ben altro il programma ricevente potrebbe iniziare ad accusare disfunzionamenti pericolosi anche per la sicurezza del sistema I due sorgenti (in realtà sono tre perchè è presente un file header comune ai due) non fanno altro che inviarsi una struttura composta di due campi di tipo stringa, il problema per la sicurezza è che qualcuno potrebbe inviare un pacchetto spazzatura con stringhe non terminate con 0x00 Ma si devono sempre trasmettere delle strutture (record)? Di certo ricevere qualcosa che è direttamente gestibile in C senza eseguire nessuna conversione non è male, così si utilizzano più di quanto si crede Il programmatore che vuole rendere il software robusto dovrebbe controllare sempre se i dati ricevuti sono entro i limiti che il programma si attende; se i dati sono fuori range il pacchetto dovrebbe essere scartato e dovrebbe essere anche inviato un messaggio di avviso al syslog E le trasmissioni cifrate? Non vanno bene per proteggere i programmi? Se nessuno riesce a capire cosa viene inviato è difficile progettare un input fatto su misura per cercare di far cadere il software L'assunzione è vera e non esonera i programmatori dal controllare che i dati ricevuti siano negli intervalli consentiti; inviare pacchetti in cifra aiuta a rendere i programmi robusti, ma non li fa diventare tali I sorgenti mettono in luce anche un altro piccolo problema il programma ricevente termina se il programma mittente viene terminato, per implementare questa funzionalità si invia un messaggio che contiene la stringa quit Cosa accade se qualcuno tenta di fare eseguire un'operazione ad un programma con dei pacchetti pirata? Le operazioni critiche che portano il software a fare delle scelte dovrebbero seguire un protocollo ben preciso del tipo chiedere conferma oppure chiedere una pass opportuna Per rendere un programma sicuro bisogna renderlo robusto, nella programmazione di software di rete a maggior ragiore poiché tutti possono provare a inviarci il proprio input Riassumendo, i problemi ed i rischi dei programmi di rete possiamo porli in due classi cattiva interpretazione di dati (che a volte generano anche i buffer overflow); esecuzione di meccanismi mal protetti Naturalmente l'assunzione tacitamente fatta è che il supporto di rete del sistema operativo sia robusto, serve a poco realizzare software robusto se poi il SO non lo è Download >>> ex6-recvc Download >>> ex6-sendc Download >>> esempih (da scaricare solo una volta e vale anche sia per il listener che per il sender) Buffer Overflow Un buon 70% di attacchi si realizzano con la tecnica del Buffer overflow, chi non ha mai sentito parlare di questo tipo di attacco ai servizi di rete? In questo paragrafo vedremo brevemente come funziona e come evitarlo Molte informazioni che viaggiano in rete non hanno una lunghezza fissa, per esempio le URL ed software che devono gestire questo tipo di informazioni sono scritti in C Consideriamo il codice riportato di seguito, come si osserva, una stringa a lunghezza variabile viene passata prima ad una funzione che presuppone che la sua lunghezza non sarà mai superiore ai 10 bytes Fin quando, come accade nella prima chiama, la lunghezza della stringa passata rimane nei termini stabili non ci sono problemi, ma sella seconda chiamata la funzione strcpy inevitabilmente sfora andando a scrivere i bytes in eccesso nella memoria consecutiva a quella allocata per il buffer #include <stdioh> #include <stringh> void funzionebacata(char *stringa) { char buffer[10]; strcpy( buffer, stringa); int main( int nargc, char **argv) { printf("attacco buffer overflow\n"); funzionebacata(" "); printf("lo stack non e' stato corrotto\n");

11 funzionebacata(" "); printf("questa riga non sara' mai stampata\n"); return 0; Putroppo la strcpy è eseguita all'interno di una funzione chiamata, il che significa che le variabili temporanee sono state allocate sullo stack Come molti sapranno, anche l'indirizzo di ritorno viene caricato sullo stack, quindi sforare con una strcpy potrebbe significare la sovrascrittura dell'indirizzo di ritorno Quando la funzione termina ed il processore preleva detto indirizzo dallo stack, da qualche parte torna, ma non dove dovrebbe! Per questo motivo un buffer overflow potrebbe tradursi nell'esecuzione di codice arbitrario, una volta che è possibile far saltare il processore dove vogliamo in teoria possiamo fargli fare quello che vogliamo Non necessariamente una stringa lunga provoca lo sforamento del buffer, a volte basta che venga omesso lo 0x00 utilizzato come terminatore di stringa strcpy parte e non si sa bene quando termina Per rendere il codice sicuro basta assicurarsi che in nessun modo si possa sovraccaricare il buffer allocato (leggi vettore di caratteri), per questo ci sono 2 varianti alla pericolosissima strcpy Se il problema non è lo zero finale è possibile usare strncpy Questa funziona copia esattamente n caratteri e purtroppo non assicura il terminatore La funzione strlcpy copia al più n caratteri e assicura la presenza del terminatore Questa funzione è stata introdotta in openbsd 24 ed è presente in FreeBSD fin dalla 33 #define MAXBUFFER 10 void funzionebacata(char *stringa) { char buffer[maxbuffer]; strlcpy( buffer, stringa,maxbuffer); Scrivere programmi di rete robusti Un programma, dolente o volente, alla fine deve fare i conti con il bug più grosso che esiste nel mondo informatico cioè l'utente Dotato di inventiva e genio creativo, l'utente riesce a trarre in imbarazzo il software più robusto attraverso l'immissione di input completamente illogici e scorrelati Una condizione necessaria affinché un programma sia robusto è che controlli in modo maniacale tutto quello che l'utente inserisce Il software presentato è la rielaborazione del listener (questa volta reso più robusto) e del sender rimasto quasi invariato Listener Come si osserva, nei listati è definito un nuovo tipo di pacchetto type_pacchetto_v2 che aggiunge nuovi campi a quelli esistenti; le nuove variabili sono volutamente poste in coda a quelle precedenti in modo da permettere ai vecchi software di continuare a funzionare anche con il nuovo formato di pacchetto In questa nuova versione il listener è stato reso molto più robusto innanzi tutto scarta i pacchetti che sono più corti di quanto dovrebbero essere e su quelli della "misura giusta" controlla il valore CRC e la versione del software mittente Se un utente malizioso volesse inviare un pacchetto che sia accettato e processato dal listener, deve necessariamente scoprire l'algoritmo usato per calcolare il CRC e questo già elimina una buona fetta di hacker Nel caso mostrato si è usata come checksum una semplice somma byte-per-byte, ma nulla ci impedisce di usare qualcosa di più sofisticato come MD5 applicato al messaggio e all'identificativo del software Se il pacchetto soddisfa tutti i requisiti, a scopo cautelativo, le stringhe devono essere terminate ponendo 0x00 in fondo ad esse; in questo punto del programma dovrebbero essere messi tutti i controlli che impediscono a dati critici di arrivare ai moduli più delicati del programma Nel caso in esame, l'unico fattore critico è l'uso della funzione printf che potrebbe dare non pochi problemi se la stringa non è terminata correttamente Download >>> ex7-recvc Download >>> ex8-recvc Download >>> esempih (da scaricare solo una volta e vale anche sia per il listener che per il sender) Sender Nella nuova versione, il sender è praticamente rimasto invariato, l'unica differenza è che gestisce il nuovo tipo di pacchetti Per evitare l'uso del make e del linker la funzione che computa il checksum è stata inserita nei

12 due sorgenti con un brutale copia ed incolla Anche se per ora tutto si risolve con una semplice invocazione del compilatore, presto si renderà necessario l'uso di strumenti un po' più evoluti quali il make ed RCS Download >>> ex7-sendc Fuoco alle polveri Ci sono quattro programmi da compilare ex6-sendc che invia i pacchetti nel vecchio formato; ex7-sendc che invia i pacchetti nel nuovo formato; ex8-recvc per ricevere e gestire i nuovi pacchetti; ex7-recvc il vecchio software di ricezione Primo esperimento il vecchio sender (ex6-sendc) invia pacchetti al vecchio e nuovo listener; la nuova versione di listener (ex8-recvc) scarta i pacchetti perchè non li riconosce Secondo esperimento il nuovo sender (ex7-sendc) invia pacchetti al vecchio e nuovo listener; la nuova versione (ex8-revc) e la vecchia del listener (ex7-recvc) accettano i pacchetti Dagli esperimenti eseguiti possiamo fare delle deduzioni su quanto è accaduto la nuova versione del listener filtra l'input e quindi è molto più robusto, ovviamente non è più compatibile con il vecchio formato di pacchetto usato in precedenza; la vacchia versione del listener è in grado di processare correttamente i nuovi pacchetti perchè legge dal socket tanti byte quanti ne servono per riempire la vecchia struttura La ragione di ciò è semplice Mentre nel caso del protocollo TCP si stabilisce un flusso continuo di dati ed il software applicativo non ha modo di sapere se sono arrivati con un singolo pacchetto o meno, nel protollo UDP il traffico dati è "quantizzato" ogni singolo datagramma porta una porzione ben definita di dati ed il software applicativo è in grado di agire su ogni singolo quanto Se il listener avesse usato il protollo TCP, letti i bytes che servono per riempire la struttura dati utilizzata, quelli in eccesso non letti sarebbero stati presenti per la lettura successiva e sarebbe stato compito del software cercare l'inizio della struttura precedente Usando il protocollo UDP se nel socket sono presenti meno dati di quelli richiesti il valore bytes_read sarà minore del dovuto; se il socket contiene più dati di quelli richiesti, letti quelli voluti gli altri sono scartati; inoltre ad ogni invocazione di recvfrom si esamina un differente datagramma UDP Grazie a questi meccanismi, la vecchia versione del listener può prelevare dal socket solo i dati necessari a riempire la vecchia struttura type_pacchetto; ovviamente non si tratta di un buon esempio di compatibilità all'indietro, ma illustra qualcosa che è possibile fare in modo semplice solo con UDP (!) Il socket BSD che è stato indicato come un buffer di memoria è in realtà qualcosa di un po' più complesso Non si tratta di un buffer nel senso classico del termine, ma di una lista di buffer; questi sotto-buffer, tecnicamente chiamati mbuf, hanno una dimensione fissa Quando si imposta la dimensione di un socket, in realtà si interviene sul numero di mbuf; lo spazio allocato in un socket non è riferito a quanta memoria è stata allocata per il socket, ma quanta ne deve essere disponibile negli mbuf e quindi si imposta indirettamente il loro numero I datagrammi ricevuti finiscono all'interno degli mbuf Quando si esegue una recvfrom il primo mbuf della lista viene letto e subito dopo scartato Occorre prestare attenzione a come un socket è fisicamente fatto perché spesso ci si trova di fronte a comportamenti anomali, soprattutto per chi ignora la vera struttura dello stack di rete Supponiamo che la dimensione di un mbuf sia fissata a 512 bytes, se la dimensione del socket è stata portata a 4096 bytes rischiate che si riempia con soli 8 datagrammi di 1 bytes! Infatti per allocare 4096 bytes servono 8 mbuf, quindi il socket potrà ricevere al più 8 datagrammi; non c'è da stupirsi se soli 8 bytes provocano il riempimento di un buffer da 4096bytes Gethostbyname ed il vostro primo programma di chat Dopo aver esaminato singolarmente i singoli moduli per l'invio e la ricezione dei pacchetti dati, il listato presentato questa volta li ingloba e li riassume in un unico programma, che permette a due utenti di dialogare in rete Il software è facilmente estendibile a più utenti e, visto il particolare protocollo utilizzato, può facilmente implementarsi la trasmissione multicast A tempo debito vedremo come fare trasmissioni multicast e non solo unicast Sebbene si utilizzi il protocollo UDP, nel software presentato è stato introdotto un rudimentale sistema di ACK basato su numeri di sequenza, concettualmente simile a quello utilizzato in TCP Grazie a questo sistema di ACK i corrispondenti possono sapere se un pacchetto è andato eventualmente perso o è arrivato fuori sequenza; la condizione di errore è data dalla mancata corrispondenza del numero di sequenza ricevuto e quello atteso Con il procollo UDP la ritrasmissione dei dati, al contrario del TCP, è a carico degli utenti come

13 lo è il riordino dei pacchetti ricevuti In questo programma è finalmente illustrato anche come verificare se il nome dell'host è un indirizzo IP o una registrazione del DNS, quindi come agire di conseguenza per poter risolvere l'hostname ed ottenere l'indirizzo; per il resto non si è detto nulla di nuovo a parte forse l'uso delle librerie ncurses Anche se non sono pertinenti con la programmazione di rete, queste librerie freeware, ma non open source, sono lo standard in ambienti *nix per la gestione dei terminali Se qualcuno ha in mente di aumentare le sue competenze su queste architetture, ncurses è un MUST Se i gestionali sono maschere collegati ad un database, spero che nessuno se la prenda per questa affermazione, ncurses è quanto di meglio esista per la produzione di maschere per l'interazione con l'utente finale Approfitto per fare notare come le API di Berkely sono alquanto astratte e male indicate per i sistemi embed o Real Time, la struttura sockaddr_in può essere meglio definita se la compatibilità con la sockaddr non è necessaria Tutte le Api dei Socket di Berkeley sono predisposte per usare la più astratta sockaddr e la sockaddr_in è solo una comodità che poggia su sockaddr per la gestione degli indirizzi del protocollo TCP/IP La vecchia sockaddr è definita come struct sockaddr { u_short sa_family; char sa_data[14]; ; mentre la sockaddr_in, per forza di cose, porta con se un inutile campo di 8 bytes, chiamato sin_zero, necessario per mantenere la stessa dimensione della sockaddr struct sockaddr_in { short sin_family; u_short sin_port; struct in_addr sin_addr; char sin_zero[8]; non usati in AF_INET ; Comunque sia, ogni volta che si utilizza la struttura sockadd_in, è sempre necessario azzerare gli 8 bytes inutilizzati Nel sorgente la riga memset(&remote_addresssin_zero, 0x00, sizeof(remote_address)/sizeof(char)) azzera l'intera struttura, i campi che sono utilizzati saranno riempiti con i valori appropriati in seguito Sempre riguardo le strutture, la funzione gethostbyname() restituisce una puntatore ad hostent così definita struct hostent { char *h_name; char **h_aliases; short h_addrtype; short h_length; char **h_addr_list; #define h_addr h_addr_list[0] ; h_name è il nome ufficiale dell'host h_aliases è un vettore che contiene nomi alternativi per l'host h_addrtype indica il tipo di indirizzo restituito per ora vale AF_INET h_lenght indica la lunghezza in byte dell'indirizzo h_addr_list è una lista di puntatori agli indirizzi del server, gli indirizzi sono memorizzati nel formato d rete h_addr è un define che punta al primo elemento della lista ed è quello che serve per poter accedere presto lesto al primo indirizzo dell'host Nel programma la riga remote_addresssin_addrs_addr = ((struct in_addr *)(host->h_addr))->s_addr ; carica nella struttura remote_address il primo indirizzo IP che è stato restituito dal server DNS Criptica nevvero? Con un po' di osservazione si nota che

14 remote_addresssin_addr è una struttura in_addr mentre (struct in_addr*)host->h_addr è un puntatore a detta struttura Il cast può anche essere evitato usando una memcpy(), ma quella usata è la soluzione più elegante per copiare l'indirizzo IP da hostent in addr_in Per controllare se il nome dell'host è già un indirizzo IP si è utilizzato la funzione inet_addr(), che, nel caso in cui processa un indirizzo IP malformato, restituisce la costante INADDR_NONE; nel programma se il nome dell'host è un indirizzo IP valido, allora si cerca di risolverlo usando gethostbyname(), la quale restituisce un puntatore a NULL invece di un puntatore alla struttura hostent se l'interrogazione del DNS fallisce Sebbene UDP va più che bene per inviare dei semplici messaggi, nella eventualità di voler supportare anche il trasferimento di file tra gli utenti che chattano si rende necessario l'uso del protocollo TCP, voler gestire a mano tutto quello che TCP fa si rischia di riscriverlo da capo UDP ed osservazioni sui sistemi Embed e Real Time Esistono due soluzioni per rendere i dispositivi aperti alla comunicazione, relativamente al procollo TCP/IP una completamente software, l'altra completamente hardware L'uso di soluzioni puramente software è la scelta migliore, sia perchè costa meno, sia perchè il software può essere modificato e non sono richiesti ulteriori chip sullo stampato Adesso, con i tempi che corrono, hanno tutti fretta di collegarsi con il web al dispositivo, sia esso un frigorifero oppure in impianto hi-fi Questo innaturale bisogno significa dover implementare il TCP o comprare chip in grado di supportarlo, vale la pena di tanto disturbo solo per poter usare il protocollo http? Io suggerisco un altro tipo di soluzione che si realizza con il protocollo UDP e le applicazioni Java, in questo modo non si perdono funzionalità ed il software Java, che funziona ovunque, può interagire con il dispositivo meglio di come potrebbe mai fare una pagina HTML Con UDP sono disponibili praticamente tutti i servizi di rete di interesse DNS, TFTP, trasmissione Multicast, SNMP, boop, DHCP etc Manca solo un sistema per trasmettere dati in modo affidabile, ma per questo può benissimo scriversi un po' di codice che usi in modo saggio UDP, il che è ben differente da dover scrivere il supporto per TCP Per quanto riguarda la sicurezza delle trasmissioni basate su UDP è facile inventarsi sistemi proprietari per la cifratura delle informazioni trasmesse, quindi aggiungere un impronta calcolata con MD5 (per esempio) per garantire l'identità del dispositivo Morale se esistessero ancora i programmatori i dispositivi embed ed in Real Time sarebbero più veloci e meno costosi, sarebbe interessante conoscere quel frigorifero che deve inviare megabyte di dati e non semplici riassunti del suo stato di funzionamento e delle scorte alimentari in esaurimento per compilare il programma udp_chatc è necessario avere installate le librerie ncurses ed impartire il comando gcc -o udp_chat udp_chatc -lncurses Per usare il programma sono necessari dei parametri udp_chat -h "nome del computer o indirizzo Ip dello stesso" -p "numero di porta verso cui inviare"-l "porta su cui ascoltare i pacchetti" -u "nickname" se omessi, il software assume i valori di default indicati nella nuova versione di esempih #include<stdioh> printf, scanf, etc #include<sys/typesh> FD_ etc #include<netinet/inh> protocols and datatypes #include<arpa/ineth> Host to Network conversions #include<netdbh> gethostbyname #include<sys/socketh> Berkely sockets #include"esempih" definizioni per il progetto #include<ncursesh> magiche librerie per la gestione dei terminali #include<ctypeh> isalpha #include<fcntlh> per impostare i socket in modalità non bloccante struct hostent *host; * l'host non è un indirizzo IP, si deve usare il DNS e non è detto * che tutto si risolva per il meglio if ( (int)inet_addr(ipaddress) == INADDR_NONE ) if ( (host = gethostbyname( IPaddress )) == NULL ) {

15 printf(msgerr_hostnotfound); return -1; else remote_addresssin_addrs_addr = ((struct in_addr *)(host->h_addr))->s_addr ; else inet_aton( IPaddress, &remote_addresssin_addr ); Download >>> udp_chatc Download >>> esempih Multicast Prima di rendere il software in grado di ricevere e trasmettere i messaggi in multicast, occorre fare qualche precisazione su questa modalità di trasmissione in modo da conoscere meglio sia le sue potenzialità che i suoi limiti Con il multicast chi desidera ricevere un certo tipo di informazione si iscrive al gruppo corrispondente, il mittente deve solo spedire i dati all'indirizzo di quel gruppo ed i partecipanti, chiunque essi siano, li riceveranno automaticamente Da qui si capisce che è impossibile implementare le trasmissioni multicast con il protocollo TCP (cioè con ricevuta di ritorno) il gruppo di destinazione può essere composto da un numero qualsiasi di host, i quali posso lasciare o aderire a detto gruppo senza alcun preavviso Il trasmittente non potrà mai sapere da chi attendersi le ricevute di ritorno, ammesso e concesso che dette ricevute non sprechino inutilmente banda passante Rendere le trasmissioni multicast affidabili è un discorso complesso, ci sono degli studi e dei RFC che per ora non interessano il nostro discorso Applicazioni delle trasmissioni in multicast sono facilmente immaginabili un sensore trasmette le misure verso più dispositivi che sono interessate a tale grandezza; se l'ingegnere progettista usa saggiamente il multicast, il software a bordo del sensore è notevolmente semplificato ed efficiente E' incredibile la semplicità con cui si può rendere uno stack di rete UDP/IP conforme al livello 1 delle specifiche multicast Uno stack TCP/IP può essere conforme alle specifiche multicast a più livelli "zero", se non supporta in nessun modo il multicast e questo non è un problema in Ipv4 perchè in questo caso non c'è obbligo di offrire un simile supporto; "uno", se si può spedire dati in multicast, ma non riceverne; "due", se è supportata sia la trasmissione che la ricezione In Ipv6 non è permessa la trasmissione broadcast e l'implementazione della trasmissione multicast è mandatoria Mentre dal punto di vista applicativo la trasmissione da uno a molti, ma non tutti, è facile e diretta, dal punto di vista del routing le cose sono particolarmente difficili E' facile cercare in rete informazioni sul "routing multicast" e sul protocollo IGMP che gestisce la formazione e lo scioglimento dei gruppi IGMP è ormai alla versione 3 (RFC 3376) Una osservazione che capita di sentire di frequente riguardo al multicast è che potrebbe essere conveniente solo nelle reti del tipo a bus condiviso, quale che è ethernet Personalmente credo che multicast conviene tutte le volte che serve inviare dati verso più destinatari e non si vuole gestire la lista a mano ; se il media trasmissivo è particolarmente favorevole, come nel caso del bus condiviso, è ovvio che multicast significa anche risparmio di tempo e banda Multicasting su reti 8023 (CSMA/CD) Le reti 8023 sono un caso particolare perchè il multicasting ed il broadcasting sono gratuiti questo é dovuto al fatto che uno parla e gli altri ascoltano Implementare il multicasting su reti 8023 é abbastanza semplice, spesso é implementato a livello hardware e ci sono varie soluzioni Funzione HASH L indirizzo destinatario del pacchetto ricevuto é inserito in una funzione HASH, questa funzione produrrà un numero compreso tra 0 ed n sia questo numero m Si controlla che il valore presente in una tabella alla posizione m non sia nullo, se il confronto dà esito positivo, il pacchetto é accettato Tabella di indirizzi Poichè la funzione HASH non é perfetta e può mappare più indirizzi nella stessa posizione in tabella, é necessario controllare che il pacchetto ricevuto sia effettivamente indirizzato al nodo interessato Utilizzando una tabella di indirizzi, é possibile verificare la corrispondenza in modo perfetto Promiscous mode La tabella degli indirizzi non é una soluzione conveniente, deve essere conservata dal chip di rete e questo limita la sua dimensione Impostando il chip di rete in modo da ricevere tutti i pacchetti che ascolta, si può eseguire il filtraggio via software in questo modo si mantengono i costi dell hardware bassi Le soluzioni correntemente in uso sono la prima e la seconda; la terza, che permette una grande flessibilità in termini di configurazione, non é accetabile, sia perchè interrompe continuamente il processore, sia perchè

16 rappresenta un grosso buco nella sicurezza Interfaccia del servizio multicast I costruttori di chips indicano come il dispositivo computa la funzione HASH, oppure dove conservare gli indirizzi Tutto quello che serve al livello superiore sono due funzioni che permettano di aggiungere, oppure eliminare indirizzi dalle tabelle Nel caso in cui l hardware di rete utilizzi una funzione HASH, il software si deve anche occupare di controllare che effettivamente il pacchetto ricevuto sia stato inviato al gruppo di cui il nodo fa parte, naturalmente questo significa mantenere una copia degli indirizzi registrati Per quando riguarda la trasmissione di pacchetti in multicast, oppure brodacast si può dire che tutto gioca sui primi 8 bit dell indirizzo di destinazione Un indirizzo multicast differisce da un indirizzo unicast, poichè ha i primi 8 bit ad 1 anzichè 0, un indirizzo broadcast invece é composto da soli 1 L IP multicasting ed indirizzi MAC L IP multicasting prevede l uso di indirizzi di classe D, in particolare sono utilizzati quelli compresi tra e Un indirizzo di classe D é assegnato ad un gruppo di nodi, che definiscono i gruppo multicast I bit più significativi di un indirizzo di classe D sono impostati a 1110, gli altri 28 bit sono chiamati ID del gruppo multicast Alcuni degli indirizzi di classe D sono registrati con l Internet Assigned Numbers Authority (IANA), per essere usati per applicazioni particolari Il blocco di indirizzi che vanno da fino a , é riservato per i protocolli di routing ed altre applicazioni di basso livello per la manutenzione della rete Gli indirizzi che vanno da fino a sono riservati per essere usati in ambito locale Un pacchetto IP indirizzato ad un gruppo di host, deve avere un indirizzo di livello MAC che corrisponde proprio al gruppo di interesse La IANA ha riservato un insieme di indirizzi IEEE 802 proprio per questo scopo, gli indirizzi che vanno da 01005E a 01005E7FFFFF possono essere usati per indirizzare direttamente il gruppo di interesse Per raggiungere lo scopo i 23 bit meno significativi dell indirizzo IP sono copiati nei 23 bit meno significativi dell indirizzo 01005E ALTRE CONSIDERAZIONI Sebbene le trasmissioni in multicast saranno di grande interesse, attualmente manca tanto a partire proprio dai router appropriati (anche se ci sono non è poi detto che siano configurati in modo corretto), cioè in grado di gestire questo tipo di trasmissioni Per ora il multicast è e rimane una esperienza locale, probabilmente alcuni lettori sono già connessi alla Mbone (la parte di internet pubblica che trasmette in multicast) e per saperlo dovrebbero installare ed eseguire sdr ( http//www-micecsuclacuk/multimedia/software/sdr ) Questo software ascolta eventuali annunci di sessioni e ne permette la registrazione Un primo esempio di MULTICAST Saranno modificati gli esempi realizzati precedentemente per mostrare da subito un esempio multicast perfettamente funzionante, le modifiche da apportare ai programmi scritti in precedenza sono veramente minime, consistono solo nell'impostazione di alcune opzioni speciali per il socket e nell'uso di un indirizzo di classe D Nei listati si nota subito la presenza della nuova funzione setsockopt(), attraverso di essa è possibile impostare alcuni parametri dei socket in modo da influenzare, a vari livelli, la trasmissione Questa funzione accetta 5 parametri il descrittore del socket, il livello su cui intervenire (per ora IPROTO_IP oppure SOL_SOCKET), il parametro da impostare, il parametro (puntatore al - ) ed il valore vero e proprio Ricordate che i parametri vanno passati in modo un po' particolare Ci sono alcuni punti che è bene mettere subito in chiaro 1 Per trasmettere in multicast verso un gruppo non è necessario essere iscritti allo stesso Semplicemente si mandano i pacchetti verso l'indirizzo di classe D corrispondente 2 Per il programma che ascolta, la trasmissione in multicast è completamente trasparente Indirizzo IP a parte ed impostazione delle opzioni per il socket, non varia nulla e quindi deve registrarsi per l'uso di una certa porta Adesso la domanda è come si fa a mandare in esecuzione più ascoltatori sulla stessa macchina se questi si registrano tutti allo stesso numero di porta? La risposta è che è perfettamente possibile impostare a livello di socket il riuso del numero di porta 3 Il valore del TTL (time to Live) dei pacchetti in multicast significa ben altro In pratica il valore TTL indica lo scope ovvero visibilità del pacchetto Per ora sarà impostato ad 1 ed indicherà che la trasmissione interessa solo la rete locale 4 Le impostazioni a livello SOL_SOCKET vanno fatte prima di eseguire il bind(), quelle a livello IPROTO_IP possono essere eseguite in un qualsiasi istante C'è di nuovo anche una struttura ip_mreq, questa serve per poter registrare il socket in un certo gruppo La ip_mreq è composta di due parti base il gruppo in cui registrarsi e l'interfaccia da usare

17 struct ip_mreq { ; in_addr imr_multiaddrs_addr; in_addr imr_interfaces_addr; L'indirizzo di classe D da usare va posto in imr_multiaddr, mentre nel campo successivo va caricato l'indirizzo IP della interfaccia di rete da usarsi, può anche usare INADDR_ANY Preparata questa struttura si chiede di essere aggiunti al gruppo con setsockopt(sockdescr, IPPROTO_IP, IP_ADD_MEMBERSHIP, &ipmreq, sizeof(ipmreq)); Allo stesso modo è possibile abbandonare un gruppo basta usare al posto di IP_ADD_MEMBERSHIP il comando IP_DROP_MEMBERSHIP La funzione non serve solo per impostare il gruppo multicast da cui ricevere pacchetti, permette anche di impostare il valore TTL dei pacchetti inviati in multicast Nel nostro esempio ridotto all'osso, è necessario usare la funzione setsockopt al più 4 volte nel sender per impostare il valore del TTL ed eventualmente il loop back, cioè la ricezione da parte del mittente dei pacchetti appena trasmessi; nel listener la prima volta per impostare le opzioni del socket in modo da condividere il numero di porta tra più programmi, la seconda per registrarsi al gruppo di interesse La cosa importante da osservare è che nel sorgente, inizializzazione dei socket a parte, non è variato nulla Se avessimo usato il protocollo TCP saremmo stati ad eseguire titanici aggiornamenti ovunque, senza per questo ottenere tutte le funzionalità che il multicast di serie offre parametri per l'impostazione multicast uchar_ttl = 1 significa che il pacchetto rimane nella LAN I pacchetti inviati sono ricevuti anche dal mittente uchar_loop = 1; u_char uchar_ttl = 1; u_char uchar_loop = 1; * Per trasmettere in multicast serve un indirizzo appropriato if(!in_multicast(htonl(inet_addr(ipaddress)))) { printf("l'indirizzo IP fornito deve appartenere alla classe MULTICAST\n"); exit(1); * attiva le opzioni per il multicasting if (setsockopt( socket_descriptor, IPPROTO_IP, IP_MULTICAST_TTL, &uchar_ttl, sizeof(uchar_ttl)) ) { printf("non posso impostare il TTL\n"); exit(1); if ( bind( socket_descriptor, (struct sockaddr *)&socket_address, sizeof(socket_address) ) ) { printf("non posso collegare il socket\n"); exit(1);

18 int reuse = 1; struct ip_mreq ipmreq; // imposta il gruppo da cui si intende ricevere UDP in multicast inet_aton(ipaddress, &ipmreqimr_multiaddr); // imposta l'interfaccia sulla quale sarà avviata la trasmissione multicast // ovvero la prima disponibile ipmreqimr_interfaces_addr = htonl(inaddr_any); // Permette di collegare il processo allo stesso numero di porta anche se già // in uso, da impostare prima di eseguire il bind() if ( setsockopt(socket_descriptor, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse)) < 0 ) { printf("non posso impostare il riuso del numero di porta\n"); printf("setsockopt%d\n", errno); exit(1); // Fa richiesta di adesione al gruppo voluto if ( setsockopt(socket_descriptor, IPPROTO_IP, IP_ADD_MEMBERSHIP, &ipmreq, sizeof(ipmreq)) < 0 ) { printf("errore non posso aderire al gruppo %s \n", IPaddress); printf("setsockopt%d\n", errno); exit(1); Download >>> ex9-sendc Download >>> ex9-recvc Raw socket Prima di passare a parlare del TCP/IP, non rimane che parlare dei RAW socket, questi permettono o meno a seconda dell'implementazione presente nel kernel, di inviare direttamente o meno pacchetti a livello 2 della pila ISO/OSI in scrittura

19 Indice generale, ' -/ 0 1 " % ' " '!! 6 ' ' & ( /! " #, ' 8! # 5 " # $ % ' # % & ) ' #! & ;% ' # % ' ( * < = * < # ' ))* ) + ( # " ' 4 + &