IMA4 2016/2017 ECP1

De Wiki d'activités IMA
Révision datée du 5 juillet 2017 à 12:44 par Hdelatte (discussion | contributions) (Résultats et conclusion)

Présentation du projet

Contexte

L'élève effectue son stage au Vietnam sans aucun matériel informatique personnel. Il lui est proposé une EC purement logicielle pouvant être développée sur n'importe quel système d'exploitation. Une machine virtuelle Linux est cependant nécessaire pour une partie du projet.

Objectif

L'objectif est de compléter un simulateur réseau écrit en JavaScript et tournant dans un navigateur. Ce simulateur se trouve à l'adresse http://rex.plil.fr/reseau.

Description du projet

Il est demandé d'ajouter la gestion des paquets ICMPv4 au simulateur. Cet ajout doit se faire proprement avec gestion des paquets ARP et mise en file d'attente des paquets IPv4 en attente de résolution d'adresses IPv4 en adresses Ethernet. Les paquets ICMPv4 à gérer sont les paquets de demande (ICMP de type 8) et de réponse d'écho (ICMP de type 0) ainsi que les paquet de redirection de route (ICMP de type 5). Les machines et les éléments du réseau pourront ainsi être testées par la commande ping mais aussi générer une erreur ICMP lorsqu'un paquet ne peut pas être routé pour diverses raisons.

En parallèle, pour tester la gestion des ICMPv4, il est demandé que le simulateur puisse gérer des éléments "externes". Le comportement de ces éléments ne sera pas pris en charge par le simulateur mais par un système d'exploitation externe. Le simulateur se contentera d'envoyer les paquets destinés aux éléments externes à une interface Ethernet virtuelle d'une machine Linux. A l'inverse, les paquets générés par l'interface virtuelle seront injectés dans le simulateur. La communication entre le simulateur et les interfaces virtuelles doit s'effectuer par des serveurs WebSocket. Vous utiliserez la bibliothèque WebSocket 2.x de Linux.

Cahier des charges

Un schéma d'ensemble précis du système est demandé. Ce schéma doit à la fois représenter l'architecture de communication entre le simulateur et les interface Ethernet virtuelles mais aussi l'architecture interne des modules du simulateur.

Modifier le programme de façon à enlever la demande d'adresse Ethernet. Les stations trouveront les adresses grâce au protocole ARP.

Planning prévisionnel

Travail effectué

Lundi 5 juin

J'ai tout d'abord commencé à me familiariser avec le logiciel de simulation et tenté de comprendre comment celui-ci fonctionnait. j'ai ensuite ouvert les pages HTML et JAVASCRIPT pour comprendre comment elles étaient organisées. Après avoir relus les notions de bases en java script, ainsi que mes cours de réseau j'ai tenté de comprendre pourquoi une des simulations fournies ne fonctionnait pas. En effet sur le schéma le plus complexe, deux stations ne sont pas capables de recevoir les paquets. Le problème venait d'une mauvaise configuration des masques. Les paquets ARP semblent fonctionner pourtant il m'est demandé de veiller à leur bon fonctionnement dans le cahier des charges.

mardi 6 juin

Après discussion avec Monsieur Redon, j'ai eu la confirmation qu'une coquille au niveau de l'adressage des stations était présente. Deux stations se trouvaient hors du réseau sur la table de routage du routeur. J'ai donc changé leurs adresses et cela refonctionne:

  • nouvelle adresse de la station 4: 192.168.1.200
  • nouvelle adresse de la station 3: 192.168.2.200

mercredi 7 juin

J'ai commencé à modifier le formulaire IP. L'adresse Ethernet source est maintenant déterminée automatiquement grâce à l'adresse IPV4. En effet grâce à l'adresse IPV4, on peut aller chercher, dans les div, directement l'adresse MAC associée. On retrouvera, dans la fonction de création de paquet, le code suivant qui permet de récupérer l'adresse MAC.

for(i=0;i<elements.length;i++){
   if (elements[i].find("[class=ip4port]").text() == ip4source_value){
   var ethsource_value = elements[i].find("[class=ethport]").text();
   var ethsource_color = elements[i].find("[class=ethport]").css('background-color');
   }
 }

Pour pouvoir trouver l'adresse MAC du destinataire, nous faisons appel au protocole ARP afin de simuler avec exactitude le fonctionnement d'un réseau. j'ai donc, pour éviter la redondance de code, relu les fonctions ARP utilisées pour le routeur. Je vais donc en utiliser plusieurs pour pouvoir mettre en place ce protocole au niveau des stations. Je pourrai, après avoir mis cela en place, m'attaquer à la construction du protocole de couche 3 de détection d'erreur: le protocole ICMP.

jeudi 8 juin

Je travaille aujourd’hui sur la recuperation de l’adresse Ethernet via protocole ARP. En utilisant des fonctions existantes, dont une que j'ai légèrement modifiée, j'arrive à faire circuler les paquets ARP dans le même réseau. Ainsi deux machines dans un même réseau peuvent communiquer avec seulement leur adresse IP comme adresse de départ. Cette partie de code permet de retrouver les éléments sources nécessaire à l'envoi d'un paquet IPV4. Toutefois nous pouvons constater qu'il nous manque l'adresse Ethernet destination. Je récupère dans cette partie le cache ARP en prévention pour la partie suivante.

//recherche de l'adresse ethernet,port,cache arp source grâce à l'adresse ip donnée
for(i=0;i<elements.length;i++){
   if (elements[i].find("[class=ip4port]").text() == ip4source_value){
    var ethsource_value = elements[i].find("[class=ethport]").text();
    var ethsource_color = elements[i].find("[class=ethport]").css('background-color');
    var port = elements[i].find("[class=port]");
    var arp=elements[i].children('.cachearp');

Dans la partie de code suivante je cherche dans le cache ARP l'adresse MAC, si l'adresse n'est pas dans le cache, je crée un paquet ARP à envoyer grâce à une fonction déjà existante que j'ai un peu adapté. J'ai donc crée "station_route_ip4" qui est fortement inspirée de "router_route_ipv4".

//requête ARP pour retrouver l'adresse Mac destinataire
var entry=cachearp_find_address(arp,ARPtarget_value);
if(entry===undefined){
  station_route_ip4_retry(form,port,ARPtarget_value);//fonction qui route un paquet arp}
else
{suite du code pour creation IPV4}

vendredi 9 juin

Le problème rencontré est le suivant: Si je possède l'adresse IP d'une machine d'un autre réseau, alors le routeur, ne reconnaissant pas son adresse IP lors de la demande ARP, ne renvoie pas son adresse MAC. Je suis donc en train de mettre au point une consultation de la table de routage de la station source. Ainsi la station émettrice enverra un paquet ARP avec l'adresse IP du routeur correspondant si elle constate que la machine réceptrice ne se trouve pas dans son réseau.

//on regarde si l'adresse IP est dans le réseau
var ip=ip4addr_to_array(ip4target_value);
   elements[i].find('.route').each(function(i,obj){
     var masque = ip4addr_to_array($(obj).children('.route_masque').text());
     var reseau = array_to_ip4addr(array_and(ip,masque));
     
     if(reseau == $(obj).children('.route_adresse').text() && reseau != '0.0.0.0')
       {ARPtarget_value=ip4target_value;bool=true;}
     if(bool==false)
       {ARPtarget_value='192.168.1.1';}
       

Dans le code ci-dessus, je cherche dans la table de routage si la station destinataire se trouve dans le réseau. Si elle se trouve dans un autre réseau,l'adresse destinataire du cache ARP sera celle de la bonne passerelle. Ces modifications ont toutes été faites dans la fonction create_ip4_packet_from_form(form).


Je peux maintenant commencer la partie ICMPV4. Je me suis donc renseigne sur les paquet ICMP que je devrai gérer:

  • Le paquet de type 8 est un echo request.
  • Le paquet de type 0 est un echo reply.

Le ping est en réalité une combinaison de ces deux paquet ICMP.

  • le paquet de type 5, ICMP redirect, indique qu'il y a un chemin plus court vers la destination.

lundi 12 juin

Avant de commencer à ajouter la gestion de paquets ICMP dans mon simulateur, j'ai d’abord décidé de créer le serveur, et de faire en sorte de gérer les paquets Ethernet entrant et sortant sur mon interface.

Le serveur Websocket, de base, fait avec la librairie Websocket linux est disponible dans la partie Document. Il porte le nom serveur.txt (ce n'est pas la version finale). Ce serveur contient les fonctions de bases pour pouvoir communiquer avec notre client simulateur. Je devrai le modifier légèrement pour qu'il puisse réceptionner des trames écrites dans le terminal afin de les transmettre directement au simulateur qui enverra le paquet de l'adresse source à l'adresse destination.

Maintenant que mon serveur tourne, je rajoute le code nécessaire dans le client, c'est à dire sur la page réseau.js. dans le main().

Toutefois je rencontre un problème: Quand je crée une connexion dans le main et que j'utilise cette connexion hors du main pour envoyer un message cela ne fonctionne pas. Je soupçonne que lors de la fin du main(), la connexion se ferme.

J'ai donc lancé cette fonction, non dans la fonction principale, mais dans la fonction "router_enqueue_packet(queue,packet,laddr,paddr)". En effet c'est cette fonction qui est exécutée à la fin du voyage d'un paquet pour le stocker en mémoire sur la station correspondante. A présent la station stocke dans sa mémoire et restitue le paquet, sous forme d'une trame d'octets, au serveur qui l'imprime dans le terminal. Il reste à l'envoyer par l'interface virtuelle.

mardi 13 juin

j'ai essayé de comprendre comment envoyer un paquet internet depuis le serveur. Mais je n'arrive toujours pas à initialiser une connexion depuis le serveur. De plus pour lire les entrées clavier, j’ai voulu utiliser la fonction read. Mais cette fonction est bloquante. Le serveur n'est donc plus à l’écoute des messages arrivant du simulateur.

mercredi 14 juin

J'ai réussi à concevoir mon interface Ethernet virtuelle. Comme vous pouvez le voir je n'ai pas encore rajouté l'option O_NONBLOCK car il me reste des soucis dont je vais parler plus bas.

int creationInterfaceVirtuelle(char *nom)
{
struct ifreq interface;
int fd,erreur; 
/* Ouverture du peripherique principal */
if((fd=open(TAP_PRINCIPAL,O_RDWR))<0)
   { 
       printf("error open");
       return fd;
   }
/* Preparation de la structure */
memset(&interface,0,sizeof(interface));
interface.ifr_flags=IFF_TUN|IFF_NO_PI;
if(nom!=NULL) strncpy(interface.ifr_name,nom,IFNAMSIZ);
/* Creation de l'interface */
if((erreur=ioctl(fd,TUNSETIFF,(void *)&interface))<0){ close(fd); return erreur; }
/* Recuperation du nom de l'interface */
strcpy(nom,interface.ifr_name);
/*retour du descripteur*/
return fd;
}


Il m'a fallu rajouter dans le main les infos importantes pour lancer mon interface

char *a_name = malloc(IFNAMSIZ);
   strcpy(a_name,"tun77");
   fd=creationInterfaceVirtuelle(a_name);

Maintenant que l'interface est fonctionnelle je peux utiliser un read() pour recevoir des trames émises par PING sur un autre terminal.

while (1)
   {
       //lws_service(context, 50);
       nread = read(fd,buffer,sizeof(buffer));
       if(nread < 0) {
           perror("Reading from interface");
           close(fd);
           return -1;
       }
       printf("Read %d bytes from device %s\n", nread, a_name);
       for(int i=0;i<84;i++){printf("%x ",buffer[i]);}
       printf(" \n");
   }

Comme vous pouvez le voir, lws_service(...) est en commentaire. Je rappelle que cette fonction est celle permettant à mon serveur de communiquer avec le simulateur. En effet le problème rencontré est que si je décommente cette ligne, je ne recevrai pas tous les messages du simulateur, car la fonction read est bloquante. Mais si je rajoute O_NONBLOCK j'ai une erreur à la lecture du read. Je n'ai pas encore trouvé de solution. Avant de pouvoir utiliser PING, j'ai du procéder à quelques commandes pour lier mon descripteur de fichier et une adresse IP. Les voici:


openvpn --mktun --dev tun77
ip link set tun77 up
ip addr add 10.0.0.1/24 dev tun77

Je dois faire ces commandes à chaque fois que j'allume le PC.

j'ai de plus, un dernier problème. Les trames reçues, qui doivent être des trames ICMP, que je demande d'afficher en hexadécimale ne sont pas celles attendues... En effet j'ai des chiffres complètement incohérents et je ne sais pas comment retrouver une trame, avec le protocole ICMP, cohérente.

Vendredi 16 juin

Après discussion avec mon professeur, Mr Redon, il s'est avéré que mes trames reçues étaient bonnes. Je suis donc en train de d'essayer de les envoyer vers mon simulateur. Toutefois je rencontre un problème: Il m'a été conseillé d'utiliser régulièrement la fonction callback_on_Writable pour pouvoir déclencher un événement de ce genre:

case LWS_CALLBACK_SERVER_WRITEABLE: {
           //envoie du paquet ICMP reçu par le PING
           unsigned char *buf = (unsigned char*) malloc(LWS_SEND_BUFFER_PRE_PADDING + len + LWS_SEND_BUFFER_POST_PADDING);
           int i;
           .........................................
           ........................................
           }
           break;
       }

Mais je ne vois pas où appeler cette fonction. Elle nécessite une instance (wsi), je ne peux donc pas l'appeler dans le main... Une fois ce problème résolu je n'aurai plus qu'à traiter l'envoi de ces trames dans le simulateur et mon projet sera terminé.

lundi 19 juin

Je bloque sur le même sujet. Je récupère de l’interface virtuelle des octets que je stocke dans un tableau de caractère. Même si je sais que un octet et un char font la même taille, je ne comprends pas comment on peut les stocker alors que ce n’est pas le bon type. De plus je veux envoyer cette chaîne de caractère au simulateur mais cette chaîne contient des bytes qui ne sont pas lisible à part avec un printf(%x). j'ai cherché comment transformer les bytes en caractère mais je n’ai pas réussi. Recevant des chaînes de caractère du simulateur, je n’arrive pas non plus à les envoyer à l’interface car elles ne sont pas du bon type (error :EINVAL).

mardi 20 juin

En utilisant la fonction sprintf() j'ai réussi à récupérer les bytes de l'interface et à les stocker sous forme de tableau de caractère. voici le code ci-dessous


if(read(fd,buffer,84)>=0)
           {
           printf("Read bytes from device %s\n", a_name);   
           for(int i=0;i<84;i++)
               {
               sprintf(&trame[(LWS_SEND_BUFFER_PRE_PADDING)+i*3],"%x",buffer[i]);
               sprintf(&trame[(LWS_SEND_BUFFER_PRE_PADDING)+(i*3)+2]," ");
               }
           printf(" \n");
           for(int i=0;i<252;i++)
               {
               printf("%c",trame[i]);
               }
           printf(" \n");
       }

La deuxième boucle for ne sert juste qu'à afficher la trame pour vérifier le contenu.

LWS_SEND_BUFFER_PRE_PADDING est une partie qu'il faut que je laisse libre pour l'envoi par le socket. Etant donné que pour représenter un nombre hexadécimal il nous faut deux caractère et que je sépare chaque groupe d'un espace, je place donc la valeur du buffer puis j'avance de trois cases dans la chaîne. Le deuxième sprintf() ne sert juste qu'à mettre des espaces dans les bonnes cases de mon tableau.

J'avais un problème au niveau de ma chaîne récupérée car je réceptionnais des valeurs de ce type "ffffffxx". J'ai compris que j'avais un dépassement dans la mémoire car la valeur était négative. J'ai juste eu à préciser que le type de ma chaîne était "unsigned char".

Après ceci je procède à l'envoi de ma trame grâce à la partie du code suivante.

case LWS_CALLBACK_SERVER_WRITEABLE: {
           //envoi du paquet ICMP reçu par le PING
           lws_write(wsi,&trame[LWS_SEND_BUFFER_PRE_PADDING],252,LWS_WRITE_TEXT);
           printf("test ok \n");          
           break;
       }

J'envoie donc mon code en laissant bien de la place au début de ma trame pour que l'envoi réussisse. Pour l'instant le simulateur n'affiche qu'une alerte de réception de la trame. Il faut donc que je gère l'envoi de cette trame comme un paquet ICMP à travers le simulateur.

Communication du ping- simulation.png

Mercredi 21, jeudi 22juin

Étant donné que je passe ma soutenance de stage mercredi prochain, j'ai mis mon projet de coté pour pouvoir finir mon rapport et avancer le plus loin possible sur le travail demandé.

lundi 26, mardi 27 juin

Après avoir réussi à recevoir une trame venant de mon PING sur le simulateur, j'ai donc commencé à la traiter. Je crée donc une fonction ICMP_create_request(Nouveau nom: serveur_create_packet). Dans cette fonction, je récupère les deux strings des adresses IP source et IP destination que je transforme en forme décimale (exemple: 192.1.23.215).

function icmp_create_request(trame){
var ip4src="";
var ip4tgt="";
var bool=false, ARPtarget_value;
for(i=36;i<=47;i++){
 ip4src+=trame[i];
 ip4tgt+=trame[i+12]
}
var ip4srcaddr= array_to_ip4addr(bytes_to_array(ip4src));
var ip4tgtaddr= array_to_ip4addr(bytes_to_array(ip4tgt));

Comme avec le formulaire, je viens récupérer l'adresse Ethernet source, la couleur de l'adresse, le cache ARP et la couleur de l’adresse IP source aussi.

//recupere l'adresse ethernet/couleur/arp/port de la source
for(i=0;i<elements.length;i++){
   if (elements[i].find("[class=ip4port]").text() == ip4srcaddr){
   var ethsource_value = elements[i].find("[class=ethport]").text();
   var ethsource_color = elements[i].find("[class=ethport]").css('background-color');
   var ip4srccolor = elements[i].find("[class=ip4port]").css('background-color');
   var port = elements[i].find("[class=port]");
   var arp=elements[i].children('.cachearp');


Comme avec le formulaire, je regarde si l'adresse IP est dans le réseau, pour savoir si la trame ARP doit garder comme IP de destination l'IP de la station ou prendre l'IP du routeur.

   //on regarde si l'adresse IP est dans le réseau
   var ip=ip4addr_to_array(ip4tgtaddr);
   elements[i].find('.route').each(function(i,obj){
     var masque = ip4addr_to_array($(obj).children('.route_masque').text());
     var reseau = array_to_ip4addr(array_and(ip,masque));
     if(reseau == $(obj).children('.route_adresse').text() && reseau != '0.0.0.0')
       {ARPtarget_value=ip4tgtaddr;bool=true;}
     });
   if(bool==false){ARPtarget_value='192.168.1.1';}
   //adresse par Default que l'on peut aller chercher dans la table de routage
   }
   if (elements[i].find("[class=ip4port]").text() == ip4tgtaddr){
     var ip4tgtcolor = elements[i].find("[class=ip4port]").css('background-color');
   }
 }

On cherche dans le cache arp pour voir si nous disposons de l'adresse ethernet, sinon on envoie une demande arp.

 var entry=cachearp_find_address(arp,ARPtarget_value);
 if(entry===undefined){
   arp_create_request(port,ARPtarget_value);
   setTimeout(function(){ icmp_create_request(trame);},IP4_RETRY_DELAY);
  }

Si on possède l'adresse Ethernet on envoie notre trame ICMP encapsulée dans un paquet IPV4.

else
 {
 var ethtarget_value=entry['ethernet'];
 var ethtarget_color=find_address_color('ethport',ethtarget_value);
 packet=create_ip4_packet(ethsource_value,ethsource_color,ethtarget_value,ethtarget_color,
          ip4srcaddr,ip4srccolor,ip4tgtaddr,ip4tgtcolor,"64","1",trame,false);
 send_packet_from_source(packet);
 }
}

J'ai de plus commencé à traiter ces paquets. Lorsqu'ils arrivent en fin de course, je reconnais le paquet ICMP et j'envoie un paquet ICMP de réponse d’écho (type 0).

jeudi 29 juin

Aujourd'hui j'ai avancé sur les paquets ICMP dans le simulateur. j'ai pu mettre en place des nouvelles fonctions:

La fonction permettant de savoir si on a affaire à un paquet ICMP.

function packet_is_icmp(packet){
 var content=packet.children("input[name='contenu']").val();
 var bytes=bytes_to_hexa(content);
 alert(bytes[34]+bytes[35]);
 return (bytes[34]+bytes[35]=='0800');
}

La fonction qui permet de renvoyer un paquet ICMP transformé en réponse. Je travaille sur cette dernière car elle n'est pas encore au point.

function icmp_return_answer(packet){
 var temp=[];
 var content=packet.children("input[name='contenu']").val();
 var bytes=bytes_to_hexa(content);

 for(i=0;i<4;i++){
   temp[i]=bytes[i+26];
   bytes[i+26]=bytes[i+30];
   bytes[i+30]=temp[i];
 }
 for(i=0;i<6;i++){
   temp[i]=bytes[i];
   bytes[i]=bytes[i+6];
   bytes[i+6]=temp[i];
 }
 for(i=0;i<4;i++){
   temp[i]=bytes[i+46];
   bytes[i+46]=bytes[i+50];
   bytes[i+50]=temp[i];
 }
 bytes[54]="00";

 alert(bytes);

 content=hexa_to_bytes(bytes);
 packet.children("input[name='contenu']").val(content);

 //on renvoie la paquet
 send_packet_from_source(packet);
}

J'ai de plus rencontré plusieurs erreurs. Je ne supprimais pas l'entête IPV4 sur mes paquets ICMP entre le serveur et le simulateur, donc j'avais deux entêtes empilées. Je n'arrive toujours pas à envoyer une trame du serveur vers le simulateur, si je n'ai pas au préalable envoyé un message du simulateur au serveur.

L’idée principale reste de réussir à faire naviguer une question réponse, en aller retour, de mon interface jusque mon simulateur.

vendredi 30 juin

J'ai réussi à récupérer un paquet ICMP de type 0 (un paquet de réponse à un écho) sur mon serveur. Je le stocke dans un tableau. Il me reste donc à l'envoyer sur mon interface virtuelle. J'ai une erreur sur le write() qui me permet d'envoyer ce paquet ICMP à l'interface. En effet l'erreur est de type EINVAL. Je sais que ça doit être ma trame le problème. Je n'arrive cependant pas à comprendre pourquoi elle serait invalide. Je cherche encore. En parallèle j'essaye de comprendre comment fonctionne le lws_on_writable. Je l'appelle dans l'initialisation mais je dois donc attendre un premier message du client. Mon serveur ne commence donc à envoyer les PING reçus, au simulateur, seulement après réception d'un premier message.

Je bloque aussi sur un problème de fermeture de serveur coté client. En effet quand je tape la commande "ws.close()" qui ferme le connexion "ws" que j'ai lancé auparavant, mon programme plante.

ICMP0 qui arrive sur le serveur

samedi 1 juillet

Ne trouvant pas pourquoi ma trame ne passe pas dans la fonction write(), j'ai réglé les autres problèmes: Mon serveur arrive à communiquer avec le simulateur dès que nous chargeons la page du simulateur. Il gère, tout seul, les paquets ICMP entrants et sortants. La fonction qui m'a permis d'automatiser cela et le suivante. J’appelle cette fonction récursive dans le main de reseau.js. Elle récupère un message envoyé du serveur, le compare à celui qu'elle a reçu précédemment. Si c’est le même la fonction ne fais rien, si il est différent, elle crée une requête ICMP. Après la requête, elle se rappelle elle même.

function received_ICMP(){
//connexion au serveur Websocket
 var ws= new WebSocket('ws://localhost:9500','my');
 ws.onmessage = function(evt) {
 var received_msg = evt.data;
 var trame = bytes_to_hexa(received_msg);
   if(temp!=trame[5]){
     icmp_create_request(received_msg);
   }
 temp=trame[5];
 //alert(temp);
 received_ICMP();
 ws.close();
 };
 ws.onerror = function () {alert("non connecte au serveur");};
 ws.onclose= function (){//alert("message envoye et connection ferme");};  
 }

}

Le petit problème et que le nombre de ping en continue est trop nombreux pour le simulateur qui crash. j'envoie donc une dizaine de ping à chaque fois, pas plus. J'ai aussi réussi à fermé mes connexions grâce à ws.close(). Il s'est avéré que je tapais cette commande au mauvais endroit.

Lundi 3 juillet

J'ai change la fonction enqueue_packet de façon à ce qu'elle laisse passer tout les paquets vers le serveur. Le serveur reçoit donc les requêtes ICMP et ARP ainsi que leurs réponses. Il reçoit de plus les paquets de donné classiques envoyés depuis le formulaire. Je cherche à mettre en place le code ICMP 5 mais je travaille sur le simulateur n'ayant qu'un routeur. Je ne sais donc pas comment tester cette partie.

J'ai travaillé sur le message ICMP5. Le routeur remarque que la route qu'a choisie l'ordinateur émetteur n'est pas optimale car le prochain routeur à passer pour atteindre le destinataire se trouve sur le même réseau que celui de l'ordinateur émetteur. Le routeur envoie l'adresse du prochain routeur à ajouter dans la table de routage de l'ordinateur émetteur de façon que le prochain envoi vers le même destinataire ne passe pas inutilement par lui.

J'ai donc implémenté une fonction dans "router_route_ip4_packet". Cette fonction "redirection(ipdest,srcip4,self)" envoie un paquet icmp 5 si elle constate que l'adresse ip source et ip destination sont dans le mème réseau et que le message ne devait pas passer par lui.

function redirection(ipdest,ipsource,self,port){
var masque = ip4addr_to_array("255.255.255.0");
var reseau_src = array_and(ipsource,masque);
var reseau_tgt = array_and(ipdest,masque);
if(reseau_src == reseau_tgt){
 var ethsrcaddr=port.children('.ethport').html();
 var ethsrccolor=find_address_color('ethport',ethsrcaddr);
 var ethtgtaddr=//a recuperer;
 var ethtgtcolor=//a recuperer;
 var icmp= "05 00 ff ff "
 icmp+=ethaddr_to_hexa(ethsrcaddr)+' '+ip4addr_to_hexa(ip4srcaddr)+' ';
 icmp+="00 00 00 00 00 ";
 packet=create_eth_packet(ethsrcaddr,ethsrccolor,ethtgtaddr,ethtgtcolor,icmp,false);
 send_packet(packet,port);
}

}

Cette fonction n'est pas encore fini car je n'ai pas encore récupéré toutes les informations pour envoyer le paquet Ethernet à la source.

De plus je ne savais pas comment faire pour définir quelle station transférerai les messages envoyés par le serveur web. Dans le code "serveur_create_packet" j'ai arbitrairement entré l'adresse d'une des quatre stations. Je ne sais pas comment faire autrement.

J'ai corrigé la fonction received_ICMP qui s’appelle maintenant Received_paquet.

mardi 4 juillet

Apres discussion avec le professeur, j'ai compris comment recevoir des trames ARP directement de l'interface virtuelle. J'ai eu plusieurs problèmes car mon interface était de type TUN mais pour recevoir les paquets ARP il fallait qu'elle soit de type TAP. Maintenant je reçois bien des paquets ARP que je peux visualiser grâce a la commande tcpdump. Hors quand je les affiches j'ai ce type de trame:

ff ff ff ff ff ff 16 46 1f 64 d4 ae 88 d6 60 c1 b8 c0 16 34 10 f1 16 46 1f 64 d4 ae 
a0 01 11 41 10 f0 f0 60 d0 a0 a0 31 e1 32 00 00 80 00 00 00 00 00 f0 60 d0 fb 14 e9 
14 e9 70 35 ce 5f 70 60 70 40 60 62 60 60 60 60 60 c0 f5 5f 69 70 70 73 74 5f 74 63 

je n'arrive pas a retrouver les différentes adresses et valeurs composants la trame. J'essaye de voir comment régler ce contre temps.

En mettant mon buffer en type char et pas unsigned char j’obtiens cette trame, qui me parait plus logique:

//ethernet
ff ff ff ff ff ff broadcast
16 46 1f 64 ff ff adresse source
f8 a6 type de protol
//ARP
00 01 Ethernet (10Mb)
08 00 IP
06 ethernet
04 ipv4
00 01 request requete
16 46 1f 64 ff ff adresse mac source
a0 a1 01 01 adresse ip source
00 00 00 00 00 00 mac destination
a0 01 01 02 adresse ip destination
//bourrage
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 

j'ai des problèmes sur ma traduction de ma trame de bytes vers string car on voie bien que mon type de protocole devrait être "08 06" et qu'il est de f8 a6 On voit aussi pour les adresse ip que je récupère "a0...." alors que je devrais récupérer "0a...." car je ping a l'adresse "10.1.1.2".

En redémarrant le serveur je me suis aperçu que l'erreur disparaissait toute seule... je ne sais pas a quoi elle était due.

J'ai un problème avec les paquets ARP réponses devant sortir du simulateur. Je n'arrive pas a les récupérer sur mon serveur. J'ai trouve la raison. Il s’avère que l'adresse Ethernet du système est différente de celle de ma machine simulée. donc la station simulée pense que le paquet n'est pas pour elle. De plus, laissant passer tout les paquets je reçois un multitude de paquets ARP qui font crasher mon simulateur.

Après discussion avec le professeur j'ai du modifier l'adresse Ethernet de système faisant le ping. Voici le résultat.

   link/ether 00:11:11:11:11:01 brd ff:ff:ff:ff:ff:ff
   inet 192.168.1.100/24 scope global tap0
      valid_lft forever preferred_lft forever

Documents

Fichier:Serveur.txt

Rendu projet final

C'est ici que vous trouverez le rendu de mon projet en pièce jointe. Il y aura un dossier nommé "serveur" qui contiendra le dossier "serveur.c" et un fichier "réseau" qui contiendra tous les fichiers relatifs au simulateur (la majorité des fonctions se trouve dans reseau.js). Je vais de plus essayer de détailler les différentes fonctions que j'ai modifiées ou créées, et d'indiquer où elles se trouvent pour que vous puissiez vous retrouver dans les différentes pages de code. J'ai partagé mon travail en plusieurs parties sur lesquelles je travaillais en parallèle. Ces parties sont listées ci dessous. Il est important de préciser que j'ai fait mes tests qu'avec la page html "routeur_s3".

Fichier:Projet simulateur.zip

Récapitulatif

  • Enlever la demande des adresses Ethernet du formulaire
  • implémenter la gestion des écho-requêtes et réponses d’écho(ICMP 8 et 0)
  • implémenter la création et gestion des paquets ICMP de type 5
  • créer un serveur Websocket
  • créer une interface virtuelle
  • faire communiquer les différents modules
  • fournir un schéma de l'architecture du projet

Enlever la demande des adresses Ethernet du formulaire

J'ai enlevé les demandes d'adresses Ethernet dans la page formip4.html. Je récupère l'adresse destination grâce au protocole ARP et l'adresse source dans les div. Toutes les modifications faites pour récupérer les adresses se trouvent dans la fonction create_ip4_packet_from_form. Dans cette fonction, je cherche aussi si l'adresse IP destination est dans le réseau. Cela me permet de savoir à qui envoyer le paquet ARP (si l'adresse IP n'est pas dans le réseau j'envoie le paquet ARP au routeur). Vous aviez codé une fonction du même type pour chercher le réseau mais je n'ai pas réussi à la comprendre. J'ai donc codé la mienne. Elle ne doit pas être très optimisée.

Implémenter la gestion des écho-requêtes et réponses d’écho

A la réception des paquets j'ai tout d’abord créé une fonction serveur_create_paquet qui faisait le même travail de création de paquet que celle utilisée avec le formulaire. Sauf qu'elle puisait les informations nécessaires dans la trame ICMP reçue et non dans le formulaire.

Je n'utilise plus cette fonction car le professeur m'a conseillé de récupérer directement les trames du serveur, de les encapsuler et de les envoyer directement tel quel sur le simulateur. C’est donc le système lui même qui, lors de la commande PING, envoie les paquets ARP à transmettre dans le simulateur, etc. vous la trouverez quand même dans mon code en commentaire car elle fonctionnait et je ne voulais pas supprimer du travail accompli. La fonction maintenant utilisée est received_paquet() dont je parlerai dans la communication entre module. Les paquets envoyés sur le réseau devaient être analysés et permettre l'envoi d'une réponse. Vous trouverez donc, dans la fonction router_behaviour, la fonction packet_is_request_icmp qui détermine si un paquet reçu par une machine est bien un paquet ICMP request et qui le cas échéant renvoie vers la fonction icmp_return_answer. Cette fonction utilise le paquet ICMP d’écho et crée à partir de là, un paquet ICMP de réponse à l'attention de la machine ayant lancé la requête. J'ai de plus codé une petite fonction permettant de savoir si un paquet est de type ICMP réponse. Je ne m'en sers plus à présent mais j'ai laissé cette fonction qui pourrai servir. Son nom est packet_is_answer_icmp. Les paquets reçus par la machine reliée au serveur sont stockés et renvoyés au serveur.

Implémenter la création et gestion des paquets ICMP de type 5

Je n'ai pas pu tester cette partie par manque de temps. Elle est donc juste théorique. J'ai écrit une fonction qui lors du routage d'un paquet par un routeur, vérifie si la destination du paquet est bien hors du réseau. Si elle se trouve dans le réseau alors le paquet est mal routé, il ne devrait pas arriver au routeur ayant reçu le paquet mais à un autre routeur. Dans ce cas le routeur envoie un message à la source en distant qu'il y a un chemin plus optimal à ajouter à la table de routage. Vous trouverez donc cette fonction nommée redirection et qui est appelée dans router_route_ip4_retry. Elle ne fonctionne sûrement pas mais c’était l’idée de base que je voulais mettre en place.

Créer un serveur Websocket

Le serveur Websocket est dans le fichier serveur.c. Vous retrouverez les fonctions appelées par lws_service(context, 50) dans le main. Le serveur utilise une fonction d’initialisation, une de récupération de donnée du simulateur quand un message arrive, et une d'envoi de donnée qui est déclenchée quand la communication est libre grâce à la fonction lws_callback_on_writable(wsi) appelée dans la fonction d'initialisation.

Créer une interface virtuelle

L'interface virtuelle est aussi définie dans le fichier serveur.c. Elle est mise en place grâce à la fonction creationInterfaceVirtuelle appelée au début du main. J'ai défini l'interface qui porte le nom "tap0". Donc quand on associe une adresse Ethernet et une adresse ip pour pouvoir utiliser la commande PING, il faut le faire pour cette interface. Je mets les commandes qu'il faut taper pour configurer l'interface ici:

  • openvpn --mktun --dev tap0

Ouvrir l'interface

  • ip link set dev tap0 address 00:11:11:11:11:01

pour avoir la meme adresse que la station 1 du simulateur

  • ip addr add 192.168.1.0/24 dev tap0

définir un réseau ou ping. Je crois avoir remarqué que le réseau doit contenir tous les réseaux du simulateur car si on ping hors de ce réseau rien ne se passe.

  • ip link set dev tap0 up

ip link opérationnel

Faire communiquer les différents modules

Dans cette partie je décris comment l'interface le serveur et le simulateur communiquent.

Le serveur et l'interface virtuelle se trouvant dans le même fichier peuvent communiquer les trames grâce à des tableaux définis en variable global. Ils peuvent donc lire et écrire dans ces tableaux à leur guise. J'ai quand même eu beaucoup de mal à récupérer une trame correcte en sortie de l'interface. Les bytes, que je demandais en hexadécimal, ne se plaçaient pas dans mes tableaux de trames comme je le voulais. De plus les chiffres en hexadécimal comportant des zéros ne les affichaient pas. Ceci explique la partie de code dans le main() où je récupère la trame dans le "buffer" et que j'effectue plusieurs opérations pour la modifier en une trame utilisable par le simulateur. La communication avec le simulateur est un peu plus compliquée. Le simulateur regarde au chargement de la page si le serveur a un message à envoyer grâce à la fonction received_paquet() qui est lancée dans le main de la page reseau.js. A partir de là, le simulateur traite la trame selon son type. Quand le simulateur arrive dans la fonction router_enqueue_packet, on lance une connexion avec le serveur et on envoie notre trame. Ici j'ai mis une condition sur l'envoi de la trame. Cette condition sert juste à définir quelle station est connectée au serveur. Donc une trame est envoyée au serveur seulement si l'adresse Ethernet de la station est celle de la station 1. Le serveur envoie à son tour un message si il en a un à faire passer. Encore une fois j'ai soumis cette réception de message par le simulateur à la même condition mais pour une raison différente. Je recevais trop de paquets d'un coup et tous les envoyer vers le simulateur le faisait crasher.

Le schéma

Vous trouverez le schéma entre serveur et simulateur ainsi que celui de l'architecture interne du simulateur dans le fichier total. J'ai apporté les modifications que vous m'aviez conseillé. Il a été dur de créer un schéma précis et détaillé mais à la fois facile à lire. J'ai donc essayé de regrouper les fonctions dans des ensembles qui forment une action particulière du simulateur afin d’alléger un peu le dessin

Résultats et conclusion

  • Enlever la demande des adresses Ethernet du formulaire

J'ai réussi à enlever les demandes Ethernet du formulaire. Les demandes ARP se font proprement.

  • implémenter la gestion des écho-requêtes et réponses d’écho(ICMP 8 et 0)

J'ai réussi à faire circuler des paquets ICP que j'avais créé au préalable grâce à la fonction serveur_create_paquet qui utilisait des paquets ICMP reçus de mon interface. J'ai réussi a gérer des paquets ICMP générés par a fonction serveur_create_paquet qui faisait la requête ARP lui même.

  • implémenter la création et gestion des paquets ICMP de type 5

J'ai codé une première fonction qui m'a donne une piste pour continuer mais je n'ai pas eu le temps de la finir ou de la tester.

  • créer un serveur Websocket

on serveur semble bien fonctionner, il envoie et réceptionne les différents paquets.

  • créer une interface virtuelle

J'ai créais une interface fonctionnant correctement. j'arrive a récupérer des trame que je ping dans le terminal ou des des demandes ARP.

J'ai eu un problème qui m'a empêché de tester ma simulation avec des paquets ICMP venant de mon interface qui sont juste encapsulés en trame Ethernet sans avoir été modifiés avec la fonction serveur_create_paquet. En effet quand je ping, je récupère tout d’abord une trame ARP qui circule correctement jusque mon simulateur qui l'encapsule et la fait circuler jusqu’à l'adresse qui a été demandée dans le PING. Le paquet ARP de réponse est correctement envoyé jusqu'au serveur. Mais je n'ai pas réussi à envoyer ce paquet ARP dans l'interface pour que le système me renvoie le paquet ICMP. C'est dommage car je pense que si j'avais réussi à récupérer ce paquet ICMP il aurait été facile de le faire circuler comme je l'avais fait avec le paquet ARP.

La question que l'on pourrait me poser est: Si tu n'arrive pas à récupérer un paquet ICMP quand tu donnes la trame ARP, comment as tu fait dans la partie du début pour récupérer des trames ICMP à analyser pour ta fonction serveur_create_paquet?

Mon interface était réglée en TUN au départ. Cela me permettait de recevoir directement le paquet ICMP sans que le système se demande à qui l'envoyer. Pour recevoir les paquets ARP du système j'ai configuré l'interface en TAP.

  • faire communiquer les différents modules

Mes différents modules communiques correctement entre eux. je peux facilement modifier et changer les configurations des communications.

  • fournir un schéma de l'architecture du projet


Le second problème que j'ai rencontré est que lorsque je PING l'adresse IP d'une machine qui n'est pas dans le réseau de la station 1 (Celle qui est liée au serveur), le paquet ARP reçus du système ne comporte pas l'adresse IP du routeur (pour récupérer son adresse MAC) mais toujours l'adresse IP du la machine hors du réseau... je ne sais pas comment changer ça non plus.

J’espère que le travail fourni sera à la hauteur de vos attentes malgré le fait que je n'ai pas pu complètement l'achever. J'ai tenté de le répartir le mieux possible avec celui que je dois faire pour mon stage.

Fichier:Projet simulateur.zip

Sources