TP22 abgeschlossen + WLAN-Roaming repariert (802.11k/v)
TP22 Schritt 5 — Datenmodell as-built aus dem Geraet erhoben (bridge/port, bridge/vlan, bridge/host + ARP/Leases), nicht aus der Doku: - crs326-core von 5 auf 26 Ports; 3 waren "GEPLANT (tentativ)", ether1 zeigte noch auf das tote sw1 #12 - Standort-Fehler: crs326-branch + ap2 standen auf Technik UG, real Technik OG - sw1/sw2/cudy2 -> Decommissioned (Endzustand), cudy1 -> Offline; sw1-Duplikat-Ports (gi*) entfernt - Schritt 4 entschieden: keine Secret-Injection (Governance-Regel 1 deckt es ab) TP21.1/4 — core ether4 geklaert und behoben: dort haengt das Wohnzimmer- Sideboard. Der Port war als ap1-Trunk vorkonfiguriert, pvid=40 nahm untagged an (Upstream ok), Egress war tagged -> TV bekam nichts. Diagnose ueber TX/RX- Asymmetrie + ARP failed bei frischem bound-Lease (ein bound-Lease beweist nur den Upstream: INIT-REBOOT-REQUEST braucht kein ACK). Auf untagged VLAN40 gezogen, Kabel bleibt auf Port 4 (Patchfeld-Nummerierung ist Infrastruktur). TP3.3 — Steering-Regression aus dem TP3-Rebuild behoben: /interface wifi steering war auf BEIDEN APs leer. 802.11r/FT macht die Uebergabe nur schnell, ausgeloest wird sie von 802.11k/v -> sticky client bis zum Abriss. st-main (rrm/wnm, neighbor-group bf-main) wiederhergestellt und ins GC-Template aufgenommen, damit eine Neuprovisionierung es nicht wieder still verliert. Compliance 20/20 -> 63/68 (dieselben 5 bekannten Altlasten): - neue Features wifi-steering, bridge-port, bridge-vlan - der L2-Layer war bisher komplett unueberwacht (core-Zweig hatte gar keine Bridge-Sektion, AP-Zweig nur VLAN ohne Ports) - Bridge-Sektionen bewusst als Literal-Block statt aus DCIM abgeleitet: pvid aus untagged_vlan zu generieren haette die akzeptierten Schiefstaende (core ether3, core ether2, branch ether9-23) zu Dauerdrift gemacht Alle Geraete-Aenderungen liefen ueber Nautobot-GC-ConfigPlans mit /system backup davor, keine Direkt-Writes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
268
nautobot/ingest-tp22-datamodell.py
Normal file
268
nautobot/ingest-tp22-datamodell.py
Normal file
@@ -0,0 +1,268 @@
|
||||
#!/usr/bin/env python3
|
||||
"""BF-Netzwerk -> Nautobot: TP22 Schritt 5 -- Datenmodell-Nachtraege (2026-08-13).
|
||||
|
||||
WAS DAS FIXT
|
||||
------------
|
||||
Das DCIM-Modell stand noch auf dem Stand VOR dem Core-Cutover (2026-08-08):
|
||||
|
||||
* `crs326-core` hatte 5 von 26 Ports modelliert, davon 3 als "GEPLANT (tentativ)" --
|
||||
das Geraet ist seit dem Cutover der produktive L3-Router mit voller Belegung.
|
||||
* `crs326-branch` ether1 zeigte auf `sw1 #8`; sw1 existiert nicht mehr, der Uplink
|
||||
haengt an `crs326-core ether8`.
|
||||
* `crs326-branch` + `ap2` standen in "Technik UG"; beide stehen real in "Technik OG"
|
||||
(TP15-Standort-Mapping: UG = core, OG = branch + ap2).
|
||||
* sw1/sw2/cudy2 waren "Decommissioning" (= Prozess laeuft), sind aber physisch weg.
|
||||
* sw1 trug 4 doppelt modellierte Ports (`gi8/gi11/gi15/gi16` neben `GE8/GE11/GE15/GE16`).
|
||||
|
||||
DATENQUELLE = DAS GERAET, NICHT DIE DOKU
|
||||
----------------------------------------
|
||||
Die Portbelegung unten ist am 2026-08-13 live erhoben worden (MikroMCP):
|
||||
`/interface/bridge/port` (pvid), `/interface/bridge/vlan` (tagged/untagged),
|
||||
`/interface/bridge/host` (gelernte MACs je Port+VID) und `/ip/arp` + DHCP-Leases zur
|
||||
Identifikation der Gegenstellen. Wo kein MAC gelernt wurde, steht "frei" bzw. die
|
||||
Unsicherheit explizit im Description-Text -- nicht geraten.
|
||||
|
||||
COMPLIANCE-SICHERHEIT (wichtig)
|
||||
-------------------------------
|
||||
Der core-Zweig in `mikrotik_routeros.j2` iteriert NUR Interfaces mit Praefix `vlan`
|
||||
(GW-IPs) und haelt die Bridge-VLAN-Tabelle als Jinja-Konstante. Ether-Ports sind damit
|
||||
compliance-neutral -- dieses Skript kann die intended config nicht verschieben.
|
||||
UMGEKEHRT GILT: die `vlanXX`-Descriptions auf dem Core NICHT anfassen, die landen als
|
||||
`comment=` in der intended config. Dieses Skript ruehrt sie nicht an.
|
||||
|
||||
export NAUTOBOT_TOKEN=...
|
||||
python3 nautobot/ingest-tp22-datamodell.py # Dry-Run
|
||||
python3 nautobot/ingest-tp22-datamodell.py --apply # schreiben
|
||||
"""
|
||||
import json, os, sys, urllib.request, urllib.error, urllib.parse
|
||||
|
||||
BASE = "http://10.40.20.30:8080/api"
|
||||
TOKEN = os.environ.get("NAUTOBOT_TOKEN", "")
|
||||
DRY = "--apply" not in sys.argv
|
||||
log = []
|
||||
|
||||
|
||||
def req(method, path, data=None):
|
||||
url = path if path.startswith("http") else f"{BASE}{path}"
|
||||
body = json.dumps(data).encode() if data is not None else None
|
||||
r = urllib.request.Request(url, data=body, method=method)
|
||||
r.add_header("Authorization", f"Token {TOKEN}")
|
||||
r.add_header("Content-Type", "application/json")
|
||||
r.add_header("Accept", "application/json")
|
||||
try:
|
||||
with urllib.request.urlopen(r, timeout=30) as resp:
|
||||
raw = resp.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
except urllib.error.HTTPError as e:
|
||||
print(f"!! {method} {url}\n payload={json.dumps(data)}\n -> {e.code} {e.read().decode()[:500]}")
|
||||
raise
|
||||
|
||||
|
||||
def find(path, **params):
|
||||
q = "&".join(f"{k}={urllib.parse.quote(str(v))}" for k, v in params.items())
|
||||
res = req("GET", f"{path}?{q}&limit=2")
|
||||
return res["results"][0] if res.get("count") else None
|
||||
|
||||
|
||||
def goc(path, match, payload, label=None):
|
||||
name = label or payload.get("name") or str(match)
|
||||
obj = find(path, **match)
|
||||
if obj:
|
||||
return obj
|
||||
if DRY:
|
||||
log.append(f" + {name} (dry-run)")
|
||||
return {"id": f"DRY-{name}"}
|
||||
obj = req("POST", path + "/", payload)
|
||||
log.append(f" + {name}")
|
||||
return obj
|
||||
|
||||
|
||||
def patch(path, obj, payload, label):
|
||||
if str(obj["id"]).startswith("DRY"):
|
||||
return obj
|
||||
delta = {}
|
||||
for k, v in payload.items():
|
||||
cur = obj.get(k)
|
||||
# Relationen kommen als {"id": ...}, Choice-Felder (type/mode) als {"value": ...}.
|
||||
if isinstance(cur, dict):
|
||||
cur = cur.get("id", cur.get("value"))
|
||||
if cur != v:
|
||||
delta[k] = v
|
||||
if not delta:
|
||||
return obj
|
||||
if DRY:
|
||||
log.append(f" ~ {label}: {sorted(delta)} (dry-run)")
|
||||
return obj
|
||||
out = req("PATCH", f"{path}/{obj['id']}/", delta)
|
||||
log.append(f" ~ {label}: {sorted(delta)}")
|
||||
return out
|
||||
|
||||
|
||||
def sid(name):
|
||||
return find("/extras/statuses", name=name)["id"]
|
||||
|
||||
|
||||
ETH, SFP, BR = "1000base-t", "10gbase-x-sfpp", "bridge"
|
||||
|
||||
S_ACTIVE = sid("Active")
|
||||
S_OFFLINE = sid("Offline")
|
||||
|
||||
# Fuer Geraete liefert Nautobot ab Werk nur "Decommissioning" (= Prozess laeuft).
|
||||
# sw1/sw2/cudy2 sind physisch ausgebaut -> Endzustand. "Decommissioned" existiert bereits
|
||||
# als mitgelieferter Status, aber nur fuer `circuits.circuit` -> content_types erweitern
|
||||
# statt einen zweiten, gleichnamigen Status anzulegen. ("Inventory" waere falsch, das
|
||||
# hiesse "auf Lager, einsatzbereit".)
|
||||
st_decom = find("/extras/statuses", name="Decommissioned")
|
||||
if not st_decom:
|
||||
st_decom = goc("/extras/statuses", {"name": "Decommissioned"},
|
||||
{"name": "Decommissioned", "color": "9e9e9e",
|
||||
"content_types": ["dcim.device"],
|
||||
"description": "Ausgebaut/ausser Betrieb (Endzustand, kein laufender Prozess)"},
|
||||
label="Status Decommissioned")
|
||||
elif "dcim.device" not in st_decom["content_types"]:
|
||||
patch("/extras/statuses", st_decom,
|
||||
{"content_types": sorted(set(st_decom["content_types"]) | {"dcim.device"})},
|
||||
"Status Decommissioned +dcim.device")
|
||||
S_DECOM_DONE = st_decom["id"]
|
||||
|
||||
vg = find("/ipam/vlan-groups", name="BF")
|
||||
V = {vid: find("/ipam/vlans", vid=vid, vlan_group=vg["id"]) for vid in (1, 10, 20, 30, 40, 50, 90, 99, 100)}
|
||||
LOC_OG = find("/dcim/locations", name="Technik OG")["id"]
|
||||
|
||||
DEVS = ("crs326-core", "crs326-branch", "ap2", "sw1", "sw2", "cudy1", "cudy2")
|
||||
D = {n: find("/dcim/devices", name=n) for n in DEVS}
|
||||
|
||||
|
||||
def iface(dev, name, typ, desc, mode=None, untagged=None, tagged=None, enabled=True):
|
||||
obj = goc("/dcim/interfaces", {"device": dev, "name": name},
|
||||
{"device": D[dev]["id"], "name": name, "type": typ,
|
||||
"status": S_ACTIVE, "description": desc, "enabled": enabled},
|
||||
label=f"{dev}:{name}")
|
||||
p = {"description": desc, "type": typ, "enabled": enabled}
|
||||
if mode:
|
||||
p["mode"] = mode
|
||||
if untagged is not None:
|
||||
p["untagged_vlan"] = V[untagged]["id"]
|
||||
if mode == "access":
|
||||
# Nautobot verweigert mode=access, solange noch tagged_vlans haengen
|
||||
# ("Clear tagged_vlans to set mode to access") -> im selben PATCH mitleeren.
|
||||
p["tagged_vlans"] = []
|
||||
obj = patch("/dcim/interfaces", obj, p, f"{dev}:{name}")
|
||||
if tagged is not None and not str(obj["id"]).startswith("DRY"):
|
||||
# ACHTUNG: `tagged_vlans` liest sich per REST als null, auch wenn korrekt gespeichert
|
||||
# (bekannte Nautobot-Falle, s. nautobot/README.md) -> dieser Vergleich schlaegt IMMER
|
||||
# an und der PATCH laeuft bei jedem Durchlauf. Der Write ist idempotent, die Zeile im
|
||||
# Log ist also kein Drift-Signal. Verifikation nur per GraphQL.
|
||||
cur = {t["id"] for t in (obj.get("tagged_vlans") or [])}
|
||||
want = {V[t]["id"] for t in tagged}
|
||||
if cur != want:
|
||||
if DRY:
|
||||
log.append(f" ~ {dev}:{name}: tagged_vlans -> {sorted(tagged)} (dry-run)")
|
||||
else:
|
||||
req("PATCH", f"/dcim/interfaces/{obj['id']}/", {"tagged_vlans": sorted(want)})
|
||||
log.append(f" ~ {dev}:{name}: tagged_vlans -> {sorted(tagged)}")
|
||||
return obj
|
||||
|
||||
|
||||
# --- crs326-core: volle As-built-Belegung (live erhoben 2026-08-13) -------------------
|
||||
# Trunk-Sets exakt wie in der Bridge-VLAN-Tabelle des Geraets -- ether1 traegt z.B. KEIN
|
||||
# VLAN20/99 (Bring-up-Rest), ether13 nur VLAN20. Bewusst nicht "geradegezogen".
|
||||
T_FULL = [10, 20, 30, 40, 50, 90, 99] # ether8 (branch), ether15 (ap1)
|
||||
T_EX_SW1 = [10, 30, 40, 50, 90, 100] # ether1, Ex-sw1-Uplink
|
||||
FREI = "frei (pvid=1, kein Link, keine VLAN-Membership)"
|
||||
|
||||
CORE = [
|
||||
("bridge", BR, "vlan-filtering=yes; L3-Router-Bridge (SVIs vlan10-100 haengen hier)", None, None, None, True),
|
||||
("ether1", ETH, "frei -- Ex-Uplink zu sw1 GE12 (sw1 ausser Betrieb 2026-08-08). Trunk-Reste "
|
||||
"+ frame-types=admit-only-vlan-tagged aus dem Bring-up, kein Link.",
|
||||
"tagged", 1, T_EX_SW1, True),
|
||||
("ether2", ETH, "frei (Access VLAN40 vorbereitet, kein Link). "
|
||||
"WARN zusaetzlich statisches untagged-Member von VLAN1 (Bring-up-Altlast).",
|
||||
"access", 40, None, True),
|
||||
("ether3", ETH, "Wohnzimmer Sideboard (untagged VLAN40) -- kein Link. "
|
||||
"WARN pvid=1 (Ex-Hybrid-VLAN1): Ingress landet in VLAN1, Egress untagged VLAN40.",
|
||||
"access", 40, None, True),
|
||||
# Descriptions sind auf 255 Zeichen begrenzt -> Langfassung steht in teilprojekte.md TP21.1/4.
|
||||
("ether4", ETH, "Wohnzimmer-Sideboard: Smart-Managed-Switch mit den VLAN40-Multimediageraeten "
|
||||
"(u.a. TV 10.40.40.11). 2026-08-13 von Trunk auf untagged VLAN40 gezogen "
|
||||
"(GC ConfigPlan) -- vorher Egress tagged = Einbahnstrasse.",
|
||||
"access", 40, None, True),
|
||||
("ether5", ETH, "Sonos SYMFONISK Schlafzimmer (R), 10.40.40.195 (Access VLAN40)", "access", 40, None, True),
|
||||
("ether6", ETH, "Sonos SYMFONISK Schlafzimmer (L), 10.40.40.193 (Access VLAN40); "
|
||||
"untagged-Membership nur dynamisch aus pvid=40", "access", 40, None, True),
|
||||
("ether7", ETH, FREI, None, None, None, True),
|
||||
("ether8", ETH, "Uplink-Trunk -> crs326-branch ether1 (native VLAN1 + tagged 10/20/30/40/50/90/99)",
|
||||
"tagged", 1, T_FULL, True),
|
||||
("ether9", ETH, FREI, None, None, None, True),
|
||||
("ether10", ETH, FREI, None, None, None, True),
|
||||
("ether11", ETH, "cudy1 (Access VLAN10, Nachfolger sw1 GE11) -- kein Link. "
|
||||
"WARN pvid=10 ohne VLAN10-Membership -> Port aktuell funktionslos.",
|
||||
"access", 10, None, True),
|
||||
("ether12", ETH, FREI, None, None, None, True),
|
||||
("ether13", ETH, "pve / Proxmox 10.40.20.100 -- Trunk: native VLAN1 + tagged VLAN20",
|
||||
"tagged", 1, [20], True),
|
||||
("ether14", ETH, "Sonoff Dongle-M 10.40.30.250 (Access VLAN30)", "access", 30, None, True),
|
||||
("ether15", ETH, "ap1 Uplink-Trunk (PoE-Injektor) -- native VLAN1 + tagged 10/20/30/40/50/90/99",
|
||||
"tagged", 1, T_FULL, True),
|
||||
("ether16", ETH, "Arris-Kabelmodem = WAN (Access VLAN100); vlan100-SVI zieht die Liwest-Public-IP",
|
||||
"access", 100, None, True),
|
||||
]
|
||||
CORE += [(f"ether{n}", ETH, FREI, None, None, None, True) for n in range(17, 25)]
|
||||
CORE += [("sfp-sfpplus1", SFP, "frei (disabled)", None, None, None, False),
|
||||
("sfp-sfpplus2", SFP, "frei (disabled)", None, None, None, False)]
|
||||
|
||||
print("== crs326-core: As-built-Portbelegung (26 Ports, live 2026-08-13)")
|
||||
for name, typ, desc, mode, unt, tag, en in CORE:
|
||||
iface("crs326-core", name, typ, desc, mode=mode, untagged=unt, tagged=tag, enabled=en)
|
||||
|
||||
# --- crs326-branch: Korrekturen ------------------------------------------------------
|
||||
print("== crs326-branch: Uplink-Ziel + Leerport-Realitaet")
|
||||
iface("crs326-branch", "ether1", ETH,
|
||||
"Uplink-Trunk -> crs326-core ether8 (native VLAN1 + tagged 10/20/30/40/50/90/99, kein 100)",
|
||||
mode="tagged", untagged=1, tagged=[10, 20, 30, 40, 50, 90, 99])
|
||||
for n in (5, 6):
|
||||
iface("crs326-branch", f"ether{n}", ETH,
|
||||
"Sonos Arbeitszimmer (10.40.40.191/.192, Access VLAN40) -- aktuell kein Link",
|
||||
mode="access", untagged=40)
|
||||
for n in range(9, 24):
|
||||
iface("crs326-branch", f"ether{n}", ETH,
|
||||
"frei (kein Link; pvid=20 aus Bootstrap Rev4, aber keine VLAN-Membership -> "
|
||||
"funktionslos. NICHT disabled -- die alte 'disabled'-Notiz war stale.)")
|
||||
for n in (1, 2):
|
||||
iface("crs326-branch", f"sfp-sfpplus{n}", SFP, "frei (kein Link, nicht disabled)")
|
||||
|
||||
# --- Standorte + Geraetestatus -------------------------------------------------------
|
||||
print("== Standorte + Status")
|
||||
patch("/dcim/devices", D["crs326-branch"], {"location": LOC_OG}, "crs326-branch location")
|
||||
patch("/dcim/devices", D["ap2"], {"location": LOC_OG}, "ap2 location")
|
||||
|
||||
patch("/dcim/devices", D["sw1"], {"status": S_DECOM_DONE,
|
||||
"comments": "ALLNET Core-Switch, ausser Betrieb 2026-08-08 -- ersetzt durch crs326-core "
|
||||
"(L3-Router + Core-Switch, Technik UG). Portbelegung GE1-GE16 bleibt als "
|
||||
"historischer Beleg stehen."}, "sw1")
|
||||
patch("/dcim/devices", D["sw2"], {"status": S_DECOM_DONE,
|
||||
"comments": "Omada ES208G, physisch abgebaut 2026-07-23 (TP15 Branch-Tausch) -- "
|
||||
"ersetzt durch crs326-branch."}, "sw2")
|
||||
patch("/dcim/devices", D["cudy2"], {"status": S_DECOM_DONE,
|
||||
"comments": "Cudy WR3600E V1.0, ausser Betrieb, wird nicht reaktiviert (TP19)."}, "cudy2")
|
||||
patch("/dcim/devices", D["cudy1"], {"status": S_OFFLINE,
|
||||
"comments": "Cudy WR3600E V1.0, Uebergangs-WLAN 'elektrosmog.c' waehrend des WLAN-Umbaus. "
|
||||
"Seit dem TP3-Rebuild (2026-08-08) nicht mehr noetig; core ether11 hat kein Link "
|
||||
"(Stand 2026-08-13) -> Offline. Endgueltiger Teardown = TP19."}, "cudy1")
|
||||
|
||||
# --- sw1: doppelt modellierte Ports entfernen ----------------------------------------
|
||||
# gi8/gi11/gi15/gi16 stammen aus einem zweiten Ingest-Lauf mit anderer Namenskonvention
|
||||
# und duplizieren GE8/GE11/GE15/GE16 (die die reichere Description tragen).
|
||||
print("== sw1: Duplikat-Ports (gi*) entfernen")
|
||||
for gi in ("gi8", "gi11", "gi15", "gi16"):
|
||||
obj = find("/dcim/interfaces", device="sw1", name=gi)
|
||||
if not obj:
|
||||
continue
|
||||
if DRY:
|
||||
log.append(f" - sw1:{gi} (dry-run)")
|
||||
else:
|
||||
req("DELETE", f"/dcim/interfaces/{obj['id']}/")
|
||||
log.append(f" - sw1:{gi}")
|
||||
|
||||
print("\n".join(log) or " (keine Aenderungen)")
|
||||
print("\nDRY-RUN -- mit --apply schreiben." if DRY else "\nGeschrieben.")
|
||||
@@ -28,6 +28,46 @@
|
||||
# Vorbereitung des Cutovers und beschreiben nicht mehr den Sollzustand.
|
||||
# =====================================================================================
|
||||
|
||||
# [0] L2-FABRIC — Bridge-Ports + Bridge-VLAN-Tabelle.
|
||||
# Literal-Block, KEINE Ableitung aus DCIM: der As-built traegt bewusste Schiefstaende
|
||||
# (ether3 ist untagged-Member von VLAN40, hat aber pvid=1; ether2 haengt zusaetzlich in
|
||||
# VLAN1). Wuerde man pvid aus `untagged_vlan` generieren, meldete die Compliance genau
|
||||
# diese akzeptierten Altlasten als Dauerdrift. Der Block ist damit das, was Golden Config
|
||||
# fuer einen handgebauten L2-Layer leisten soll: Drift-Erkennung gegen den Ist-Zustand.
|
||||
# `/interface bridge` selbst ist bewusst NICHT abgedeckt (traegt die geraetespezifische
|
||||
# admin-mac -> waere pro Geraet verschieden und ist kein Fleet-Standard).
|
||||
/interface bridge port add bridge=bridge comment=defconf frame-types=admit-only-vlan-tagged interface=ether1
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=ether2 pvid=40
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=ether3
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=ether4 pvid=40
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=ether5 pvid=40
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=ether6 pvid=40
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=ether7
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=ether8
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=ether9
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=ether10
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=ether11 pvid=10
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=ether12
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=ether13
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=ether14 pvid=30
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=ether15
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=ether16 pvid=100
|
||||
{% for n in range(17, 25) %}
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=ether{{ n }}
|
||||
{% endfor %}
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=sfp-sfpplus1
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=sfp-sfpplus2
|
||||
# ether4 = Wohnzimmer-Sideboard: 2026-08-13 von Trunk auf untagged VLAN40 gezogen (TP21.1/4).
|
||||
/interface bridge vlan add bridge=bridge untagged=ether1,ether2 vlan-ids=1
|
||||
/interface bridge vlan add bridge=bridge tagged=ether1,ether8,ether15 vlan-ids=10
|
||||
/interface bridge vlan add bridge=bridge tagged=ether8,ether13,ether15 vlan-ids=20
|
||||
/interface bridge vlan add bridge=bridge tagged=ether1,ether8,ether15 untagged=ether14 vlan-ids=30
|
||||
/interface bridge vlan add bridge=bridge tagged=ether1,ether8,ether15 untagged=ether2,ether3,ether4,ether5 vlan-ids=40
|
||||
/interface bridge vlan add bridge=bridge tagged=ether1,ether8,ether15 vlan-ids=50
|
||||
/interface bridge vlan add bridge=bridge tagged=ether1,ether8,ether15 vlan-ids=90
|
||||
/interface bridge vlan add bridge=bridge tagged=ether8,ether15,bridge vlan-ids=99
|
||||
/interface bridge vlan add bridge=bridge tagged=ether1 untagged=ether16 vlan-ids=100
|
||||
|
||||
# [1] INTERFACE-LISTS
|
||||
/interface list add comment=TP15 name=WAN
|
||||
/interface list add comment=TP15 name=LAN
|
||||
@@ -108,6 +148,31 @@
|
||||
/ip address add address={{ obj.primary_ip4.address }} comment=TP21-mgmt-target interface=vlan99 network={{ obj.primary_ip4.host.split(".")[:3] | join(".") }}.0
|
||||
/ip dns set servers=10.40.99.1
|
||||
/ip route add comment=TP21-default dst-address=0.0.0.0/0 gateway=10.40.99.1
|
||||
|
||||
# L2-FABRIC — wie beim Core ein Literal-Block (Begruendung dort).
|
||||
# ether9-23 tragen pvid=20 OHNE VLAN-Membership (Bootstrap-Rev4-Rest, funktionslos) —
|
||||
# bewusst so abgebildet, damit eine spaetere Bereinigung als Drift sichtbar wird.
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=ether1
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=ether2
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=ether3 pvid=10
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=ether4 pvid=10
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=ether5 pvid=40
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=ether6 pvid=40
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=ether7 pvid=20
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=ether8
|
||||
{% for n in range(9, 24) %}
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=ether{{ n }} pvid=20
|
||||
{% endfor %}
|
||||
/interface bridge port add bridge=bridge comment=defconf interface=ether24
|
||||
/interface bridge port add bridge=bridge comment=defconf disabled=yes interface=sfp-sfpplus1
|
||||
/interface bridge port add bridge=bridge comment=defconf disabled=yes interface=sfp-sfpplus2
|
||||
/interface bridge vlan add bridge=bridge comment=MAIN tagged=ether1,ether24,bridge untagged=ether3,ether4 vlan-ids=10
|
||||
/interface bridge vlan add bridge=bridge comment=INFRA tagged=ether1,ether24,bridge,ether2,ether8 untagged=ether7 vlan-ids=20
|
||||
/interface bridge vlan add bridge=bridge comment=IOT tagged=ether1,ether24,bridge vlan-ids=30
|
||||
/interface bridge vlan add bridge=bridge comment=MEDIA tagged=ether1,ether24,bridge untagged=ether5,ether6 vlan-ids=40
|
||||
/interface bridge vlan add bridge=bridge comment=UNTRUSTED tagged=ether1,ether24,bridge vlan-ids=50
|
||||
/interface bridge vlan add bridge=bridge comment=GUEST tagged=ether1,ether24,bridge vlan-ids=90
|
||||
/interface bridge vlan add bridge=bridge comment=MGMT tagged=ether1,ether24,bridge vlan-ids=99
|
||||
{% elif obj.role.name == "Wireless AP" %}
|
||||
{% set ap = obj.get_config_context()["ap"] %}
|
||||
{% set mgmt_host = obj.primary_ip4.host %}
|
||||
@@ -150,13 +215,23 @@
|
||||
/interface wifi security add authentication-types={{ z.auth }} ft={{ z.ft }}{% if z.ft == "yes" %} ft-over-ds={{ z.ft_over_ds }}{% endif %} name=sec-{{ z.name }} passphrase="<<REDACTED>>"
|
||||
{% endfor %}
|
||||
|
||||
# [3b] STEERING — 802.11k (rrm: Neighbor Reports) + 802.11v (wnm: BSS Transition).
|
||||
# OHNE das klebt der Client am sterbenden AP, bis die Verbindung abreisst; 802.11r/FT
|
||||
# macht die Uebergabe nur SCHNELL, es loest sie nicht aus. Genau das fehlte nach dem
|
||||
# TP3-Rebuild auf beiden APs und hat Videocalls beim Durchgehen abgerissen (TP3.3).
|
||||
# `neighbor-group` ist site-weit identisch -- nur dann gelten ap1/ap2 als Nachbarn.
|
||||
# Konstante statt ConfigContext: es gibt genau EINE Roaming-Domain pro Standort.
|
||||
{% set steering = {"name": "st-main", "neighbor_group": "bf-main"} %}
|
||||
/interface wifi steering add name={{ steering.name }} neighbor-group={{ steering.neighbor_group }} rrm=yes wnm=yes
|
||||
|
||||
# [4] CONFIGURATION — main sitzt auf den Master-Radios und bringt den Channel mit
|
||||
# (je Band eine configuration); alle anderen Zonen sind Slave-VAPs ohne channel=.
|
||||
# steering= haengt nur an den Master-Zonen (RouterOS exportiert es nach ssid=).
|
||||
{% for z in ap.zones %}
|
||||
{% if z.master %}
|
||||
{% for r in ap.radios %}
|
||||
{% if r.key in z.bands %}
|
||||
/interface wifi configuration add channel=ch-{{ r.key }} country={{ ap.country }} datapath=dp-{{ z.name }} mode=ap name=cfg-{{ z.name }}-{{ r.key }} security=sec-{{ z.name }} ssid={{ z.ssid }}
|
||||
/interface wifi configuration add channel=ch-{{ r.key }} country={{ ap.country }} datapath=dp-{{ z.name }} mode=ap name=cfg-{{ z.name }}-{{ r.key }} security=sec-{{ z.name }} ssid={{ z.ssid }} steering={{ steering.name }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
@@ -164,6 +239,17 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
# [4b] BRIDGE-PORTS — ether2 fehlt bewusst: seit der Uplink-Umstellung auf ether1 (PoE)
|
||||
# ist er KEIN Bridge-Port mehr. ether1 traegt kein comment=defconf (beim Rebuild
|
||||
# neu angelegt), die uebrigen schon — deshalb hier nicht ueber einen Kamm geschoren.
|
||||
/interface bridge port add bridge={{ ap.bridge }} comment=defconf interface=ether3
|
||||
/interface bridge port add bridge={{ ap.bridge }} comment=defconf interface=ether4
|
||||
/interface bridge port add bridge={{ ap.bridge }} comment=defconf interface=ether5
|
||||
{% for r in ap.radios %}
|
||||
/interface bridge port add bridge={{ ap.bridge }} comment=defconf interface={{ r.iface }}
|
||||
{% endfor %}
|
||||
/interface bridge port add bridge={{ ap.bridge }} interface={{ uplink }}
|
||||
|
||||
# [5] BRIDGE-VLAN — Nur Master-Radios sind Bridge-Ports (Slave-VAPs forwarden darueber),
|
||||
# deshalb muessen wifi1/wifi2 tagged-Member je Client-VLAN sein. Die frueheren
|
||||
# "Ausnahmen" fallen hier aus den Daten heraus: IOT hat bands=["2g"] -> nur wifi2.
|
||||
|
||||
Reference in New Issue
Block a user