{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# TP réseaux - TP 1 : Couches basses, structurer les trames\n",
    "\n",
    "On dispose dans ce TP d'une interface réseau fonctionnelle capable d'émettre sur le médium de communication ou de recevoir depuis ce médium une chaîne d'octets. Dans un premier temps, on cherchera à ajouter les notions essentielles permettant de communiquer (c'est le protocole **Ethernet**) :\n",
    " - préciser émetteur et récepteur;\n",
    " - préciser le type de contenu envoyé.\n",
    " \n",
    "Une fois cette étape passée, et dans le but d'utiliser des adresses de plus haut niveau donc plus utilisables (**IP**), on élaborera un protocole de traduction adresses MAC/adresse IP, c'est le protocole **ARP**. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 0. Contexte et code fourni\n",
    "\n",
    "### 0.1. Une brève histoire d'asyncio\n",
    "\n",
    "En programmation réseaux (ou dans d'autres contextes comme les systèmes d'exploitation), on est régulièrement amené à lancer plusieurs applications \"en parallèle\", typiquement :\n",
    "- des programmes qui \"écoutent\" le réseau, on les appellera serveurs;\n",
    "- des programmes qui s'occupent d'envoyer de l'information, on les appellera clients.\n",
    "\n",
    "Il n'y a pas a priori de synchronisation figée entre les différents clients et serveurs et on souhaite que tous soient capables de réagir au contexte à tout moment. Pour cela, il est nécessaire d'exécuter toutes ces applications en concurrence : chacune peut prendre la main lorsque le contexte le demande, mais elle la rend dès qu'elle revient à un état d'attente. C'est ce qui permet à plusieurs logiciels de fonctionner en même temps sur votre ordinateur, ou à votre ordinateur d'avoir plusieurs échanges simultanément avec différents serveurs sur internet.\n",
    "\n",
    "Lorsqu'on programme avec Python3, c'est le module `asyncio` qui permet de gérer la concurrence. On l'utilise dans le code fourni pour permettre à un (voire plusieurs) serveur(-s) d'écouter en permanence le réseau pour réagir dès qu'une trame arrive, sans empêcher d'autres applications (en particulier un client) d'utiliser le réseau. Les fonctions qui doivent travailler de manière asynchrone sont définies avec le mot clé `async` avant `def`, et appelées avec le mot clé `await`.\n",
    "\n",
    "Prenez le temps de regarder le code dans `linkproto.py` et `utilisateur.py` pour repérer les occurrences de ces mots clés et comprendre l'intérêt de leur utilisation. Vous n'aurez pas à manipuler ce module `asyncio` dans la suite de ce TP, mais il est important de comprendre le fonctionnement général du code.\n",
    "\n",
    "Le fichier `ARPpacket.py` n'est pas utile pour le moment, on s'y intéressera plus tard.\n",
    "\n",
    "### 0.2. Ce qui vous est proposé\n",
    "\n",
    "Le code fourni est directement utilisable. Lisez le fichier `utilisateur.py`. Vous constatez que deux fonctions sont appelées en concurrence. La première est un serveur, en écoute permanente, il ne sera pas nécessaire de modifier cette fonction (tout se fera dans la fonction `ETHrcv`). La seconde est un client, qui vous propose en boucle d'entrer une commande au clavier. La seule commande acceptée en début de TP est la commande `raw` qui vous permet d'envoyer une chaîne de caractères (à entrer au clavier) sans structure. Pour éviter d'envoyer des chaînes trop courtes, on complète jusqu'à 50 caractères si nécessaire.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Pour débuter\n",
    "\n",
    "Ce TP est à réaliser dans un lab Kathará. Récupérez l'archive `tp1-Ethernet-ARP.tar.gz` contenant le code sur celene et éventuellement afficher les slides des cours sur les couches basses (voire la couche IP).\n",
    "\n",
    "Kathará est un système d'émulation de réseau open source basé sur des conteneurs, destiné à la présentation de démonstrations et de cours interactifs, au test de réseaux de production dans un environnement de test, ou au développement de nouveaux protocoles réseau.\n",
    "\n",
    "L'architecture du réseau est décrite dans le fichier `lab.conf`. Les commande lancées au démarage de chaque machine <nomMachine> se trouvent dans le fichier `<nomMachine>.startup`.\n",
    "\n",
    "Pour lancer la construction du réseau virtuel, on se place dans le répertoire du lab et on donne, sur le terminal de la machine hote, la commande :\n",
    "\n",
    "``kathara lstart``\n",
    "\n",
    "L'arrêt du réseau et ses machines se fait à laide de la commande : `kathara wipe` ou `kathara wipe -f` pour forcer l'arrêt.\n",
    "\n",
    "Tout savoir sur kathará : [kathara.org](https://www.kathara.org)\n",
    "\n",
    "### 1.1 Première manipulation\n",
    "\n",
    "Exécuter la commande suivante sur les deux machines : \n",
    "\n",
    "``./shared/utilisateur.py NomDeLInterface`` (normalement `eth0`)\n",
    "\n",
    "Pour visualiser les échanges sur le réseau, ouvrez une instance de wireshark sur la machine hote avec la commande :\n",
    "\n",
    "``kathara wire DomaineDeCollision``\n",
    "\n",
    "Vous devez voir passer les messages que vous envoyez. Pour le moment (commande `raw`), il n'y a aucune structure à la séquence binaire envoyée, le but de ce TP est d'ajouter des informations : émetteur, destinataire et type en premier lieu. Chaque message envoyé est donc visible par toutes les machines présentes sur le LAN.\n",
    "\n",
    "### 1.2 Questions d'encodage\n",
    "\n",
    "Les signaux qui circulent sur le LAN sont des suites d'octets, il est donc nécessaire de faire le lien entre les objets manipulés par Python et ces octets envoyés, en premier lieu les chaînes de caractères que vous venez d'envoyer au moyen de la commande `raw`. La gestion de l'encodage se fait au moyen des fonctions que vous trouverez au début du fichier `linkproto.py`. On propose ici une notion de format \"haut niveau\" représenté par la chaîne `LinkFormat`. Cette chaîne décrit la liste ordonnée des champs que l'on souhaite envoyer. On se restreint ici à 4 types décrits dans `linkproto.py` : une chaîne de caractères (`S`), une adresse MAC (`M`), un entier codé sur 2 octets (`I`) ou un paquet ARP (`A`). \n",
    "\n",
    "Par exemple, lors de l'envoi d'une chaîne de caractères avec la commande `raw`, on utilise le format `LinkFormat = 'S'`. Et si on souhaite envoyer un entier suivi d'une adresse MAC, on prendra  `LinkFormat = 'IM'`, la trame sera alors de taille 8 octets. La variable `LinkFormat` est globale et doit être définie avant tout encodage.\n",
    "\n",
    "La fonction `encodage` prend en entrée la liste de tous les champs à encoder (par exemple `[2,\"ab:cd:ef:01:23:45\"]` pour le format `'IM'`) et renvoie la suite d'octets correspondante. La fonction `decodage` fait le chemin inverse.\n",
    "\n",
    "On note que pour pouvoir décoder, il faut qu'un champ au plus soit de longueur variable, le format `'SIS'` est par exemple proscrit puisqu'il serait impossible de savoir combien d'octets correspondent à la première chaîne de caractères."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Ethernet\n",
    "\n",
    "Pour échanger avec une machine sur le même LAN, il faut notamment identifier émetteur et destinataire dans chacune des trames envoyées, c'est un des rôles du protocole Ethernet. Pour rappel, une trame Ethernet a pour format :"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "   +----+--------+--------+----+-------+---+\n",
    "   |Sync|@MAC dst|@MAC src|Type|Données|CRC|\n",
    "   +----+--------+--------+----+-------+---+\n",
    "   |  8 |    6   |    6   |  2 |46-1500| 4 |\n",
    "   +----+--------+--------+----+-------+---+"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La carte réseau s'occupe elle même, au niveau matériel donc, d'ajouter les drapeaux de synchronisation et le CRC terminal. De même elle repère et teste ces éléments à la réception et ne transmet au système d'exploitation que le contenu, c'est à dire :"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "   +--------+--------+----+-------+\n",
    "   |@MAC dst|@MAC src|Type|Données|\n",
    "   +--------+--------+----+-------+\n",
    "   |    6   |    6   |  2 |46-1500|\n",
    "   +--------+--------+----+-------+"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "On propose dans un premier temps d'utiliser le type `0x88b5`. Dans la définition des types Ethernet, c'est un numéro associé à un type expérimental, donc sans format de données attendu.\n",
    "\n",
    "Commencez par observer le code de la fonction `client` dans `utilisateur.py` ainsi que celui de `linksend` dans `linkproto.py`. La fonction `ETHsend` est une copie de `linksend`, on va maintenant la modifier pour faire un envoi de trame Ethernet. Ajoutez une commande `eth` dans la fonction `client` de `utilisateur.py` pour implémenter le protocole Ethernet avec le type `0x88b5` en plaçant comme donnée une chaîne de caractères (comme lors de l'envoi avec la commande `raw`). Cela nécessite de modifier la fonction `client` de sorte qu'elle appelle la fonction `ETHsend` lorsque la commande est `eth`. Il faut ensuite modifier `ETHrcv` dans `linkproto.py`. \n",
    "- Lors de l'envoi, `ETHsend` doit créer puis envoyer la trame désirée, c'est à dire définir le `LinkFormat` adapté à une trame Ethernet et demander à la fonction `encodage` de construire la séquence binaire à envoyer.\n",
    "- Lors de la réception, `ETHrcv` utilise la fonction `getETHtype` qui prend en entrée une chaîne d'octets censée être une trame bien formée et interprète les octets 12 et 13 comme un entier. Cela permet, dans le code fourni, de filtrer le trafic IPv6 (protocoles MDNS et ICMPv6) que vous pouvez voir dans wireshark. On s'attend maintenant à trouver le type `0x88b5` qui nous indique le format attendu pour la trame reçue.\n",
    "\n",
    "Parmi toutes les trames qui circulent, pensez à ne conserver que celles dont l'adresse de destination est votre adresse MAC.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Avec une adresse IP : le protocole ARP\n",
    "\n",
    "On associe maintenant une adresse IP à l'interface réseau que l'on utilise : `10.0.1.i/24` où `i` est le numéro de la prise réseau murale sur laquelle est branchée la carte réseau. Il n'y a pas de commande à exécuter ici permettant de récupérer l'adresse IP : personne ne gère le protocole IP à notre place. On peut donc par exemple demander à l'utilisateur de définir l'adresse IP qu'il souhaite utiliser (tout comme la commande `ip addr add 10.0.1.54/24 dev eth0` permet à l'utilisateur de choisir son adresse IP lors de l'utilisation de la pile réseau habituelle). On pourra \"oublier\" ici le masque IP qui n'est pas utile pour ARP.\n",
    "\n",
    "Si on souhaite échanger avec une machine d'adresse IP connue, il est nécessaire de commencer par obtenir son adresse MAC, sans quoi on ne pourra pas envoyer de trame ethernet jusqu'à destination. C'est le rôle du protocole ARP vu en cours. Un paquet ARP est envoyé dans une trame Ethernet de type `0x806`, le paquet lui-même constitue les données (payload) de la trame. Un paquet ARP est constitué des champs suivants :\n",
    "- type de protocole de couche liaison : ce sera toujours Ethernet dans notre cas, représenté par l'entier 1\n",
    "- type de protocole de couche réseau : ce sera toujours IP dans notre cas, représenté par l'entier `0x800`\n",
    "- longueur d'une adresse de couche liaison\n",
    "- longueur d'une adresse de couche réseau\n",
    "- type d'opération : 1 pour une requête, 2 pour une réponse\n",
    "- adresse liaison de l'émetteur\n",
    "- adresse réseau de l'émetteur\n",
    "- adresse liaison du destinataire\n",
    "- adresse réseau du destinataire\n",
    "\n",
    "Vous pouvez constater qu'un paquet ARP Ethernet/IP est de longueur 28 octets.\n",
    "\n",
    "On s'intéresse donc maintenant aux champs de type `'A'` dans `LinkFormat`. Ces champs représentent des paquets ARP qui doivent être fournis sous forme de suite d'octets. Le fichier `ARPpacket.py` fournit des primitives pour manipuler les paquets ARP. Lisez la définition de la classe `ARPpacket` (méthode `__init__` et rôle des méthodes `tobytes` et `frombytes`).\n",
    "\n",
    "Déterminez les arguments nécessaires aux fonctions `ARPreq` et `ARPreply` dans `ARPpacket.py`, puis écrivez ces fonctions.\n",
    "\n",
    "Modifiez les fonctions `ETHsend` et `Ethrcv` de `linkproto.py` pour implémenter le protocole ARP. En réception, il faut tester si la trame reçue est de type ARP et est une requête adressée à votre machine. Si ces conditions sont remplies, il faut envoyer la réponse ARP.\n",
    "\n",
    "Pour finir, modifiez `utilisateur.py` pour ajouter la commande `arp` permettant d'envoyer une requête ARP pour une adresse IP renseignée par l'utilisateur."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Expérimentation\n",
    "\n",
    "Écrivez un programme qui répertorie toutes les machines connectées sur le LAN et affiche la correspondance MAC/IP."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Pour aller plus loin\n",
    "\n",
    "Si vous avez terminé, vous pouvez commencer à regarder le prochain TP ! On introduira plus en détail le protocole IP et on fera de nos machines des routeurs en les amenant à forwarder les paquets IP."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. Fin du TP\n",
    "\n",
    "Récupérez les fichiers que vous avez écrits, reconnectez votre machine au switch d'accès à internet puis rebootez votre machine et vérifiez la connexion internet."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "celltoolbar": "Aucun(e)",
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
