Modélisation orientée objet
---
id: a108d77a-0bec-4d40-a0e5-c009d0c0f9d6
---
classDiagram
%% Hiérarchie des équipements réseau
Machine <|-- PC
Machine <|-- Routeur
Machine <|-- Switch
%% Une machine possède une ou plusieurs interfaces (composition)
Machine "1" *-- "1..*" Interface : possède
%% Deux interfaces sont reliées par un lien physique (câble)
Interface "2" -- "1" Lien : relie
%% Tables propres à chaque type d'équipement
PC "1" *-- "1" TableARP
Routeur "1" *-- "1" TableRoutage
Switch "1" *-- "1" TableMAC
%% Le routeur délègue le calcul des routes à un protocole
Routeur "1" *-- "1" ProtocoleRoutage : utilise
ProtocoleRoutage <|-- RIP
ProtocoleRoutage <|-- OSPF
%% Le protocole alimente la table de routage
ProtocoleRoutage ..> TableRoutage : met à jour
%% Encapsulation : une trame contient un paquet
Trame *-- Paquet : encapsule
class Machine{
+str nom
+list interfaces
+ajouter_interface(iface)
+envoyer(trame, iface)
+recevoir(trame)
}
class Interface{
+str mac %% ex: "aa:bb:cc:dd:ee:ff"
+str ip %% ex: "192.168.1.1"
+str masque %% ex: "255.255.255.0"
+Lien lien
}
class Lien{
%% Câble reliant deux interfaces
+Interface bout1
+Interface bout2
+int cout
+transmettre(trame, depuis_iface)
}
class PC{
+TableARP table_arp
+ping(ip_dest)
}
class Routeur{
+TableRoutage table_routage
+ProtocoleRoutage protocole
+router(paquet)
+tick() %% avance la simulation d'un pas de temps
}
class Switch{
+TableMAC table_mac
+commuter(trame, port_entrant)
}
class ProtocoleRoutage{
<<abstract>>
+Routeur routeur
+demarrer()
+tick() %% appelé à chaque pas de temps
+mise_a_jour() %% recalcule les routes
}
class RIP{
%% Vecteur de distance - Bellman-Ford
%% Metrique = nombre de sauts (max 15)
+int periodicite %% envoi toutes les N ticks
+envoyer_table() %% diffuse la table aux voisins
+recevoir_table(table, voisin) %% applique Bellman-Ford
}
class OSPF{
%% Etat de lien - Dijkstra
%% Metrique = cout (bande passante)
+dict lsdb %% LSDB : graphe du réseau complet
+demarrer() %% génère et floode le LSA
+recevoir_lsa(lsa) %% met à jour la LSDB
+calculer_routes() %% lance Dijkstra sur la LSDB
}
class LSA{
%% Link State Advertisement
+str routeur_id
+list voisins %% [(voisin_id, cout), ...]
+list reseaux %% [(reseau, masque), ...]
}
class Trame{
%% Couche 2 - adresses MAC
+str mac_src
+str mac_dest
+Paquet payload
}
class Paquet{
%% Couche 3 - adresses IP
+str ip_src
+str ip_dest
+bytes donnees
}
class TableARP{
%% ip -> mac
+dict table
+resoudre(ip) str
+ajouter(ip, mac)
}
class TableRoutage{
%% (réseau, masque, passerelle, interface, metrique)
+list routes
+ajouter(reseau, masque, passerelle, iface, metrique)
+chercher(ip_dest) tuple
}
class TableMAC{
%% mac -> interface (appris dynamiquement)
+dict table
+apprendre(mac, iface)
+chercher(mac) Interface
}
Chapitre 1 - Faire communiquer deux PCs
classDiagram
Machine <|-- PC
Machine "1" *-- "1..*" Interface : possède
Interface "2" -- "1" Lien : relie
PC "1" *-- "1" TableARP
Trame *-- Paquet : encapsule
class Machine{
+str nom
+list interfaces
+ajouter_interface(iface)
+envoyer(trame, iface)
+recevoir(trame, iface)
}
class Interface{
+str mac
+str ip
+str masque
+meme_reseau(ip_dest) bool
}
class Lien{
+Interface bout1
+Interface bout2
+transmettre(trame, depuis)
}
class PC{
+TableARP table_arp
+ping(ip_dest)
}
class TableARP{
+dict table
+ajouter(ip, mac)
+resoudre(ip) str
}
class Trame{
+str mac_src
+str mac_dest
+payload
}
class Paquet{
+str ip_src
+str ip_dest
+bytes donnees
}
Topologie
graph LR
PC1["**PC1**
IP : 192.168.1.1
MAC : aa:aa:aa:aa:aa:01"]
PC2["**PC2**
IP : 192.168.1.2
MAC : aa:aa:aa:aa:aa:02"]
PC1 -- câble --- PC2
Ce qu'on veut obtenir
PC1 (192.168.1.1) ----câble---- PC2 (192.168.1.2)
PC1.ping("192.168.1.2")
[PC1] ARP : qui a 192.168.1.2 ?
[PC2] ARP réponse : c'est moi (aa:aa:aa:aa:aa:02)
[PC1] ARP appris : 192.168.1.2 -> aa:aa:aa:aa:aa:02
[PC2] reçu de 192.168.1.1 : b'ping!'
Avant d'envoyer un paquet IP, un PC doit connaitre le MAC de destination : c'est le rôle du protocole ARP.
Les messages échangés
class Paquet:
"""Couche 3 - transporté dans une Trame"""
def __init__(self, ip_src, ip_dest, donnees=b""):
self.ip_src = ip_src
self.ip_dest = ip_dest
self.donnees = donnees
class RequeteARP:
"""Broadcast : qui possède cette IP ?"""
def __init__(self, ip_src, mac_src, ip_cible):
self.ip_src = ip_src
self.mac_src = mac_src
self.ip_cible = ip_cible
class ReponseARP:
"""Réponse unicast : cette IP m'appartient"""
def __init__(self, ip_annoncee, mac_annoncee):
self.ip_annoncee = ip_annoncee
self.mac_annoncee = mac_annoncee
class Trame:
"""Couche 2 - unité de transfert sur le câble"""
MAC_BROADCAST = "ff:ff:ff:ff:ff:ff"
def __init__(self, mac_src, mac_dest, payload):
self.mac_src = mac_src
self.mac_dest = mac_dest
self.payload = payload # Paquet, RequeteARP ou ReponseARP
L'infrastructure
import ipaddress
class TableARP:
"""Associe des adresses IP à des adresses MAC (cache ARP)."""
def __init__(self):
self.table = {} # ip (str) -> mac (str)
def ajouter(self, ip, mac):
"""Enregistre l'association ip -> mac.
>>> t = TableARP()
>>> t.ajouter("192.168.1.2", "aa:bb:cc:dd:ee:ff")
>>> t.resoudre("192.168.1.2")
'aa:bb:cc:dd:ee:ff'
"""
self.table[ip] = mac
def resoudre(self, ip):
"""Retourne le MAC associé à ip, ou None si inconnu.
>>> t = TableARP()
>>> t.resoudre("192.168.1.99") is None
True
>>> t.ajouter("192.168.1.1", "aa:00:00:00:00:01")
>>> t.resoudre("192.168.1.1")
'aa:00:00:00:00:01'
"""
return self.table.get(ip)
class Interface:
"""Carte réseau d'une machine : adresse MAC, IP et masque."""
def __init__(self, mac, ip, masque):
self.mac = mac
self.ip = ip
self.masque = masque
self.lien = None # rempli par Lien.__init__
self.machine = None # rempli par Machine.ajouter_interface
def meme_reseau(self, ip_dest):
"""Retourne True si ip_dest appartient au même sous-réseau.
>>> iface = Interface("aa:bb:cc:dd:ee:ff", "192.168.1.1", "255.255.255.0")
>>> iface.meme_reseau("192.168.1.42")
True
>>> iface.meme_reseau("192.168.2.1")
False
>>> iface.meme_reseau("10.0.0.1")
False
"""
reseau = ipaddress.ip_network(f"{self.ip}/{self.masque}", strict=False)
return ipaddress.ip_address(ip_dest) in reseau
class Lien:
"""Câble reliant deux interfaces : livre les trames d'un bout à l'autre."""
def __init__(self, bout1, bout2):
self.bout1 = bout1
self.bout2 = bout2
bout1.lien = self
bout2.lien = self
def transmettre(self, trame, depuis):
"""Livre la trame à l'extrémité opposée à depuis."""
arrivee = self.bout2 if depuis is self.bout1 else self.bout1
arrivee.machine.recevoir(trame, arrivee)
class Machine:
"""Equipement réseau de base : gère une liste d'interfaces."""
def __init__(self, nom):
self.nom = nom
self.interfaces = []
def ajouter_interface(self, iface):
"""Attache iface à cette machine et met à jour la back-reference.
>>> m = Machine("test")
>>> iface = Interface("aa:bb:cc:dd:ee:ff", "10.0.0.1", "255.0.0.0")
>>> m.ajouter_interface(iface)
>>> iface.machine is m
True
>>> m.interfaces == [iface]
True
"""
iface.machine = self
self.interfaces.append(iface)
def envoyer(self, trame, iface):
"""Affiche la trame et la dépose sur le câble connecté à iface."""
print(f" [{self.nom}] --> {trame.mac_src} -> {trame.mac_dest}")
iface.lien.transmettre(trame, iface)
def recevoir(self, trame, iface):
"""Traite une trame arrivant sur iface. A implémenter dans les sous-classes."""
raise NotImplementedError
Le PC
class PC(Machine):
def __init__(self, nom):
super().__init__(nom)
self.table_arp = TableARP()
# --- méthodes privées ---
def _trouver_interface_vers(self, ip_dest):
"""Renvoie l'interface sur le même réseau que ip_dest, ou None.
>>> pc = PC("test")
>>> iface = Interface("aa:bb:cc:dd:ee:01", "192.168.1.1", "255.255.255.0")
>>> pc.ajouter_interface(iface)
>>> pc._trouver_interface_vers("192.168.1.99") is iface
True
>>> pc._trouver_interface_vers("10.0.0.1") is None
True
"""
for iface in self.interfaces:
if iface.meme_reseau(ip_dest):
return iface
return None
def _envoyer_requete_arp(self, ip_dest, iface):
requete = RequeteARP(iface.ip, iface.mac, ip_dest)
trame = Trame(iface.mac, Trame.MAC_BROADCAST, requete)
print(f" [{self.nom}] ARP : qui a {ip_dest} ?")
self.envoyer(trame, iface)
# --- réception ---
def recevoir(self, trame, iface):
payload = trame.payload
if isinstance(payload, RequeteARP):
if payload.ip_cible == iface.ip:
# Apprendre l'émetteur au passage
self.table_arp.ajouter(payload.ip_src, payload.mac_src)
# Répondre en unicast
reponse = ReponseARP(iface.ip, iface.mac)
rep = Trame(iface.mac, trame.mac_src, reponse)
print(f" [{self.nom}] ARP réponse : {iface.ip} -> {iface.mac}")
self.envoyer(rep, iface)
elif isinstance(payload, ReponseARP):
self.table_arp.ajouter(payload.ip_annoncee, payload.mac_annoncee)
print(f" [{self.nom}] ARP appris : {payload.ip_annoncee} -> {payload.mac_annoncee}")
elif isinstance(payload, Paquet):
if payload.ip_dest == iface.ip:
print(f" [{self.nom}] reçu de {payload.ip_src} : {payload.donnees}")
# --- émission ---
def ping(self, ip_dest):
print(f"\n[{self.nom}] ping {ip_dest}")
iface = self._trouver_interface_vers(ip_dest)
if not iface:
print(f" [{self.nom}] pas de route vers {ip_dest}")
return
mac_dest = self.table_arp.resoudre(ip_dest)
if not mac_dest:
self._envoyer_requete_arp(ip_dest, iface)
mac_dest = self.table_arp.resoudre(ip_dest)
if mac_dest:
paquet = Paquet(iface.ip, ip_dest, b"ping!")
trame = Trame(iface.mac, mac_dest, paquet)
self.envoyer(trame, iface)
else:
print(f" [{self.nom}] {ip_dest} injoignable (pas de réponse ARP)")
Exemple complet
if __name__ == "__main__":
pc1 = PC("PC1")
pc2 = PC("PC2")
iface1 = Interface("aa:aa:aa:aa:aa:01", "192.168.1.1", "255.255.255.0")
iface2 = Interface("aa:aa:aa:aa:aa:02", "192.168.1.2", "255.255.255.0")
pc1.ajouter_interface(iface1)
pc2.ajouter_interface(iface2)
Lien(iface1, iface2)
pc1.ping("192.168.1.2") # 1er ping : ARP nécessaire
pc1.ping("192.168.1.2") # 2e ping : ARP déjà en cache
pc1.ping("10.0.0.1") # hors réseau
Sortie attendue :
[PC1] ping 192.168.1.2
[PC1] ARP : qui a 192.168.1.2 ?
[PC1] --> aa:aa:aa:aa:aa:01 -> ff:ff:ff:ff:ff:ff
[PC2] ARP réponse : 192.168.1.2 -> aa:aa:aa:aa:aa:02
[PC2] --> aa:aa:aa:aa:aa:02 -> aa:aa:aa:aa:aa:01
[PC1] ARP appris : 192.168.1.2 -> aa:aa:aa:aa:aa:02
[PC1] --> aa:aa:aa:aa:aa:01 -> aa:aa:aa:aa:aa:02
[PC2] reçu de 192.168.1.1 : b'ping!'
[PC1] ping 192.168.1.2
[PC1] --> aa:aa:aa:aa:aa:01 -> aa:aa:aa:aa:aa:02
[PC2] reçu de 192.168.1.1 : b'ping!'
[PC1] ping 10.0.0.1
[PC1] pas de route vers 10.0.0.1
Chapitre 2 - Trois PCs via un Switch
classDiagram
Machine <|-- PC
Machine <|-- Switch
Machine "1" *-- "1..*" Interface : possède
Interface "2" -- "1" Lien : relie
PC "1" *-- "1" TableARP
Switch "1" *-- "1" TableMAC
class Machine{
+str nom
+list interfaces
+ajouter_interface(iface)
+envoyer(trame, iface)
+recevoir(trame, iface)
}
class Interface{
+str mac
+str ip
+str masque
}
class Lien{
+transmettre(trame, depuis)
}
class PC{
+TableARP table_arp
+ping(ip_dest)
}
class Switch{
+TableMAC table_mac
+commuter(trame, port_entrant)
}
class TableARP{
+dict table
+ajouter(ip, mac)
+resoudre(ip) str
}
class TableMAC{
+dict table
+apprendre(mac, iface)
+chercher(mac) Interface
}
Topologie
graph LR
PC1["**PC1**
IP : 192.168.1.1
MAC : aa:00:00:00:00:01"]
PC2["**PC2**
IP : 192.168.1.2
MAC : aa:00:00:00:00:02"]
PC3["**PC3**
IP : 192.168.1.3
MAC : aa:00:00:00:00:03"]
SW1[["**SW1**
port1 | port2 | port3"]]
PC1 -- câble --- SW1
PC2 -- câble --- SW1
PC3 -- câble --- SW1
Ce qu'on veut obtenir
PC1 ---Lien--- port1 \
PC2 ---Lien--- port2 SW1
PC3 ---Lien--- port3 /
PC1.ping("192.168.1.2")
[PC1] ARP broadcast
[SW1] flood sur port2 et port3 <- MAC inconnue
[PC2] ARP réponse
[SW1] forward sur port1 <- MAC de PC1 apprise
[PC1] envoie le ping
[SW1] forward sur port2 <- MAC de PC2 apprise
[PC2] reçu !
Le switch apprend les MACs au fur et à mesure : après le premier échange, il sait sur quel port se trouve chaque machine et ne floode plus.
TableMAC
class TableMAC:
"""Associe les adresses MAC aux ports (interfaces) du switch."""
def __init__(self):
self.table = {} # mac (str) -> Interface
def apprendre(self, mac, iface):
"""Enregistre que mac est joignable via iface.
>>> t = TableMAC()
>>> iface = Interface("sw:00:00:00:00:01", "0.0.0.0", "0.0.0.0")
>>> t.apprendre("aa:00:00:00:00:01", iface)
>>> t.chercher("aa:00:00:00:00:01") is iface
True
"""
self.table[mac] = iface
def chercher(self, mac):
"""Retourne l'interface associée à mac, ou None si inconnue.
>>> t = TableMAC()
>>> t.chercher("aa:00:00:00:00:99") is None
True
"""
return self.table.get(mac)
Switch
class Switch(Machine):
"""Commutateur réseau : apprend les MACs et commute les trames à la couche 2."""
def __init__(self, nom):
super().__init__(nom)
self.table_mac = TableMAC()
def recevoir(self, trame, iface_entree):
"""Apprend le MAC source puis commute la trame."""
self.table_mac.apprendre(trame.mac_src, iface_entree)
self.commuter(trame, iface_entree)
def commuter(self, trame, port_entrant):
"""Forward vers le port connu, flood si destination inconnue ou broadcast."""
if trame.mac_dest == Trame.MAC_BROADCAST:
print(f" [{self.nom}] flood (broadcast)")
self._flood(trame, port_entrant)
else:
dest = self.table_mac.chercher(trame.mac_dest)
if dest:
num_port = self.interfaces.index(dest) + 1
print(f" [{self.nom}] forward -> port{num_port}")
dest.lien.transmettre(trame, dest)
else:
print(f" [{self.nom}] flood (MAC inconnue)")
self._flood(trame, port_entrant)
def _flood(self, trame, port_entrant):
"""Envoie la trame sur tous les ports sauf celui d'entrée."""
for iface in self.interfaces:
if iface is not port_entrant:
iface.lien.transmettre(trame, iface)
Exemple complet
if __name__ == "__main__":
pc1 = PC("PC1")
pc2 = PC("PC2")
pc3 = PC("PC3")
sw = Switch("SW1")
# Interfaces des PCs
eth1 = Interface("aa:00:00:00:00:01", "192.168.1.1", "255.255.255.0")
eth2 = Interface("aa:00:00:00:00:02", "192.168.1.2", "255.255.255.0")
eth3 = Interface("aa:00:00:00:00:03", "192.168.1.3", "255.255.255.0")
# Ports du switch (couche 2 : pas d'IP)
p1 = Interface("sw:00:00:00:00:01", "0.0.0.0", "0.0.0.0")
p2 = Interface("sw:00:00:00:00:02", "0.0.0.0", "0.0.0.0")
p3 = Interface("sw:00:00:00:00:03", "0.0.0.0", "0.0.0.0")
pc1.ajouter_interface(eth1)
pc2.ajouter_interface(eth2)
pc3.ajouter_interface(eth3)
sw.ajouter_interface(p1)
sw.ajouter_interface(p2)
sw.ajouter_interface(p3)
Lien(eth1, p1)
Lien(eth2, p2)
Lien(eth3, p3)
pc1.ping("192.168.1.2") # flood ARP, puis forward
pc1.ping("192.168.1.3") # ARP flood, puis forward
pc2.ping("192.168.1.3") # PC2 connu du switch, ARP flood pour PC3
Sortie attendue :
[PC1] ping 192.168.1.2
[PC1] ARP : qui a 192.168.1.2 ?
[PC1] --> aa:00:00:00:00:01 -> ff:ff:ff:ff:ff:ff
[SW1] flood (broadcast)
[PC2] ARP réponse : 192.168.1.2 -> aa:00:00:00:00:02
[PC2] --> aa:00:00:00:00:02 -> aa:00:00:00:00:01
[SW1] forward -> port1
[PC1] ARP appris : 192.168.1.2 -> aa:00:00:00:00:02
[PC1] --> aa:00:00:00:00:01 -> aa:00:00:00:00:02
[SW1] forward -> port2
[PC2] reçu de 192.168.1.1 : b'ping!'
[PC1] ping 192.168.1.3
[PC1] ARP : qui a 192.168.1.3 ?
[PC1] --> aa:00:00:00:00:01 -> ff:ff:ff:ff:ff:ff
[SW1] flood (broadcast)
[PC3] ARP réponse : 192.168.1.3 -> aa:00:00:00:00:03
[PC3] --> aa:00:00:00:00:03 -> aa:00:00:00:00:01
[SW1] forward -> port1
[PC1] ARP appris : 192.168.1.3 -> aa:00:00:00:00:03
[PC1] --> aa:00:00:00:00:01 -> aa:00:00:00:00:03
[SW1] forward -> port3
[PC3] reçu de 192.168.1.1 : b'ping!'
[PC2] ping 192.168.1.3
[PC2] ARP : qui a 192.168.1.3 ?
[PC2] --> aa:00:00:00:00:02 -> ff:ff:ff:ff:ff:ff
[SW1] flood (broadcast)
[PC3] ARP réponse : 192.168.1.3 -> aa:00:00:00:00:03
[PC3] --> aa:00:00:00:00:03 -> aa:00:00:00:00:02
[SW1] forward -> port2
[PC2] ARP appris : 192.168.1.3 -> aa:00:00:00:00:03
[PC2] --> aa:00:00:00:00:02 -> aa:00:00:00:00:03
[SW1] forward -> port3
[PC3] reçu de 192.168.1.2 : b'ping!'
Chapitre 3 - Routage entre deux réseaux
classDiagram
Machine <|-- PC
Machine <|-- Routeur
Machine "1" *-- "1..*" Interface : possède
Interface "2" -- "1" Lien : relie
PC "1" *-- "1" TableARP
Routeur "1" *-- "1" TableARP
Routeur "1" *-- "1" TableRoutage
class Machine{
+str nom
+list interfaces
+ajouter_interface(iface)
+envoyer(trame, iface)
+recevoir(trame, iface)
}
class Interface{
+str mac
+str ip
+str masque
+meme_reseau(ip_dest) bool
}
class Lien{
+transmettre(trame, depuis)
}
class PC{
+TableARP table_arp
+str passerelle
+ping(ip_dest)
}
class Routeur{
+TableARP table_arp
+TableRoutage table_routage
+router(paquet)
}
class TableARP{
+dict table
+ajouter(ip, mac)
+resoudre(ip) str
}
class TableRoutage{
+list routes
+ajouter(reseau, masque, passerelle, iface)
+chercher(ip_dest) tuple
}
Topologie
graph LR
PC1["**PC1**
192.168.1.1/24
GW : 192.168.1.254
MAC : aa:00:00:00:01:01"]
R1_eth0["eth0
192.168.1.254/24
MAC : rr:00:00:00:00:01"]
R1_eth1["eth1
192.168.2.254/24
MAC : rr:00:00:00:00:02"]
PC2["**PC2**
192.168.2.1/24
GW : 192.168.2.254
MAC : aa:00:00:00:02:01"]
R1[["**R1**"]]
PC1 -- câble --- R1_eth0
R1_eth0 --- R1
R1 --- R1_eth1
R1_eth1 -- câble --- PC2
Le principe fondamental
Quand PC1 (192.168.1.1) envoie un paquet à PC2 (192.168.2.1) :
Hop 1 : PC1 -> R1
Trame : src=PC1_MAC dst=R1_eth0_MAC <- adresses MAC changent à chaque saut
Paquet : src=192.168.1.1 dst=192.168.2.1 <- adresses IP restent identiques
Hop 2 : R1 -> PC2
Trame : src=R1_eth1_MAC dst=PC2_MAC
Paquet : src=192.168.1.1 dst=192.168.2.1 <- inchangées !
Le routeur décapsule la trame, lit l'IP destination, ré-encapsule dans une nouvelle trame vers le prochain saut.
TableRoutage
class TableRoutage:
"""Table de routage : associe des réseaux à des interfaces de sortie."""
def __init__(self):
self.routes = []
# chaque route : (reseau, masque, passerelle, iface)
# passerelle = None si le réseau est directement connecté
def ajouter(self, reseau, masque, passerelle, iface):
"""Ajoute une route statique.
>>> t = TableRoutage()
>>> iface = Interface("aa:bb:cc:dd:ee:ff", "192.168.1.254", "255.255.255.0")
>>> t.ajouter("192.168.1.0", "255.255.255.0", None, iface)
>>> len(t.routes)
1
"""
self.routes.append((reseau, masque, passerelle, iface))
def chercher(self, ip_dest):
"""Retourne (passerelle, iface) pour la meilleure route, ou None.
Applique le principe du plus long préfixe (longest prefix match).
>>> t = TableRoutage()
>>> iface = Interface("aa:bb:cc:dd:ee:ff", "192.168.1.254", "255.255.255.0")
>>> t.ajouter("192.168.1.0", "255.255.255.0", None, iface)
>>> passerelle, i = t.chercher("192.168.1.42")
>>> passerelle is None
True
>>> i is iface
True
>>> t.chercher("10.0.0.1") is None
True
"""
addr = ipaddress.ip_address(ip_dest)
meilleure_longueur = -1
meilleur = None
for reseau, masque, passerelle, iface in self.routes:
net = ipaddress.ip_network(f"{reseau}/{masque}", strict=False)
if addr in net and net.prefixlen > meilleure_longueur:
meilleure_longueur = net.prefixlen
meilleur = (passerelle, iface)
return meilleur
Routeur
Le routeur gère lui-même l'ARP sur chacune de ses interfaces.
class Routeur(Machine):
"""Routeur : relaie les paquets IP entre réseaux en ré-encapsulant les trames."""
def __init__(self, nom):
super().__init__(nom)
self.table_routage = TableRoutage()
self.table_arp = TableARP()
def _envoyer_requete_arp(self, ip_dest, iface):
requete = RequeteARP(iface.ip, iface.mac, ip_dest)
trame = Trame(iface.mac, Trame.MAC_BROADCAST, requete)
print(f" [{self.nom}] ARP : qui a {ip_dest} ?")
self.envoyer(trame, iface)
def recevoir(self, trame, iface):
payload = trame.payload
if isinstance(payload, RequeteARP):
if payload.ip_cible == iface.ip:
self.table_arp.ajouter(payload.ip_src, payload.mac_src)
reponse = ReponseARP(iface.ip, iface.mac)
rep = Trame(iface.mac, trame.mac_src, reponse)
print(f" [{self.nom}] ARP réponse : {iface.ip} -> {iface.mac}")
self.envoyer(rep, iface)
elif isinstance(payload, ReponseARP):
self.table_arp.ajouter(payload.ip_annoncee, payload.mac_annoncee)
print(f" [{self.nom}] ARP appris : {payload.ip_annoncee} -> {payload.mac_annoncee}")
elif isinstance(payload, Paquet):
self._router(payload)
def _router(self, paquet):
"""Cherche la route, résout le MAC du prochain saut, ré-encapsule et envoie."""
print(f" [{self.nom}] route {paquet.ip_src} -> {paquet.ip_dest}")
resultat = self.table_routage.chercher(paquet.ip_dest)
if not resultat:
print(f" [{self.nom}] pas de route vers {paquet.ip_dest}")
return
passerelle, iface_sortie = resultat
# prochain saut = passerelle si définie, sinon la destination elle-même
ip_prochain_saut = passerelle if passerelle else paquet.ip_dest
mac_dest = self.table_arp.resoudre(ip_prochain_saut)
if not mac_dest:
self._envoyer_requete_arp(ip_prochain_saut, iface_sortie)
mac_dest = self.table_arp.resoudre(ip_prochain_saut)
if mac_dest:
# nouvelle trame, même paquet IP (src/dst IP inchangés)
trame = Trame(iface_sortie.mac, mac_dest, paquet)
self.envoyer(trame, iface_sortie)
else:
print(f" [{self.nom}] {ip_prochain_saut} injoignable")
Mise à jour de PC : passerelle par défaut
PC doit maintenant connaitre sa passerelle pour joindre d'autres réseaux.
class PC(Machine):
def __init__(self, nom, passerelle=None):
super().__init__(nom)
self.table_arp = TableARP()
self.passerelle = passerelle # IP du routeur sur le réseau local
def ping(self, ip_dest):
print(f"\n[{self.nom}] ping {ip_dest}")
iface = self._trouver_interface_vers(ip_dest)
if iface:
# destination locale : ARP direct
ip_arp = ip_dest
elif self.passerelle:
# destination distante : on envoie à la passerelle
iface = self._trouver_interface_vers(self.passerelle)
ip_arp = self.passerelle
else:
print(f" [{self.nom}] pas de route vers {ip_dest}")
return
mac_dest = self.table_arp.resoudre(ip_arp)
if not mac_dest:
self._envoyer_requete_arp(ip_arp, iface)
mac_dest = self.table_arp.resoudre(ip_arp)
if mac_dest:
paquet = Paquet(iface.ip, ip_dest, b"ping!") # IP dest = cible finale
trame = Trame(iface.mac, mac_dest, paquet)
self.envoyer(trame, iface)
else:
print(f" [{self.nom}] {ip_arp} injoignable")
Exemple complet
if __name__ == "__main__":
# Machines
pc1 = PC("PC1", passerelle="192.168.1.254")
pc2 = PC("PC2", passerelle="192.168.2.254")
r1 = Routeur("R1")
# Interfaces
pc1_eth0 = Interface("aa:00:00:00:01:01", "192.168.1.1", "255.255.255.0")
r1_eth0 = Interface("rr:00:00:00:00:01", "192.168.1.254", "255.255.255.0")
r1_eth1 = Interface("rr:00:00:00:00:02", "192.168.2.254", "255.255.255.0")
pc2_eth0 = Interface("aa:00:00:00:02:01", "192.168.2.1", "255.255.255.0")
pc1.ajouter_interface(pc1_eth0)
r1.ajouter_interface(r1_eth0)
r1.ajouter_interface(r1_eth1)
pc2.ajouter_interface(pc2_eth0)
# Câblage
Lien(pc1_eth0, r1_eth0)
Lien(r1_eth1, pc2_eth0)
# Table de routage du routeur (routes directement connectées)
r1.table_routage.ajouter("192.168.1.0", "255.255.255.0", None, r1_eth0)
r1.table_routage.ajouter("192.168.2.0", "255.255.255.0", None, r1_eth1)
pc1.ping("192.168.2.1") # inter-réseau via R1
pc1.ping("192.168.2.1") # 2e ping : ARP déjà en cache partout
Sortie attendue :
[PC1] ping 192.168.2.1
[PC1] ARP : qui a 192.168.1.254 ?
[PC1] --> aa:00:00:00:01:01 -> ff:ff:ff:ff:ff:ff
[R1] ARP réponse : 192.168.1.254 -> rr:00:00:00:00:01
[R1] --> rr:00:00:00:00:01 -> aa:00:00:00:01:01
[PC1] ARP appris : 192.168.1.254 -> rr:00:00:00:00:01
[PC1] --> aa:00:00:00:01:01 -> rr:00:00:00:00:01
[R1] route 192.168.1.1 -> 192.168.2.1
[R1] ARP : qui a 192.168.2.1 ?
[R1] --> rr:00:00:00:00:02 -> ff:ff:ff:ff:ff:ff
[PC2] ARP réponse : 192.168.2.1 -> aa:00:00:00:02:01
[PC2] --> aa:00:00:00:02:01 -> rr:00:00:00:00:02
[R1] ARP appris : 192.168.2.1 -> aa:00:00:00:02:01
[R1] --> rr:00:00:00:00:02 -> aa:00:00:00:02:01
[PC2] reçu de 192.168.1.1 : b'ping!'
[PC1] ping 192.168.2.1
[PC1] --> aa:00:00:00:01:01 -> rr:00:00:00:00:01
[R1] route 192.168.1.1 -> 192.168.2.1
[R1] --> rr:00:00:00:00:02 -> aa:00:00:00:02:01
[PC2] reçu de 192.168.1.1 : b'ping!'
Chapitre 4 - Protocole RIP
Dans le chapitre précédent, les routes étaient statiques : configurées à la main sur chaque routeur. Dès qu'on ajoute un routeur ou qu'un lien tombe, il faut reconfigurer manuellement. Les protocoles de routage dynamiques automatisent cela.
RIP (Routing Information Protocol) est le plus simple : chaque routeur diffuse périodiquement sa table à ses voisins. Les voisins mettent à jour leur propre table si un meilleur chemin est découvert. C'est l'algorithme de Bellman-Ford.
- Métrique = nombre de sauts
- Maximum 15 sauts (16 = infini, réseau inaccessible)
- Convergence en quelques tours d'échange
Topologie
graph LR
PC1["**PC1**
192.168.1.1/24
GW: 192.168.1.254"]
PC2["**PC2**
192.168.4.1/24
GW: 192.168.4.254"]
R1[["**R1**
eth0: 192.168.1.254
eth1: 10.0.1.1"]]
R2[["**R2**
eth0: 10.0.1.2
eth1: 10.0.2.1"]]
R3[["**R3**
eth0: 10.0.2.2
eth1: 192.168.4.254"]]
PC1 --- R1
R1 --- R2
R2 --- R3
R3 --- PC2
Convergence RIP pas à pas
Après demarrer() :
R1 : {192.168.1.0: 1, 10.0.1.0: 1}
R2 : {10.0.1.0: 1, 10.0.2.0: 1}
R3 : {10.0.2.0: 1, 192.168.4.0: 1}
Après tick 1 :
R1 apprend de R2 : 10.0.2.0 (métrique 2)
R2 apprend de R1 : 192.168.1.0 (2) | de R3 : 192.168.4.0 (2)
R3 apprend de R2 : 10.0.1.0 (2)
Après tick 2 :
R1 apprend de R2 : 192.168.4.0 (métrique 3) <- réseau de PC2 enfin connu !
R3 apprend de R2 : 192.168.1.0 (métrique 3)
Convergence atteinte.
Mise à jour de TableRoutage
On remplace la liste de tuples par un dictionnaire pour permettre la mise à jour des routes existantes.
class TableRoutage:
"""Table de routage indexée par (reseau, masque) pour faciliter les mises à jour RIP."""
def __init__(self):
# (reseau, masque) -> [passerelle, iface, metrique]
self.routes = {}
def ajouter_ou_maj(self, reseau, masque, passerelle, iface, metrique):
"""Ajoute ou met à jour une route.
>>> t = TableRoutage()
>>> iface = Interface("aa:bb:cc:dd:ee:ff", "10.0.1.1", "255.255.255.0")
>>> t.ajouter_ou_maj("10.0.1.0", "255.255.255.0", None, iface, 1)
>>> t.routes[("10.0.1.0", "255.255.255.0")][2]
1
>>> t.ajouter_ou_maj("10.0.1.0", "255.255.255.0", None, iface, 2)
>>> t.routes[("10.0.1.0", "255.255.255.0")][2]
2
"""
self.routes[(reseau, masque)] = [passerelle, iface, metrique]
def ajouter(self, reseau, masque, passerelle, iface, metrique=1):
"""Alias de ajouter_ou_maj pour la compatibilité avec le chapitre 3."""
self.ajouter_ou_maj(reseau, masque, passerelle, iface, metrique)
def chercher(self, ip_dest):
"""Longest prefix match - retourne (passerelle, iface) ou None.
>>> t = TableRoutage()
>>> iface = Interface("aa:bb:cc:dd:ee:ff", "10.0.1.1", "255.255.255.0")
>>> t.ajouter("10.0.1.0", "255.255.255.0", None, iface, 1)
>>> passerelle, i = t.chercher("10.0.1.42")
>>> i is iface
True
>>> t.chercher("192.168.1.1") is None
True
"""
addr = ipaddress.ip_address(ip_dest)
meilleure_longueur = -1
meilleur = None
for (reseau, masque), (passerelle, iface, _) in self.routes.items():
net = ipaddress.ip_network(f"{reseau}/{masque}", strict=False)
if addr in net and net.prefixlen > meilleure_longueur:
meilleure_longueur = net.prefixlen
meilleur = (passerelle, iface)
return meilleur
def afficher(self, nom):
"""Affiche la table de routage de façon lisible."""
print(f"\n Table de routage de {nom}:")
print(f" {'Réseau':<22} {'Passerelle':<18} {'Iface':<16} Métrique")
print(f" {'-'*65}")
for (reseau, masque), (passerelle, iface, metrique) in self.routes.items():
net = ipaddress.ip_network(f"{reseau}/{masque}", strict=False)
gw = passerelle or "connecté"
print(f" {str(net):<22} {gw:<18} {iface.ip:<16} {metrique}")
RIP
Simplification de simulation
En réalité, RIP envoie ses annonces sous forme de datagrammes UDP port 520 en broadcast sur chaque interface. Ces datagrammes traversent la pile réseau complète : MessageRIP encapsulé dans un paquet IP, lui-même encapsulé dans une trame Ethernet broadcast.
Dans cette simulation, tick() appelle directement voisin.protocole.recevoir_table() en Python, court-circuitant Trame, Paquet et ARP. Ce raccourci est assumé volontairement : modéliser UDP/IP pour les messages de contrôle alourdirait l'implémentation sans apporter de compréhension supplémentaire sur Bellman-Ford et la convergence, qui sont l'objet de ce chapitre.
class RIP:
"""Protocole RIP : vecteur de distance par échange de tables entre voisins."""
INFINI = 16
def __init__(self, routeur):
self.routeur = routeur
def demarrer(self):
"""Peuple la table avec les réseaux directement connectés (métrique 1).
>>> r = Routeur("R")
>>> iface = Interface("aa:00:00:00:00:01", "192.168.1.1", "255.255.255.0")
>>> r.ajouter_interface(iface)
>>> r.protocole = RIP(r)
>>> r.protocole.demarrer()
>>> ("192.168.1.0", "255.255.255.0") in r.table_routage.routes
True
"""
for iface in self.routeur.interfaces:
net = ipaddress.ip_network(f"{iface.ip}/{iface.masque}", strict=False)
reseau = str(net.network_address)
self.routeur.table_routage.ajouter_ou_maj(reseau, iface.masque, None, iface, 1)
def tick(self):
"""Envoie la table courante à chaque voisin RIP direct."""
for iface in self.routeur.interfaces:
if not iface.lien:
continue
autre = iface.lien.bout2 if iface.lien.bout1 is iface else iface.lien.bout1
voisin = autre.machine
if isinstance(voisin, Routeur) and isinstance(voisin.protocole, RIP):
# autre = interface du voisin sur ce lien = sa porte de sortie vers nous
voisin.protocole.recevoir_table(
self.routeur.table_routage.routes,
iface.ip, # notre IP = passerelle pour les routes que le voisin va apprendre
autre # interface du voisin sur ce lien = iface de sortie pour ces routes
)
def recevoir_table(self, routes_voisin, ip_passerelle, iface_sortie):
"""Bellman-Ford : met à jour la table si on trouve un chemin plus court.
ip_passerelle : IP du voisin qui nous envoie sa table (sera notre gateway).
iface_sortie : notre interface connectée à ce voisin.
"""
for (reseau, masque), (_, _, metrique) in routes_voisin.items():
nouvelle_metrique = metrique + 1
if nouvelle_metrique >= self.INFINI:
continue
route = self.routeur.table_routage.routes.get((reseau, masque))
if route is None or route[2] > nouvelle_metrique:
net = ipaddress.ip_network(f"{reseau}/{masque}", strict=False)
print(f" [{self.routeur.nom}] RIP : {net} via {ip_passerelle} métrique {nouvelle_metrique}")
self.routeur.table_routage.ajouter_ou_maj(
reseau, masque, ip_passerelle, iface_sortie, nouvelle_metrique
)
Simulateur
class Simulateur:
"""Orchestre les ticks RIP jusqu'à convergence."""
def __init__(self, routeurs):
self.routeurs = routeurs
def converger(self, max_ticks=20):
"""Lance des ticks jusqu'à ce que les tables ne changent plus."""
for tick in range(1, max_ticks + 1):
avant = [dict(r.table_routage.routes) for r in self.routeurs]
print(f"\n=== Tick {tick} ===")
for r in self.routeurs:
if r.protocole:
r.protocole.tick()
apres = [dict(r.table_routage.routes) for r in self.routeurs]
if avant == apres:
print(f"\nConvergence atteinte après {tick} tick(s).")
return
print("Pas de convergence dans le délai imparti.")
Mise à jour de Routeur
class Routeur(Machine):
def __init__(self, nom):
super().__init__(nom)
self.table_routage = TableRoutage()
self.table_arp = TableARP()
self.protocole = None # RIP ou OSPF, configuré après instanciation
Exemple complet
if __name__ == "__main__":
pc1 = PC("PC1", passerelle="192.168.1.254")
pc2 = PC("PC2", passerelle="192.168.4.254")
r1, r2, r3 = Routeur("R1"), Routeur("R2"), Routeur("R3")
pc1_eth = Interface("aa:00:00:00:01:01", "192.168.1.1", "255.255.255.0")
r1_e0 = Interface("rr:00:00:01:00:00", "192.168.1.254", "255.255.255.0")
r1_e1 = Interface("rr:00:00:01:00:01", "10.0.1.1", "255.255.255.0")
r2_e0 = Interface("rr:00:00:02:00:00", "10.0.1.2", "255.255.255.0")
r2_e1 = Interface("rr:00:00:02:00:01", "10.0.2.1", "255.255.255.0")
r3_e0 = Interface("rr:00:00:03:00:00", "10.0.2.2", "255.255.255.0")
r3_e1 = Interface("rr:00:00:03:00:01", "192.168.4.254", "255.255.255.0")
pc2_eth = Interface("aa:00:00:00:04:01", "192.168.4.1", "255.255.255.0")
for machine, ifaces in [
(pc1, [pc1_eth]), (r1, [r1_e0, r1_e1]),
(r2, [r2_e0, r2_e1]), (r3, [r3_e0, r3_e1]), (pc2, [pc2_eth])
]:
for iface in ifaces:
machine.ajouter_interface(iface)
Lien(pc1_eth, r1_e0)
Lien(r1_e1, r2_e0)
Lien(r2_e1, r3_e0)
Lien(r3_e1, pc2_eth)
for r in [r1, r2, r3]:
r.protocole = RIP(r)
r.protocole.demarrer()
sim = Simulateur([r1, r2, r3])
sim.converger()
for r in [r1, r2, r3]:
r.table_routage.afficher(r.nom)
pc1.ping("192.168.4.1")
Sortie attendue :
=== Tick 1 ===
[R1] RIP : 10.0.2.0/24 via 10.0.1.2 métrique 2
[R2] RIP : 192.168.1.0/24 via 10.0.1.1 métrique 2
[R2] RIP : 192.168.4.0/24 via 10.0.2.2 métrique 2
[R3] RIP : 10.0.1.0/24 via 10.0.2.1 métrique 2
=== Tick 2 ===
[R1] RIP : 192.168.4.0/24 via 10.0.1.2 métrique 3
[R3] RIP : 192.168.1.0/24 via 10.0.2.1 métrique 3
=== Tick 3 ===
Convergence atteinte après 3 tick(s).
Table de routage de R1:
Réseau Passerelle Iface Métrique
-----------------------------------------------------------------
192.168.1.0/24 connecté 192.168.1.254 1
10.0.1.0/24 connecté 10.0.1.1 1
10.0.2.0/24 10.0.1.2 10.0.1.1 2
192.168.4.0/24 10.0.1.2 10.0.1.1 3
Table de routage de R2:
Réseau Passerelle Iface Métrique
-----------------------------------------------------------------
10.0.1.0/24 connecté 10.0.1.2 1
10.0.2.0/24 connecté 10.0.2.1 1
192.168.1.0/24 10.0.1.1 10.0.1.2 2
192.168.4.0/24 10.0.2.2 10.0.2.1 2
Table de routage de R3:
Réseau Passerelle Iface Métrique
-----------------------------------------------------------------
10.0.2.0/24 connecté 10.0.2.2 1
192.168.4.0/24 connecté 192.168.4.254 1
10.0.1.0/24 10.0.2.1 10.0.2.2 2
192.168.1.0/24 10.0.2.1 10.0.2.2 3
[PC1] ping 192.168.4.1
[PC1] ARP : qui a 192.168.1.254 ?
...
[PC2] reçu de 192.168.1.1 : b'ping!'
Chapitre 5 - Protocole OSPF
RIP vs OSPF
RIP mesure les routes en sauts : un routeur à 2 sauts est toujours préféré à un routeur à 3 sauts, quelle que soit la bande passante des liens. OSPF associe un coût à chaque lien (inversement proportionnel à sa bande passante) et calcule les chemins de coût minimal avec l'algorithme de Dijkstra.
Pour cela, chaque routeur inonde un LSA (Link State Advertisement) qui décrit ses voisins et ses réseaux directement connectés. Chaque routeur accumule tous les LSA dans une LSDB (Link State Database) : c'est le graphe complet du domaine. Dijkstra calcule alors les plus courts chemins depuis chaque routeur.
Classes impliquées
classDiagram
Machine <|-- Routeur
Routeur --> TableRoutage
Routeur --> OSPF
OSPF --> "lsdb *" LSA
Routeur "1" --> "*" Interface
Interface --> Lien
class Lien{
+int cout
+transmettre(trame, depuis)
}
class Interface{
+str mac
+str ip
+str masque
}
class Routeur{
+TableRoutage table_routage
+OSPF protocole
}
class OSPF{
+dict lsdb
+demarrer()
+calculer_routes()
}
class LSA{
+str routeur_id
+list voisins
+list reseaux
}
class TableRoutage{
+dict routes
+ajouter_ou_maj(reseau, masque, passerelle, iface, metrique)
+chercher(ip_dest) tuple
}
Topologie - Le Diamant
R1 est relié à R2 par un lien lent (coût 10) et à R3 par un lien rapide (coût 1). R3 est aussi relié à R2 (coût 1). RIP choisirait le chemin direct R1 - R2 (1 saut) ; OSPF choisit R1 - R3 - R2 (coût total 2 contre 10).
graph LR
PC1["PC1<br/>192.168.1.1"] --- R1["R1"]
R1 -- "coût 10" --- R2["R2"]
R1 -- "coût 1" --- R3["R3"]
R3 -- "coût 1" --- R2
R2 --- PC2["PC2<br/>192.168.4.1"]
Mise à jour de Lien
Le paramètre cout est ajouté (valeur par défaut 1 : compatible avec tous les chapitres précédents).
class Lien:
def __init__(self, bout1, bout2, cout=1):
self.bout1 = bout1
self.bout2 = bout2
self.cout = cout
bout1.lien = self
bout2.lien = self
LSA
class LSA:
"""Link State Advertisement : décrit les voisins et réseaux d'un routeur."""
def __init__(self, routeur_id, voisins, reseaux):
"""
routeur_id : nom du routeur émetteur (str)
voisins : [(voisin_id, cout), ...] - routeurs OSPF adjacents
reseaux : [(reseau, masque), ...] - réseaux directement connectés
"""
self.routeur_id = routeur_id
self.voisins = voisins
self.reseaux = reseaux
OSPF
Simplification de simulation
En réalité, OSPF envoie ses LSA en multicast IP (224.0.0.5), encapsulés dans des paquets de protocole 89. Le flooding s'accompagne de numéros de séquence et d'acquittements pour éviter les boucles et les doublons.
Ici, _flooder_lsa() parcourt directement les objets Python par BFS, et recevoir_lsa() est appelé sans passer par la pile réseau. L'objectif est d'illustrer la construction de la LSDB et l'algorithme de Dijkstra, qui sont le coeur d'OSPF.
class OSPF:
"""Protocole OSPF - état de lien avec algorithme de Dijkstra."""
def __init__(self, routeur):
self.routeur = routeur
self.lsdb = {} # routeur_id -> LSA
def demarrer(self):
"""Ajoute les routes directes, génère le LSA local et le floode.
Appeler pour tous les routeurs avant calculer_routes().
"""
for iface in self.routeur.interfaces:
net = ipaddress.ip_network(f"{iface.ip}/{iface.masque}", strict=False)
reseau = str(net.network_address)
self.routeur.table_routage.ajouter_ou_maj(reseau, iface.masque, None, iface, 0)
lsa = self._generer_lsa()
self.lsdb[self.routeur.nom] = lsa
self._flooder_lsa(lsa)
def _generer_lsa(self):
"""Construit le LSA à partir des interfaces du routeur."""
voisins = []
reseaux = []
for iface in self.routeur.interfaces:
if iface.lien is None:
continue
net = ipaddress.ip_network(f"{iface.ip}/{iface.masque}", strict=False)
reseaux.append((str(net.network_address), iface.masque))
autre = iface.lien.bout2 if iface.lien.bout1 is iface else iface.lien.bout1
if isinstance(autre.machine, Routeur) and isinstance(getattr(autre.machine, 'protocole', None), OSPF):
voisins.append((autre.machine.nom, iface.lien.cout))
return LSA(self.routeur.nom, voisins, reseaux)
def _flooder_lsa(self, lsa):
"""Propage le LSA à tous les routeurs OSPF voisins par BFS."""
visites = {self.routeur.nom}
file = [self.routeur]
while file:
courant = file.pop(0)
for iface in courant.interfaces:
if iface.lien is None:
continue
autre = iface.lien.bout2 if iface.lien.bout1 is iface else iface.lien.bout1
voisin = autre.machine
if isinstance(voisin, Routeur) and voisin.nom not in visites:
visites.add(voisin.nom)
if isinstance(getattr(voisin, 'protocole', None), OSPF):
voisin.protocole.recevoir_lsa(lsa)
file.append(voisin)
def recevoir_lsa(self, lsa):
"""Stocke un LSA reçu s'il est nouveau."""
if lsa.routeur_id not in self.lsdb:
self.lsdb[lsa.routeur_id] = lsa
def calculer_routes(self):
"""Dijkstra sur la LSDB pour installer les meilleures routes.
Appeler après que tous les routeurs ont appelé demarrer().
"""
import heapq
src = self.routeur.nom
dist = {src: 0}
via = {} # routeur_id -> premier saut (routeur_id) depuis src
file = [(0, src)]
while file:
cout, courant = heapq.heappop(file)
if cout > dist.get(courant, float('inf')):
continue
lsa = self.lsdb.get(courant)
if lsa is None:
continue
for voisin_id, cout_lien in lsa.voisins:
nouveau = cout + cout_lien
if nouveau < dist.get(voisin_id, float('inf')):
dist[voisin_id] = nouveau
via[voisin_id] = voisin_id if courant == src else via[courant]
heapq.heappush(file, (nouveau, voisin_id))
direct_nets = set()
for iface in self.routeur.interfaces:
net = ipaddress.ip_network(f"{iface.ip}/{iface.masque}", strict=False)
direct_nets.add((str(net.network_address), iface.masque))
meilleures = {}
for routeur_id, lsa in self.lsdb.items():
if routeur_id == src:
continue
prochain_nom = via.get(routeur_id)
if prochain_nom is None:
continue
ip_pg, iface_loc = self._trouver_passerelle(prochain_nom)
if ip_pg is None:
continue
for reseau, masque in lsa.reseaux:
if (reseau, masque) in direct_nets:
continue
cout_route = dist[routeur_id]
if (reseau, masque) not in meilleures or cout_route < meilleures[(reseau, masque)][2]:
meilleures[(reseau, masque)] = (ip_pg, iface_loc, cout_route)
for (reseau, masque), (ip_pg, iface_loc, cout_route) in meilleures.items():
self.routeur.table_routage.ajouter_ou_maj(reseau, masque, ip_pg, iface_loc, cout_route)
def _trouver_passerelle(self, voisin_nom):
"""Retourne (ip_voisin, iface_locale) pour joindre un voisin direct."""
for iface in self.routeur.interfaces:
if iface.lien is None:
continue
autre = iface.lien.bout2 if iface.lien.bout1 is iface else iface.lien.bout1
if autre.machine.nom == voisin_nom:
return autre.ip, iface
return None, None
Exemple complet
if __name__ == "__main__":
pc1 = PC("PC1", passerelle="192.168.1.254")
pc2 = PC("PC2", passerelle="192.168.4.254")
r1, r2, r3 = Routeur("R1"), Routeur("R2"), Routeur("R3")
pc1_eth = Interface("aa:00:00:00:01:01", "192.168.1.1", "255.255.255.0")
r1_eth0 = Interface("rr:00:00:01:00:00", "192.168.1.254", "255.255.255.0")
r1_eth1 = Interface("rr:00:00:01:00:01", "10.0.1.1", "255.255.255.0")
r1_eth2 = Interface("rr:00:00:01:00:02", "10.0.2.1", "255.255.255.0")
r2_eth0 = Interface("rr:00:00:02:00:00", "10.0.1.2", "255.255.255.0")
r2_eth1 = Interface("rr:00:00:02:00:01", "10.0.3.2", "255.255.255.0")
r2_eth2 = Interface("rr:00:00:02:00:02", "192.168.4.254", "255.255.255.0")
r3_eth0 = Interface("rr:00:00:03:00:00", "10.0.2.2", "255.255.255.0")
r3_eth1 = Interface("rr:00:00:03:00:01", "10.0.3.1", "255.255.255.0")
pc2_eth = Interface("aa:00:00:00:02:01", "192.168.4.1", "255.255.255.0")
pc1.ajouter_interface(pc1_eth)
for iface in [r1_eth0, r1_eth1, r1_eth2]: r1.ajouter_interface(iface)
for iface in [r2_eth0, r2_eth1, r2_eth2]: r2.ajouter_interface(iface)
for iface in [r3_eth0, r3_eth1]: r3.ajouter_interface(iface)
pc2.ajouter_interface(pc2_eth)
Lien(pc1_eth, r1_eth0)
Lien(r1_eth1, r2_eth0, cout=10) # lien lent
Lien(r1_eth2, r3_eth0) # liens rapides (cout=1 par défaut)
Lien(r3_eth1, r2_eth1)
Lien(r2_eth2, pc2_eth)
for r in [r1, r2, r3]:
r.protocole = OSPF(r)
for r in [r1, r2, r3]:
r.protocole.demarrer() # phase 1 : génère et floode les LSA
for r in [r1, r2, r3]:
r.protocole.calculer_routes() # phase 2 : Dijkstra sur la LSDB complète
for r in [r1, r2, r3]:
r.table_routage.afficher(r.nom)
pc1.ping("192.168.4.1")
Sortie attendue :
Table de routage de R1:
Réseau Passerelle Iface Métrique
-----------------------------------------------------------------
192.168.1.0/24 connecté 192.168.1.254 0
10.0.1.0/24 connecté 10.0.1.1 0
10.0.2.0/24 connecté 10.0.2.1 0
10.0.3.0/24 10.0.2.2 10.0.2.1 1
192.168.4.0/24 10.0.2.2 10.0.2.1 2
Table de routage de R2:
Réseau Passerelle Iface Métrique
-----------------------------------------------------------------
10.0.1.0/24 connecté 10.0.1.2 0
10.0.3.0/24 connecté 10.0.3.2 0
192.168.4.0/24 connecté 192.168.4.254 0
192.168.1.0/24 10.0.3.1 10.0.3.2 2
10.0.2.0/24 10.0.3.1 10.0.3.2 1
Table de routage de R3:
Réseau Passerelle Iface Métrique
-----------------------------------------------------------------
10.0.2.0/24 connecté 10.0.2.2 0
10.0.3.0/24 connecté 10.0.3.1 0
192.168.1.0/24 10.0.2.1 10.0.2.2 1
10.0.1.0/24 10.0.2.1 10.0.2.2 1
192.168.4.0/24 10.0.3.2 10.0.3.1 1
[PC1] ping 192.168.4.1
[PC1] ARP : qui a 192.168.1.254 ?
...
[R1] ARP : qui a 10.0.2.2 ?
...
[R3] ARP : qui a 10.0.3.2 ?
...
[R2] ARP : qui a 192.168.4.1 ?
...
[PC2] reçu de 192.168.1.1 : b'ping!'
R1 envoie son ARP pour 10.0.2.2 (R3) et non pour 10.0.1.2 (R2 direct) : Dijkstra a sélectionné le chemin R1 - R3 - R2 (coût 2) plutôt que le lien direct R1 - R2 (coût 10).