devis switch : les rayons de l'étoile ne sont pas des ports de bord
L'ajout du spanning-tree venait de rendre dangereuse une imprécision de titre. 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 revenait à mettre portfast sur les liens qui portent les BPDU — désactiver la protection anti-boucle là où elle sert. Les rayons sont dérivés et émis à part (4c côté routeur, B3a côté accès), un par switch d'accès, marqués comme non-bord. La section 4 ne désigne plus que les hyperviseurs ; la partie B distingue sa montante de son trunk terminal. `underlay.switches_acces()` devient la source unique du « qui est un switch d'accès » — pour leur devis ET pour les rayons côté routeur. Vérifié : 3 commandes portfast avec `stp` déclaré, 0 sans lui, 0 sur un rayon. Preuves : 24 OK, 0 échec. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
8f61721cb3
commit
92d31c0406
3 changed files with 80 additions and 4 deletions
21
CHANGELOG.md
21
CHANGELOG.md
|
|
@ -1,5 +1,26 @@
|
||||||
# CHANGELOG — Set-OPS
|
# 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
|
## 2026-08-01 (suite 8) — spanning-tree dérivé de la topologie déclarée
|
||||||
|
|
||||||
### Ajouté — `underlay.stp` et les sections 6 / B4
|
### Ajouté — `underlay.stp` et les sections 6 / B4
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,33 @@ def section_underlay(underlay: dict | None) -> list[str]:
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def port_vers(nom_hote: str) -> str:
|
||||||
|
"""Marqueur de port physique vers un equipement nomme."""
|
||||||
|
return f"<PORT-VERS-{nom_hote.upper()}>"
|
||||||
|
|
||||||
|
|
||||||
|
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]:
|
def _stp_lignes(dialecte: str, mode: str, priorite: int) -> list[str]:
|
||||||
"""Mode + priorite de pont, dans la forme du dialecte.
|
"""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}"]
|
f"ip default-gateway {mgmt['passerelle']} ! le routeur {r_nom}"]
|
||||||
else:
|
else:
|
||||||
out.append("! Aucun reseau underlay avec passerelle : gestion a definir a la main.")
|
out.append("! Aucun reseau underlay avec passerelle : gestion a definir a la main.")
|
||||||
out += ["!", "! ----- B3. Trunks -----",
|
out += ["!", "! ----- B3a. Montante vers le routeur (rayon de l'etoile) -----",
|
||||||
"! Vers les hyperviseurs ET vers le routeur : memes VLANs etiquetes.",
|
"! /!\\ PAS un port de bord : il porte les BPDU. Ne jamais y mettre portfast.",
|
||||||
"interface <PORT-TRUNK>",
|
f"interface {port_vers(r_nom)}",
|
||||||
|
f" description montante vers {r_nom}",
|
||||||
" switchport mode trunk",
|
" switchport mode trunk",
|
||||||
f" switchport trunk allowed vlan add {vlans}",
|
f" switchport trunk allowed vlan add {vlans}",
|
||||||
|
"!",
|
||||||
|
"! ----- B3b. Trunk vers les hyperviseurs -----",
|
||||||
|
"! Port terminal : rien derriere lui ne participe au spanning-tree.",
|
||||||
|
"interface <PORT-VERS-PROXMOX>",
|
||||||
|
" 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)
|
s = underlay_mod.stp(underlay)
|
||||||
if s:
|
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.append(f" deny ip {reseau} {m} {u_reseau} {u_m}")
|
||||||
out += remarque(dialecte, "Reste (Internet / inter-tenant controle) -> passerelle OPNsense")
|
out += remarque(dialecte, "Reste (Internet / inter-tenant controle) -> passerelle OPNsense")
|
||||||
out.append(f" permit ip {reseau} {m} any")
|
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)
|
transit = underlay_mod.reseau_transit(underlay)
|
||||||
# Le VLAN de transit ne va PAS vers Proxmox : aucun hyperviseur n'a d'interface
|
# 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.
|
# 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(" switchport mode trunk")
|
||||||
out.append(f" switchport trunk allowed vlan add {vlans}")
|
out.append(f" switchport trunk allowed vlan add {vlans}")
|
||||||
out += ["!"] + section_frontiere(transit)
|
out += ["!"] + section_frontiere(transit)
|
||||||
|
out += ["!"] + section_rayons(underlay, ",".join(vlans_underlay + vlans_tenants))
|
||||||
out += ["!"] + section_routes(underlay)
|
out += ["!"] + section_routes(underlay)
|
||||||
out += section_stp(underlay, dialecte, ["<PORT-VERS-PROXMOX>", "<PORT-VERS-FRONTIERE>"])
|
out += section_stp(underlay, dialecte, ["<PORT-VERS-PROXMOX>", "<PORT-VERS-FRONTIERE>"])
|
||||||
out += ["end", "write memory"]
|
out += ["end", "write memory"]
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,23 @@ def routeur(underlay: dict | None) -> str | None:
|
||||||
return (underlay or {}).get("routeur") or 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:
|
def reseau_transit(underlay: dict | None) -> dict | None:
|
||||||
"""Le reseau de TRANSIT vers la frontiere nord/sud, s'il est declare.
|
"""Le reseau de TRANSIT vers la frontiere nord/sud, s'il est declare.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue