|
|
 | |  |  | Introduction à la libpcap |  |
by Frédéric Lavécot (04/12/2000)
--[ Introduction ]---------------------------------------------------------
La libpcap est une bibliothéque de fonctions qui, d'après le README qui
accompagne l'archive, sert d'interface à la capture de paquets et est
indépendante du système.
En clair la libpcap permet d'écouter le réseau aves ses propres filtres
puisqu'elle inclus un mécanisme de filtrage basé sur le Berkeley packet filter
(BPF).
Ce tip se propose de donner un aperçu rapide des fonctions offertes par la
libpcap et d'expliquer les étapes nécessaires à la réalisation d'un sniffer.
Les informations qui suivent ont été obtenues en regardant les sources de
programmes utilisant la libpcap comme tcpdump et hping2 et en consultant le
manuel (man pcap).
--[ libpcap ]--------------------------------------------------------------
Pour récupérer l'archive deux choix :
- version stable : ftp://ftp.ee.lbl.gov/
- version maintenue : http://www.tcpdump.org/
--[ Installation ]---------------------------------------------------------
Sous *BSD : il y a de fortes chances pour que la libpcap soit déjà installée
Sinon :
tar zxvf libpcap-*.tgz
$ ./configure
$ make
# make install
# make install-incl
(pour installer les entêtes nécessaires au développement )
# make install-man
(pour installer le man)
--[ Comment dévelloper un sniffer ? ]--------------------------------------
Les fonctions présentées ici le sont dans l'ordre où elles apparaissent
généralement dans un sniffer.
Les en-têtes à inclure pour accéder aux fonctions et aux #define
#include <pcap.h>
#include <net/if.h>
#include <netinet/in.h>
Conseil : si le programme que vous développez doit être porté sur plusieurs
plate-formes, redéfinissez vous mêmes les structure de paquets ip et tcp.
Les champs de ces structures sont différents d'une plate-forme à une autre.
(Rien ne vous empêche de reprendre celles de votre système, de les recopier
dans le même repertoire que votre programme et de faire #include "fichier")
(Le plus souvent les structures des paquets ip et tcp sont définies dans
/usr/include/netinet/ip.h et /usr/include/netinet/tcp.h)
Première étape : choisir l'interface que l'on veut "sniffer".
deux options :
- soit on la choisit : dans ce cas on stocke le nom de l'interface dans un
char *.
- soit on essaye de la détecter grâce à la fonction pcap_lookupdev()
cette fonction retourne un pointeur sur la première interface réseau
utilisable ou NULL si une erreur se produit.
char err_buff[PCAP_ERRBUF_SIZE];
char device[512];
device=NULL;
if(device==NULL)
/* If device not specified -> autodetect */
if ((device = pcap_lookupdev (err_buff)) == NULL)
{
printf ("pcap_lookupdev : %s\n", err_buff);
return (-1);
}
printf("device %s detected\n",device);
Deuxième étape : obtenir un descripteur de capture de paquets.
Cela se fait en utilisant la fonction pcap_open_live().
pcap_open_live() renvoie un pointeur de type pcap_t. Les paramètres sont :
- char *device : le nom de l'interface à sniffer
- int snaplen : taille maximale de bytes à capturer (sur Ethernet 1514)
- int promisc : faut-il placer l'interface en mode promiscuous(1) ?
- int to_ms : timeout de lecture en millisecondes
- char *ebuf : utilisé pour récuperer le message d'erreur
IFF_PROMISC est définie dans /usr/include/net/if.h
#define SNAP_LEN 1514
pcap_t *pdes;
char err_buff[PCAP_ERRBUF_SIZE];
if ((pdes=pcap_open_live(device,SNAP_LEN,IFF_PROMISC,1000,err_buff)) == NULL)
/* obtain a packet descriptor to sniff the network */
{
printf("\npcap_open_live error : %s\n",err_buff);
return(-1);
}
printf("listening on device %s\n",device);
Note sur le paramètre de timeout : 1000 semble être une bonne valeur
(c'est celle adoptée par hping2 et tcpdump). Attention en mettant 0 certains
systèmes ne traiteront plus aucun paquet.
(1) promiscuous : Sur un réseau Ethernet, les trames sont envoyées à toutes
les stations du réseau. Une interface normale ne remonte que les trames qui
lui sont destinées. Une interface en mode promiscuous remonte toutes les
trames qu'elle voit passer.
Si l'on souhaite placer un filtre sur les paquets à remonter :
Troisième étape : obtenir le numéro et le masque associé au réseau de
l'interface.
La fonction pcap_lookupnet() permet de les obtenir.
pcap_lookupnet renvoie -1 en cas d'erreur. Les paramètres sont :
- char * device : interface dont on souhaite obtenir les descripteurs
- bpf_u_int32 * netp : variable dans laquelle l'adresse de réseau sera stockée
- bpf_u_int32 * maskp : variable dans laquelle le masque de réseau sera stocké
- char * errbuff : buffer dans lequel une éventuelle erreur sera écrite
char err_buff[PCAP_ERRBUF_SIZE];
bpf_u_int32 netp,maskp;
if ( pcap_lookupnet(device,&netp,&maskp,err_buff) == -1)
{
printf("\n\npcap_lookupnet error : %s",err_buff);
return(-1);
}
Quatrième étape : transformer le filtre écrit en texte en un vrai filtre.
Pour cela on utilise la fonction pcap_compile() :
Comme d'habitude la fonction renvoie -1 si une erreur survient.
Les paramètres sont :
pcap_t *pdes : descripteur de paquets capturés
struct bpf_program *bp : structure remplie par pcap_compile()
char *str : filtre écrit sous forme de texte
exemple : "src port 80 or dst port 80" pour capturer tous les paquets issus ou
à destination du port 80.
int optimize : contrôle l'optimisation du résultat.
(traduction directe de la page man, je n'en sais pas plus)
bpf_u_int32 maskp : masque de réseau (précédemment obtenu)
#define FILTER "src port 80 or dst port 80"
struct bpf_program bp;
if (pcap_compile(*pdes, &bp, FILTER, 0x100, maskp) < 0)
/* set a filter to capture the packets that match FILTER */
{
printf("\n\n pcap_compile error : %s\n",pcap_geterr(*pdes));
return(-1);
}
Cinquième étape : Appliquer le filtre.
C'est la fonction pcap_setfilter() qui va s'en charger :
Paramètres :
pcap_t *pdes : le descripteur de paquets capturés
struct bpf_program bp : la structure contenant la description du filtre
(entre autres)
if (pcap_setfilter(*pdes, &bp) < 0)
{
printf("\n\n pcap_setfilter error : %s\n",pcap_geterr(*pdes));
return(-1);
}
printf("pcap_setfilter OK\n\n");
Voila, vous avez maintenant un descripteur de paquets capturés qui
correspond parfaitement à vos attentes. Il ne reste plus qu'a lire dedans :
Pour cela on utilise la fonction pcap_loop() dont les paramètres sont :
pcap_t *pdes : le descripteur de paquets capturés
int cnt : nombre de paquets à traiter (-1 : boucle infinie)
pcap_handler callback : fonction de traitement à exécuter pour chaque paquet
capturé
u_char *user : voir fonction callback()
char * buff = NULL;
if( pcap_loop(pdes,-1,callback,buff) <0 )
{
(void)fprintf(stderr, "pcap_loop: %s\n",pcap_geterr(pdes));
exit(1);
}
La fonction callback
les paramètres de cette fonction sont :
u_char *user : le pointeur passé en paramètre de pcap_loop()
const struct pcap_pkthdr *h : pointeur sur le descripteur du paquet capturé
const u_char *buff : données effectivement capturées
(si vous êtes sur un réseau Ethernet ces données
commencent avec l'en-tête Ethernet du paquet)
void callback(u_char *user, const struct pcap_pkthdr *h, const u_char *buff)
{
struct iphdr *ip_hdr;
unsigned short int len;
ip_hdr=(struct iphdr *)(buff+14);
len=htons(ip_hdr->tot_len);
...
}
La dernière étape : compiler votre programme. Pour pouvoir accéder aux
fonctions de la libpcap ne pas oublier le paramètre -lpcap dans la commande
permettant de compiler votre programme.
Quelques information supplémentaires :
* Il est possible de capturer tous les paquets d'un seul coup avec tcpdump
et de les sauvegarder dans un fichier pour faire le traitement après coup.
Pour cela il suffit d'utiliser la fonction pcap_open_offline() à la place de
la fonction pcap_open_live().
* Attention sous linux il n'est par défaut pas possible de savoir si le noyau
a perdu des paquets pendant la capture. Si vous utilisez linux et que vous avez
besoin de cette information il existe un patch pour les noyaux 2.2 disponible.
* Si vous avez encore des questions : "man 3 pcap" sinon :
Frederic.Lavecot@hsc.fr
|