next   previous   contents

La libreria LibPcap

 Come gia' esposto nella sezione precedente, nell'implementazione del prototipo servknock si e' fatto ricorso alla libreria LibPcap per la cattura dei pacchetti di rete al livello datalink.
 Tale scelta e' giustificata dalla necessita' di accedere agli header dei pacchetti prima che essi possano essere scartati dal firewall (che tipicamente lavora al terzo o quarto livello della pila ISO/OSI), in modo da poterli analizzare e capire se facciano parte di una sequenza di knock valida o meno. Il datalink layer e' dunque il piu' basso che puo' essere “visto” dal software, poiche' il livello fisico inferiore e' rappresentato dall'hardware.
 Si ricorda infatti che il datalink layer si trova al secondo livello del modello ISO/OSI, ed il suo compito e' quello di assicurare la consegna dei messaggi al giusto device e dividerli in frame che vengono trasmessi utilizzando lo strato fisico sottostante.
 Oltre all'evidente pregio di esportare un'API unica per l'accesso al datalink layer, in modo portabile tra i vari sistemi Unix-like, la librera LibPcap presenta un ulteriore vantaggio: l'emulazione in software della possibilita' del BPF dei sistemi BSD, di creare dei filtri tra il processo user space ed il kernel. Sfruttando questa feature diventa infatti estremamente semplice prelevare esclusivamente i pacchetti che rispettino particolari caratteristiche esempi di filtri sono “icmp or udp”, oppure “tcp and ( port 25 or port 80)” ).
 Su sistemi diversi da quelli della famiglia BSD (Berkeley Software Development), dove ovviamente questa feature non viene emulata, l'utilizzo dei filtri bpf probabilmente non migliora le prestazioni, dato che la libreria e' comunque forzata a ricevere comunque tutti i pacchetti in arrivo dell'interfaccia. Questa feature resta pero' estremamente utile, poiche' essa permette di discriminare i diversi pacchetti in maniera molto piu' semplice per il programmatore, rispetto all'analisi delle caratteristiche dei vari header.
 Allo scopo di capire al meglio i dettagli implementativi trattati nella sezione 4, si illustrano ora in maniera sintetica i passi da seguire per realizzare un semplice sniffer scritto sfruttando questa libreria.
 Per iniziare ad analizzare (in gergo “sniffare”) il traffico e' necessario aprire un descrittore per la cattura dei pacchetti tramite la funzione pcap_open_live. La funzione si occupa di accedere a basso livello al Datalink layer, in base alla implementazione del sistema operativo e di allocare e restituire un descrittore ( per la precisione un puntatore di tipo pcap_t ) da passare alle altre funzioni utilizzate per l'accesso ai pacchetti.
 Prima di procedere e' opportuno ricordare che al livello Datalink i pacchetti sono incapsulati in uno speciale header, quindi per potere accedere ai dati e agli header dei protocolli di livello superiore, si deve prima determinare il tipo di link layer.
 E' necessario infatti sapere che tipo di frame vengono inviati e ricevuti, per calcolare le dimensioni degli header ed i valori degli offset a partire dai quali sono effettivamente memorizzate le varie informazioni o i dati. Ad esempio, ipotizzando di trovarsi in una rete Ethernet, si potrebbe ricevere un frame del tipo raffigurato, con i relativi offset :

 Anche nel caso non si sia interessati all'informazione contenuta nell'intestazione Ethernet (per esempio i MAC address di mittente e destinatario), desiderando esclusivamente accedere al pacchetto IP e' comunque necessario conoscere la dimensione dell'header ethernet per poterlo cosi' “saltare”. Interessa infatti conoscere l'offset espresso in byte, del pacchetto IP all'interno del frame.
 Per determinare questo offset solitamente si fa una chiamata alla funzione pcap_datalink, che restituisce un numero rappresentante tipo di link layer, in base al quale decidere il valore dell'offset. Ad esempio nel caso del frame ethernet lo scostamento da utilizzare e' di 14 bytes. Svantaggio di questo sistema e' quello di dover includere a priori tutti i tipi di link con cui il programma deve funzionare. In caso di contrario si potrebbe incorrere nel rischio di non riuscire leggere il traffico da alcuni tipi di interfacce.
 Determinato l'offset da usare per i pacchetti, si puo' passare alla loro cattura, con un ciclo while al cui interno sia richiamata la funzione pcap_next, la quale si occupa di riempire una struttura del tipo pcap_pkthdr con le informazioni di base sul pacchetto, e restituisce un puntatore (u_char* ) ad un buffer contenente la parte di pacchetto catturata.

struct pcap_pkthdr
 {
 struct timeval ts; /* time stamp */
 bpf_u_int32 caplen; /* length of portion present */
 bpf_u_int32 len; /* length this packet (off wire) */
 };

 Il paccheto catturato puo' essere visto come una “collezione” di strutture (ad esempio per un pacchetto TCP/IP saranno compresi gli header ethernet, IP, TCP, ed il payload del pacchetto stesso). Semplicemente il contenuto delle strutture rappresentanti il pacchetto catturato viene posto in una stringa accessibile da un puntatore u_char *. Tale puntatore puo' essere visto come una rappresentazione “serializzata” di queste strutture, e quindi per accedere ad esse si rende necessario ricorrere a delle operazioni di typecasting. Naturalmente le suddette strutture devono essere note e definite a priori. A titolo esemplificativo si puo' supporre di utilizzare le struct iphdr e tcphdr, definite negli header files /usr/src/linux/tcp.h e /usr/src/linux/ip.h
 Ogni pacchetto catturato puo' venire poi analizzato, da una funzione da noi definita atta a processarlo, alla quale venga passato il puntatore ritornato da pcap_next . Poiche' questo puntatore non e' altro che una variabile contenente l'indirizzo di memoria, dove si trovano le strutture che definiscono il pacchetto, diventa estremamente semplice accedere all'header IP, essendo noti l'indirizzo di base, e l'offset all'interno del frame Ethernet :

ip = (struct ipv4_hdr *) (packet+offset);

 E' inoltre importante conoscere anche la dimensione dell'header IP per accedere ai pacchetti incapsulati. Un datagramma IP puo' infatti incapsulare pacchetti di protocolli di livello superiore, come mostrato dalla figura successiva :


All'indirizzo di base e' quindi necessario aggiungere oltre all'offset anche la lunghezza espressa in bytes dell'header IP :

tcp = (struct tcp_hdr *) (packet + SIZE_IPV4_HEADER + offset);

 Grazie ai puntatori ip e tcp , e' possibile a questo punto accedere ai vari campi dei rispettivi header, in maniera semplice e veloce come si farebbe con una qualsiasi struttura dati.

next   previous   contents