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 :
-
Vérifier que la somme de contrôle des entêtes IP est correcte avec la
fonction genericChecksum fournie.
- Vérifier que le TTL est strictement positif et que la taille du paquet
dans l'entête IP correspond à la réalité.
- Vérifier que le paquet IP est à destination de l'adresse IP de diffusion générale
ou à destination d'une interface de la machine virtuelle (c'est à dire à destination de
l'adresse IP d'une interface ou à destination de l'adresse de diffusion du réseau de
l'interface).
- Envoyer les données au protocole IP approprié.
En ce qui concerne l'envoi de paquet (fonction ipSendPacket dans l'archive) les
points suivants sont à considérer :
-
Le paquet IP est à constituer en fonction des paramètres d'appel obligatoires
de la fonction (protocole IP, adresse IP de destination et données IP), en fonction
des paramètres optionnels (voir la structure IPv4_send_options et les constantes
IPV4_SEND_OPTION_xxxx dans l'archive) et enfin des paramètres disponibles
dans la structure de pile (adresse IP source).
- Le paquet IP doit être envoyé via la fonction d'envoi de la couche liaison; il
faut donc vérifier la taille du paquet (en toute théorie il faudrait fragmenter si le
paquet IP ne tient pas dans la trame de la couche liaison) et trouver l'adresse MAC de
destination.
- Si le cache ARP ne contient pas la bonne association, une requête ARP doit être
envoyée et le paquet IP doit être stocké dans une queue de paquets à ré-émettre. La queue
doit être parcourue dans la fonction principale de la pile stackOn et les paquets
présents doivent être retransmis (prévoir un compteur pour les éliminer à terme).
- Un protocole IP spécial doit être prévu pour les paquets à retransmettre, ces
paquets sont effet complets avec leurs entêtes IP, ipSendPacket doit donc les
traiter de façon particulière.
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.