CNS/Minithins.net vous présente: Programmer un sniff de deux facons. Utilitée. Somaire: 0) Introduction 1) Sniffer via libpcap 2) Sniffer avec les sockets en RAW 3) Exploiter les résultats 0) Introduction Un sniffeur est un programme lancé permettant de capter les datagrammes qui passent sur un réseau, et de les rendre lisible pour l'être humain. On peut ainsi savoir ce qu'il se passe sur le réseau, et ainsi détecter les sources de problèmes. Je connais à ce jour, deux facons différentes de programmer un sniffeur. La premiere est d'utiliser les sockets en RAW, la seconde d'utiliser libpcap (Packet Capture library), et tous deux sous Linux ou *BSD. Meme si le captage des datagrammes est simple, il va faloir ensuite interpreter ce qu'il se passe sur le réseau, et pour cela nous allons avoir besoin de connaitre (avoir des notions générales) le tcp/i, et comment il marche. Le matériel pour établir ce tutoriel se compose de 2 machines, l'une sous FreeBSD, l'autre sous Linux (redhat 6.2). 1) Sniffer via libpcap (note: je découvre sans doute en même temps que vous libpcap :-) Libpcap, comme je l'ai déjà dis, est une librairie pour la capture de paquets. Cela signifie qu'a l'appel des fonctions qui la compose, le programme va lire sans arret ce qui passe sur un des périphériques réseaux, et nous donner ce qu'il lit. Libpcap est téléchargeable à www.tcpdump.org (tcpdump est aussi un très bon sniffeur, une fois que l'on sait s'en servir). Pour l'installer, lisez le INSTALL ou le README avec lequel libpcap est fournit. (RTFM !#@!) Une fois cela fait, une seule commande est à faire: virginie:/usr/www/htdocs/mags$ man pcap (et oui, vous aurez l'aide.) En lisant la doc, donc, j'ai remarqué (on peut dire trouvé) les deux fonctions nécessaire pour la réalisation de notre programme: pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf) u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h) La première, pcap_open_live, permet: 'pcap_open_live() is used to obtain a packet capture descriptor to look at packet on the network'. Elle est utilisée pour obtenir un descripteur de capture de paquets pour surveiller les datagrammes sur le réseau. Ca va renvoyer, une fois les paramètres bien choisis, une variable, de type 'pcap_t', qui va permettre d'agir sur ce genre d'interface virtuel qui joue sur la capture. Les paramètres sont : 'char *device', chaine de caractères devant comporter le nom de l'interface réseau, 'int snaplen', entier qui doit spécifier le nombre maximum d'octets à capturer, 'int promisc', qui doit soit contenir 0, soit un autre nombre (faux, vrai), pour dire si l'interface doit se situer en mode promiscious (pour ecouter tout les paquets, meme ceux qui ne sont pas déstinés à nous), 'int to_ms', le timeout en lecture, 'char *ebuf', le buffer de sortie d'erreur. (utilisé quand pcap_open_live ne marche pas, ce qui lui fait retourner une erreur). Pour pcap_next: Une fois le descripteur ouvert, on va recevoir des paquets dans les buffers. pcap_next permet de les lire, un par un, a chaque appel de cette fonction, en renvoyant l'adresse du prochain paquet. Qu'est ce que ca donne en programmant ? # -- test 1 -- # #include // pour les entrées sorties (printf ...) #include // pour utiliser libpcap int main(void) { int i; // on va en avoir besoin pour notre boucle :) pcap_t *desc = NULL; // desc = notre descripteur struct pcap_pkthdr usefull; // usefull est une structure pcap_pkthdr (header de paquet), type de pcap // dans notre programme, on doit la définir pour utiliser pcap_next, mais // on l'utilise pas. u_char *packet; // pointeur sur nos paquets captés if ((desc = pcap_open_live("xl0", 1500, 0, 1000, NULL))==NULL) { perror("pcap_open_live()"); exit(1); } // on ouvre le device xl0 (BSD: 3com vortex/boomerang), on lit 1500 octets // par paquets, on ne met pas le périphérique en mode prosmisc, avec un // timeout de 1000 secondes, et sans buffer d'erreur. // Si pcap_open_live se passe mal, alors le if s'executera, et on aura le // droit à l'erreur, et le programme quitera (perror, exit) printf("A partir de maintenant, je surveille le traffic sur xl0\n"); // Si tout se passe bien while (1) // boucle sans fin // on attend de recevoir pleins de paquets, et donc on doit les recevoir // pendant cette boucle. Il lira le réseau sans arret. { packet = (u_char *) pcap_next(desc, &usefull); // on lit le prochain paquet. Si il n'y a pas de paquets en attente, alors // la fonction renvoit NULL (et au bout d'une seconde, 1000 ms (notre timeout) // sinon, elle renvoit l'adresse du paquet recu, et on le traite après: if (packet != NULL) { // si le paquet n'est pas NULL for (i=0; i<26;i++) { printf("%.2x ",*packet); // on va faire afficher les 26 premiers caractères, et on les affiche à // l'écran packet++; // prochain paquet. } printf("\n"); // fin du paquet, retour à la ligne } } } # -- fin test 1 -- # On compile: virginie:~$ gcc -lpcap -o test test.c virginie:~$ su Password: virginie:/usr/home/cns$ id uid=0(root) gid=0(wheel) groups=0(wheel), 2(kmem), 3(sys), 4(tty), 5(operator), 20(staff), 31(guest) virginie:/usr/home/cns$ Le programme, ayant besoin d'ouvrir le périphérique dans un mode spécial, doit le faire en root (pas nécessaire sous linux, sous réserves.) On va executer le programme, en même temps que tcpdump, et on va lancer un ping pour voir: virginie:~$ ping -c 1 192.168.0.2 PING 192.168.0.2 (192.168.0.2): 56 data bytes 64 bytes from 192.168.0.2: icmp_seq=0 ttl=32 time=0.780 ms --- 192.168.0.2 ping statistics --- 1 packets transmitted, 1 packets received, 0% packet loss round-trip min/avg/max/stddev = 0.780/0.780/0.780/0.000 ms virginie:~$ virginie:/usr/home/cns$ ./test A partir de maintenant, je surveille le traffic sur xl0 ff ff ff ff ff ff 00 10 5a df 2d 92 08 06 00 01 08 00 06 04 00 01 00 10 5a df 00 10 5a df 2d 92 00 20 af f3 24 29 08 06 00 01 08 00 06 04 00 02 00 20 af f3 00 20 af f3 24 29 00 10 5a df 2d 92 08 00 45 00 00 54 7d ad 00 00 ff 01 bc a7 00 10 5a df 2d 92 00 20 af f3 24 29 08 00 45 00 00 54 b7 05 00 00 20 01 62 50 ^C virginie:/usr/home/cns$ virginie:~$ tcpdump tcpdump: listening on xl0 11:58:11.532055 arp who-has virgo.toone.org tell virginie.toone.org 11:58:11.532318 arp reply virgo.toone.org is-at 0:20:af:f3:24:29 11:58:11.532351 virginie.toone.org > virgo.toone.org: icmp: echo request 11:58:11.532662 virgo.toone.org > virginie.toone.org: icmp: echo reply ^C 4 packets received by filter 0 packets dropped by kernel virginie:~$ J'avoue, l'hexa c'est pas très lisible. Décomposons nos datagrammes un par un: le premier: ff ff ff ff ff ff 00 10 5a df 2d 92 08 06 00 01 08 00 06 04 00 01 00 10 5a df déjà, il faut savoir que ce qu'on lit est directement ce que l'on recoit de la carte, et donc c'est ce qui se passe à la couche liaison. Rappel de ce que l'on recoit à cette couche: +------------+------------+----+----/ /----+--------+ | Adr Dest. | Adr Source |type| Données | CRC | +------------+------------+----+----/ /----+--------+ 6 octets 6 octets 2 46-1500 4 octets (RFC 894) On a: adresse MAC de destination: ff ff ff ff ff ff adresse MAC source: 00 10 5a df 2d 92 type: 08 06 on consulte la doc sur le tcp/ip, on voit que cela est de l'arp (type 08 06), un broadcast de la machine avec comme adresse MAC 00:10:5a:df:2d:92 Je vérifie ma config: virginie:~$ ifconfig xl0 xl0: flags=8943 mtu 1500 inet 192.168.0.1 netmask 0xffffff00 broadcast 192.168.0.255 ether 00:10:5a:df:2d:92 media: autoselect (10baseT/UTP) status: active supported media: autoselect 100baseTX 100baseTX 10baseT/UTP 10baseT/UTP 100baseTX virginie:~$ C'est mon adresse MAC qui a envoyé ca. Au même moment, tcpdump nous a dit: 11:58:11.532055 arp who-has virgo.toone.org tell virginie.toone.org une requete ARP de virginie.toone.org (ma machine) demandant à tout le monde qui est virgo.toone.org (via le broadcast). C'est bon, les données entre tcpdump et notre sniffeur marche, on l'a vérifié :) On verra plus loin, comment construire un sniffeur permettant de décoder toutes les trames. Une autre fonction que l'on peut utiliser existant dans libpcap, est: char *pcap_lookupdev(char *errbuf) Elle permet de récupérer l'interface réseau par défaut. Si on veut l'utiliser dans notre programme, on n'a qu'à remplacer "xl0" dans if ((desc = pcap_open_live("xl0", 1500, 0, 1000, NULL))==NULL) par pcap_lookupdev(NULL), ce qui nous donne: (..) if ((desc = pcap_open_live(pcap_lookupdev(NULL), 1500, 0, 1000, NULL))==NULL) (..) Une autre fonction de libpcap, que j'ai découvert apres avoir sortis la 1ère release de cette doc, c'est pcap_stats. Elle permet de sortir des stats (et oui). Pour l'utiliser: //int pcap_stats(pcap_t *p, struct pcap_stat *ps) // //struct pcap_stat { // u_int ps_recv; /* number of packets received */ // u_int ps_drop; /* number of packets dropped */ // u_int ps_ifdrop; /* drops by interface XXX not yet supported */ //}; Cette fonction marche en deux temps. On doit l'appeler après initialisation du descripteur de pcap (avec pcap_open_live), pour initialiser le 'log', et une autre fois quand on veut récuperer les résultats. ex: // ... Début du programme ... struct pcap_stat *ps; // Déclaration du pointeur // ... suite ... ps = (struct pcap_stat*)malloc(sizeof(struct pcap_stat)); // Allocation en mémoire // ... suite ... desc = pcap_open_live(device, 1518, 1, 1000, NULL); pcap_stats(desc,ps); // Initialisation (après pcap_openlive) // ... suite du programme ... pcap_stats(desc,ps); printf("nombre de paquets total : %6i",ps->ps_recv); printf("nombre de paquets droppés : %6i",ps->drop); // ... fin Voila pour libpcap. Elle incorpore plus de fonctions, que j'introduierai peut etre plus tard, lors d'un autre cours, sans doute porté que là dessus. 2) Sniffer avec les sockets en RAW Sur certains systèmes, on ne trouve pas libpcap. Ca arrive, même si celui ci est très portable. On est donc obligé d'utiliser autre chose, de facon plus violente (enfin, je trouve), et cela est les RAW sockets. Qu'est ce qu'une socket ? Une socket est un point de terminaison d'une communication. Quand on va lire et envoyer des données dans un réseau, on le fera souvent avec, et ce, même sans le savoir. C'est un point essentiel dans la communication des systèmes Unix. Qu'est ce qu'une RAW socket ? Une RAW socket permet de lire et de construire des paquets en brut, construits à la main. Elles permettent l'acces aux protocoles internes du réseau et des interfaces. Le type RAW est uniquement utilisable par le super utilisateur (un utilisateur normal ne pourra pas faire n'importe quoi :) Comment utiliser ces RAW socket ? on utilise la primitive socket() virginie:/usr/www/htdocs/mags$ man socket (...) SYNOPSIS #include #include int socket(int domain, int type, int protocol) (...) Le domaine, c'est un peu 'ou' on va utiliser la socket. Nous, on va lire dans l'internet (néanmoins votre réseau local qui marche comme), et donc utiliser: (Adresse families, /usr/include/sys/socket.h) #define AF_INET 2 /* internetwork: UDP, TCP, etc. */ Le type, c'est le type de socket, nous RAW: SOCK_RAW Le protocole, on va prendre un protocole dans /etc/protocols, comme par exemple ICMP (1) Une fois la socket ouverte et créée, on va devoir la lire :) Pour cela, on peut (et on va) utiliser recvfrom(), qui ressemble à ca: ssize_t recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen) D'apparence ca fait peur, je sais. 'int s' est la socket, 'void *buf' est le buffer, 'size_t len' sa taille, 'int flags' sont les flags optionnels (nous = 0), 'struct sockaddr *from' est une structure associée à ce que l'on recoit, 'socklen_t *fromlen' est la taille de la précédente structure. Après construction du programme: # -- sniffeur en raw -- # #include #include #include #include // les fichiers d'entete (pour socket(), recvfrom(), les types différents int main(void) { int i; int sock, bytes_recieved, fromlen; char buffer[65535]; struct sockaddr_in from; // variables sock = socket(AF_INET,SOCK_RAW,0); // ouverture de la socket elle méme while (1) { bytes_recieved = recvfrom(sock, buffer, sizeof buffer, 0, (struct sockaddr *)&from, &fromlen); // reception des données if (bytes_recieved > 0) { for (i=0; i<26;i++) printf("%.2x ",buffer[i]); printf("\n"); } else perror("recvfrom()"); } } # -- fin -- # On compile, et on met en route (en meme temps qu'un tcpdump et un ping) virginie:~/fin$ ping -c 1 192.168.0.2 PING 192.168.0.2 (192.168.0.2): 56 data bytes 64 bytes from 192.168.0.2: icmp_seq=0 ttl=32 time=0.735 ms --- 192.168.0.2 ping statistics --- 1 packets transmitted, 1 packets received, 0% packet loss round-trip min/avg/max/stddev = 0.735/0.735/0.735/0.000 ms virginie:~/fin$ virginie:~$ gcc -o truc2 truc2.c ; ./truc2 45 00 40 00 0c 00 00 00 20 01 0d 56 ffffffc0 ffffffa8 00 02 ffffffc0 ffffffa8 00 01 00 00 1a ffffffd6 ffffffcc 12 virginie:/home/cns$ tcpdump tcpdump: listening on xl0 13:50:29.020921 arp who-has virgo.toone.org tell virginie.toone.org 13:50:29.021189 arp reply virgo.toone.org is-at 0:20:af:f3:24:29 13:50:29.021215 virginie.toone.org > virgo.toone.org: icmp: echo request 13:50:29.021512 virgo.toone.org > virginie.toone.org: icmp: echo reply Et oui, c'est tout ce que l'on recoit. On a pas les données à la couche liaison, non plus les requètes ARP, et on a que la réponse du ping. (On en recoit qu'un tiers). Je préfère libpcap :) 3) Exploiter les résultats. L'objectif de ce chapitre est de passer de quelque chose de la forme: virginie:/usr/home/cns$ ./test A partir de maintenant, je surveille le traffic sur xl0 ff ff ff ff ff ff 00 10 5a df 2d 92 08 06 00 01 08 00 06 04 00 01 00 10 5a df 00 10 5a df 2d 92 00 20 af f3 24 29 08 06 00 01 08 00 06 04 00 02 00 20 af f3 00 20 af f3 24 29 00 10 5a df 2d 92 08 00 45 00 00 54 7d ad 00 00 ff 01 bc a7 00 10 5a df 2d 92 00 20 af f3 24 29 08 00 45 00 00 54 b7 05 00 00 20 01 62 50 ^C virginie:/usr/home/cns$ à quelque chose de plus lisible pour l'être humain. On va donc faire pas mal de formatage et compagnie pour cela. De plus, on repart à partir de la libpcap, car on a beaucoup plus d'informations et donc c'est plus utile. On repart du code: #include #include int main(void) { int i; pcap_t *desc = NULL; struct pcap_pkthdr usefull; u_char *packet; if ((desc = pcap_open_live(pcap_lookupdev(NULL), 1500, 0, 1000, NULL))==NULL) { perror("pcap_open_live()"); exit(1); } printf("A partir de maintenant, je surveille le traffic sur %s\n", pcap_lookupdev(NULL)); while (1) { packet = (u_char *) pcap_next(desc, &usefull); if (packet != NULL) { for (i=0; i<26;i++) { /* Tout le traitement que l'on va effectuer va avoir lieu ici. Evidement, les deux lignes suivantes vont disparaitre. */ printf("%.2x ",*packet); packet++; } printf("\n"); } } } Durant mes investigations sur les différents systèmes pour le rendre portable, j'ai vu que BSD et linux n'avait pas les même strutures pour les headers tcp, ip et udp, même si c'est censé être standardisé. On va donc creer nos propres structures, (qui vont rejoindre l'exemple BSD), pour pouvoir être compilé sur un plus large panel de plate formes. On rappelle que d'après la RFC 894, on a: +------------+------------+----+----/ /----+--------+ | Adr Dest. | Adr Source |type| Données | CRC | +------------+------------+----+----/ /----+--------+ 6 octets 6 octets 2 46-1500 4 octets qui définit le formatage de la couche liaison. Pour ce qui est des adresses de destinations, de source, de type, c'est assez facile, on a qu'à les lire. Reprenons la structure de l'entète d'un paquet ethernet: struct ether_header { u_char ether_dhost[6]; //destination host (adresse de destination) u_char ether_shost[6]; //source host (adresse source) u_short ether_type; //type de trame } On pourrait creer une structure englobant tout le paquet, mais le problème est que celui ci est de taille non fixe, et donc on risque d'englober dans données le CRC, et on aurait alors un paquet erroné. Créons une fonction, qui à partir d'un ether_header, affiche en une ligne ce qu'il contient void print_ether_header(struct ether_header *paquet) { int i; for (i=0;i<6;i++) { printf("%.2x",paquet->ether_shost[i]); if (i!=5) printf(":"); } printf(" -> "); for (i=0;i<6;i++) { printf("%.2x",paquet->ether_dhost[i]); if (i!=5) printf(":"); } printf(" type: %.4x\n",paquet->ether_type); } Une fois la fonction et la structure incorporées au début du programme, on peut l'executer pour voir (on appelle la fonction dans la boucle principale du programme, et de cette facon: print_ether_header((struct ether_header*)packet); (on appelle la fonction, en disant qu'il faut convertir sous le format de cette structure.) On compile, et execute: virginie:/usr/www/htdocs/mags/progs$ gcc -lpcap -o test test.c ; ./test A partir de maintenant, je surveille le traffic sur xl0 00:10:5a:df:2d:92 -> 00:20:af:f3:24:29 type: 0008 00:20:af:f3:24:29 -> 00:10:5a:df:2d:92 type: 0008 ^C virginie:/usr/www/htdocs/mags/progs$ (en lancant un ping, en parallèle) Effectivement, on obtient quelquechose de plus lisible. Bon, les adresses ARP c'est bien, mais un peu dur à lire, surtout quand on a un réseau de 1000 machines (ou plus, bien sur :-) J'ai un peu cherché, mais à part faire une requète rarp, ou autre, il n'y a pas de moyen direct pour convertir une adresse arp en adresse IP, voir le nom de la machine. Souvenez vous, si vous avez lu la doc que j'ai écris sur le tcp/ip, je disais qu'en principe, soit le type de trame est 08 (donc paquet IP), alors les adresses IP sont récupérable via l'entete IP, soit c'est une requète ARP/RARP, dans quels cas il est aussi possible de récupérer l'adresse IP (les adresses IP en fait, car on a source et destination.) On doit remarquer aussi que le type donnée 0008 est 'incorrect', car inversé. Il faut inverser les 2 octects pour avoir la donnée correcte, qui est 0800. On va alors ne pas se compliquer la vie, et rechercher les IPs en lisant le paquet de données. Pour ne pas avoir des écrans de résultats à ralonge à chaque paquets (80 colonnes ca fait peu :-), on va utiliser des variables globales qu'on initialise en passant des paramètres au programme, afin de gérer la sortie visuelle. on modifie déjà la clause main, qui passe de int main(void) à int main(int argn, char **argv) On va pouvoir récupérer les arguments, et cela en créant une fonction void initvars(int argn, char **argv) qui va lire chaque arguments un par un, et gérer nos variables. On retouche le programme, et on a: # -- Sniffeur avec pcap, version alpha 0.02 -- # #include #include /***************************************************************************/ // Variables int affiche_ethernet_header; /***************************************************************************/ // Déclaration des structures: struct ether_header { u_char ether_dhost[6]; //destination host (adresse de destination) u_char ether_shost[6]; //source host (adresse source) u_short ether_type; //type de trame }; /***************************************************************************/ void initvars(int argn, char **argv) { int arg; // on met tout au paramètres par défaut affiche_ethernet_header = 0; while ((arg = getopt(argn, argv, "aE")) != EOF) // c'est BEAUCOUP plus simple quand on connait getopt :-) { switch(arg) { case 'E': affiche_ethernet_header = 1; break; default: printf("\nE: en tete Ethernet\n"); break; } } } /***************************************************************************/ void print_ether_header(struct ether_header *paquet) { int i; for (i=0;i<6;i++) { printf("%.2x",paquet->ether_shost[i]); if (i!=5) printf(":"); } printf(" -> "); for (i=0;i<6;i++) { printf("%.2x",paquet->ether_dhost[i]); if (i!=5) printf(":"); } printf(" "); printf("Type de trame: %.4x\n",paquet->ether_type); } int main(int argn, char **argv) { int i; pcap_t *desc = NULL; struct pcap_pkthdr usefull; u_char *packet; /* Initialisation des variables globales */ initvars(argn, argv); /* Pour l'affichage: si à 1, alors on affiche, sinon on n'affiche pas. */ if ((desc = pcap_open_live(pcap_lookupdev(NULL), 1500, 0, 1000, NULL))==NULL) { perror("pcap_open_live()"); exit(1); } printf("A partir de maintenant, je surveille le traffic sur %s\n", pcap_lookupdev(NULL)); while (1) { packet = (u_char *) pcap_next(desc, &usefull); if (packet != NULL) { if (affiche_ethernet_header) print_ether_header((struct ether_header*)packet); } } } # -- FIN 'Sniffeur avec pcap, version alpha 0.02' -- # Pour le faire marcher, faites: virginie:/usr/www/htdocs/mags/progs$ ./test -E A partir de maintenant, je surveille le traffic sur xl0 00:10:5a:df:2d:92 -> 00:20:af:f3:24:29 Type de trame: 0008 00:20:af:f3:24:29 -> 00:10:5a:df:2d:92 Type de trame: 0008 (-E pour faire afficher l'entète Ethernet) Passons à l'entète IP (je passe le formatage complet de l'entète ethernet, c'est trop trivial) Rappelons le format du header IP: 0 7 15 16 31 +--------+--------+----------------+--------------------------------+ | version|longueur| type de service| longueur totale sur 16 bits | | 4 bits | 4 bits | (TOS) 8 bits | (en octets) | +--------+--------+----------------+------+-------------------------+ | identification |3 bits| 13 bits fragment offset | | |flags | | +-----------------+----------------+------+-------------------------+ | durée de vie | protocole | somme de contrôle d'en tête | | (TTL) 8 bits | 8 bits | (16 bits) | +-----------------+----------------+--------------------------------+ | adresse IP source sur 32 bits | | | +-------------------------------------------------------------------+ | adresse IP destination sur 32 bits | | | +-------------------------------------------------------------------+ | | / options (s'il y en a) / / données / | | +-------------------------------------------------------------------+ Et hop, la structure associée à ca: struct ip { #if BYTE_ORDER == LITTLE_ENDIAN // l'ordre des bits dépendant des systèmes u_char ip_hl:4, // header length (taille) u_char ip_v:4; // version #endif #if BYTE_ORDER == BIG_ENDIAN u_char ip_v:4, // version u_char ip_hl:4; // header length #endif u_char ip_tos; // Type de service short ip_len; // taille totale u_short ip_id; // identification short ip_off; // champ de décalage du fragment #define IP_DF 0x4000 // drapeau de non fragmentation #define IP_MF 0x2000 // drapeau de fragmentation #define IP_OFFMASK 0x1fff // masque pour la fragmentation u_char ip_ttl; // time to live u_char ip_p; // protocole u_short ip_sum; // somme de controle struct in_addr ip_src; // adresse source struct in_addr ip_dest; // adresse destination }; (Note, on définit aussi in_addr, ca ne mange pas de pain: struct in_addr { u_long s_addr; }; ) Voila ce que l'on doit traiter si le type de trame est 0x0800 Pour récupérer la structure ip quand on a un paquet, il suffit de déclarer un pointeur vers cette structure: struct ip *paquetip; Et de l'assigner dès qu'on a le paquet: paquetip=(struct ip*)(packet+sizeof(struct ether_header)); Et voila, il ne reste plus qu'à appeler printf("%x\n",paquetip->ip_p); //pour avoir le protocole printf("%l\n",paquetip->ip_src.s_addr); //pour avoir l'adresse IP On pourra en faire de même avec les protocoles UDP, TCP ... Mais jusqu'à la, si vous savez programmer en C, si vous avez la doc sur le TCP/IP (une fois complète), et un peu de jujotte, vous pourrez faire un sniffeur plus puissant que tcpdump lui même. J'espère vous avoir appris quelquechose.