5. Réseaux et algorithmique

Les réseaux, c'est aussi beaucoup d'algorithmes : pour le routage, pour la détermination des IP, la correction des erreurs, etc...

Voici quelques petites activités en Python autour des réseaux.

Calculs dans un sous-réseau

Adresse de sous-réseau

On rappelle que l'adresse d'un sous-réseau est ce qui permet à une machine de savoir si une autre est située sur le même sous-réseau qu'elle.

Comment trouver cette adresse ? Rappelez-vous comment ça marche : les bits de poids fort à 1 du masque de sous-réseau indiquent les bits de l'IP qui correspondent à la partie réseau ( et les bits de poids faible à 0 indiquent la partie machine.)

On montre alors facilement que pour déterminer l'adresse du sous-réseau, il suffit de faire une opération de ET logique octet par octet entre l'IP et le masque.

  1. Montrer que l'opération ET logique entre l'IP 192.168.1.24 et le masque 255.255.255.0 permet bien d'obtenir l'adresse du sous-réseau ( 192.168.1.0 ); penser pour cela à travailler en binaire !
    		192.168.1.24
    	ET	255.255.255.0
    	=	192.168.1.0 ?					
    					

    On rappelle la table de vérité du ET logique :

    A B A ET B
    0 0 0
    1 0 0
    0 1 0
    1 1 1
  2. Ecrire en Python une fonction sous_reseau(ip, masque), qui :
    • prend 2 paramètres ip et masque sous forme de chaînes de caractères de la forme "XXX.XXX.XXX.XXX"
    • détermine l'adresse de sous-réseau correspondant à l'IP et au masque fourni
    • retourne cette adresse sous forme d'une chaîne de caractères de la même forme "XXX.XXX.XXX.XXX"
    
    	Entrez l'IP sous la forme xxx.xxx.xxx.xxx : 192.168.1.24
    	Entrez le masque sous la même forme : 255.255.255.0
    	
    	L'adresse du sous-réseau est : 192.168.1.0					
    					

    Quelques indications :

    • il existe en Python la fonction split() qui permet de séparer des "mots" dans une chaîne selon un caractère séparateur présent dans la chaîne.
      Cette fonction retourne une liste dont chaque élément est un des "mots", sans le caractère séparateur.
      Ainsi, pour séparer les 4 octets de l'IP selon le caractère séparateur ".", on pourra utiliser l'instruction :
      
      	octets = ip.split('.')						
      						
    • l'opérateur ET logique en Python se note &. Il peut très bien travailler avec des nombres en base 10, inutile de convertir en binaire au préalable !
  3. Vérifiez le bon fonctionnement de votre fonction avec les exemples du chapitre 2.

Adresse de diffusion

On montre de même que, pour trouver l'adresse de diffusion du sous-réseau, il faut faire une opération de OU logique octet par octet entre l'IP et le complément à 1 du masque; le complément à 1 du masque correspond au masque avec tous ses bits inversés : 0 → 1 et 1 → 0 .

Le complément à 1 du masque peut lui-même s'obtenir par une opération de OU EXCLUSIF entre le masque et la valeur 255.255.255.255

On rappelle la table de vérité du OU et du OU EXCLUSIF logiques :

A B A OU B A OU EXCLUSIF B
0 0 0 0
1 0 1 1
0 1 1 1
1 1 1 0
  1. En appliquant la fonction OU EXCLUSIF entre le masque 255.255.255.0 et la valeur 255.255.255.255, montrer que le complément à 1 du masque est 0.0.0.255
  2. Montrer que l'opération OU logique entre l'IP 192.168.1.24 et le complément à 1 du masque permet bien d'obtenir l'adresse de diffusion ( 192.168.1.255 ) ( penser à travailler en binaire ! )
  3. Compléter le script précédent en écrivant une fonction diffusion(ip, masque), qui :
    • prend 2 paramètres ip et masque sous forme de chaînes de caractères de la forme "XXX.XXX.XXX.XXX"
    • détermine l'adresse de diffusion du sous-réseau
    • retourne cette adresse sous forme d'une chaîne de caractères de la même forme "XXX.XXX.XXX.XXX"
    
    	Entrez l'IP sous la forme xxx.xxx.xxx.xxx : 192.168.1.24
    	Entrez le masque sous la même forme : 255.255.255.0
    	
    	L'adresse du sous-réseau est : 192.168.1.0
    	L'adresse de diffusion est : 192.168.1.255					
    					

    Quelques indications :

    • l'opérateur OU logique en Python se note |. Il peut lui aussi très bien travailler avec des nombres en base 10, inutile de convertir en binaire au préalable !
    • l'opérateur Ou EXCLUSIF se note ^
  4. Vérifiez le bon fonctionnement de votre fonction avec les exemples du chapitre 2.

Décodage d'une trame réseau

Vous avez décodé une trame Ethernet à la main....plutôt fastidieux, non ? Et encore, c'était une trame simple !!

Pour une trame contenant des données, comme celle dans ce fichier, nous allons faire faire ce décodage par un ordinateur, qui est beaucoup plus performant pour ce genre de travail "bête et méchant"...

Extraction des données

La première chose à faire sera d'extraire les données du fichier texte ci-dessus, de façon à récupérer les octets sous forme d'une liste de chaînes de caractères, chaque élément de la liste correspondant à un des octets.

→ écrire une fonction extraire_octets(fichier), qui :

  • prend comme paramètre un nom de fichier, ou un chemin vers un fichier, sous forme de chaîne.
  • retourne la liste des octets contenus dans ce fichier, chaque élément correspondant à un des octets.

Quelques indications :

  • penser à aller consulter la page dédiée de la rubrique Outils pour vous rappeler comment manipuler les fichiers avec Python.
  • Dans le fichier texte, les octets sont rangés en lignes de 16 octets; à la fin de chaque ligne se trouve le caractère "saut de ligne" ( \n ), non imprimable mais quand même présent dans le fichier : il faudra donc le retirer de chaque ligne, ce qui s'effectue avec la fonction rstrip() :
    
    	chaine = chaine.rstrip('\n')			
    				
  • penser à utiliser la fonction split() déja vue précédemment pour séparer les octets de chaque ligne; quel est le caractère séparateur ici ?
  • n'oubliez pas ensuite de tester votre fonction en l'appelant depuis le programme principal, puis en affichant la liste :
    
    	def extraire_octets(fichier) :
    	
    		............
    		
    		return liste_octets
    		
    	octets = extraire_octets('trame.txt')
    	print(octets)	
    	
    	>>> ['00', '24', 'd4', '78', '74', '23', '50', 'e5', ............]	
    					

Au cas où vous n'arriviez pas à faire cette tâche, voila le fichier Python avec la liste déja construite, ce qui vous permettra de continuer l'activité.

Analyse de la trame

Vous allez maintenant compléter le programme principal, de façon à ce que le script affiche, dans le format adapté à chaque information :

  • les adresses MAC de la destination et de la source
  • le type de protocole utilisé dans la couche réseau
  • les adresses IP de la source et de la destination
  • les numéros de port de l'application source et de l'application destination
  • et enfin les données d'application contenues dans ce paquet

C'est un travail laborieux mais pas très compliqué : il suffit d'indiquer dans le script "où" dans la liste des octets se trouvent les données qui nous intéressent...

Quelques indications :

  • pour savoir "où se trouvent les données", reportez-vous à l'activité de décodage "à la main"...
  • vous aurez intérèt, pour chacune des couches de protocoles, à suivre le même principe que vous aviez déja utilisé dans l'activité de décodage :
    1. extraction de la sous-liste des octets de l'en-tête
    2. identification dans cette sous-liste des données qui nous intéressent
    3. suppression des octets de l'en-tête pour ne garder que les octets de données de la couche supérieure
    4. ...passer à la couche suivante...
    Comme le script s'éxécute ligne après ligne, on diminue ainsi la taille de la liste des octets de la trame après la "traversée" de chaque couche : on "désencapsule" vraiment les données !!
  • pour extraire une sous-liste de la liste des octets, vous pouvez utiliser le principe de construction d'une liste par compréhension ( voir la page d'aide dans la rubrique Outils au besoin )
  • faites attention aux numéros d'index dans une liste : on commence à 0 !
  • il existe une fonction qui, à l'inverse de la fonction split(), permet de construire une chaîne à partir des éléments d'une liste, la méthode join(); elle permet de préciser le caractère à intercaler entre chaque "mot" de la chaîne de caractères.
    Par exemple, pour construire une chaîne avec les éléments d'une liste de mots en séparant ceux-ci par deux-points :
    
    	chaine = ':'.join(liste_mots)						
    					
  • vous aurez souvent besoin de faire des conversions hexadécimal → base 10; retrouvez comment on procède en Python pour cela...
  • les données de l'application sont ( dans le cas de cette trame ) codées en ASCII; on rappelle que la fonction chr(code_ascii) permet d'obtenir le caractère correspondant à un code ASCII.

Voila si tout fonctionne les informations qui doivent s'afficher :


	MAC destination 00:24:d4:78:74:23
	MAC source :  50:e5:49:67:3d:dd
	
	Version IP : IPv4
	IP source :  192.168.1.35
	IP destination :  212.27.63.106
	
	Port source :  51738
	Port destination :  80
	
	GET / HTTP/1.1
	Host: nsivaugelas.free.fr
	User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0
	Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
	Accept-Language: fr-FR
	Accept-Encoding: gzip, deflate
	Connection: keep-alive
	Upgrade-Insecure-Requests: 1
	If-Modified-Since: Sun, 01 Dec 2019 20:45:39 GMT
	If-None-Match: "11943c6d1-58a-5de42673"
	Cache-Control: max-age=0
			

( Vous avez bien entendu reconnu la requête HTTP d'un client vers le serveur Free de la page nsivaugelas...)