Tutorat de programmation réseau
(Polytech'Lille, département GIS, quatrième année).
Conception et réalisation d'une pile TCP/IP.

Xavier Redon

1   Cahier des charges

Dans ce projet, votre but est de réaliser une pile TCP/IP basique. Pour la promotion 2012, l'implantation doit aller jusqu'à permettre le fonctionnement d'un serveur UDP Daytime Protocol (voir le RFC 867). La pile TCP/IP doit être le principal constituant d'une machine virtuelle tournant dans un processus Unix. Cette machine virtuelle doit avoir au moins une interface réseau virtuelle de type Ethernet. Les interfaces des machines virtuelles doivent être connectées à la machine réelle via un commutateur virtuel. Le commutateur virtuel doit être implanté, lui aussi, sous la forme d'un processus Unix. Un dernier processus doit gérer l'interface réseau de la machine réelle la connectant au commutateur virtuel.

2   Indications pratiques

Seule la lecture du cahier des charges est indispensable. Cependant un certain nombre d'indications pour faciliter la tâche va être donné dans la suite de ce document. Si vous avez besoin d'aide pour architecturer votre projet, vous pouvez utiliser l'archive http://rex.plil.fr/Enseignement/Reseau/Tutorat9.Reseau.GIS4/NetStackStudents.tgz. Elle contient une arborescence de répertoires avec les Makefile pour les deux bibliothèques et les trois exécutables nécessaires au projet. Vous y trouvez aussi des fichiers contenant des prototypes de fonctions. Ces prototypes proposent une structuration du projet. Enfin l'archive contient du code pour réaliser les communications entre les processus Unix et la création de l'interface réseau supplémentaire pour la machine réelle. Dans cette version de l'archive, les communications inter-processus sont réalisées par des sockets Internet, ce qui ne manque pas de sel.

3   Commutateur virtuel

Il est conseillé de commencer par concevoir et réaliser le commutateur virtuel. Le principe de ce processus est d'attendre la connexion d'éléments, puis les paquets (au format Ethernet) de ces éléments et enfin de transmettre ces paquets aux éléments concernés.

3.1   Paquets Ethernet

Plutot que de dupliquer les structures et les fonctions concernant Ethernet dans les répertoires des trois exécutables, il est proposé de centraliser ce code dans des fichiers de la bibliothèque de la pile TCP/IP (i.e. le répertoire Stack). Les fichiers en question sont netinterf.c (fonctions liées à Ethernet en général; adresses, paquets et interfaces), ifinfos.h (déclarations publiques pour Ethernet) et netinterf.h (déclarations internes à la pile pour Ethernet). Certaines définitions sont déjà présentes dans ces fichiers, vous pouvez les utiliser ou les remplacer par d'autres que vous trouveriez plus adaptés.

3.2   Le déverminage

Dans un projet de cette taille, il est indispensable d'ajouter du code conditionnel pour effectuer des affichages de déverminage. La constante proposée pour cet usage dans l'archive est VERBOSE. Le système de Makefile est configuré pour propager cette constante lors de la compilation. Il est fortement conseillé d'ajouter de tels affichages dans le commutateur virtuel pour signaler sur quels ports des paquets arrivent et sur quels autres ils sont aiguillés.

3.3   La commutation

Votre commutateur virtuel doit se comporter comme un vrai. A savoir apprendre les ports sur lesquels se trouvent les éléments réseau et ne transmettre les paquets sur un port que lorsque cela est absolument nécessaire. Pour pouvoir tester simplement le commutateur, on impose que les pseudos-paquets Ethernet soient transmis à la mode kvm; d'abord la taille du paquet sur 4 octets en mode gros-boutiste, puis ensuite les données du paquet.

3.4   Une bibliothèque

Les deux autres exécutables du projet vont devoir lire des paquets en provenance du commutateur virtuel et envoyer des paquets vers le dit commutateur. Il est suggéré de créer une mini-bibliothèque comportant ces deux fonctions plutot que de dupliquer le code dans les deux composants. L'archive comporte tout le nécessaire à la création de la bibliothèque (hormis le code des fonctions).

3.5   Test

Grâce au mode de transmission des paquets imposé en 3.3, un test simple est possible pour tester votre commutateur. Vous commencez par lancer deux machines virtuelles kvm. Pour les lancer il faut utiliser, dans la ligne de commande, les options -net socket,connect=<ip>:<port> nécessaires à la création d'une interface réseau qui se connecte sur votre commutateur virtuel. Enfin vous configurez ces interfaces dans le même réseau IP et vous tentez une communication entre les deux machines virtuelles kvm.

4   Interface pour le réseau virtuel

4.1   La théorie

Pour rendre le commutateur virtuel accessible à partir de la machine réelle, il est suggéré d'ajouter une interface réseau à la machine virtuelle qui conduira au commutateur virtuel. Pour cela il est conseillé d'utiliser le pilote d'interfaces virtuelles TAP du noyau de Linux.

4.2   Travail à réaliser

L'archive contient une fonction permettant de créer une interface TAP. Il vous suffit d'ajouter le code récupérant les paquets sur cette interface ou sur le commutateur virtuel et les envoyant de l'autre coté. L'archive contient même une bonne partie de la fonction principale de cet exécutable. Notez que la lecture et l'écriture sur une interface TAP est triviale; les paquets sont envoyés directement sans mention de leur taille.

4.3   Au sujet des permissions

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.

5   Machine Virtuelle

5.1   Principes

Vous pouvez maintenant vous attaquer au dernier exécutable du projet, à savoir la machine virtuelle. Le principe est d'avoir un exécutable comprenant une pile TCP/IP mais sans les autres composants classiques d'un système d'exploitation (gestion mémoire, gestion des processus, gestion des systèmes de fichiers). Cela pose bien entendu un problème puisque les paquets sont sensés provenir de processus et aboutir à des processus. Le problème sera contourné en exécutant ces processus sur la machine réelle et en leur permettant de communiquer avec notre machine virtuelle au travers d'une bibliothèque socket un peu particulière. Plus de précisions seront données dans la section consacrée à cette bibliothèque (voir 10).

5.2   Structure de la pile TCP/IP

La pile TCP/IP de la machine virtuelle va nécessiter des structures de données. Il est proposé de définir et de gérer ces structures dans des fichiers de la bibliothèque de la pile TCP/IP (i.e. le répertoire Stack). Les fichiers en question sont netstack.c (fonctions liées à la pile), stackinfos.h (déclarations publiques pour la pile) et netstack.h (déclarations internes à la pile). Un exemple de champ à inclure dans la structure de la pile est la liste des interfaces réseau de la machine virtuelle. Il est recommandé de prévoir une structure hiérachisée distribuée dans les fichiers de définition des diverses couches. Par exemple si la structure principale est définie dans le fichier stackinfos.h, la structure d'une interface sera définie dans ifinfos.h.

5.3   Travail à réaliser

Vous pouvez vous aider des fichiers présents dans le répertoire Machine de l'archive. Le programme principal de l'exécutable y est complétement écrit. Il vous reste à écrire la fonction d'initialisation de la structure de pile. La configuration de la machine virtuelle est effectuée à l'aide d'un fichier texte dont vous trouverez un exemple dans le fichier Machine/config. Il est conseillé de placer les fonctions d'accès aux champs de la structure de pile dans le répertoire Stack et de ne placer que la fonction de lecture du fichier de configuration dans Machine.

5.4   Le déverminage

Il est suggéré d'afficher la structure de la pile TCP/IP après son initialisation en fonction du fichier de configuration.

5.5   Un test global

Lancez votre commutateur virtuel, connectez-y une interface de la machine réelle puis une machine virtuelle. Faites en sorte que l'interface TAP de la machine réelle et que l'interface de la machine virtuelle soient dans le même réseau IP. Lancez, de votre machine réelle, un ping sur la machine virtuelle. Vérifiez à l'aide de vos affichages de déverminage que les paquets transitent bien comme ils le doivent.

6   Bibliothèque pour la pile TCP/IP

6.1   Présentation

Comme nous l'avons vu à la section 5 notre machine virtuelle est juste une coquille vide qui se contente d'initialiser une structure de pile TCP/IP et qui démarre la pile en appelant la fonction stackOn de la bibliothéque "pile TCP/IP" située dans le répertoire Stack. Tout le travail est donc effectué par cette bibliothèque.

6.2   Architecture de la pile

La pile doit être architecturée en couches. Vous avez déjà eu un aperçu des deux premières; le fichier netstack.c implante ce qui pourrait être assimilé à la couche physique et le fichier netinterf.c ce qui pourrait être assimilé à la couche liaison. Le principe est que les paquets entrants, c'est à dire provenant du commutateur virtuel, soient récupérés par la couche physique puis passent par les autres couches; liaison pour le traitement Ethernet, réseau pour le traitement IP et transport pour le traitement UDP ou TCP. Quand une application transmet des données le traitement se fait dans l'autre sens; transport pour le traitement UDP ou TCP, réseau pour le traitement IP, liaison pour le traitement Ethernet puis physique pour l'envoi au commutateur virtuel.

6.3   Couche physique

Au niveau de la couche physique vous devez implanter la réception des paquets en provenance du commutateur virtuel et l'envoi des paquets vers le commutateur virtuel. Le nexus pour la réception est la fonction stackOn. Il est recommandé, dans cette fonction, de se mettre en écoute sur tous les descripteurs obligement fournis par les pilotes des interfaces (utilisez DescriptorsListen). Dès qu'un descripteur signale des données en lecture c'est qu'un paquet est arrivé sur l'interface correspondante, il est suggéré d'en déléguer la gestion à la fonction incomingPacket. La récupération du paquet se fait avec la fonction fournie par le pilote de l'interface et le paquet récupéré est à envoyer à la fonction de traitement de la couche liaison. Pour l'envoi de paquet, il suffit encore une fois d'utiliser la fonction fournie par le pilote de l'interface concernée. La fonction d'envoi de paquet outgoingPacket est appelée par la fonction d'envoi de la couche liaison.

6.4   Couche liaison

Avec la couche liaison on commence réellement le travail d'implantation d'une pile réseau. Vous êtes fortement incités à commencer par concevoir une structure de données représentant une trame Ethernet (la structure Ethernet_fields de l'archive). Par la suite vous pouvez envisager d'écrire la fonction de traitement des trames entrantes ethernetDecodePacket. Il vous faut vérifier que la trame est bien destinée à cette interface et aiguiller le packet vers les couches supérieures (protocoles ARP ou IP). La fonction d'envoi ethernetSendPacket a une tâche différente. Elle doit constituer la trame à envoyer en fonction de l'adresse de destination (passée en paramètre), de l'adresse locale (trouvée dans la structure de l'interface), du protocole (passé en paramètre) et des données (passées en paramètre). La taille des données doit éventuellement être vérifiée. Une fois la trame construite, il est naturel de l'envoyer en utilisant la fonction outgoingPacket de la couche physique.

6.5   Le déverminage

Un code n'affichant aucune information de déverminage est impossible à corriger. Vous afficherez chaque paquet entrant, au niveau de chaque couche avec tous les champs décodés. De la même façon vous afficherez les paquets sortants.

7   Protocole ARP

Vous devez avoir maintenant compris ce qu'il faut développer pour le protocole ARP sur le modèle de la couche liaison. Concevez les structures; la structure ARP_fields pour modéliser la trame ARP, la structure ARP_cache pour réprésenter le cache ARP. La structure ARP_cache doit être incluse dans la structure générale de la pile. Ecrivez la fonction arpDecodePacket pour décoder les paquets ARP entrants. Si le paquet est de type requête, générez le paquet de réponse en utilisant la fonction d'envoi ARP arpSendPacket. Sinon regardez si la réponse ARP correspond à une demande que votre machine virtuelle générât, et dans l'affirmative insérez la réponse dans le cache ARP. La fonction d'envoi se base sur le paramètre eth_target pour savoir s'il doit émettre une requête ou une réponse (adresse nulle dans le cas d'une requête). N'oubliez pas les affichages de déverminage.

8   Protocole IP version 4

Passons à la couche réseau. Pour la réception (fonction ipDecodePacket dans l'archive) il faut prendre en compte les points suivants : En ce qui concerne l'envoi de paquet (fonction ipSendPacket dans l'archive) les points suivants sont à considérer :

9   Protocole ICMP version 4

Le protocole ICMPv4 est simple à implanter. Dans la fonction de réception icmpDecodePacket il faut vérifier la somme de contrôle ICMPv4 (toujours avec la fonction genericChecksum). Comme unique action, il vous est demandé de répondre à un ICMPv4 requête d'écho par un ICMPv4 réponse d'écho. Vous réaliserez ceci en utilisant la fonction icmpSendPacket. La fonction d'envoi de paquet ICMPv4 ne fait que calculer la somme de contrôle ICMPv4 avant d'appeler ipSendPacket. Vous n'aurez, bien sur, pas oublié d'insérer du code de déverminage permettant, au minimum, d'afficher les paquets ICMPv4 entrants et sortants. Une fois ICMPv4 implanté vous vous précipiterez pour tester le bon fonctionnement de votre pile et lançant de la machine réelle un ping sur l'adresse IP de votre pile (via l'interface tap0).

10   Bibliothèque Socket maison

Avant de s'attaquer au protocole UDP, il va falloir réfléchir à la communication entre les processus de notre machine virtuelle et notre pile TCP/IP virtuelle. Si vous avez bien lu le programme principal de la dite machine virtuelle vous aurez constaté la création d'une socket d'écoute plisten. Au nom de la fonction permettant de la créer (UnixInit), vous vous doutez qu'il s'agit d'une socket de la famille PF_UNIX. C'est à cette socket que les processus se connecteront pour communiquer avec notre pile en utilisant la fonction UnixConnect. Vous trouverez dans le fichier socketinfos.h la structure LibraryMessage qu'il vous est conseillé d'utiliser pour réaliser la communication entre les processus et la pile. Vous trouverez aussi dans netsocket.c les fonctions permettant d'envoyer et de recevoir ces messages; socketSendMessage et socketGetMessage. Les messages échangés vont en particulier permettre de maintenir à jour la liste des sockets virtuelles. Le type proposé pour une socket s'appelle InetSocket. C'est dans le fichier netsocket.h que vous trouverez les prototypes des fonctions que vous pourriez écrire pour gérer cette structure. D'autres structures sont nécessaires à la gestion des sockets virtuelles UDP. Ces structures sont regroupées dans la structure sckinfos. Il va sans dire que cette structure doit faire partie de la structure globale de la pile TCP/IP. Enfin vous allez devoir écrire dans le fichier libsck.c du répertoire Library les primitives de votre bibliothèque des sockets. Ces primitives feront appel aux fonctions correpondantes dans netsocket.c via le canal de communication entre le processus et la pile TCP/IP virtuelle. Vous trouverez dans le répertoire Processes un programme utilisant la bibliothèque des sockets virtuelles. Vous pouvez l'améliorer faisant en sorte qu'il réponde sans cesse aux requêtes des clients UDP.

11   Protocole UDP

La programmation de la couche de transport UDP est à peine plus complexe que la programmation pour ICMPv4. Dans la fonction de réception udpDecodePacket il faut effectuer les vérifications suivantes; la taille du paquet doit correspondre au champ taille des entêtes UDP et la somme de contrôle UDP doit être correcte. Attention la somme de contrôle ne doit être vérifiée que si elle est non nulle et elle doit être calculée sur l'ensemble du paquet UDP préfixé d'une pseudo entête comportant les adresses IPv4, le code du protocole (0x0011) et la taille du paquet UDP. L'action a effectuer dépend du port UDP de destination. Si ce port n'est pas trouvé dans la liste des sockets UDP actives, il faut retourner un paquet ICMPv4 indiquant que le port est inaccessible. Dans le cas contraire, il faut soit envoyer directement le paquet UDP au processus en attente, soit le stocker dans la file du port UDP de destination. Quand à la fonction d'envoi udpSendPacket, sa seule tâche est de calculer la somme de contrôle UDP avant de passer la main à ipSendPacket.

Voila le travail est terminé pour 2010/2011. Testez votre pile en utilisant nc -u sur l'adresse IP de votre pile et sur le port 13. Envoyez un texte quelconque, ce texte doit s'afficher au niveau du serveur UDP et l'heure doit être retournée sur la sortie standard de la commande nc.

12   Protocole TCP

Pas avant 2011/2012.


This document was translated from LATEX by HEVEA.