Вы находитесь на странице: 1из 14

Ceci est un extrait électronique d'une publication de

Diamond Editions :

http://www.ed-diamond.com

Ce fichier ne peut être distribué que sur le CDROM offert


accompagnant le numéro 100 de GNU/Linux Magazine France.
La reproduction totale ou partielle des articles publiés dans Linux
Magazine France et présents sur ce CDROM est interdite sans accord
écrit de la société Diamond Editions.

Retrouvez sur le site tous les anciens numéros en vente par


correspondance ainsi que les tarifs d'abonnement.

Pour vous tenir au courant de l'actualité du magazine, visitez :

http://www.gnulinuxmag.com

Ainsi que :

http://www.linux-pratique.com
et

http://www.miscmag.com
sécurité Réseau
 Programmation réseau
avec Libnet et Libpcap
Cyril Nocton

EN DEUX MOTS Les bibliothèques C Libnet et Libpcap accord avec les règles de filtrage actives et applique des tests
d’intégrité dépendant de la plate-forme considérée. En mode
simplifient la programmation d’applications réseau
link, les datagrammes sont injectés au niveau de la couche de
de bas niveau. Nous appuierons notre présentation liaison, court-circuitant les contrôles du noyau.
sur une version simplifiée de l’utilitaire traceroute
baptisée YAT (Yet Another Traceroute). Plate- Somme de Adresse
Identifiant
forme contrôle source
Linux 2+ Fixée Fixé si 0 Fixée si 0
Pré-requis OpenBSD Fixée
a lecture de cet article réclame une 2.8+
bonne connaissance du langage
C et de la suite de protocoles Solaris 2.6+ Fixée
Internet. Le programme est Tests d’intégrité en mode raw
compilé avec GCC. Les tests ont été effectués
sous Mac OS X. Libpcap est une bibliothèque de capture de datagrammes
initialement développée pour épauler l’utilitaire Tcpdump.
Elle peut être combinée avec un filtre de paquets pour des
Présentation des performances optimales.
bibliothèques Libnet et
Libpcap Installation des bibliothèques
Libnet est une bibliothèque portable Libnet et Libpcap
conçue pour construire et injecter des Téléchargez les sources des bibliothèques (cf. URL en annexe).
datagrammes dans tout type de réseau. Elle Lancez les scripts d’installation.
apporte un niveau d’abstraction suffisant
pour un déploiement rapide d’applications $ tar -xzf libnet-1.1.0.tar.gz
$ cd libnet-1.1.0
réclamant un contrôle fin des données $ ./configure
transmises. Libnet gère nativement de $ make
nombreux protocoles. Représentons les $ sudo make install
plus communs dans l’optique d’un modèle $ cd ..
$ tar -xzf libpcap-0.8.3.tar.gz
réseau simplifié à quatre couches. $ cd libpcap-0.8.3
$ make
$ sudo make install

Le script libnet-config retourne des options qui devront


être saisies lors de la compilation.
$ libnet-config --defines
-DLIBNET_BIG_ENDIAN
$ libnet-config --libs
-lnet

Les expressions little endian et big endian font référence


à l’ordonnancement en mémoire des données codées sur
plusieurs octets. Ici, l’option GCC -D crée une macro BIG_ENDIAN
(processeur PowerPC). L’option -l est suivie d’un nom de
bibliothèque à lier au code d’une application.YAT repose sur
Lib(net) et Lib(pcap). Dans notre cas, la commande sera :

gcc -Wall -DLIBNET_BIG_ENDIAN yat.c -o yat -lnet –lpcap

Gestion des erreurs


Quelques protocoles gérés par Libnet. Les bibliothèques Libnet et Libpcap copient les messages
d’erreur dans des tampons de LIBNET_ERRBUF_SIZE et
Une conception modulaire autorise l’ajout de
PCAP_ERRBUF_SIZE caractères. Nous réservons un tampon
nouveaux protocoles. En mode raw socket , global en conséquence et créons une fonction annexe error()
les datagrammes sont injectés au niveau de qui accepte les mêmes options de formatage que la fonction
la couche IP. Le noyau contrôle le routage en fprintf().

70  GNU Linux Magazine France


#define ERRBUF_SIZE MAX(LIBNET_ERRBUF_SIZE, PCAP_ERRBUF_SIZE)

char err[ERRBUF_SIZE]; /* Tampon messages d’erreur */


char *command; /* Nom de la commande shell */

/* error - affiche un message d’erreur et quitte le programme */

void
error(const char *format, ...)
{
va_list list;

va_start(list, format);
fprintf(stderr, «%s: «, command);
vfprintf(stderr, format, list);
va_end(list);
exit(EXIT_FAILURE);
}
Les fonctions libnet_geterror() et pcap_geterr() retournent
un message explicatif quand une des bibliothèques génère
une erreur.

char *libnet_geterror(libnet_t *l)

Contexte Libnet
Argument ou résultat Description
l Descripteur Libnet Voici son prototype
SUCCES Un message d’erreur libnet_t *libnet_init(int injection_type, char *device, char *err_buf)
Fonction libnet_geterror()
Et la description des arguments

char *pcap_geterr(pcap_t *p) Argument ou résultat Description


Mode d’injection des
Argument ou résultat Description injection_type
datagrammes
p Descripteur Libpcap Interface réseau
device
SUCCES Un message d’erreur sélectionnée
Fonction pcap_geterr() err_buf Un message d’erreur
SUCCES Un descripteur Libnet
Contrôle des privilèges du ECHEC NULL
programme Fonction libnet_init()
L’emploi des bibliothèques Libnet et Libpcap nécessite
généralement des droits étendus. Une fonction check_uid() Les principaux modes d’injection sont :
contrôle l’identifiant UID du processus qui l’invoque.
Mode Description
/* check_uid – vérifie l’UID du processus */ LIBNET_RAW4 Couche IP (version 4)
void LIBNET_RAW6 Couche IP (version 6)
check_uid(void) LIBNET_LINK Couche de liaison
{
if (getuid() != 0) Mode d’injection des datagrammes
{
error(«you must be root to run this program\n»); Le mode link est plus complexe à programmer
} car les informations d’adressage doivent
} être communiquées.YAT travaille en mode
raw. Une fonction libnet_init() amorce
Initialisation de la bibliothèque l’action de la commande.
Libnet char *device; /* Interface réseau */
La fonction libnet_init() initialise la bibliothèque Libnet. Elle libnet_t *netd; /* Descripteur Libnet */
alloue une structure de type libnet_context regroupant les
informations relatives à la session. void
init_libnet(void)
{

Numéro 76 / Octobre 2005 71


sécurité réseau

netd = libnet_init(LIBNET_RAW4, device, err); Argument ou résultat Description


if (netd == NULL)
{ sp Port source
error(«can’t init libnet (%s)\n», err);
} dp Port destination
}
len Longueur du datagramme
Libération d’un sum Somme de contrôle ou 0
environnement de payload
Pointeur données
travail Libnet encapsulées ou NULL
La fonction libnet_destroy() libère un payload_size Taille données encapsulées
environnement de travail Libnet.
l Descripteur Libnet
void libnet_destroy(libnet_t *l)
ptag Tag Libnet ou 0
Argument Description SUCCES Un tag Libnet
l Descripteur Libnet
ECHEC -1
Fonction libnet_destroy()
Fonction libnet_build_udp()

Construction de
datagrammes Internet libnet_ptag_t libnet_build_tcp(u_short sp, u_short dp, u_long seq,
Les fonctions de construction de datagrammes u_long ack, u_char control, u_short win, u_short sum, u_short urg,
ont des prototypes homogènes. On procède u_short len, u_char *payload, u_long payload_size, libnet_t *l,
de la couche de transport vers la couche libnet_ptag_t ptag)
de liaison.

Argument ou résultat Description


sp Port source
dp Port destination
seq Numéro de séquence
ack Numéro d’acquittement
control Drapeaux de contrôle
Taille de la fenêtre de
win
réception
sum Somme de contrôle ou 0
urg Pointeur données urgentes
len Longueur du datagramme
Pointeur données
payload
encapsulées ou NULL
payload_size Taille données encapsulées
l Descripteur Libnet
ptag Tag Libnet ou 0
SUCCES Un tag Libnet

Construction d’un datagramme ECHEC -1


Fonction libnet_build_tcp()
Détaillons celles destinées à la suite de
protocoles Internet.
libnet_ptag_t libnet_build_ipv4(u_short len, u_char tos, u_short
libnet_ptag_t libnet_build_udp(u_short sp, u_short dp, u_short len, u_short sum, id, u_short frag, u_char ttl, u_char proto, u_short sum, u_long
u_char *payload, u_long payload_size, libnet_t *l, libnet_ptag_t ptag) src, u_long dst, u_char *payload, u_long payload_size, libnet_t
*l, libnet_ptag_t ptag);

72  GNU Linux Magazine France


Programmation réseau avec Libnet et Libpcap

YAT injecte des sondes UDP de TTL croissante


Argument ou résultat Description
dans le réseau et interprète les messages ICMP
len Longueur du datagramme TTL Exceeded in Transit retournés par les
routeurs. Une fonction send_probe() attache
tos Qualité de service
un identifiant unique à chaque datagramme
id Identifiant IP afin de lever toute ambiguïté lors du calcul
du temps de propagation RTT. Dans le cas
frag Drapeaux de fragmentation de figure suivant, deux sondes sont émises
ttl Durée de vie du datagramme en séquence, mais seule la seconde réponse
doit être prise en compte dans le calcul.
proto Protocole encapsulé
sum Somme de contrôle ou 0
src Adresse IP source (big endian)
dst Adresse IP destination (big endian)
Pointeur données encapsulées
payload
ou NULL
payload_size Taille données encapsulées
l Descripteur Libnet
Discrimination des réponses ICMP
ptag Tag Libnet ou 0
Voici le format d’une sonde UDP. Notez
SUCCES Un tag Libnet l’absence d’options IP.
ECHEC -1
Fonction libnet_build_ipv4()

Libnet calcule automatiquement la


somme de contrôle du datagramme
si l’argument sum vaut 0. L’argument
payload pointe sur les données qui
complètent l’en-tête. Libnet maintient
les données d’un bloc dans une
structure de type Pblock. L’argument
ptag joue un rôle important à cet
égard. Sonde UDP

On évite la duplication d’une structure Le code exploite les fonctions Libnet


en mémoire en conservant la valeur décrites précédemment.
retournée lors d’un premier appel
#define PROBE_LEN 28 /* Longueur d’une sonde IP/UDP */
libnet_build_protocole().
Le chaînage des Pblocks reflète le u_long src_ip; /* Adresse IP locale (big endian) */
mode de construction incrémental u_long dst_ip; /* Adresse IP cible (big endian) */
des blocs. u_short src_port; /* Port UDP local */
Pblock u_short dst_port; /* Port UDP cible */

/* Informations relatives à une sonde */

struct probe_info_t
{
u_long ip; /* Adresse IP passerelle (big endian) */
double tm_snt; /* Heure émission sonde UDP (µs) */
double tm_rcv; /* Heure réception réponse ICMP (µs) */
char *flag; /* Drapeau associé au message */
bool reject; /* Rejet par la passerelle */
};

/* now - retourne l’heure courante en µs */

double
now(void)
{
Chaînage de Pblocks

Numéro 76 / Octobre 2005 73


sécurité réseau

struct timeval tv; {


struct sockaddr_in name;
gettimeofday(&tv, NULL); int sockd;
return tv.tv_sec * 1E6 + tv.tv_usec; socklen_t size;
}
sockd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
/* send_probe - injecte une sonde UDP */ if (sockd == -1)
{
void error(«can’t create a socket\n»);
send_probe(u_char ttl, u_short id, struct probe_info_t *pinfo) }
{ size = sizeof(name);
static libnet_ptag_t udp_tag, ip_tag; bzero(&name, size);
name.sin_family = AF_INET;
udp_tag = libnet_build_udp( if (bind(sockd, (struct sockaddr *)&name, size) == -1)
src_port, /* Port source */ {
dst_port, /* Port destination */ error(«can’t bind a socket\n»);
LIBNET_UDP_H, /* Longueur datagramme */ }
0, /* Somme de contrôle auto */ if (getsockname(sockd, (struct sockaddr *)&name, &size) == -1)
NULL, /* Pointeur données */ {
0, /* Longueur données */ error(«can’t retrieve socket name\n»);
netd, /* Descripteur Libnet */ }
udp_tag); /* Tag UDP */ src_port = ntohs(name.sin_port);
if (udp_tag == -1) }
{
error(„can‘t build udp packet (%s)\n“, libnet_
geterror(netd)); Résolution d’adresses IP
} La fonction libnet_get_ipaddr4() retourne l’adresse IPv4 (big
ip_tag = libnet_build_ipv4(
PROBE_H, /* Taille du datagramme */
endian) de l’interface réseau référencée par un descripteur
0, /* ToS */ Libnet.
id, /* Identifiant */
0, /* Bits de fragmentation */ u_long libnet_get_ipaddr4(libnet_t *l)
ttl, /* TTL */
IPPROTO_UDP, /* Protocole encapsulé */
0, /* Somme de contrôle auto */ Argument ou résultat Description
src_ip, /* IP source */ l Descripteur Libnet
dst_ip, /* IP destination */
NULL, /* Pointeur données */ Adresse IPv4 de l’interface
SUCCES
0, /* Longueur données */ (big endian)
netd, /* Descripteur Libnet */
ip_tag); /* Tag IP */ ECHEC -1
if (ip_tag == -1) Fonction libnet_get_ipaddr4()
{
error(„can‘t build ip packet (%s)\n“, libnet_
geterror(netd)); La fonction libnet_addr2name4() convertit une adresse IPv4
} (big endian) en sa contrepartie textuelle. Le résultat est soit
if (libnet_write(netd) < PROBE_LEN) un nom canonique soit un quadruplet d’entiers selon la valeur
{
de l’argument use_name.
error(„can‘t write packet (%s)\n“, libnet_geterror(netd));
} u_char *libnet_addr2name4(u_long in, u_short use_name)
pinfo->tm_snt = now();
}
Argument ou résultat Description
Allocation d’un port in Adresse IPv4 (big endian)
UDP local LIBNET_RESOLVE ou
L’allocation d’un port UDP libre permet de use_name
LIBNET_DONT_RESOLVE
ne pas interférer avec un serveur local ou
Nom canonique ou
avec d’autres instances de YAT. SUCCES
quadruplet d’entiers
Une fonction allocate_port() crée une
ECHEC -1
socket UDP et réserve le port attribué
Fonction libnet_addr2name4()
par le système jusqu’à la terminaison du
programme.
/* allocate_port - alloue un port UDP libre */ La fonction libnet_name2addr4() remplit le rôle opposé.
u_long libnet_name2addr4(libnet_t *l, u_char *name, u_short
void use_name)
allocate_port(void)

74  GNU Linux Magazine France


Programmation réseau avec Libnet et Libpcap

Argument ou résultat Description int data_offset; /* Offset données couche réseau */

l Descripteur Libnet /* init_libpcap - initialise la capture */


Nom canonique ou
name void
quadruplet d’octets
init_libpcap(void)
LIBNET_RESOLVE ou {
use_name
LIBNET_DONT_RESOLVE capd = pcap_open_live(device, SNAP_LEN, 0, PCAP_TIMEOUT, err);
if (capd == NULL)
SUCCES Adresse IPv4 (big endian) {
ECHEC -1 error(«can’t open device %s (%s)\n», device, err);
}
Fonction libnet_name2addr4()
if (pcap_setnonblock(capd, 1, err) == -1)
Une fonction set_ip() fixe les adresses IPv4 source et {
cible. error(«can’t put %s in non blocking mode (%s)», device, err);
}
/* set_ip - fixe les adresses IP source et destination */ if ((data_offset = datalink_offset(pcap_datalink(capd))) == -1)
{
void error(«%s link layer type unknown\n», device);
set_ip(const char *host) }
{ }
src_ip = libnet_get_ipaddr4(netd);
if (src_ip == -1) La fonction pcap_setnonblock() bascule
{ l’interface en mode non bloquant. Une action
error(«bad source ip (%s)\n», libnet_geterror(netd)); nécessaire en raison d’une incompatibilité
} qui sera exposée plus loin.
dst_ip = libnet_name2addr4(netd, (u_char*)host, LIBNET_RESOLVE);
if (dst_ip == -1) int pcap_setnonblock(pcap_t *p, int non_block, char *err_buf)
{
error(«bad destination ip (%s)\n», libnet_geterror(netd));
Argument ou résultat Description
}
} P Descripteur Libpcap
Mode bloquant si 0, non
Initialisation de la bibliothèque non_block
bloquant sinon
Libpcap err_buf Un message d’erreur
La fonction pcap_open_live() retourne un descripteur
SUCCES 0
employé pour la capture de datagrammes.
ECHEC -1
pcap_t *pcap_open_live(const char *device, int snap_len, int
Fonction pcap_setnonblock()
promisc, int to_ms,
char *err_buf)
La fonction pcap_lookupdev() retourne
le nom d’une interface réseau que nous
Argument ou résultat Description passons à pcap_openlive().
device Interface réseau capturée char *pcap_lookupdev(char *err_buf)
Taille du tampon de
snap_len
réception Argument ou résultat Description
promisc Mode promiscuous err_buf Un message d’erreur
to_ms Délai d’expiration (ms) SUCCES Un nom d’interface
err_buf Un message d’erreur ECHEC NULL
SUCCES Un descripteur Libpcap Fonction pcap_lookupdev()

ECHEC NULL Une fonction set_device() procède à


Fonction pcap_open_live() l’affectation.
/* set_device - fixe l’interface réseau */
Un datagramme est tronqué si sa longueur est supérieure à la
valeur de l’argument snap_len.YAT recueille des datagrammes void
de 128 octets, prologue compris. Une fonction init_libpcap() set_device(void)
initialise la capture. {
if (device == NULL)
#define SNAP_LEN 128 /* Longueur tampon de capture */ {
#define PCAP_TIMEOUT 100 /* Délai d’expiration pcap_next() en ms */ device = pcap_lookupdev(err);
if (device == NULL)
pcap_t *capd; /* Descripteur Libpcap */ {

Numéro 76 / Octobre 2005 75


sécurité réseau

error(«no interface available (%s)\n», err); #ifdef DLT_RAW /* IP brut */


} { DLT_RAW, 0 },
} #endif
} { -1, -1 }
};
Offset des données /* Offset des données de la couche réseau */
capturées
Les données capturées comportent un int
prologue dépendant du protocole de liaison. datalink_offset(int type)
{
Il est par exemple de 14 octets dans le cas
int i;
d’Ethernet (norme IEEE 802.3).
i = 0;
while (DATALINK_INFO[i].type != -1)
{
if (DATALINK_INFO[i].type == type)
{
return DATALINK_INFO[i].offset;
Prologue Ethernet en aval du contrôleur }
i++;
La fonction pcap_datalink() retourne le }
type de protocole de liaison référencé par return -1;
}
un descripteur.
int pcap_datalink(pcap_t *p) Libération d’un environnement de
Argument ou résultat Description
travail Libpcap
La fonction pcap_close() libère un environnement de travail
p Descripteur Libpcap Libpcap.
Un type du protocole de void pcap_close(pcap_t *p)
SUCCES
liaison
Fonction pcap_datalink() Argument ou résultat Description
p Descripteur Libpcap
Nous stockons l’offset des données réseau
Fonction pcap_close()
dans une table statique. Une fonction
datalink_offset() balaie la table à la
recherche d’un offset particulier. Compilation d’un filtre de paquets
/* Offset des données réseau transportées par divers protocoles de BPF/LSF
liaison */ Les datagrammes transitant par l’interface réseau écoutée
ne concernent pas tout le processus de traçage en cours. Il
struct datalink_info_t
{
est pertinent de mettre en place un filtre s’exécutant dans
int type; /* Type liaison */ l’espace du noyau afin de limiter les transferts de données
int offset; /* Offset données */ dans l’espace utilisateur.Voici l’architecture générale du filtre
}; BPF (BSD Packet Filter).
struct datalink_info_t DATALINK_INFO[] =
{
#ifdef DLT_EN10MB /* Ethernet */
{ DLT_EN10MB, 14 },
#endif
#ifdef DLT_IEEE802_11 /* WiFi */
{ DLT_IEEE802_11, 24 },
#endif
#ifdef DLT_LOOP /* Loopback */
{ DLT_LOOP, 4 },
#endif
#ifdef DLT_NULL /* Loopback BSD */
{ DLT_NULL, 4 },
#endif
#ifdef DLT_PPP /* PPP */
{ DLT_PPP, 4 },
#endif
#ifdef DLT_PPP_ETHER /* PPP over Ethernet */
{ DLT_PPP_ETHER, 12 },
#endif Architecture du filtre BPF

76  GNU Linux Magazine France


Programmation réseau avec Libnet et Libpcap

Les datagrammes sont captés en aval du pilote réseau. Un Voici l’expression du filtre dans le langage
programme écrit dans un langage d’assemblage spécifique intermédiaire adopté par Tcpdump.
décide de leur validité. Les datagrammes retenus sont dupliqués #define CODE «ip dst host %s and ip[0] & 15 == 5 and ip[2:2] == 56 \
dans l’espace utilisateur sans emprunter la pile IP. Linux 2.2+ and (icmp[0] == 11 or icmp[0] == 3) and icmp[8] & 240 == 64 \
intègre un filtre LSF (Linux Socket Filter) dérivé de BPF. Nous and icmp[8] & 15 == 5 and icmp[17] == 17 and icmp[20:4] == %lu \
considérons le cas de sockets de type PF_PACKET. and icmp[24:4] == %lu and icmp[28:2] == %u and icmp[30:2] == %u»
Et sa traduction en langage d’assemblage
BPF.
(000) ldh [12]
(001) jeq #ETHERTYPE_IP jt 2 jf 34 ‘ Prologue suivi d’un datagramme IPv4
(002) ld [30]
(003) jeq #SRC_IP jt 4 jf 34 ‘ Adresse IP destination ok
(004) ldb [14]
(005) and #0xf
(006) jeq #0x5 jt 7 jf 34 ‘ En-tête IP sans options
(007) ldh [16]
(008) jeq #0x38 jt 9 jf 34 ‘ Datagramme IP de 56 octets
(009) ldb [23]
(010) jeq #PROTO_ICMP jt 11 jf 34 ‘ Datagramme IP encapsule ICMP
(011) ldh [20]
(012) jset #0x1fff jt 34 jf 13 ‘ Offset données nul
(013) ldxb 4*([14]&0xf)
(014) ldb [x + 14]
(015) jeq #ICMP_TIMXCEED jt 17 jf 16 ‘ Message ICMP Time Exceeded ou...
(016) jeq #ICMP_UNREACH jt 17 jf 34 ‘ Message ICMP Destination Unreachable
(017) ldb [x + 22]
(018) and #0xf0
(019) jeq #0x40 jt 20 jf 34 ‘ Citation IP de type IPv4
Architecture du filtre LSF (020) ldb [x + 22]
(021) and #0xf
(022) jeq #0x5 jt 23 jf 34 ‘ Citation IP sans options
Un datagramme validé par une appel sk_run_filter() est (023) ldb [x + 31]
cloné au niveau de la couche de liaison. (024) jeq #PROTO_UDP jt 25 jf 34 ‘ Citation IP encapsule UDP
YAT analyse les messages ICMP TTL Excedeed in Transit et (025) ld [x + 34]
(026) jeq #SRC_IP jt 27 jf 34 ‘ Citation adresse IP source ok
Destination Unreachable retournés par les routeurs placés
(027) ld [x + 38]
sur le chemin recherché. Ils sont formés en cascade de blocs (028) jeq #DST_IP jt 29 jf 34 ‘ Citation adresse IP destination ok
IPv4, ICMP, IPv4 et UDP. En effet, la RFC 792 stipule que ces (029) ldh [x + 42]
messages sont suivis de l’en-tête IP et des 8 premiers octets (030) jeq #SRC_PORT jt 31 jf 34 ‘ Citation port UDP source ok
de données du datagramme qui a déclenché leur envoi. Le (031) ldh [x + 44]
schéma suivant montre cet enchaînement. Les champs de (032) jeq #DST_PORT jt 33 jf 34 ‘ Citation port UDP destination ok
(033) ret #SNAP_LEN ‘ Succès
couleur rouge seront testés par un filtre BPF/LSF.
(034) ret #0 ‘ Echec

La pseudo-machine adopte des instructions


de taille fixe.

Format d’une instruction BPF

Le champ opcode indique le type et le mode


d’adressage de l’instruction. Les champs jt et
jf servent aux instructions conditionnelles.
Ils correspondent à un saut relatif selon le
résultat du test. Le champ k32 est un champ
générique.
La bibliothèque Libpcap encapsule les
mécanismes de filtrage BPF. La fonction
pcap_compile() traduit une expression en
Réponse ICMP code d’assemblage BPF.

Numéro 76 / Octobre 2005 77


sécurité réseau

int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, Calcul d’une somme de contrôle
bpf_u_int32 netmask)
Internet
Les datagrammes échangés sur Internet possèdent un champ
Argument ou résultat Description somme de contrôle qui permet de vérifier leur intégrité.
p Descripteur Libpcap Elle est définie par la RFC 791 comme le complément à 1
fp Un programme BPF sur 16 bits de la somme en complément à 1 des mots de
16 bits composant le bloc. Le champ somme de contrôle
str Expression BPF est initialisé à 0 lors du calcul. La somme de contrôle d’un
Optimise le code si datagramme IP porte sur son en-tête. La somme de contrôle
optimize
différent de 0 d’un datagramme ICMP englobe les données. Une fonction
netmask Masque réseau ou 0 in_checksum() reprend un algorithme de calcul proposé
par la RFC 1071.
ECHEC -1
Fonction pcap_compile()
/* in_checksum - calcule une somme de contrôle Internet */

La fonction pcap_setfilter() active un u_short


in_checksum(const u_short *addr, u_short len)
filtre. {
u_long sum ;
int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
sum = 0;
Argument ou résultat Description while (len > 1)
{
p Descripteur Libpcap sum += *addr++;
fp Programme BPF len -= 2;
}
SUCCES 0 if (len != 0)
{
ECHEC -1 sum += htons(*(u_char *)addr << 8);
Fonction pcap_setfilter() }
while (sum >> 16 != 0)
{
Finalement, la fonction pcap_freecode() sum = (sum & 0xFFFF) + (sum >> 16);
libère les ressources occupées par un }
programme BPF. return ~sum;
}
void pcap_freecode(struct bpf_program *fp)
En complément à 1, l’opposé d’un nombre positif est obtenu en
Argument ou résultat Description inversant ses bits. L’addition a lieu sans report de l’éventuelle
retenue. Par exemple :
fp Programme BPF
Fonction pcap_freecode() +5 00000101
-3 11111100
Une fonction attach_filter() attache notre --------
S 00000001
filtre à l’interface capturée.
R 00000001
--------
/* attach_filter - attache un filtre à l’interface capturée */ S 00000010

void
En C, les additions ont lieu en complément à 2. L’opposé d’un
attach_filter(void)
{ nombre positif est obtenu en ajoutant 1 à son complément à
struct bpf_program prog; 1. L’addition a lieu sans report de l’éventuelle retenue.
char code[256];
+5 00000101
sprintf(code, CODE, libnet_addr2name4(src_ip, LIBNET_DONT_RESOLVE), -3 11111101
ntohl(src_ip), ntohl(dst_ip), src_port, dst_port); --------
if (pcap_compile(capd, &prog, code, 1, 0) == -1) S 00000010
{ R 00000001
error(«can’t compile packet filter (%s)\n», pcap_geterr(capd)); --------
} S 00000010
if (pcap_setfilter(capd, &prog) == -1) Le passage en complément à 1 s’opère en reportant les
{
error(«can’t attach packet filter (%s)\n», pcap_geterr(capd));
retenues. Pour ce faire, les mots de 16 bits sont additionnés
} dans un accumulateur de 32 bits. Puis les 16 bits de plus
pcap_freecode(&prog); fort poids sont additionnés aux bits de plus faible poids au
} plus deux fois.

78  GNU Linux Magazine France


Programmation réseau avec Libnet et Libpcap

Alignement des données Multiplexage avec


capturées en mémoire select()
Les processeurs modernes sont optimisés pour lire des La fonction select() opère sur un ensemble
données stockées en mémoire à des adresses multiples de de descripteurs de fichier élaboré à l’aide
leur longueur.A défaut, les accès sont dégradés ou déclenchent de macros.
une exception. Considérons un processeur dont le bus de
données a une largeur de 32 bits et comparons la lecture de
blocs de 4 octets situés aux adresses 0 et 1.

0 0 1
1 1 1 2
2 2 2 3
1
3 3 3
2
3
4 4
4
5 5
6 6
7 7 4

Chargement de données alignées vs non aligné

Le chargement du bloc non aligné implique deux accès Macro Description


mémoire au lieu d’un et des traitements arithmétiques
supplémentaires. YAT lit le champ ip_id de la structure Initialise l’ensemble de
FD_ZERO(fd_set *set)
libnet_ipv4_hdr déclarée dans le fichier d’en-tête libnet- descripteurs set
headers.h. FD_SET(int fd, fd_set *set) Ajoute un descripteur fd à set
struct libnet_ipv4_hdr FD_CLR(int fd, fd_set *set) Retire le descripteur fd de set
{ Teste si le descripteur fd est
#if (LIBNET_LIL_ENDIAN) FD_ISSET(int fd, fd_set *set)
présent dans set
u_char ip_hl:4,
ip_v:4; Macros select
#endif
Un appel select() indique si des données
#if (LIBNET_BIG_ENDIAN)
u_char ip_v:4, sont présentes dans un des fichiers fixés
ip_hl:4; par les macros.
#endif
u_char ip_tos; int select(int num_fds, fd_set *read_fds, fd_set *write_fds, fd_set *except_fds,
u_short ip_len; struct timeval *timeout)
u_short ip_id;
u_short ip_off; Argument
u_char ip_ttl; Description
ou résultat
u_char ip_p;
u_short ip_sum; num_fds Valeur du plus grand descripteur plus un
struct in_addr ip_src;
read_fds Ensemble de descripteurs interrogés en lecture
struct in_addr ip_dst;
}; Ensemble de descripteurs interrogés en
write_fds
écriture
Une macro assure le chargement octet par octet de mots Ensemble de descripteurs ayant levé une
except_fds
non alignés. exception
Timeout Délai d’expiration
/* EXTRACT_SHORT - extrait un mot de 16 bits non aligné en mémoire Nombre de descripteurs prêts ou 0 si
*/ SUCCES
expiration du délai
#define EXTRACT_SHORT(p) ((u_short)*(u_char *)(p) << 8 \ ERREUR -1
| (u_short)*((u_char *)(p) + 1))
Fonction select()

Numéro 76 / Octobre 2005 79


sécurité réseau

La variable globaleerrno prend une des icmp_flag(u_short msg)


valeurs suivantes en cas d’erreur : {
int i;
Code Description
i = 0;
EBADF Un des descripteurs est invalide while (ICMP_INFO[i].msg != 0)
EINTR Réception d’un signal avant expiration {
if (ICMP_INFO[i].msg == msg)
EINVAL Le délai d’expiration spécifié est invalide {
Codes errno de la fonction select() return ICMP_INFO[i].flag;
}
Le recours à la fonction select() limite i++;
}
la charge processeur d’un programme return NULL;
recevant des données par intermittence. }
Cette technique est malheureusement
inopérante lorsqu’elle est appliquée à une Réception des messages ICMP
interface filtrée sous BSD. Une macro DO_ La fonction pcap_fileno() retourne le descripteur de fichier
SELECT gère ce cas. associé à un descripteur Pcap.
/* Commentez si l’OS ne supporte pas la combinaison select() + filtre BPF */
int pcap_fileno(pcap_t *p)
#define DO_SELECT 1
Argument ou résultat Description
Messages ICMP p Descripteur Pcap
interprétés SUCCES Un descripteur de fichier
Une combinaison (code, type) identifie Fonction pcap_fileno()
un message ICMP. Nous interprétons les
messages TTL Exceeded in Transit et La fonction pcap_next() retourne un pointeur vers le prochain
Destination Unreachable. Une fonction datagramme en file ou NULL si aucune donnée n’est prête
icmp_flag() retourne un drapeau pour ou en cas d’échec.
chaque message traité. const u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)
/* Table associant un drapeau aux messages ICMP traités */
Argument ou
Description
struct icmp_info_t résultat
{
p Descripteur Libpcap
u_short msg; /* Type icmp << 8 + code icmp */
char *flag; /* Drapeau sur 2 caractères */ h Pointeur données d’en-tête
};
Pointeur prologue + données
SUCCES
struct icmp_info_t ICMP_INFO[] = datagramme
{ NULL si aucune donnée n’est prête
{ 0x0B00, «» }, /* TTL Exceeded in Transit */ ECHEC
ou en cas d’échec
{ 0x0B01, «FR» }, /* Fragment Reassembly Time Exceeded */
{ 0x0300, «UN» }, /* Network Unreachable */ Fonction pcap_next()
{ 0x0301, «UH» }, /* Host Unreachable */
{ 0x0302, «Up» }, /* Protocol Unreachable */ Une fonction received_msg() retourne les informations
{ 0x0303, «UP» }, /* Port Unreachable */ relatives au prochain message ICMP valide.
{ 0x0304, «FN» }, /* Fragmentation Needed and DF Was Set */
{ 0x0305, «RF» }, /* Source Route Failed */ #define ICMP_LEN 36 /* Longueur d’une réponse ICMP */
{ 0x0306, «KN» }, /* Destination Network Unknown */
{ 0x0307, «KH» }, /* Destination Host Unknown */ typedef struct libnet_ipv4_hdr ip_t;
{ 0x0308, «IH» }, /* Source Host Isolated */ typedef struct libnet_icmpv4_hdr icmp_t;
{ 0x0309, «XN» }, /* Destination Network Prohibited */ extern int errno; /* Code d’erreur LibC */
{ 0x030A, «XH» }, /* Destination Host Prohibited */ /* usec_to_time - convertit une durée de µs en timeval */
{ 0x030B, «TN» }, /* Network Unreachable for ToS */
void
{ 0x030C, «TH» }, /* Host Unreachable for ToS */
usec_to_time(double usec, struct timeval *tv)
{ 0x030D, «XC» }, /* Communication Prohibited */
{
{ 0x030E, «PV» }, /* Host Precedence Violation */
tv->tv_sec = usec / 1E6;
{ 0x030F, «PC» }, /* Precedence Cutoff in Effect */
tv->tv_usec = usec - tv->tv_sec * 1E6;
{ 0, NULL }
}
};
/* received_msg - capture un message ICMP */
/* icmp_flag - retourne le drapeau associé à un message ICMP */ bool
received_msg(u_short id, struct probe_info_t *pinfo)
char * {

80  GNU Linux Magazine France


Programmation réseau avec Libnet et Libpcap

Commentez la macro DO_SELECT si votre


struct pcap_pkthdr lnk; système d’exploitation ne supporte pas la
struct libnet_ipv4_hdr *ip, *ip_old; combinaison select() + BPF.YAT procédera
struct libnet_icmpv4_hdr *icmp; alors systématiquement à un appel pcap_
u_char *dgram; next(). La variable max_rtt fixe le temps
char *flag;
double elapsed;
d’attente d’une réponse ICMP. L’essentiel
u_short msg, dgram_len; de la validation a lieu au niveau du filtre.
#ifdef DO_SELECT Les vérifications complémentaires portent
fd_set set; sur les sommes de contrôle IP et ICMP,
struct timeval timeout; l’identifiant IP cité et le type de message
int caph;
ICMP. Les informations sont remontées dans
caph = pcap_fileno(capd); une structure de type probe_info_t.
FD_ZERO(&set);
FD_SET(caph, &set);
#endif L’algorithme de traçage
while ((elapsed = now() - pinfo->tm_snt) <= max_rtt) La fonction trace_route() établit une route
{ proprement dit. L’algorithme est simple.
#ifdef DO_SELECT
YAT procède par envoi de sondes UDP de
usec_to_time(max_rtt - elapsed, &timeout);
switch(select(caph + 1, &set, NULL, NULL, &timeout)) TTL croissante. Les routeurs rencontrés
{ retournent généralement un message ICMP
case 0 : TTL Exceeded in Transit quand le champ
continue; TTL passe à 0. On établit ainsi la route de
case -1 : proche en proche. La boucle s’interrompt si
error(„select() failed (%s), strerror(errno)\n“);
}
une des conditions suivantes est réalisée :
#endif L’adresse IP cible est atteinte.
dgram = (u_char *)pcap_next(capd, &lnk); Le champ TTL de la sonde dépasse
if (dgram == NULL)
MAX_TTL.
{
continue; N messages Destination Unreachable
} sont reçus.
pinfo->tm_rcv = now(); #define MAX_TTL 64 /* Diamètre Internet (RFC 1340) */
dgram_len = lnk.caplen - data_offset;
ip = (ip_t*)(dgram + data_offset); u_char n_queries; /* Nb de requêtes */
if (dgram_len != ANSWER_LEN)
{ /* trace_route - trace une route IP */
continue;
} void
if (in_checksum((u_short *)ip, LIBNET_IPV4_H) != 0) trace_route(void)
{ {
continue; u_long ip, last_ip;
} struct probe_info_t pinfo;
icmp = (icmp_t *)((char *)ip + LIBNET_IPV4_H); bool found;
if (in_checksum((u_short *)icmp, ICMP_LEN) != 0) u_short id, qry;
{ u_char ttl, n_reject;
continue;
} printf(«route to %s (%s)\n», libnet_addr2name4(dst_ip, LIBNET_RESOLVE),
ip_old = (ip_t *)((char *)icmp + LIBNET_ICMPV4_H + ICMP_UNUSED_H); libnet_addr2name4(dst_ip, LIBNET_DONT_RESOLVE));
if (EXTRACT_SHORT(&ip_old->ip_id) != id) id = 1;
{ ttl = 1;
continue; found = false;
} do
msg = ((u_short)icmp->icmp_type << 8) + icmp->icmp_code; {
flag = icmp_flag(msg); printf(«%2d «, ttl);
if (flag == NULL) fflush(stdout);
{ last_ip = INADDR_ANY;
continue; n_reject = 0;
} for (qry = 1; qry <= n_queries; qry++, id++)
pinfo->ip = ip->ip_src.s_addr; {
pinfo->flag = flag; send_probe(ttl, id, &pinfo);
pinfo->reject = (icmp->icmp_type != ICMP_TIMXCEED); if (received_msg(id, &pinfo))
return true; {
} ip = pinfo.ip;
return false; if (ip != last_ip)
} {
if (last_ip != INADDR_ANY)

Numéro 76 / Octobre 2005 81


sécurité réseau

{ # yat www.freeix.net
printf(«\n%2d «, ttl); route to www2.free.fr (213.228.0.181)
} 1 192.168.0.1 (192.168.0.1) 20.6 ms 1.6 ms 1.5 ms
printf(«%s (%s) «, libnet_addr2name4(ip, LIBNET_RESOLVE), 2 1.1.99-84.rev.gaoland.net (84.99.1.1) 14.3 ms 14.1 ms 14.5 ms
libnet_addr2name4(ip, LIBNET_DONT_RESOLVE)); 3 165.226.96-84.rev.gaoland.net (84.96.226.165) 14.4 ms * 34.9 ms
last_ip = ip; 4 v3855.rou1-co-1.gaoland.net (84.96.251.109) 14.4 ms 14.2 ms *
} 5 v3856.par1-co-2.gaoland.net (84.96.251.106) 16.2 ms * 30.3 ms
printf(«%.1f ms %s «, (pinfo.tm_rcv - pinfo.tm_snt) / 1E3, 6 v4078.th21-co2.n9uf.net (62.39.148.66) 37.9 ms * *
pinfo.flag); 7 v4070.th21-co-1.n9uf.net (62.39.148.97) 27.1 ms * 29.1 ms
n_reject += pinfo.reject; 8 th1-6k-2.free.n9uf.net (84.96.235.38) 83.6 ms 16.5 ms 23.4 ms
found = found || ip == dst_ip; /* Big Endian */ 9 vlq-6k-2-v806.intf.routers.proxad.net (212.27.50.42) 16.3 ms * 32.4 ms
} 10 vlq-6k-1-po1.intf.routers.proxad.net (212.27.50.2) 16.1 ms 16.6 ms *
else 11 www2.free.fr (213.228.0.181) 27.0 ms UP 16.3 ms UP 25.0 ms UP
{
printf(«* «);
}
L’étoile marque l’absence de réponse dans un délai fixé par
fflush(stdout); défaut à une seconde. Le drapeau UP repère un message
} ICMP Port Unreachable.
putchar(‘\n’);
ttl++;
}
while (ttl <= MAX_TTL && !found && n_reject < n_queries);  Liens
if (!found)
{ Sources de la bibliothèque Libnet
printf(«Destination not reached\n»); Téléchargez ici les sources officielles de la bibliothèque
} Libnet :
}
http://www.packetfactory.net/projects/libnet/
Chaque adresse IP intermédiaire est
enregistrée dans la variable last_ip.
Un passage à la ligne est généré si la valeur
change pour une distance TTL donnée.
Ceci peut arriver si une technique de
répartition de charge (load balancing) est
mise en œuvre par exemple.

Compilation du
programme
Les sources de YAT sont disponibles en ligne
(http://cyril.nocton.neuf.fr/lm/yat.zip). Pour
compiler le programme, tapez :
$ gcc -Wall `libnet-config --defines` yat.c -o yat -lnet -lpcap

La syntaxe générale de la commande est : Sources de la bibliothèque Libpcap


yat [-h] [-i interface] [-p port] [-q Téléchargez ici les sources officielles de la bibliothèque
requêtes] [-w secondes] hôte Libpcap :
Les options de la commande sont : http://www.tcpdump.org/
BPF : A New Architecture for User-level Packet
Option Description Pacture
-h Affiche de l’aide Ce papier présente l’architecture du filtre de paquets
BSD
-i interface Interface réseau sélectionnée http://www.tcpdump.org/papers/bpf-usenix93.pdf
-p port Port UDP de destination [1..65535] Inside the Linux Packet Filter
-q requêtes Nombre de requêtes par étape [1..10] Ces deux articles décrivent le filtre de paquets Linux.
Délai d’obtention d’une réponse en http://www.linuxjournal.com/article/4852
-w secondes
secondes [1..10] http://www.linuxjournal.com/article/5617
Options de la commande YAT

Cyril Nocton,
Traçons par exemple la route vers le site web
du nœud d’échange Internet Freeix.

82  GNU Linux Magazine France

Вам также может понравиться