IMA4 2016/2017 ECP1

De Wiki de Projets IMA
Révision datée du 29 juin 2017 à 15:51 par Hdelatte (discussion | contributions) (Vendredi 16 juin)

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 a enlever la demande d'adresse Ethernet. les stations trouverons 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 stocks dans un tableau de char. 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 charactere au simulateur mais cette chaîne contient des bytes qui ne sont pas lisible a part avec un printf(%x). j'ai cherché comment transformer les bytes en charactere mais je n’ai pas réussi.( recevant des chaînes de charactere du simulateur je n’arrive pas non plus a les envoyer a l’interface car elle ne sont pas du bon type (error :EINVAL)

mardi 20 juin

En utilisant la fonction sprintf() j'ai réussi a récupérer les bytes de l'interface et de les stocker sous forme de tableau de charactere. 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'a 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. étant donne que pour représenter un nombre hexadécimal il nous faut deux charactere 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 chaine. le deuxième sprintf() ne sert juste qu'a mettre des espaces dans les bonnes cases de mon tableau.

j'avais un problème au niveau de ma chaîne recuperee 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 a préciser que le type de ma chaîne était "unsigned char".

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

case LWS_CALLBACK_SERVER_WRITEABLE: {
           //envoie du paquet ICMP recu 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'envoie 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'envoie de cette trame comme un packet ICMP a 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. 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.

Documents

Fichier:Serveur.txt

Sources