diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a6bfab..953a23d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # CHANGELOG — Set-OPS +## 2026-08-01 (suite 9) — rayons de l'étoile séparés des ports terminaux + +L'ajout du spanning-tree venait de rendre dangereuse une imprécision qui, jusque-là, n'était +qu'un titre approximatif. La section 4 s'appelait « Trunk vers Proxmox **+ inter-switch** » +et n'émettait qu'un port, que la section 6 déclarait en bord de réseau. Réutiliser ce +placeholder pour les rayons vers les switches d'accès revenait à mettre `portfast` sur les +liens qui portent les BPDU — c'est-à-dire à désactiver la protection anti-boucle exactement +là où elle sert. + +Les rayons sont désormais **dérivés** et émis à part (section 4c côté routeur, B3a côté +accès), un par switch d'accès, avec l'avertissement qu'ils ne sont pas des ports de bord. +La section 4 ne désigne plus que les hyperviseurs, et la partie B distingue sa montante +(`B3a`) de son trunk terminal (`B3b`). + +`underlay.switches_acces()` devient la source unique du « qui est un switch d'accès » — +utilisée pour leur devis **et** pour les rayons côté routeur : les deux ne peuvent pas +diverger. + +Vérifié : trois commandes `portfast` émises avec `stp` déclaré, **aucune** sans lui, et +**aucune** sur un rayon. + ## 2026-08-01 (suite 8) — spanning-tree dérivé de la topologie déclarée ### Ajouté — `underlay.stp` et les sections 6 / B4 diff --git a/scripts/devis_reseau.py b/scripts/devis_reseau.py index 6a4a3bd..5c9430b 100644 --- a/scripts/devis_reseau.py +++ b/scripts/devis_reseau.py @@ -102,6 +102,33 @@ def section_underlay(underlay: dict | None) -> list[str]: return out +def port_vers(nom_hote: str) -> str: + """Marqueur de port physique vers un equipement nomme.""" + return f"" + + +def section_rayons(underlay: dict | None, vlans: str) -> list[str]: + """Les rayons de l'etoile : un trunk du routeur vers chaque switch d'acces. + + Ils sont emis A PART du trunk vers les hyperviseurs parce qu'ils n'ont pas le meme + statut vis-a-vis du spanning-tree : un rayon porte les BPDU de la fabric et ne doit + JAMAIS etre declare en bord de reseau, sinon la protection tombe la ou elle sert. + """ + acces = underlay_mod.switches_acces(underlay) + if not acces: + return [] + out = ["! ----- 4c. Rayons de l'etoile (vers les switches d'acces) -----", + "! /!\\ CES PORTS NE SONT PAS DES PORTS DE BORD. Ils portent les BPDU de la", + "! fabric : y appliquer `portfast` desactiverait la protection anti-boucle", + "! exactement la ou elle sert. La section 6 ne les declare volontairement pas."] + for h in acces: + out += [f"interface {port_vers(h['nom'])}", + f" description rayon vers {h['nom']}", + " switchport mode trunk", + f" switchport trunk allowed vlan add {vlans}"] + return out + + def _stp_lignes(dialecte: str, mode: str, priorite: int) -> list[str]: """Mode + priorite de pont, dans la forme du dialecte. @@ -210,11 +237,19 @@ def partie_acces(underlay: dict | None, tenants: list, vlans: str, f"ip default-gateway {mgmt['passerelle']} ! le routeur {r_nom}"] else: out.append("! Aucun reseau underlay avec passerelle : gestion a definir a la main.") - out += ["!", "! ----- B3. Trunks -----", - "! Vers les hyperviseurs ET vers le routeur : memes VLANs etiquetes.", - "interface ", + out += ["!", "! ----- B3a. Montante vers le routeur (rayon de l'etoile) -----", + "! /!\\ PAS un port de bord : il porte les BPDU. Ne jamais y mettre portfast.", + f"interface {port_vers(r_nom)}", + f" description montante vers {r_nom}", " switchport mode trunk", f" switchport trunk allowed vlan add {vlans}", + "!", + "! ----- B3b. Trunk vers les hyperviseurs -----", + "! Port terminal : rien derriere lui ne participe au spanning-tree.", + "interface ", + " switchport mode trunk", + f" switchport trunk allowed vlan add {vlans}", + " spanning-tree portfast trunk" if underlay_mod.stp(underlay) else "!", "!"] s = underlay_mod.stp(underlay) if s: @@ -391,7 +426,9 @@ def generer(tenants: list[tuple[str, str, dict]], dialecte: str | None = None) - out.append(f" deny ip {reseau} {m} {u_reseau} {u_m}") out += remarque(dialecte, "Reste (Internet / inter-tenant controle) -> passerelle OPNsense") out.append(f" permit ip {reseau} {m} any") - out += ["!", "! ----- 4. Trunk vers Proxmox + inter-switch (a adapter) -----"] + out += ["!", "! ----- 4. Trunk vers les hyperviseurs -----", + "! Ports TERMINAUX : rien derriere eux ne participe au spanning-tree.", + "! Les liens vers les autres switches sont en section 4c, pas ici."] transit = underlay_mod.reseau_transit(underlay) # Le VLAN de transit ne va PAS vers Proxmox : aucun hyperviseur n'a d'interface # dessus. Il sort par le port de la frontiere (ci-dessous), et lui seul. @@ -409,6 +446,7 @@ def generer(tenants: list[tuple[str, str, dict]], dialecte: str | None = None) - out.append(" switchport mode trunk") out.append(f" switchport trunk allowed vlan add {vlans}") out += ["!"] + section_frontiere(transit) + out += ["!"] + section_rayons(underlay, ",".join(vlans_underlay + vlans_tenants)) out += ["!"] + section_routes(underlay) out += section_stp(underlay, dialecte, ["", ""]) out += ["end", "write memory"] diff --git a/scripts/underlay.py b/scripts/underlay.py index d3c6f78..93ad6f4 100644 --- a/scripts/underlay.py +++ b/scripts/underlay.py @@ -107,6 +107,23 @@ def routeur(underlay: dict | None) -> str | None: return (underlay or {}).get("routeur") or None +def switches_acces(underlay: dict | None) -> list[dict]: + """Les switches d'acces : hotes du reseau de management, hors routeur. + + Source unique du « qui est un switch d'acces » — utilisee pour leur devis (partie B) + ET pour les rayons de l'etoile emis cote routeur. Les deux ne peuvent pas diverger. + """ + r_nom = routeur(underlay) + if not r_nom: + return [] + fabric = fabric_du_routeur(underlay) + mgmt = next((r for r in reseaux_de_fabric(underlay, fabric) if r.get("passerelle")), None) + if not mgmt: + return [] + return [h for h in hotes(underlay) + if h.get("nom") != r_nom and h.get("reseau") == mgmt["nom"]] + + def reseau_transit(underlay: dict | None) -> dict | None: """Le reseau de TRANSIT vers la frontiere nord/sud, s'il est declare.