INFO625 : Réseau
Plan détailé du cours
Introduction Historique
TP 1
Objectifs Globaux
Il s'agit de comprendre la programmation d'applications réseau utilisant les protocoles TCP et UDP d'IPv4 (on ne s'intéressera pas malheureusement à IPv6 faute d'infrastructure réseau sur le site ... Il est pourtant possible d'adapter le code pour qu'il marche sur IPv4 et IPv6)
L'application sera une messagerie instantanée de paire-à-paire (Peer-to-Peer instant messagerie) utilisant UDP et implémentant la fiabilité de manière distribuée. On utilisera TCP pour la connexion initiale aux salles de discussion et, si le temps le permet, pour enregistrer les salles de discussions sur un serveur web afin de les rendre visibles.
Vous serez évalué sur un compte-rendu répondant aux questions (en gras) de ce document et le rendu de votre programme par mail.
ATTENTION : si votre programme ne compile pas vous serez noté seulement sur le compte-rendu. De plus, chaque warning avec les options -Wall -pedantic -std=c99 enlève 1 point.
Préliminaires
Utiliser les commandes Unix ifconfig, route et arp -a pour connaître la configuration de votre machine. Utiliser ping et traceroute entre les machines fixes de la salle et les portables en vpn pour découvrir une partie de l'achitecture réseau de l'université. Notez vos conclusions.
Compilation et analyse du programme initial
Le programme initial est constitué d'un seul fichier p2pchat.c. Ce programme a été testé sous Linux et OSX ... Il se peut même qu'il marche sous windows, certaines des fonctions étant communes (c'est quand même vraiment pas certain). Vous pouvez le compiler avec la commande:
$ gcc -o p2pchat -Wall -pedantic -std=c99 p2pchat.c
Lorsque vous lancez le programme en tapant
$ ./p2pchat mon_pseudo_favori
Tout ce que vous tapez est vu par les autres sur le même sous-réseau et vice-versa (les messages sont envoyés ligne par ligne. Pour terminer la conversation, il faut taper Ctrl-D qui ferme le fichier d'entrée standard stdin.
Vous pouvez changer le port avec l'option --port numero_de_port si vous voulez pouvoir faire des salles de discussions séparées (chaque numéro de port correspondra à une salle différente).
Ce programme d'un peu moins de 200 lignes contient quelques fonctions auxiliaires qui n'ont pas grand chose à voir avec le réseau et d'autres fonctions qui s'occupe de la communication. Voici un synopsis du mécanisme de communication via les sockets, dans le cas particulier d'UDP/IP:
- On crée un socket avec la fonction socket. Un socket est un descripteur de fichier (un entier donc) dans lequel on peut lire et écrire, mais ces lectures et écritures correspondent respectivement à des réceptions et émissions sur le réseau. On peut changer quelques options de ce socket avec la fonction setsockopt.
- On relie ce socket à notre propre adresse, cela est indispensable car la même machine peut avoir beaucoup d'interfaces réseaux différentes (ethernet, wifi, bluetooth, ...), utilisant des protocoles de communications parfois différents.
- En UDP, on peut alors lire et écrire dans le socket avec les fonctions sendto et rcvfrom car le mode UDP est sans connexion.
Identifiez les différentes fonctions de notre programme s'occupant du réseau et tentez à l'aide des pages de manuels des fonctions citées ci-dessus d'anaysez ce qu'elles font, de manière assez surperficielle, car il y a de nombreux détails subtiles ... Notez vos conclusions.
- Quel est la convention utilisée pour les messages sur le réseau ?
- La taille maximum des messages n'est pas garantie (MAX_MSG). Trouvez l'origine de ce problème et corrigez le.
Gestion distribuées de la fiabilté
le protocole UDP/IP ne gère par la fiabilité. On pourrait utiliser TCP/IP, mais on perdrait la possibilité de demander les messages non reçus à quelqu'un de proche de nous ... Voici donc une proposition de gestion distribuée de la fiabilité :
Stockage des messages
Stockez les messages dans un dossier local, avec un fichier associé à chaque pseudo. Il faudra ajouter une option pour contrôler le nom de ce dossier. Cela permet deux choses, garder trace des discussions anciennes et réexpédier n'importe quel message qui aurait été perdu par une machine.
Vous devrez donc créer des fonctions pour faire les trois choses suivantes:
- Stocker un nouveau message pour un pseudo donné.
- Savoir les messages qui nous manque (on reçoit le message numéro N du pseudo X, quels sont les messages qui me manque avant N).
- Récupérer le message numéro N d'un pseudo X pour pouvoir le renvoyer à une machine qui ne l'aurait pas.
Conseil: sauvegarder les messages avec une taille fixe, ce qui perd un peu de place mais permet d'accéder directement au message numéro n avec lseek (qui permet aussi d'agrandir le fichier).
Alternative: créer, pour chaque pseudo, un fichier d'index donnant la position du message dans un autre fichier (ça fait donc deux fichiers par pseudo).
Rappel : les fonctions pour manipuler les fichiers dont vous aurez besoin sont open, close, lseek, write, read. Pour déterminer la taille du fichier (et donc le nombre initial de messages, on pourra utiliser lseek ou bien stat. Remarque : on ne cherchera pas à ouvrir tous les fichiers au départ; on ouvrira le fichier correspondant à un pseudo uniquement à la réception d'un message provenant de ce pseudo. De plus, il ne faut pas laisser trop de fichiers ouverts, donc on prendra soin de refermer les fichiers après usage.
Demande de réexpédition
On détectera les messages perdus grâce au numéro de message de l'émetteur. Une fois détectée la perte d'un message, on le redemande à une machine sur le réseau de discussion, qui répond seulement si elle a le message.
Essayez de choisir la machine le mieux possible... Par exemple au hasard parmis les machines ayant expédié un paquet récemment. Si l'on imagine un message perdu pour presque toutes les machines, il faut mieux éviter de la redemander au même expéditeur. Il faudra aussi créer de nouveaux type de message pour les demandes de réexpédition et leur réponse.
Remarque : que doit faire une machine recevant une demande de réexpédition pour un message qu'elle n'aurait pas reçu ?
Note : on n'essaiera pas de redemander la réexpédition après un certain temps, d'abord parce que c'est un peu complexe et nécessite des éléments d'un cours du prochain semestre ... et aussi car on redemandera la réexpédition de tous les messages encore manquant à chaque réception d'un message pour le même pseudo.
Note bis : toute machine du réseau garde tous les messages passées et une machine qui se reconnecte après une absence redemandera tous les messages manquant pour une pseudo donné, dès qu'elle recevra un message de ce pseudo.
Test
Il faut tester la politique que vous avez choisi pour demander la réexpédition. Pour cela, il suffit d'ajouter du code avec la fonction random pour que l'envoie et la réception puissent échouer avec une probabilité donnée. Si l'envoie n'est pas fait, on simule un paquet perdu pour tout le monde (la machine émettrice a été déconnectée du réseau), si la réception n'est pas faite, on simule un paquet perdu pour une machine (la machine réceptrice a été déconnectée).
Documenter dans le compte-rendu les tests que vous avez fait pour différente politique de réexpédition
Connexion aux salles
Le programme précédent souffre de deux limitations majeures : on doit connaître le numéro de port de la salle de discussion et l'on est limité au réseau local.
Pour remédier à cela, on va utiliser un port fixe (424242) pour écouter sur un autre socket des demandes de connexion à des salles (il faudra donc nommer les salles). On pourra donc démarrer notre programme pour créer une nouvelle salle (avec un nom pour la salle et un numéro de port) ou pour se connecter sur une salle existante (il faudra alors donner l'adresse IP d'une machine déjà dans cette salle et le nom de la salle).
Attention : on est en paire-à-paire, il faut donc que toutes machines dans une salle accepte les demandes de connexion. Ainsi une salle de discussion continuera d'être visible même si la machine l'ayant initialement crée s'est déconnectée.
On distinguera deux cas:
- Si la machine est sur notre réseau local, on lui renvoie simplement le numéro de port de la salle de discussion et on utilise le code précédent.
- Si la machine n'est pas sur notre réseau local, il faudra lui réexpédier tous les messages dans la salle. Il faut donc créer un nouveau type de message pour les transmissions entre machine sur des réseaux locaux différents (on appellera ces messages des messages distants). Remarque: dès qu'une machine est connectée à une salle, elle va recevoir les IPs d'autres machines dans la même salle... Elle ne sera donc pas obligée de passer par la machine initiale pour envoyer des messages distants et pourra même transmettre des messages distants à des machines sur d'autres réseaux locaux ...
- L'étape suivante est donc de trouver les machines proches de nous sur des réseaux locaux différents ... un accusé de réception des messages distants serait alors le bienvenu ... et permettrait de mesurer le temps d'aller et retour
du message ... Imaginer un moyen pour les machines dans la même salle d'optimiser la vitesse de transmission des messages dans la salle ... Faites appel à votre cours de graphe ! Attention ici il faudra travailler.
Questions:
- Que manque-t-il à nos messages pour savoir si les expéditeurs sont sur le même réseau local ?
- Est-il possible d'obtenir facilement et portablement cette information (faites une rechercher sur internet) ?
- au lieu des IPs on peut utiliser gethosbyname pour faire une requête DNS et obtenir l'IP d'une machine à partir de son nom.
Les demandes de connexions aux salles ne devrait pas être faites en UDP ... Ici on ne peut pas gérer la fiabilité de manière distribuée et donc il faudrait passer à des socket TCP (en utilisant en plus les fonctions listen, accept pour le processus attendant les demandes de connexion à la salle et connect avant d'envoyer les demandes de connexion à une salle.
Liste des salles sur le web
Si vous êtes arrivé jusque là, on peut assez facilement améliorer notre programme pour gérer plusieurs salles de discussion en même temps ... et transmettre aux autres machines les noms des salles existantes (et peut-être une courte desciption) ... Mais alors, si le port 80 (http) de la machine est libre, pourquoi ne pas répondre aux requêtes http avec une liste des salles existantes, les dernières IP actives de la salle, voire même proposer des pages webs donnant les derniers messages d'une salle donnée !
La dernière chose qui manque alors est de demander à une machine de rejoindre une salle (en lui envoyant le nom de la salle et son numéro de port) ... Ainsi un programme servant juste de catalogue de salles sur le web pour rejoindre votre salle de discussion et l'afficherait sur le web ! On pourrait alors voire naître des sites de référencement des salles, qui serait simplement des programmes identiques au votre, mais rejoignant toutes les salles proposées et répondant aux requêtes http.
Bonus
Vous pouvez obtenir toute la considération de l'enseignant (et quelques point en plus) en vous attaquant aux taches suivantes (qui ne sont pas toute vraiment réseau).
- une (jolie) interface graphique
- des messages spéciaux (non sauvegardé) pour le statut des paticipants (déconnexion, absent pour un instant, etc ...)
- une gestion de la dépendance des messages. Au moment de l'envoie d'un message on peut se souvenir que ce message est la réponse à un autre message ... Cela permet de réordonner les messages de manière logique sans dépendre de la synchronisation des horloges ... Cela améliore aussi la récupération des messages perdues (si un message reçu répond à un message que l'on a pas eu, on demande alors la réexpédition).
- me rendre en plus de votre programme personnel, un programme réalisant la synthèse des meilleurs idées et des bonus dans les autres programmes ...
TP 2
On va partir d'une version améliorée du programme de chat. Les fichiers sont les suivants:
Définitions de quelques constantes
Interface d'un module permettant de sauvegarder des messages numérotés dans des fichiers et de les relire.
Implémentation du module
Le programme de chat décrit plus bas
Un Makefile (il suffit de taper make pour tout compiler, si on a ramené tous les fichiers).
Comme précédemment, le programme p2chat communique tous les messages passés en broadcast aux autres programmes. La sauvegarde et la réexpédition des messages non reçus a été implémentée (pour ne pas pénaliser ceux qui n'avait pas fini).
La vrai nouveauté est que le programme écoute sur un port TCP via tcp_socket des connexions entrantes. Une fois ces connexions acceptés, tous les messages reçus (en broadcast local par exemple) sont réémit sur le socket TCP. De même, les messages reçus sur ce nouveau socket sont réémis (en broadcast et sur les autres sockets similaires).
Un processus de ce type tourne dans mon bureau pendant le TP. Vous pouvez vous y connecter en tapant:
p2pchat -c 193.48.123.45 pseudo
Cela devrait permettre à ceux qui sont sur des portables de parler à ceux qui sont sur des fixes !
Quelques remarques:
- Le programme fait toujours un fork, où le processus père lit ce qui est tapé au clavier et l'envoie en broadcast. Le fils lui écoute
tous les messages (y compris ceux venant du père) pour éventuellement réexpédier les messages ...
- Le fils pouvant recevoir des messages (ou des demandes de connexion) sur plusieurs socket doit faire un select ... On expliquera select en début de TP (mais cela devrait faire partie du cours de synchronisation de processus).
- Une fonction sanitize dans messages.c vous empêchera de jouer avec les caractères de contrôles.
Description du programme
Expliquer le fonctionnement global du programme et des différentes fonctions. N'hésitez pas à faire des schémas ... et à utiliser ce que j'aurais dit en cours de TP.
Essayer de bien structurer cette description.
Il y a quelques subtilités: si il y a des boucles dans les connexions, les messages pourraient tourner en rond infiniment longtemps. En fait ce n'est pas possible (pour les vrais messages). Pourquoi ? Pensez au cours de graphe ...
Fin de connexion
Lorsqu'un processus ce termine, le code fourni ne fait rien de particulier et ça ne se passe pas toujours très bien. On peut observer des messages d'erreurs variés, voire des boucles infinies. Regarder comment on détecte la fermeture d'un socket et corriger le programme.
Indication: tout ce passe dans la fonction my_recv
Nom de machine
Utiliser des IP est assez pénible ... utiliser la fonction gethostbyname pour corriger ce problème (pour tester, le nome de la machine dans mon bureau est d45.lama.univ-savoie.fr et il faudrait donc que la commande suivante marche :
p2pchat -c d45.lama.univ-savoie.fr pseudo
Indication: tout ce passe dans la fonction connect_to_distant
Corriger la réexpédition
Les message de type ASK ne sont pas bien gérés et ceci pour deux raisons :
Imaginons deux machines A et B toutes deux connectées à C, mais pas connectées entre elles. Si un message manque à B et C, mais pas à A et que B s'en aperçoit, B demande le message à C qui retransmet la demande à A. Par contre la réponse de A reçue par C n'est pas réexpédiée à B.
De plus, les messages de type ASK, peuvent tourner en rond indéfiniment si le message n'est présent nulle part ...
Implanter une solution aux deux problèmes en se souvenant des demandes "ASK" déjà émises.
Attendre sur le port HTTP (80)
Créer un nouveau socket TCP écoutant sur le port 80 (en vous inspirant de l'utilisation du socket tcp_socket) Lorsque l'on reçoit des messages HTTP de type 'GET' sur ce socket on répond de sorte que
- http://IP/pseudo/NNN renvoie une page HTML avec le message numéro NNN de l'utilisateur nommé pseudo.
- http://IP/pseudo renvoie une page HTML avec tous les messages de l'utilisateur nommé pseudo.
- http://IP/ renvoie une page HTML avec tous les messages.
Utilisation de la date et du message précédant
Il serait bien de garder la date (par exemple pour trier les messages dans la question précédente) ... Ajouter cette possibilité :
- Ajouter la date de départ (UTC) dans les messages et les fichiers de sauvegarde. Choisir un format compatible entre vous tous !
- Trier les messages par date pour répondre à l'URL http://IP/
- Ajouter aussi le message précédent (id et pseudo) dans le message et les pseudos pour pouvoir demander la réexpidition de ce message
si vous ne l'avez pas. Au fait c'est quoi le message précédent ...
Question difficile et optionnelle: garder la connexité et optimiser le réseau
Proposer une solution pour garder la connexité entre les machines lorsque une machine s'en va. Exemple: deux machines A et B toutes deux connectées à C, mais pas connecté entre elle. Si C s'en va, il faudrait que A et B se connectent.
On pourrait aussi essayer de connecter plutôt les machines qui sont proches ...
Attention: c'est vraiment dur, surtout si l'on suppose que les machines peuvent exécuter des programmes différents (c'est probablement impossible dans ce cas !). Pensez à votre cours de graphe ...