3 Bibliothèques fournies
Deux bibliothèques vous sont fournies pour mener à bien ce tutorat réseau. Une bibliothèque
de gestion d'évenements, très générique et qui pourrait être utilisée dans un autre projet.
Une autre bibliothèque concernant les interfaces Ethernet virtuelles, déjà utilisée dans le
tutorat précédent de réalisation d'une couche liaison.
3.1 Bibliothèque de tableaux associatifs
Le but de ces tableaux est de remplacer les structures statiques de C. De nombreuses
structures seraient nécessaires pour communiquer entre les différentes couches de
la pile TCP/IP. Les tableaux associatifs remplacent ces structures.
Un tableau associatif s'initialise à NULL :
AssocArray *a=NULL;
Il est possible d'ajouter un élément au tableau (ici on ajoute
l'entier value avec l'indice index) :
arraysSetValue(&a,"index",(void *)&value,sizeof(value),drapeau);
La valeur doit toujours être passée par référence. Le dernier paramètre
drapeau peut modifier le comportement de la fonction :
-
le bit AARRAY_FORCE_NUMERIC indique l'indice est de
type entier, ce qui permet des optimisations mémoire ;
- le bit AARRAY_DONT_COMPACT indique qu'il ne faut pas
tenter d'optimiser l'espace mémoire pour des valeurs de petite taille ;
- le bit AARRAY_DONT_DUPLICATE indique qu'il ne faut pas
dupliquer la valeur (en utilisant un malloc) mais utiliser directement
le pointeur passé en paramètre, du coup cette valeur ne sera pas libérée
par un arraysFreeArray.
Bien sur la valeur pour un indice peut être retouvée :
int size;
int *value=(int *)arrayGetValue(a,"index",&size,drapeau);
Le paramètre drapeau doit être AARRAY_FORCE_NUMERIC si la
valeur a été ajouté de façon identique. La taille de la valeur désignée
par le pointeur retourné est stockée dans le paramètre size (sauf
si ce paramètre est NULL).
Enfin un tableau peut être affiché par la fonction arraysDisplayArray
et détruit par la fonction arraysFreeArray.
3.2 Bibliothèque de gestion d'événements
Cette bibliothèque est une version étendue de la bibliothèque que vous avez utilisé pour
le projet de réalisation d'une couche liaison. Dans cette version un événement n'est pas
forcement déclenché par une activité sur un descripteur de fichier mais peut l'être par
programmation avec effet immédiat ou différé.
La création et la suppression d'événements se font avec les fonctions
int eventsCreate(int priority,void *data);
void eventsRemove(int identity);
A ce niveau on associe une priorité à l'événement; si plusieurs événements sont actifs
en même temps, les actions liées au plus prioritaire seront exécutées en premier. On
associe aussi une donnée à l'événement sous la forme d'un pointeur générique; les actions
de l'événement peuvent l'exploiter lorsqu'elles sont appellées. Pour associer une ou
plusieurs actions à un événement utilisez la fonction
int eventsAddAction(int identity,
unsigned char (*handler)(EventsEvent *,EventsSelector *),
int level);
Comme les événements, les actions peuvent être exécutées dans un ordre précis en jouant
sur le paramètre level. Les deux paramètres passés à la fonction d'action permettent
d'accéder aux champs de l'événement et aux champs du sélecteur ayant déclenché l'événement.
Dans les champs utiles de l'événement on peut citer le champ data_init contenant la
donnée liée à l'événement. Dans les champs utiles du sélecteur on peut citer les champs
data_this contenant la donnée liée au selecteur et le champ selector contenant,
par exemple, le numéro du descripteur, dans le cas d'une activation sur descripteur de fichier.
Enfin, pour décrire comment un événement est déclenché, trois fonctions permettent d'associer
des sélecteurs à des événements :
int eventsTrigger(int identity,void *data);
int eventsSchedule(int identity,long timeout,void *data);
int eventsAssociateDescriptor(int identity,int descriptor,void *data);
La première fonction déclenche l'événement immédiatement; ses actions sont appelées
dès que la fonction principale de surveillance des événements s'active. La seconde
fonction programme un déclenchement différé au bout de timeout micro-secondes.
Enfin la dernière fonction associe un événement à un descripteur; les actions de
l'événement sont exécutées dès que des données en lecture sont disponible sur le
descripteur.
Enfin le gestionnaire d'événement est démarré par la fonction
void eventsScan(void);
L'appel de cette fonction est bloquant. La fonction ne peut se terminer que si aucun
événement ne possède plus de sélecteur. Il faut noter que c'est une situation qui
peut se produire; les sélecteurs insérés par les fonctions eventsTrigger et
eventsSchedule sont supprimés après l'exécutions des actions correspondantes.
De même si une des actions liées à un événement déclenché sur un sélecteur de type
descripteur retourne une valeur négative le sélecteur est supprimé.
3.3 Bibliothèque de gestion d'interfaces Ethernet virtuelles
Comme dans le projet précédent cette bibliothèque contient une unique fonction
permettant de créer une interface Ethernet virtuelle. Ne pas oublier les drapeaux
IFF_TAP et IFF_NO_PI lors de l'appel. Il est rappelé que la lecture et
l'écriture sur une interface TAP est triviale; les paquets sont envoyés directement
sans mention de leur taille. La création d'une interface TAP nécessite des droits
spécifiques. Pour donner ces droits à votre programme vous utiliserez le script
setcap déclaré dans le fichier super.tab des machines de TP. De la
même façon, un utilisateur normal ne peut pas configurer une interface réseau;
utilisez à nouveau la commande super mais en appelant cette fois le script
ifconfig.
3.4 Prise en main de la bibliothèque de gestion d'événements
Commencez par comprendre le fonctionnement de la bibliothèque en ajoutant des
fonctions (dont une fonction main) dans le fichier stack.c. Vous devez
créer des événements de différents types :
-
à déclenchement immédiat et affichant la donnée de l'événement et celle du
sélecteur (données de type chaîne de caractères), créez un événement de ce
type avec 10 sélecteurs, tous possédant des données différentes ;
- à déclenchement différé et affichant la donnée du sélecteur, créez un
événement de ce type avec deux sélecteurs, un sélecteur avec un minuteur
de 10 secondes et non récurrent, un sélecteur avec un minuteur de 5 secondes
et se réinitialisant 10 fois ;
- à déclenchement lié à un descripteur et affichant les données lues sur le
descripteur préfixées d'une chaîne quelconque, créez un événement de ce type
et un sélecteur lié à l'entrée standard.
3.5 Prise en main de l'ensemble des bibliothèques
L'idée est maintenant de programmer un embryon de pile TCP/IP capable d'afficher les
paquets Ethernet reçus. Pour cela vous trouverez dans les fichiers netether.[hc]
les fonctions de décodage et d'émission de paquets Ethernet. Commencez par utiliser
la fonction de décodage comme action d'un événement auquel vous allez associer un
sélecteur par descripteur de fichier. Vous aurez compris que le descripteur en
question doit être obtenu en utilisant la bibliothèque de gestion des interfaces
Ethernet virtuelles. Attention, pour que la fonction de décodage fonctionne correctement,
l'événement doit être initialisé avec une donnée de type EthernetInterface.
Testez votre exécutable stack après l'avoir passé par le script setcap.
Pour tester votre embryon de pile, vous devez configurer l'interface TAP avec le
script ifconfig. Générez des paquets ARP via la commande ping, votre pile
doit les capturer et les afficher.
Dans un second temps, tentez d'émettre un paquet Ethernet. Pour cela créez un
événement avec la fonction d'émission Ethernet comme action. Associez à cet
événement un sélecteur de type minuteur (réglé à 30s). Prenez garde à bien initialiser
le sélecteur avec une donnée de type tableau associatif. Regardez le code pour trouver
les éléments que le tableau doit contenir. En particulier, en lisant le code, vous
devez vous apercevoir que la donnée associée à l'indice data est utilisée dans
un realloc. Il faudra donc insérer la valeur dans le tableau en utilisant le
bit AARRAY_DONT_DUPLICATE. Pour vérifier que le paquet est bien envoyé par
votre embryon de pile, lancez l'utilitaire ether sur l'interface tap0.
Attention ether doit être utilisé, lui aussi, via super.