From 2048b77f8f832231becc576c34533057cfc9754e Mon Sep 17 00:00:00 2001 From: Christoph Emsenhuber Date: Sun, 23 Aug 2026 00:54:21 +0200 Subject: [PATCH] TP6: CAKE gebaut -- und der Bau beweist, dass der CRS326 keine Queue fuehrt Entscheidung dieser Sitzung war "bauen statt schliessen". Gebaut ist es. Das Ergebnis stellt TP6 aber neu: die Queue steht, ist compliant, und tut nichts. DREI BELEGE AM GERAET, unabhaengig voneinander: /queue tree nach 6 MB echtem Client-Upload -> 0 Bytes Simple Queue auf 10.40.0.0/16 (Gegenprobe) -> 0 Bytes Upload gegen cake-bandwidth=8.5Mbps -> 9,48 Mbit Der dritte ist der harte. Ein Zaehler auf 0 laesst sich als Anzeigeproblem wegerklaeren, ein Durchsatz UEBER dem gesetzten Limit nicht. URSACHE (sehr wahrscheinlich, nicht abschliessend bewiesen): CRS326 mit Marvell-98DX3236. Bridge faehrt hw-offload, alle Ports only-hardware-queue, Bridge-Interface no-queue. Der Switch-Chip serialisiert, nicht die CPU. Dass l3-hw-offloading=no ist, widerspricht dem nicht: die CPU routet (Firewall zaehlt 32,9 Mio Pakete), aber sie serialisiert nicht. WAS BESTAND HAT -- die ComplianceRule `queue`: - nautobot/ingest-queue-rule.py (idempotent, Dry-Run default) - match_config = "/queue type\n/queue tree"; /queue interface bewusst NICHT, das traegt nur die Default-Portzuordnung und hat mit QoS nichts zu tun - Wirksamkeit bewiesen, nicht behauptet: erst 99/100 mit exakt der fehlenden Queue als `missing`, nach dem Deploy 100/100, 0 Drifts - Zaehler jetzt 25 Rules x 4 Geraete = 100 Checks Damit ist dieselbe blinde Stelle zu, die TP26 fuer /ip firewall filter geschlossen hat -- und zwar VOR der Queue, wie Runbook 4.1 es verlangt. ZWEI DESIGN-ENTSCHEIDUNGEN, die im Template begruendet stehen: - nur die Senderichtung: der Download liegt mit +23..28 ms UNTER der 3-Schwelle und ist nach demselben Kriterium Fall A - parent=vlan100 statt target=10.40.0.0/16: eine Simple Queue aufs LAN-Supernetz haette Inter-VLAN (NAS <-> MAIN, geroutet ueber dieselbe CPU) mitgedrosselt VIER SACKGASSEN, dokumentiert in ref_crs326_keine_software_queue: 1. active-queue=queue-tree heisst nur "verdrahtet", nicht "wirksam" 2. parent=global OHNE packet-mark faengt per Definition nichts 3. target=0.0.0.0/0 wird stillschweigend zu target="" verworfen 4. router-eigener Verkehr durchlaeuft die Queues nicht -- Test-Pings vom Router beweisen nichts, es braucht einen echten LAN-Client Zwei davon sahen wie Hardware-Befunde aus und waren eigene Testfehler. FORMATFALLE: RouterOS exportiert 8.5Mbps statt 8500000 und schiebt eine eigene Hinweiszeile vor die Tree-Zeile, wodurch der Backup-Normalisierer /queue tree nicht abflacht. Template baut beides nach -> 100/100. Alle Testaenderungen liefen als Config-Transaktion und sind zurueckgebaut; keine Akte offen. Die wirkungslose Queue bleibt vorerst am Geraet und in der SoT -- dokumentiert, nicht als aktives QoS misszuverstehen -- bis ueber die drei Wege entschieden ist (hw-offload opfern / Chip-QoS ohne CAKE / TP6 schliessen). Co-Authored-By: Claude Opus 5 --- docs/teilprojekte.md | 63 ++++++++- .../TEST-cake-parameter-probe.json | 16 +++ .../TEST-cake-parameter-probe.rollback.rsc | 6 + .../TEST-cake-parameter-probe.snapshot.rsc | 4 + .../TEST-queue-global-zaehlprobe.commands.rsc | 2 + .../TEST-queue-global-zaehlprobe.json | 16 +++ .../TEST-queue-global-zaehlprobe.rollback.rsc | 11 ++ .../TEST-queue-global-zaehlprobe.snapshot.rsc | 9 ++ nautobot/ingest-queue-rule.py | 129 ++++++++++++++++++ nautobot/mikrotik_routeros.j2 | 47 +++++++ 10 files changed, 299 insertions(+), 4 deletions(-) create mode 100644 docs/transaktionen/TEST-cake-parameter-probe.json create mode 100644 docs/transaktionen/TEST-cake-parameter-probe.rollback.rsc create mode 100644 docs/transaktionen/TEST-cake-parameter-probe.snapshot.rsc create mode 100644 docs/transaktionen/TEST-queue-global-zaehlprobe.commands.rsc create mode 100644 docs/transaktionen/TEST-queue-global-zaehlprobe.json create mode 100644 docs/transaktionen/TEST-queue-global-zaehlprobe.rollback.rsc create mode 100644 docs/transaktionen/TEST-queue-global-zaehlprobe.snapshot.rsc create mode 100755 nautobot/ingest-queue-rule.py diff --git a/docs/teilprojekte.md b/docs/teilprojekte.md index 80d1730..0b4c4f5 100644 --- a/docs/teilprojekte.md +++ b/docs/teilprojekte.md @@ -52,8 +52,8 @@ ab jetzt bei jeder Änderung mitgezogen, nicht nur die TP-Abschnitte.)* 08-18) — TP19 ist geschlossen; `cudy1` hängt stromlos an core `ether11`, physischer Ausbau ist hands-on. - **Kontrolle / Governance:** MikroMCP für Reads/Debug (alle 4 Geräte in der Registry), **Config-Änderungen ausschließlich über Nautobot Golden Config** (TP21/TP22 — Deploy-Pipeline scharf, - **96/96 Compliance-Checks grün, 0 Drifts** bei **24 Rules × 4 Geräten**, Stand 2026-08-23 — - neu dazu: **`wifi-access-list`**, s. TP22-Nachtrag). + **100/100 Compliance-Checks grün, 0 Drifts** bei **25 Rules × 4 Geräten**, Stand 2026-08-23 — + neu dazu: **`wifi-access-list`** (08-22) und **`queue`** (08-23, TP6), s. TP22-Nachtrag).
📜 Historische Fassung (Stand bis 2026-08-08, Topologie vor dem Core-Cutover — bewusst erhalten, nicht als Ist lesen) @@ -421,7 +421,53 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". - **(b) Live-Torch als Ad-hoc-Observability:** DNS ist bei direktem 1.1.1.1 blind, **Flows nicht** (NATen über ap1). `torch` auf `vlan30`/`vlan50` gefiltert auf die Geräte-IP zeigt Ziel-IPs live (Versuni → AWS `eu-west-1` IoT erkennbar). Voraussetzung: Gerät aktiv. Merkregel: Airfryer nur online wenn er kocht → Capture ans Einschalten koppeln. - **Abh.:** UNTRUSTED-Move des Airfryers (Kandidat, s. VLAN50) + TP5-Deny-by-default. Kein Blocker. -### TP6 — QoS/CAKE + Call-Quality-A/B-Test ▸ 🟡 **GEMESSEN 2026-08-20/21 — Fall B, aber „messbar, nicht hörbar"** +### TP6 — QoS/CAKE + Call-Quality-A/B-Test ▸ 🔴 **GEBAUT 2026-08-23 — UND NACHWEISLICH WIRKUNGSLOS. Der CRS326 fuehrt keine Software-Queue.** +> 🔴 **Der Befund, der TP6 neu stellt (2026-08-23):** CAKE ist gebaut, deployt und compliant +> (100/100) — **und es passiert nichts.** Drei unabhaengige Belege am Geraet: +> +> | Beleg | erwartet | gemessen | +> |---|---|---| +> | `/queue tree` Zaehler nach 6 MB Client-Upload | > 0 Bytes | **0 Bytes** | +> | Simple Queue auf `10.40.0.0/16` (Gegenprobe) | > 0 Bytes | **0 Bytes** | +> | Upload-Durchsatz gegen `cake-bandwidth=8.5Mbps` | <= 8,5 Mbit | **9,48 Mbit** | +> +> Der dritte ist der harte: die Queue **drosselt nicht**, sie zaehlt nicht, sie wirkt nicht. +> Das ist kein Zaehler-Anzeigeproblem, sondern die Sache selbst. +> +> **Sehr wahrscheinliche Ursache (nicht abschliessend bewiesen):** `crs326-core` ist ein +> **CRS326 mit Marvell-98DX3236-Switch-Chip**. Die Bridge faehrt `hw-offload` (`H`-Flag auf +> ether16), **alle** Ports stehen auf `only-hardware-queue`, das Bridge-Interface auf +> `no-queue`. Ein Geraet, dessen Ports der Switch-Chip bedient, hat an dieser Stelle keine +> Software-Queue, in der ein Scheduler arbeiten koennte. Passend dazu: `l3-hw-offloading=no` +> und `qos-hw-offloading=no` — die CPU *routet* zwar (die Firewall zaehlt 32,9 Mio Pakete), +> aber die Serialisierung passiert im Chip. +> +> 🔎 **Kontrollen, die ausgeschlossen sind:** kein WARP auf dem Testclient (Default-Route +> `10.40.20.1`, en0); der Verkehr lief ueber den Core; Fasttrack ist ohnehin tot (0 von 445). +> Zwei eigene Testfehler sind dokumentiert und korrigiert: ein `parent=global`-Tree **ohne** +> `packet-mark` faengt per Definition nichts, und `target=0.0.0.0/0` wird von RouterOS +> stillschweigend zu `target=""` verworfen — beide sahen wie Befunde aus und waren keine. +> +> ⚖️ **Damit ist die offene Frage nicht mehr „lohnt CAKE“, sondern „geht CAKE auf dieser +> Hardware ueberhaupt“.** Drei Wege, keiner geschenkt: +> 1. **`hw-offload` auf dem WAN-Pfad abschalten** — dann sieht die CPU den Verkehr und die +> Queue greift. Preis: der Switch-Chip faellt fuer diesen Pfad aus, die 800-MHz-CPU traegt +> ihn allein. Bei 9,6 Mbit WAN plausibel, **aber die Inter-VLAN-Messung dazu steht bis +> heute aus** (dieselbe offene Messung wie in §4.2 zu Fasttrack). +> 2. **QoS auf den Switch-Chip legen** (`qos-hw-offloading`) — kann Priorisierung, **kein +> CAKE**. Waere ein anderes, aermeres Werkzeug. +> 3. **TP6 schliessen** — mit dem staerksten Argument, das es je hatte: nicht nur „messbar, +> nicht hoerbar“, sondern „auf dieser Hardware nicht ohne Gegenleistung baubar“. +> +> ✅ **Was aus diesem Anlauf bleibt und Bestand hat:** die **ComplianceRule `queue`** +> (Feature + Rule, `match_config` = `/queue type` + `/queue tree`) schliesst die blinde +> Stelle aus §4.1 — dieselbe, die TP26 fuer `/ip firewall filter` geschlossen hat. Sie hat +> sich sofort bewiesen: erst 99/100 mit exakt der fehlenden Queue als `missing`, nach dem +> Deploy 100/100. Der Zaehler steht jetzt bei **25 Rules x 4 Geraeten = 100 Checks**. +> Die Queue selbst steht am Geraet und in der SoT — **wirkungslos, aber dokumentiert**, bis +> ueber die drei Wege oben entschieden ist. Sie NICHT fuer aktives QoS halten. + +*(Vorheriger Kopf, als Messbefund weiter gueltig:)* 🟡 **GEMESSEN 2026-08-20/21 — Fall B, aber „messbar, nicht hörbar"** > ✅ **Die Prämisse ist geprüft. Ergebnis + Rohdaten: [`tp6-praemisse-messrunbook.md`](tp6-praemisse-messrunbook.md) §5.** > > | | idle / C0 | Upload-Last | Download-Last | @@ -1962,6 +2008,15 @@ TP5 E1 ✅ ─► E2 ✅ ─► E2-Nachtrag ✅ ─► E3 Drop ✅ ─► E4 inp > nächste Sitzung entscheidet daher **bauen oder begründet schließen** — sie misst nicht neu. > Übergabe mit den Sitzungszuschnitten: [`handoff-20260822-naechste-schritte.md`](handoff-20260822-naechste-schritte.md). +> 🔴 **Update 2026-08-23: die Entscheidung ist gefallen — gebaut — und der Bau hat die +> Frage veraendert.** CAKE steht auf `crs326-core` (ComplianceRule `queue` + Template-Block, +> 100/100 compliant) und ist **nachweislich wirkungslos**: 0 Bytes in der Queue nach 6 MB +> Client-Upload, und 9,48 Mbit Durchsatz gegen ein 8,5-Mbit-Limit. Ursache sehr wahrscheinlich +> die **Switch-Chip-Architektur** des CRS326 (Bridge-`hw-offload`, alle Ports +> `only-hardware-queue`). **TP6 steht damit weiter auf dem Pfad**, aber die offene Frage ist +> jetzt eine Hardware-Abwaegung (`hw-offload` opfern? Chip-QoS? schliessen?) und keine +> Nutzenfrage mehr. Details und die drei Wege: TP6-Abschnitt oben. + **TP5 ist am 2026-08-18 abgeschlossen.** Damit steht rechnerisch nur noch **TP6** auf dem kritischen Pfad — 🟠 **aber dessen Prämisse ist offen**: eine Messung von 2026-06-16 (`docs/call-quality-diagnosis.md`) bescheinigt dem Netz bereits **A/A+** und hält CAKE für „marginal". Der nächste Schritt ist deshalb ein @@ -1977,7 +2032,7 @@ Ziel ist. Alles andere läuft nebenher und blockiert nichts. Anlernen eines IoT-Geraets (User 2026-08-17). Wer im IOT-SSID sitzt, ist im selben VLAN wie das Geraet und beruehrt die `forward`-Kette gar nicht; fuer Ad-hoc-Zugriffe aus MAIN gibt es jetzt die selbstverfallende Gruppe `jit-admin`. -2. 🟠 **TP6 — QoS/CAKE + Call-Quality-A/B-Test.** ~~Das eigentliche Projektziel; **keine offenen Blocker mehr**.~~ +2. 🔴 **TP6 — QoS/CAKE + Call-Quality-A/B-Test.** **Stand 2026-08-23: gebaut, aber wirkungslos** (s. TP6-Abschnitt — Switch-Chip-Befund). Der naechste Schritt ist weder Messen noch Bauen, sondern die Entscheidung ueber `hw-offload`. ~~Das eigentliche Projektziel; **keine offenen Blocker mehr**.~~ **Korrigiert 2026-08-19:** kein *Blocker*, aber eine offene **Prämisse** — der Satz oben und dieser Punkt widersprachen sich seit dem 18. Richtig ist: der nächste Schritt ist eine **Messung**, kein Bau. Runbook liegt bereit: `docs/tp6-praemisse-messrunbook.md`. Fällt sie A/A+ aus, wird TP6 **geschlossen diff --git a/docs/transaktionen/TEST-cake-parameter-probe.json b/docs/transaktionen/TEST-cake-parameter-probe.json new file mode 100644 index 0000000..1517719 --- /dev/null +++ b/docs/transaktionen/TEST-cake-parameter-probe.json @@ -0,0 +1,16 @@ +{ + "change_id": "TEST-cake-parameter-probe", + "device": "crs326-core", + "scope": "queue", + "match_config": [ + "/queue type", + "/queue tree" + ], + "reason": "TP6: CAKE-Parameternamen und Defaults am Geraet auslesen statt raten (Type ohne Queue-Tree = wirkungslos)", + "started": "2026-08-23", + "expires": "2026-08-24", + "state": "rolledback", + "snapshot": "docs/transaktionen/TEST-cake-parameter-probe.snapshot.rsc", + "device_backup": "", + "closed": "2026-08-23" +} diff --git a/docs/transaktionen/TEST-cake-parameter-probe.rollback.rsc b/docs/transaktionen/TEST-cake-parameter-probe.rollback.rsc new file mode 100644 index 0000000..cbe3f3e --- /dev/null +++ b/docs/transaktionen/TEST-cake-parameter-probe.rollback.rsc @@ -0,0 +1,6 @@ +# Rueckbau-VORSCHLAG fuer TEST-cake-parameter-probe (crs326-core, Scope queue) +# Aus dem Snapshot vom 2026-08-23. NICHT ungeprueft deployen: +# `add`-Zeilen legen neu an, statt zu aendern — was hier gebraucht wird, +# ist in der Regel ein `set [find ...]`. Zeilen entsprechend anpassen. + + diff --git a/docs/transaktionen/TEST-cake-parameter-probe.snapshot.rsc b/docs/transaktionen/TEST-cake-parameter-probe.snapshot.rsc new file mode 100644 index 0000000..c99a55a --- /dev/null +++ b/docs/transaktionen/TEST-cake-parameter-probe.snapshot.rsc @@ -0,0 +1,4 @@ +# Snapshot des Scopes 'queue' auf crs326-core, 2026-08-23 (Transaktion TEST-cake-parameter-probe) +# Bewusst NUR dieser Abschnitt — der Vollexport gehoert nicht ins Repo. + + diff --git a/docs/transaktionen/TEST-queue-global-zaehlprobe.commands.rsc b/docs/transaktionen/TEST-queue-global-zaehlprobe.commands.rsc new file mode 100644 index 0000000..37ffe6f --- /dev/null +++ b/docs/transaktionen/TEST-queue-global-zaehlprobe.commands.rsc @@ -0,0 +1,2 @@ +/queue simple remove [find where name="probelan"] +/queue tree remove [find where name="probeglobal"] diff --git a/docs/transaktionen/TEST-queue-global-zaehlprobe.json b/docs/transaktionen/TEST-queue-global-zaehlprobe.json new file mode 100644 index 0000000..b047e11 --- /dev/null +++ b/docs/transaktionen/TEST-queue-global-zaehlprobe.json @@ -0,0 +1,16 @@ +{ + "change_id": "TEST-queue-global-zaehlprobe", + "device": "crs326-core", + "scope": "queue", + "match_config": [ + "/queue type", + "/queue tree" + ], + "reason": "TP6: prueft, ob parent=global auf dem CRS326 ueberhaupt zaehlt (queue=default, KEIN Limit, drosselt nichts)", + "started": "2026-08-23", + "expires": "2026-08-24", + "state": "rolledback", + "snapshot": "docs/transaktionen/TEST-queue-global-zaehlprobe.snapshot.rsc", + "device_backup": "", + "closed": "2026-08-23" +} diff --git a/docs/transaktionen/TEST-queue-global-zaehlprobe.rollback.rsc b/docs/transaktionen/TEST-queue-global-zaehlprobe.rollback.rsc new file mode 100644 index 0000000..92d6d14 --- /dev/null +++ b/docs/transaktionen/TEST-queue-global-zaehlprobe.rollback.rsc @@ -0,0 +1,11 @@ +# Rueckbau-VORSCHLAG fuer TEST-queue-global-zaehlprobe (crs326-core, Scope queue) +# Aus dem Snapshot vom 2026-08-23. NICHT ungeprueft deployen: +# `add`-Zeilen legen neu an, statt zu aendern — was hier gebraucht wird, +# ist in der Regel ein `set [find ...]`. Zeilen entsprechend anpassen. + +/queue type +add cake-bandwidth=8.5Mbps cake-diffserv=diffserv4 cake-nat=yes kind=cake \ +name=cake-wan-up +/queue tree +add comment="TP6: CAKE auf der Senderichtung (WAN-Egress)" name=qos-wan-up \ +parent=vlan100 queue=cake-wan-up diff --git a/docs/transaktionen/TEST-queue-global-zaehlprobe.snapshot.rsc b/docs/transaktionen/TEST-queue-global-zaehlprobe.snapshot.rsc new file mode 100644 index 0000000..d38c041 --- /dev/null +++ b/docs/transaktionen/TEST-queue-global-zaehlprobe.snapshot.rsc @@ -0,0 +1,9 @@ +# Snapshot des Scopes 'queue' auf crs326-core, 2026-08-23 (Transaktion TEST-queue-global-zaehlprobe) +# Bewusst NUR dieser Abschnitt — der Vollexport gehoert nicht ins Repo. + +/queue type +add cake-bandwidth=8.5Mbps cake-diffserv=diffserv4 cake-nat=yes kind=cake \ +name=cake-wan-up +/queue tree +add comment="TP6: CAKE auf der Senderichtung (WAN-Egress)" name=qos-wan-up \ +parent=vlan100 queue=cake-wan-up diff --git a/nautobot/ingest-queue-rule.py b/nautobot/ingest-queue-rule.py new file mode 100755 index 0000000..2e1be5d --- /dev/null +++ b/nautobot/ingest-queue-rule.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +""" +ComplianceRule `queue` anlegen (idempotent). + +Die Luecke, die das schliesst +----------------------------- +`nautobot/mikrotik_routeros.j2` hatte bis 2026-08-23 **keinen `/queue`-Block**, und +es gab keine ComplianceRule dafuer. Eine von Hand gesetzte CAKE-Queue waere damit +**unsichtbar fuer die Compliance** gewesen (sie meldet weiter "compliant", waehrend +die Queue driftet) und beim naechsten Deploy **nicht geschuetzt**. + +Das ist exakt dieselbe blinde Stelle, die **TP26** fuer `/ip firewall filter` +geschlossen hat -- dort war sie 25 Firewallregeln gross. + +TP6 baut jetzt genau dorthin: CAKE auf der Senderichtung (`/queue tree` auf +`vlan100`, Type aus `/queue type`). Die Regel kommt deshalb VOR der Queue, nicht +danach -- so, wie es `tp6-praemisse-messrunbook.md` §4.1 verlangt. + +Warum zwei Praefixe und nicht `/queue` +-------------------------------------- +`/queue interface` traegt die Default-Zuordnung aller 26 Ports +(`only-hardware-queue`) und `bridge=no-queue`. Die stehen zwar heute in keinem +Backup (RouterOS exportiert Defaults nicht), aber ein Praefix `/queue` wuerde sie +mitfangen, sobald sich daran je etwas aendert -- und das hat mit QoS nichts zu tun. +Gematcht wird deshalb nur, was TP6 tatsaechlich setzt. + + export NAUTOBOT_TOKEN=... # aus docs/nas-vlan20/stacks/210-nautobot.yml + python3 nautobot/ingest-queue-rule.py # Dry-Run + python3 nautobot/ingest-queue-rule.py --apply +""" +import json +import os +import sys +import urllib.error +import urllib.parse +import urllib.request + +BASE = "http://10.40.20.30:8080/api" +TOKEN = os.environ.get("NAUTOBOT_TOKEN", "") +APPLY = "--apply" in sys.argv + +FEATURE = "queue" +BESCHREIBUNG = ("TP6 QoS/CAKE: Queue-Types und Queue-Tree. Vor 2026-08-23 stand " + "/queue unter keiner Rule -- eine handgesetzte Queue waere unsichtbar " + "gedriftet. Analog zu TP26 fuer /ip firewall filter.") +MATCH = "/queue type\n/queue tree" +PLATTFORM = "RouterOS" + + +def req(method, path, data=None): + url = path if path.startswith("http") else 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", "Token " + TOKEN) + r.add_header("Content-Type", "application/json") + r.add_header("Accept", "application/json") + try: + with urllib.request.urlopen(r, timeout=60) as resp: + raw = resp.read() + return json.loads(raw) if raw else {} + except urllib.error.HTTPError as e: + # 🔴 Fehlerkoerper ausgeben: Nautobot begruendet 400er praezise, und ohne den + # Text raet man an Feldnamen herum (TP22-Erfahrung). + sys.exit("!! %s %s -> %s\n %s" % (method, url, e.code, e.read().decode()[:600])) + + +def eins(pfad, **filter): + q = urllib.parse.urlencode(filter) + res = req("GET", "%s?%s" % (pfad, q)).get("results", []) + if len(res) > 1: + sys.exit("FEHLER: %s?%s liefert %d Treffer, erwartet 0 oder 1" % (pfad, q, len(res))) + return res[0] if res else None + + +def main(): + if not TOKEN: + sys.exit("NAUTOBOT_TOKEN fehlt.") + print("Modus:", "APPLY" if APPLY else "DRY-RUN (nichts wird geschrieben)") + + plat = eins("/dcim/platforms/", name=PLATTFORM) + if not plat: + sys.exit("Plattform %r nicht gefunden." % PLATTFORM) + print("Plattform : %s (%s)" % (plat["display"], plat["id"])) + + feat = eins("/plugins/golden-config/compliance-feature/", name=FEATURE) + if feat: + print("Feature : existiert bereits (%s)" % feat["id"]) + else: + print("Feature : anlegen -> %s" % FEATURE) + if APPLY: + feat = req("POST", "/plugins/golden-config/compliance-feature/", + {"name": FEATURE, "slug": FEATURE, "description": BESCHREIBUNG}) + print(" angelegt (%s)" % feat["id"]) + + if not feat: + print("\nDry-Run: ohne Feature kann die Rule nicht geprueft werden. Mit --apply erneut.") + return + + rule = eins("/plugins/golden-config/compliance-rule/", + feature=feat["id"], platform=plat["id"]) + if rule: + print("Rule : existiert bereits (%s), match_config=%r" + % (rule["id"], rule.get("match_config"))) + if rule.get("match_config") != MATCH: + print(" ⚠️ match_config weicht ab -- erwartet %r" % MATCH) + return + + nutzlast = { + "feature": feat["id"], + "platform": plat["id"], + "description": BESCHREIBUNG, + # Reihenfolge ist hier bedeutungslos: Queue-Trees haengen ueber `parent=` + # am Namen, nicht an der Position. Anders als /ip firewall filter. + "config_ordered": False, + "config_type": "cli", + "match_config": MATCH, + "custom_compliance": False, + "config_remediation": False, + } + print("Rule : anlegen -> %s auf %s, match_config=%r" % (FEATURE, PLATTFORM, MATCH)) + if APPLY: + r = req("POST", "/plugins/golden-config/compliance-rule/", nutzlast) + print(" angelegt (%s)" % r["id"]) + else: + print(" Nutzlast: %s" % json.dumps(nutzlast)) + + +if __name__ == "__main__": + main() diff --git a/nautobot/mikrotik_routeros.j2 b/nautobot/mikrotik_routeros.j2 index c26a0df..ba37947 100644 --- a/nautobot/mikrotik_routeros.j2 +++ b/nautobot/mikrotik_routeros.j2 @@ -287,6 +287,53 @@ /interface wireguard peers add allowed-address=0.0.0.0/0 comment="TP16.1 CF-PoP" endpoint-address=162.159.193.2 endpoint-port=2408 interface=cf-mesh name=cf-pop persistent-keepalive=25s public-key="bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=" /ip address add address=100.96.0.6 comment="TP16.1 Mesh-Overlay" interface=cf-mesh network=100.96.0.6 /ip route add comment="TP16.1 Mesh" disabled=no distance=1 dst-address=100.96.0.0/12 gateway=cf-mesh + +# [11] QoS / CAKE (TP6, 2026-08-23) — gebaut gegen eine Zahl, nicht gegen ein Ritual. +# +# GEMESSEN am 2026-08-20/21 (tp6-praemisse-messrunbook.md §5), Linerate erstmals +# dokumentiert: Upload 9,6 Mbit, Download 54,6 Mbit. +# +# idle Upload-Last Download-Last +# Durchlauf 1 12,9 ms 60,4 ms 41,3 ms +# Referenz 06-16 12 ms 17,5 ms nie gemessen +# +# Der Anstieg liegt in der SENDERICHTUNG bei +28..+47 ms und damit ueber der +# §3-Schwelle von <=30 ms -> Fall B. Der Download liegt mit +23..+28 ms DARUNTER +# und ist damit nach demselben Kriterium Fall A. +# +# 🔴 Deshalb wird hier NUR die Senderichtung geshaped. Das ist keine Sparsamkeit, +# sondern dasselbe Abbruchkriterium in beide Richtungen angewandt: eine Queue fuer +# den Download waere gegen ein Problem gebaut, das die Messung nicht gezeigt hat. +# Ingress-Shaping kostet auf RouterOS ausserdem Mangle-Umwege, weil man die +# Gegenstelle nicht kontrolliert -- Aufwand fuer einen unbelegten Nutzen. +# +# 🔴 parent=vlan100, NICHT target=10.40.0.0/16. Eine simple queue auf das +# LAN-Supernetz wuerde auch INTER-VLAN-Verkehr erfassen (NAS <-> MAIN laeuft +# geroutet ueber dieselbe CPU) und ihn auf WAN-Tempo drosseln. vlan100 traegt +# ausschliesslich WAN-Verkehr, der Rest bleibt unberuehrt. +# +# Bandbreite = 8,5 Mbit ~= 88 % der gemessenen 9,6. CAKE muss die Warteschlange +# selbst halten koennen; bleibt der Puffer beim ISP, regelt es nichts. Der Preis +# sind ~1,1 Mbit Spitzendurchsatz und er ist im A/B-Nachweis sichtbar. +# +# cake-nat=yes ist hier kein Beiwerk: hinter srcnat saehe CAKE sonst nur EINE +# Adresse und koennte die Hosts nicht gegeneinander fair halten -- genau das ist +# aber der Zielfall ("jemand laedt hoch, waehrend jemand telefoniert"). +# cake-diffserv=diffserv4 trennt dabei latenzkritisch von bulk. +# +# ⚠️ Fasttrack ist auf diesem Geraet faktisch tot (accept steht vor fasttrack, +# 0 von 445 Verbindungen) und BLEIBT es -- Entscheid 2026-08-19, §4.2. Fasttrack +# umgeht Queues; fuer CAKE ist der Zustand die guenstigere Ausgangslage. Wer die +# Regelreihenfolge je "repariert", setzt diese Queue mit ausser Kraft. +# ⚠️ FORMAT: RouterOS exportiert `cake-bandwidth=8.5Mbps`, nicht `8500000` (gleiche +# Klasse wie `hw-offload=true` vs `yes`), UND schiebt vor die Tree-Zeile eine eigene +# Hinweiszeile. Dadurch flacht der Backup-Normalisierer `/queue tree` nicht ab: die +# add-Zeile steht ohne Pfad-Praefix da. Beide Eigenheiten muessen hier nachgebaut +# werden, sonst meldet die Compliance einen Dauer-Drift, bei dem das GERAET recht hat. +/queue type add cake-bandwidth=8.5Mbps cake-diffserv=diffserv4 cake-nat=yes kind=cake name=cake-wan-up +/queue tree +# CAKE type with bandwidth setting detected, configure traffic limits within queue itself +add comment="TP6: CAKE auf der Senderichtung (WAN-Egress)" name=qos-wan-up parent=vlan100 queue=cake-wan-up {% elif obj.role.name == "Switch" %} # ===================================================================================== # L2-Switch (crs326-branch) — Sollzustand des Mgmt-Layers.